/** * Spike de integración contra Bucéfalo CRM. Solo lecturas. * * Existe porque el proyecto hermano ya pagó el precio de descubrir tarde que * ninguna escritura funcionaba: los tests estaban escritos desde la * implementación y no contra el contrato real de la API. Aquí no se da por * buena ninguna capacidad sin haberla ejercido. * * node scripts/run-tsx.mjs platform/scripts/crm-spike.ts */ import { loadEnv } from "../lib/env.ts"; import { ctxDesdeEnv } from "../crm/ctx.ts"; import { crmRequest } from "../crm/client.ts"; loadEnv(); const LOC = process.env.CRM_LOCATION_ID!; const CTX = ctxDesdeEnv(); async function probe(label: string, fn: () => Promise) { process.stdout.write(`\n── ${label}\n`); try { const out = await fn(); console.log(JSON.stringify(out, null, 2).slice(0, 1800)); return out as any; } catch (e: any) { console.log(` ✗ ${e.message}`); return null; } } async function main() { console.log(`Subcuenta: ${LOC}`); await probe("GET /locations/{id} — ¿el token ve la subcuenta?", async () => { const r: any = await crmRequest("GET", `/locations/${LOC}`, { token: CTX.token }); return { name: r?.location?.name, timezone: r?.location?.timezone, country: r?.location?.country, }; }); await probe("POST /contacts/search — forma real de un contacto", async () => { const r: any = await crmRequest("POST", `/contacts/search`, { token: CTX.token, body: { locationId: LOC, page: 1, pageLimit: 2 }, }); const c = r?.contacts?.[0]; return { total: r?.total, devueltos: r?.contacts?.length, claves_de_un_contacto: c ? Object.keys(c).sort() : null, attributionSource: c?.attributionSource ?? null, customFields_ejemplo: c?.customFields?.slice(0, 3) ?? null, }; }); const pipes = await probe("GET /opportunities/pipelines — pipeline y etapas", async () => { const r: any = await crmRequest("GET", `/opportunities/pipelines?locationId=${LOC}`, { token: CTX.token }); return (r?.pipelines ?? []).map((p: any) => ({ id: p.id, name: p.name, stages: (p.stages ?? []).map((s: any) => ({ id: s.id, name: s.name, position: s.position })), })); }); await probe("GET /locations/{id}/customFields — campos personalizados", async () => { const r: any = await crmRequest("GET", `/locations/${LOC}/customFields`, { token: CTX.token }); return (r?.customFields ?? []).map((f: any) => ({ id: f.id, name: f.name, fieldKey: f.fieldKey, dataType: f.dataType, })); }); await probe("GET /users/?locationId — personal del CRM", async () => { const r: any = await crmRequest("GET", `/users/?locationId=${LOC}`, { token: CTX.token }); return (r?.users ?? []).map((u: any) => ({ id: u.id, name: u.name, email: u.email, roles: u.roles?.role, })); }); await probe("GET /calendars/?locationId — ¿EXISTEN calendarios?", async () => { const r: any = await crmRequest("GET", `/calendars/?locationId=${LOC}`, { token: CTX.token, version: "v3" }); return { cuantos: r?.calendars?.length ?? 0, calendarios: (r?.calendars ?? []).map((c: any) => ({ id: c.id, name: c.name, isActive: c.isActive, })), }; }); await probe("GET /calendars/services/catalog — ¿hay catálogo de servicios?", async () => { const r: any = await crmRequest("GET", `/calendars/services/catalog?locationId=${LOC}`, { token: CTX.token, version: "v3", }); return r; }); await probe("POST /conversations/search — conversaciones", async () => { const r: any = await crmRequest("GET", `/conversations/search?locationId=${LOC}&limit=2`, { token: CTX.token }); const c = r?.conversations?.[0]; return { total: r?.total, claves_de_una_conversacion: c ? Object.keys(c).sort() : null, muestra: c ? { id: c.id, contactId: c.contactId, lastMessageType: c.lastMessageType, type: c.type } : null, }; }); const pipeline = (pipes ?? [])[0]; if (pipeline) { console.log( `\n>> Pipeline por defecto: ${pipeline.name} (${pipeline.id}) con ${pipeline.stages.length} etapas` ); } } main().catch((e) => { console.error(e); process.exit(1); });