/** * MEDIDO: el CRM rechaza una segunda oportunidad para el mismo contacto aunque * la primera esté en `won` (400 OPPORTUNITY_NO_DUPLICATE). * * Antes de rediseñar el mapeo hay que saber si eso es un límite duro o un * ajuste de la subcuenta que el cliente puede cambiar. Tres sondeos: * a) ¿el ajuste aparece en la ficha de la subcuenta? * b) ¿el bloqueo es por (contacto, pipeline) o global por contacto? * c) ¿el buscador de oportunidades permite recuperar la existente para * reciclarla? — es el plan B, y tiene que funcionar sí o sí. */ 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(); const CONTACTO = "WzBTBaHkNnpmjMb1Avx3"; const ok = (s: string) => console.log(` ✔ ${s}`); const fail = (s: string) => console.log(` ✗ ${s}`); async function main() { console.log("── a) ¿La ficha de la subcuenta expone algún ajuste de duplicados?"); try { const r: any = await crmRequest("GET", `/locations/${LOC}`, { token: CTX.token }); const loc = r?.location ?? {}; const claves = Object.keys(loc).sort(); console.log(" claves:", claves.join(", ")); const settings = loc.settings ?? null; console.log(" settings:", JSON.stringify(settings, null, 2)); } catch (e: any) { fail(e.message); } console.log("\n── b) ¿Se pueden listar los pipelines y crear uno propio de spa?"); try { const r: any = await crmRequest("POST", "/opportunities/pipelines", { token: CTX.token, body: { locationId: LOC, name: "AgendaMax — Agenda Spa (prueba)" }, }); ok(`pipeline creado id=${r?.pipeline?.id ?? JSON.stringify(r).slice(0, 200)}`); console.log(" >> Se puede rediseñar el pipeline a etapas de spa desde la plataforma."); } catch (e: any) { fail(`${e.message} — ${JSON.stringify(e.body).slice(0, 250)}`); console.log(" >> El token no tiene `pipelines.create`; el pipeline se rediseña a mano."); } console.log("\n── c) PLAN B: recuperar la oportunidad existente para reciclarla"); try { const r: any = await crmRequest("GET", "/opportunities/search", { token: CTX.token, query: { location_id: LOC, contact_id: CONTACTO, limit: 20 }, }); const opps = r?.opportunities ?? []; ok(`el buscador devuelve ${opps.length} oportunidad(es) del contacto`); console.log( JSON.stringify( opps.map((o: any) => ({ id: o.id, name: o.name, status: o.status, monetaryValue: o.monetaryValue, pipelineId: o.pipelineId, pipelineStageId: o.pipelineStageId, })), null, 2 ) ); console.log(" >> Con esto se puede localizar y actualizar la existente en vez de crear."); } catch (e: any) { fail(`${e.message} — ${JSON.stringify(e.body).slice(0, 300)}`); } console.log("\n── d) ¿El PUT general permite renombrar y cambiar el importe (reciclar)?"); const OPP = "IMkYdAkBowggN9aKVbfc"; try { const nuevoNombre = `Pedicura — Prueba AgendaMax (reciclada ${Date.now()})`; await crmRequest("PUT", `/opportunities/${OPP}`, { token: CTX.token, body: { pipelineId: "Mrclt4VzRZV1DI4Vbt5c", name: nuevoNombre, monetaryValue: 400 }, }); const r: any = await crmRequest("GET", `/opportunities/${OPP}`, { token: CTX.token }); const o = r?.opportunity; if (o?.name === nuevoNombre && o?.monetaryValue === 400) { ok(`releído: nombre e importe reciclados (status sigue «${o.status}»)`); console.log(" >> PLAN B VIABLE: la oportunidad se reutiliza por clienta."); } else { fail(`al releer nombre=${o?.name} importe=${o?.monetaryValue}`); } } catch (e: any) { fail(`${e.message} — ${JSON.stringify(e.body).slice(0, 250)}`); } console.log("\n── e) ¿Y volver a abrirla (open) tras haberla cerrado?"); try { await crmRequest("PUT", `/opportunities/${OPP}/status`, { token: CTX.token, body: { status: "open" } }); const r: any = await crmRequest("GET", `/opportunities/${OPP}`, { token: CTX.token }); if (r?.opportunity?.status === "open") ok("sí: una oportunidad cerrada se puede reabrir"); else fail(`al releer status=${r?.opportunity?.status}`); } catch (e: any) { fail(e.message); } } main().catch((e) => { console.error(e); process.exit(1); });