import { ctxDe } from "./ctx.ts"; import { obtenerContacto } from "./contacts.ts"; import { upsertClienteDesdeCrm } from "./syncContacts.ts"; import { sincronizarConversacion } from "./syncConversations.ts"; import { obtenerMensaje } from "./conversations.ts"; import { proyectarCita } from "./syncAppointments.ts"; import { publicarServicio } from "./services.ts"; export const ENTIDADES = ["contacto", "conversacion", "mensaje", "cita", "servicio"] as const; export type Entidad = (typeof ENTIDADES)[number]; export function esEntidad(v: string): v is Entidad { return (ENTIDADES as readonly string[]).includes(v); } export interface ResultadoUno { entidad: Entidad; id: string; accion: string; detalle: Record; } /** * Sincroniza UNA entidad por su identificador. * * La dirección no es la misma para las cinco, y no es un capricho: * * - **contacto, conversación y mensaje se TRAEN**: el CRM es su dueño. Es donde * viven la deduplicación y las automatizaciones. * - **cita y servicio se EMPUJAN.** MEDIDO (hallazgos 29 y 32): el calendario * del CRM tiene UNA cita en dos años y su catálogo de servicios está vacío. * No hay nada que arrastrar; la agenda y el catálogo nacen en la plataforma. * * Si algún día el spa empieza a agendar dentro del CRM, esa premisa se cae y * habrá que decidir cuál de los dos manda cuando difieran. Conviene decidirlo * antes de que ocurra. */ export async function sincronizarPorId( businessId: number, entidad: Entidad, id: string ): Promise { switch (entidad) { case "contacto": { const ctx = await ctxDe(businessId); const c = await obtenerContacto(ctx, id); if (!c) throw { status: 404, error: "Ese contacto no existe en Bucéfalo CRM" }; const r = await upsertClienteDesdeCrm(businessId, c); return { entidad, id, accion: r === "created" ? "creado" : r === "updated" ? "actualizado" : "sin cambios", detalle: { resultado: r }, }; } case "conversacion": { const r = await sincronizarConversacion(businessId, id); return { entidad, id, accion: "espejada", detalle: r }; } case "mensaje": { const ctx = await ctxDe(businessId); const m = await obtenerMensaje(ctx, id); if (!m) throw { status: 404, error: "Ese mensaje no existe en Bucéfalo CRM" }; if (!m.conversationId) { throw { status: 409, error: "El mensaje no dice a qué conversación pertenece, y sin ella no se puede guardar", }; } // Se sincroniza el hilo entero: `messages.conversation_id` es obligatorio, // así que un mensaje suelto sin su conversación no tiene dónde ir. const r = await sincronizarConversacion(businessId, m.conversationId); return { entidad, id, accion: "espejado con su hilo", detalle: { ...r, conversacion_crm: m.conversationId }, }; } case "cita": { const n = Number(id); if (!Number.isFinite(n)) { throw { status: 400, error: "El identificador de la cita es el numérico de la plataforma" }; } const r = await proyectarCita(businessId, n); return { entidad, id, accion: "empujada", detalle: r as unknown as Record }; } case "servicio": { const n = Number(id); if (!Number.isFinite(n)) { throw { status: 400, error: "El identificador del servicio es el numérico de la plataforma", }; } const r = await publicarServicio(businessId, n); return { entidad, id, accion: r.yaEstaba ? "ya estaba publicado" : "publicado", detalle: r as unknown as Record, }; } } }