/** * scripts/diagnose-zpro-list.ts * * Script CLI temporário e seguro para descobrir o contrato real do * endpoint: * * GET /v2/api/external/{ApiID}/appointment/list * * Faz apenas UMA requisição GET de leitura (page=1, limit=5), com timeout * de 15s, sem retry e sem modificar nenhum dado. Imprime a resposta * sanitizada e um resumo estrutural. * * O script nunca imprime token, header Authorization, stack trace ou * dados pessoais completos dos pacientes. */ import { loadSchedulingConfig } from "../src/config/env.config.js"; import { buildAppointmentListUrl, fetchAppointmentList, NetworkRequestError, RequestTimeoutError, } from "../src/diagnostics/client.js"; import { buildResponseReport } from "../src/diagnostics/response.js"; import { sanitizeText } from "../src/diagnostics/sanitize.js"; import type { StructureSummary } from "../src/diagnostics/analyze.js"; function list(values: string[]): string { return values.length > 0 ? values.join(", ") : "(nenhum)"; } function classifyHttpStatus(status: number): string { if (status === 401 || status === 403) { return `Erro de autenticação (status ${status}). Verifique ZPRO_API_TOKEN.`; } if (status === 404) { return `ApiID inválido ou recurso não encontrado (status ${status}). Verifique ZPRO_API_ID.`; } return `Status HTTP ${status} (esperado 2xx).`; } function printStructureSummary(summary: StructureSummary): void { console.log(`Tipo da raiz: ${summary.rootType}`); if (summary.rootType === "array") { console.log(`Quantidade de itens no array: ${summary.arrayItemCount}`); console.log(`Chaves do primeiro item: ${list(summary.firstItemKeys)}`); } else if (summary.rootType === "object") { console.log(`Chaves na raiz: ${list(summary.rootKeys)}`); } console.log("Tipos dos campos:"); for (const [key, type] of Object.entries(summary.fieldTypes)) { console.log(` ${key}: ${type}`); } console.log( `Possíveis campos de paginação: ${list(summary.possiblePaginationFields)}`, ); console.log( `Possíveis campos de identificação: ${list(summary.possibleIdFields)}`, ); console.log(`Possíveis campos de data: ${list(summary.possibleDateFields)}`); console.log( `Possíveis campos de status: ${list(summary.possibleStatusFields)}`, ); } async function main(): Promise { let config; try { config = loadSchedulingConfig(); } catch (error) { const message = error instanceof Error ? error.message : "Falha ao carregar a configuração."; console.error(message); return 1; } const secrets = [config.apiToken]; let response; try { response = await fetchAppointmentList(config); } catch (error) { if (error instanceof RequestTimeoutError) { console.error("Timeout ao consultar a API do Z-PRO (15s)."); } else if (error instanceof NetworkRequestError) { console.error("Falha de rede ao consultar a API do Z-PRO."); } else { console.error("Erro inesperado ao consultar a API do Z-PRO."); } return 1; } console.log("=== Informações da requisição ==="); console.log("Método: GET"); console.log(`URL: ${buildAppointmentListUrl(config)}`); console.log(`Status: ${response.status}`); console.log(`Content-Type: ${response.contentType ?? "(ausente)"}`); console.log(`Duração: ${response.durationMs}ms`); if (response.status < 200 || response.status >= 300) { console.log(""); console.log("=== Erro HTTP ==="); console.log(classifyHttpStatus(response.status)); if (response.bodyText) { console.log(""); console.log("Corpo da resposta (primeiros 500 caracteres, sanitizado):"); console.log(sanitizeText(response.bodyText, secrets)); } return 1; } const report = buildResponseReport(response.bodyText, secrets); if (!report.isJson) { console.log(""); console.log("=== A resposta não é JSON ==="); console.log("Conteúdo (primeiros 500 caracteres, sanitizado):"); console.log(report.sanitizedText); return 1; } console.log(""); console.log("=== Estrutura da resposta ==="); console.log(report.sanitizedText); if (report.structure) { console.log(""); console.log("=== Resumo estrutural ==="); printStructureSummary(report.structure); } return 0; } main() .then((code) => { process.exit(code); }) .catch(() => { process.exit(1); });