/** * Sondeo de SOLO LECTURA de las rutas que hacen falta para sincronizar por id. * * El objetivo es «traer por id» contactos, conversaciones, mensajes, citas y * servicios. De esas cinco, solo contactos está ejercida hoy (HALLAZGOS 1-23); * el resto está sin medir, y en este proyecto lo medido gana a lo documentado. * * NO escribe nada en la subcuenta. Todas las peticiones son GET. * * node scripts/run-tsx.mjs platform/scripts/crm-spike-lectura-id.ts */ import { crmRequest, CrmError, VERSION_CALENDARS } from "../crm/client.ts"; import { ctxDesdeEnv } from "../crm/ctx.ts"; import { requireEnv } from "../lib/env.ts"; const LOC = requireEnv("CRM_LOCATION_ID"); const CTX = ctxDesdeEnv(); let ok = 0; let fail = 0; async function probe(titulo: string, fn: () => Promise) { process.stdout.write(`\n── ${titulo}\n`); try { const detalle = await fn(); ok++; console.log(` ✔ ${detalle}`); } catch (e: any) { fail++; if (e instanceof CrmError) { const cuerpo = typeof e.body === "string" ? e.body.slice(0, 200) : JSON.stringify(e.body)?.slice(0, 300); console.log(` ✖ ${e.status} — ${e.message}`); if (cuerpo && cuerpo !== "null") console.log(` cuerpo: ${cuerpo}`); } else { console.log(` ✖ ${e?.message ?? e}`); } } } const claves = (o: unknown, n = 14) => o && typeof o === "object" ? Object.keys(o as object).slice(0, n).join(", ") : String(o); async function main() { console.log(`Subcuenta ${LOC} — sondeo de lectura por id (sin escrituras)`); // ── CONVERSACIONES ──────────────────────────────────────────────────────── let convId = ""; let contactoDeConv = ""; await probe("GET /conversations/search — listar para obtener un id real", async () => { const r = await crmRequest("GET", "/conversations/search", { token: CTX.token, query: { locationId: LOC, limit: 3 }, }); const c = r?.conversations?.[0]; if (!c) throw new Error("no devolvió ninguna conversación"); convId = c.id; contactoDeConv = c.contactId ?? ""; return `total=${r.total} · primera id=${convId} · campos: ${claves(c)}`; }); await probe("GET /conversations/{id} — traer UNA conversación por su id", async () => { if (!convId) throw new Error("sin id de conversación"); const r = await crmRequest("GET", `/conversations/${convId}`, { token: CTX.token }); const c = r?.conversation ?? r; return `campos: ${claves(c)}`; }); await probe("GET /conversations/search?contactId= — conversaciones de un contacto", async () => { if (!contactoDeConv) throw new Error("la conversación no traía contactId"); const r = await crmRequest("GET", "/conversations/search", { token: CTX.token, query: { locationId: LOC, contactId: contactoDeConv, limit: 5 }, }); return `contacto ${contactoDeConv} → ${r?.conversations?.length ?? 0} conversaciones (total=${r?.total})`; }); // ── MENSAJES ────────────────────────────────────────────────────────────── let msgId = ""; await probe("GET /conversations/{id}/messages — mensajes del hilo", async () => { if (!convId) throw new Error("sin id de conversación"); const r = await crmRequest("GET", `/conversations/${convId}/messages`, { token: CTX.token, query: { limit: 5 }, }); const lista = r?.messages?.messages ?? r?.messages ?? []; msgId = lista[0]?.id ?? ""; return `${lista.length} mensajes · paginación: ${claves(r?.messages)} · campos del mensaje: ${claves(lista[0])}`; }); await probe("GET /conversations/messages/{id} — traer UN mensaje por su id", async () => { if (!msgId) throw new Error("sin id de mensaje"); const r = await crmRequest("GET", `/conversations/messages/${msgId}`, { token: CTX.token }); return `campos: ${claves(r?.message ?? r)}`; }); // ── CALENDARIOS Y CITAS ─────────────────────────────────────────────────── let calId = ""; await probe("GET /calendars/ — calendarios de la subcuenta", async () => { const r = await crmRequest("GET", "/calendars/", { token: CTX.token, query: { locationId: LOC }, version: VERSION_CALENDARS, }); const cals: any[] = r?.calendars ?? []; calId = cals[0]?.id ?? ""; return `${cals.length} calendarios: ${cals.map((c) => `${c.name}(${c.id})`).join(", ").slice(0, 260)}`; }); await probe("GET /calendars/events — citas en un rango de fechas", async () => { if (!calId) throw new Error("sin calendario"); const ahora = Date.now(); const r = await crmRequest("GET", "/calendars/events", { token: CTX.token, query: { locationId: LOC, calendarId: calId, startTime: String(ahora - 90 * 86400000), endTime: String(ahora + 90 * 86400000), }, version: VERSION_CALENDARS, }); const ev: any[] = r?.events ?? []; return `${ev.length} eventos en ±90 días · campos: ${claves(ev[0])}`; }); await probe("GET /calendars/events/appointments/{id} — traer UNA cita por id", async () => { const ahora = Date.now(); if (!calId) throw new Error("sin calendario"); const lista = await crmRequest("GET", "/calendars/events", { token: CTX.token, query: { locationId: LOC, calendarId: calId, startTime: String(ahora - 365 * 86400000), endTime: String(ahora + 365 * 86400000), }, version: VERSION_CALENDARS, }); const id = lista?.events?.[0]?.id; if (!id) throw new Error("no hay ninguna cita en ±365 días con la que probar"); const r = await crmRequest("GET", `/calendars/events/appointments/${id}`, { token: CTX.token, version: VERSION_CALENDARS, }); return `cita ${id} · campos: ${claves(r?.appointment ?? r)}`; }); // ── SERVICIOS ───────────────────────────────────────────────────────────── await probe("GET /calendars/services/catalog — catálogo de servicios", async () => { const r = await crmRequest("GET", "/calendars/services/catalog", { token: CTX.token, query: { locationId: LOC }, version: VERSION_CALENDARS, }); const s: any[] = r?.services ?? []; return `${s.length} servicios${s.length ? ` · campos: ${claves(s[0])}` : " (vacío, confirma el hallazgo 6)"}`; }); await probe("GET /calendars/groups — agrupaciones de calendarios", async () => { const r = await crmRequest("GET", "/calendars/groups", { token: CTX.token, query: { locationId: LOC }, version: VERSION_CALENDARS, }); return `${r?.groups?.length ?? 0} grupos · ${claves(r?.groups?.[0])}`; }); // ── CONTACTO POR ID (ya medido; se reconfirma para tener la forma) ──────── await probe("GET /contacts/{id} — traer UN contacto por id", async () => { const b = await crmRequest("POST", "/contacts/search", { token: CTX.token, body: { locationId: LOC, pageLimit: 1 }, }); const id = b?.contacts?.[0]?.id; if (!id) throw new Error("el buscador no devolvió contactos"); const r = await crmRequest("GET", `/contacts/${id}`, { token: CTX.token }); return `contacto ${id} · campos: ${claves(r?.contact, 20)}`; }); console.log(`\n────────────\n${ok} rutas responden · ${fail} fallan`); } main().catch((e) => { console.error("\nEl sondeo se detuvo:", e?.message ?? e); process.exit(1); });