Files
KLINGO-CLINICAL-SYSLIFE-GCHAT/gti-scheduling-mcp/scripts/diagnose-zpro-list.ts
T
GustavoandClaude Sonnet 5 257afe98c0 Snapshot: gti-scheduling-mcp (Z-PRO + Klingo adapters) e workflow n8n de agendamento funcionando de ponta a ponta
- gti-scheduling-mcp: MCP server com ZProAdapter e KlingoAdapter (agendar() completo pros dois)
- Workflow n8n 'Agendamento - GChat - Z-PRO': webhook -> AI Agent -> MCP -> Z-PRO, testado com sucesso real
- Documentacao: specs, plano, runbook de problemas reais e export do workflow

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 17:23:10 -03:00

147 lines
4.3 KiB
TypeScript

/**
* 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<number> {
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);
});