From fb20cf8bd578240646aa98e775e09693e6d7015e Mon Sep 17 00:00:00 2001 From: urieljareth Date: Sat, 30 May 2026 20:16:12 -0600 Subject: [PATCH] =?UTF-8?q?descripci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-comparativa-auditoria-completa-buckets.md | 10 +- ...-incoherencia-canal-fuente-lead-digital.md | 94 + ...5-30-opp-marca-cf-vacios-mapeo-contacto.md | 63 + docs/casos/INDEX.md | 2 + n8n/_add_contact_to_opp_mapping.py | 233 + n8n/_add_fuente_reconcile.py | 255 + n8n/_add_redes_sociales_branch.py | 273 ++ ...ping_Cfgwp0bOtDW8zuKW_20260530_163623.json | 4148 ++++++++++++++++ ...ping_Cfgwp0bOtDW8zuKW_20260530_164248.json | 4148 ++++++++++++++++ ...ping_Cfgwp0bOtDW8zuKW_20260530_165900.json | 4228 +++++++++++++++++ ...ping_Cfgwp0bOtDW8zuKW_20260530_165946.json | 4228 +++++++++++++++++ ...cile_ddUEORBEtZLzsQF2_20260530_185416.json | 1903 ++++++++ ...cile_ddUEORBEtZLzsQF2_20260530_190108.json | 1903 ++++++++ ...ales_ddUEORBEtZLzsQF2_20260530_192327.json | 2069 ++++++++ ...ales_ddUEORBEtZLzsQF2_20260530_192343.json | 2069 ++++++++ ...yrun_Cfgwp0bOtDW8zuKW_20260530_163623.json | 2033 ++++++++ ...yrun_Cfgwp0bOtDW8zuKW_20260530_165900.json | 2041 ++++++++ ...yrun_ddUEORBEtZLzsQF2_20260530_185416.json | 955 ++++ ...yrun_ddUEORBEtZLzsQF2_20260530_192327.json | 1096 +++++ scripts/_investigate_erika.py | 82 + scripts/audit_origen_fuente_incoherencia.py | 137 + scripts/backfill_brand_opp_cf_from_source.py | 178 + scripts/fix_origen_fuente_incoherencia.py | 242 + 23 files changed, 32388 insertions(+), 2 deletions(-) create mode 100644 docs/casos/2026-05-30-incoherencia-canal-fuente-lead-digital.md create mode 100644 docs/casos/2026-05-30-opp-marca-cf-vacios-mapeo-contacto.md create mode 100644 n8n/_add_contact_to_opp_mapping.py create mode 100644 n8n/_add_fuente_reconcile.py create mode 100644 n8n/_add_redes_sociales_branch.py create mode 100644 n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_163623.json create mode 100644 n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_164248.json create mode 100644 n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_165900.json create mode 100644 n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_165946.json create mode 100644 n8n/backup_fuente_reconcile_ddUEORBEtZLzsQF2_20260530_185416.json create mode 100644 n8n/backup_fuente_reconcile_ddUEORBEtZLzsQF2_20260530_190108.json create mode 100644 n8n/backup_redes_sociales_ddUEORBEtZLzsQF2_20260530_192327.json create mode 100644 n8n/backup_redes_sociales_ddUEORBEtZLzsQF2_20260530_192343.json create mode 100644 n8n/dryrun_Cfgwp0bOtDW8zuKW_20260530_163623.json create mode 100644 n8n/dryrun_Cfgwp0bOtDW8zuKW_20260530_165900.json create mode 100644 n8n/dryrun_ddUEORBEtZLzsQF2_20260530_185416.json create mode 100644 n8n/dryrun_ddUEORBEtZLzsQF2_20260530_192327.json create mode 100644 scripts/_investigate_erika.py create mode 100644 scripts/audit_origen_fuente_incoherencia.py create mode 100644 scripts/backfill_brand_opp_cf_from_source.py create mode 100644 scripts/fix_origen_fuente_incoherencia.py diff --git a/docs/casos/2026-05-30-comparativa-auditoria-completa-buckets.md b/docs/casos/2026-05-30-comparativa-auditoria-completa-buckets.md index 23dcbef..c03072e 100644 --- a/docs/casos/2026-05-30-comparativa-auditoria-completa-buckets.md +++ b/docs/casos/2026-05-30-comparativa-auditoria-completa-buckets.md @@ -89,9 +89,15 @@ python -c "import sync_engine as se; a=next(x for x in se.parse_accounts_csv() i - Descomponer cualquier bucket grande por (expected_branch → actual_branch) con Counter antes de concluir "error de datos": muchas veces es mapeo del Verificador, no datos. - `cleanup_brand_duplicate_replica_opps.py --only-link ` es el camino seguro para el descuadre positivo de opps por doble réplica (verifica en vivo + snapshot + script_audit). +## ACCIÓN (3ª tanda — reasignación opp por homónimos) +Al ir a "crear la opp de miguel en Temixco" se descubrió que **NO procedía**: el lead digital $80,200 es de OTRO "Miguel Angel" (Eugenia `+525530454950`, contacto Marca `hE9U9Q`), distinto del Miguel-Temixco (`+527775114949`, contacto `RwxMQr0`). Ese lead ya estaba replicado (Eugenia `kGda02`↔Marca `1A3P5b`), pero `1A3P5b` colgaba del contacto EQUIVOCADO (`RwxMQr0`) porque la réplica n8n matcheó por nombre. `hE9U9Q` (su dueño real) tenía 0 opps. +- **Fix:** `PUT /opportunities/1A3P5b` con `contactId=hE9U9Q` (+ name/pipeline/stage/mv). **GHL v2 SÍ acepta cambiar contactId vía PUT** (status 200) — el gotcha de `build_brand_opp_payload` no aplicó. run_id `reassign-miguel-opp-20260530`, snapshot `reassign_miguel_opp_20260530_155526.json`. Verificado: RwxMQr0 solo $56,671, hE9U9Q solo $80,200. + ## PENDIENTES -- **test21** (fantasma de índice): GET 400 / search 200. No accionable; se auto-corrige cuando GHL reconcilie el índice. Sigue en `without_tienda` + `not_in_any_branch`. -- **Lead digital de miguel** (opp Marca 1A3P5b $80,200, sin link): es un lead digital creado directo en Marca que nunca bajó a su sucursal (Temixco). Idealmente lo baja la cascada n8n Marca→Sucursal. Queda solo-Marca; el conteo cuadra igual. Revisar si ese lead digital es válido y debe cascar. +- **test21** (fantasma de índice): GET 400 / search 200. No accionable; se auto-corrige cuando GHL reconcilie el índice. +- **Causa raíz n8n homónimos**: la réplica Sucursal→Marca debe matchear por teléfono/`id_contacto_sucursal` antes que por nombre (si no, vuelve a colgar opps de homónimos en el contacto equivocado). Ver [[matching_rules]], [[n8n_workflows_v2_hardened]]. +- ~~Leads digitales nuevos~~ **RESUELTO**: `David Arturo Vega` (Satélite) y `PER,LA MARIA VILLA` (La Viga) aparecieron ~15:56 (actividad viva) como descuadre +2. NO eran leads sin cascar: sus contactos+opps YA estaban en sucursal; el +2 eran **réplicas duplicadas en Marca sin link** (mismo patrón isai/Patricia, pero sin link compartido → caían en `missing_id_field`, no `duplicate_link`). Fix: (1) TIENDA llenada (`leads-tienda-20260530`); (2) borradas las 2 duplicadas sin link `LJPKIsd`+`OyRtrlGb`, conservadas las enlazadas `Mt9Dafz3`+`aZf3pzuc` (run_id `delete-dup-leads-opps-20260530`, snapshot `delete_dup_leads_opps_20260530_160744.json`). → opps 1342=1342, diff 0. +- Patrón a vigilar: el n8n de opps sigue generando réplicas duplicadas en Marca (Patricia, isai, PER,LA, David — 4 en un día). Unas con link compartido, otras sin link. La causa raíz (idempotencia) está en [[n8n_opp_idempotency_baserow_mapping]]; revisar por qué siguen apareciendo. ## ENLACES - Memorias: [[positive_opp_descuadre_double_replica]], [[verificador_tipo_de_tienda_colapso]], [[positive_descuadre_stale_cache]], [[name_account_with_location_id]], [[audit_hub_map_metepec_pilares]] diff --git a/docs/casos/2026-05-30-incoherencia-canal-fuente-lead-digital.md b/docs/casos/2026-05-30-incoherencia-canal-fuente-lead-digital.md new file mode 100644 index 0000000..302eefa --- /dev/null +++ b/docs/casos/2026-05-30-incoherencia-canal-fuente-lead-digital.md @@ -0,0 +1,94 @@ +--- +id: CASE-2026-05-30-incoherencia-canal-fuente-lead-digital +fecha: 2026-05-30 +categoria: custom_field, cascada_n8n, descuadre +location_ids: [GbKkBpCmKu2QmloKFHy3 (Monte Providencia/Marca), KEZ7dAhgwzK4uZfMvZuj (Puebla), uJEn2iuUficuml9zxAnt (Cancún), uZnMH5bO6MXTHcgHeyq9 (Pilares), nF1uEaYB3mCK5em9bPn2 (Eugenia), jE41bVhhnb5T505BFm4F (Morelia 1), WCHyow6KpjLFYriQWdbJ (Tampico)] +run_ids: [origen-fuente-3c50e43ec6a9, "n8n ddUEORBEtZLzsQF2 versionId 11c7184b→e8f6f33f (Patrón A)→37f780e1 (Patrón B)"] +snapshots: [n8n/backup_fuente_reconcile_ddUEORBEtZLzsQF2_20260530_190108.json, n8n/backup_redes_sociales_ddUEORBEtZLzsQF2_20260530_192343.json] +status: resuelto +memorias: [[incoherencia_canal_fuente_lead_digital]], [[super_script_fix_branch_user_origin]], [[n8n_2004_canal_origen_complemento]], [[createdby_only_in_individual_get]] +playbooks: [] +--- + +## TRIGGERS +- `LEAD DIGITAL no cuadra con FORMULARIO + FACEBOOK`, `Recuento de Contactos por CANAL DE ORIGEN vs Fuente de prospecto` +- `929 vs 901`, `descuadre 28 LEAD DIGITAL`, `Canal=SUCURSAL pero Fuente=LEAD DIGITAL`, viola AGENTS Cap.3 +- `REDES SOCIALES` fuente de prospecto (valor no canónico), Tampico 7 contactos +- `fix_branch_user_origin.py NO toca Fuente de Prospecto`, n8n `[2004]` pone Canal=SUCURSAL a WEB_USER sin reconciliar Fuente +- `createdBy.source WEB_USER MOBILE_USER` = captura manual sucursal; `source` de Marca (Formulario/Sucursal) lo estampa la replicación n8n y NO es fiable +- scripts: `audit_origen_fuente_incoherencia.py`, `fix_origen_fuente_incoherencia.py` + +## SÍNTOMA +Panel de Marca (GbKkBpCmKu2QmloKFHy3, 1328 contactos). Dos widgets descuadran: +- `Recuento por CANAL DE ORIGEN`: FORMULARIO 612, SUCURSAL 426, FACEBOOK 289 (digital = 901). +- `Fuente de prospecto`: LEAD DIGITAL 929, SUCURSAL 374, PROSPECCIÓN 11, REDES SOCIALES 7, CLIENTE CONOCIDO 3, ALIANZA 2, GALLARDETES 1. +Regla: LEAD DIGITAL debería = FORMULARIO + FACEBOOK. 929 ≠ 901 → descuadre +28. Además REDES SOCIALES sospechoso (canal digital mal clasificado). + +## DIAGNÓSTICO +Cross-tab Canal × Fuente desde la cache (`generated/data/mp_manager.sqlite`, `contacts.custom_fields_json` + `object_schemas`): +- FUENTE=LEAD DIGITAL (929) → 612 FORMULARIO + 289 FACEBOOK + **28 SUCURSAL** ← el descuadre. +- FUENTE=REDES SOCIALES (7) → 7 canal=SUCURSAL. +- PROSPECCIÓN/CLIENTE CONOCIDO/ALIANZA/GALLARDETES → todos canal=SUCURSAL (OK, fuentes de prospección manual válidas). + +Global (todas las cuentas): los 28 incoherentes de Marca = 16 Puebla + 4 Cancún + 3 Pilares + 3 Eugenia + 2 Morelia (suma 28 → replica 1:1 a Marca). Los 7 REDES SOCIALES = todos Tampico. **La incoherencia nace en sucursal, no es artefacto de replicación.** + +Callejón descartado: el `source` de los contactos en Marca decía 19 "Sucursal" / 9 "Formulario", sugiriendo partir los 28 en dos. PERO el `source` del lado **sucursal** era 26 null + 2 Formulario (no coincide). Verificación en vivo con `createdBy.source` (GET individual, `audit_origen_fuente_incoherencia.py --all`): **los 28 son WEB_USER/MOBILE_USER** (captura manual sucursal), incluidos los 9 que en Marca figuraban "Formulario". El `source` de Marca lo estampa la replicación n8n y NO es fiable; `createdBy.source` es el gold standard. + +## CAUSA RAÍZ +1. Workflow n8n `[2004]` (ddUEORBEtZLzsQF2) y su gemelo Python `fix_branch_user_origin.py` ponen `Canal de Origen=SUCURSAL` a los contactos creados por empleado (WEB_USER/MOBILE_USER) **pero deliberadamente NO tocan "Fuente de Prospecto"** ([fix_branch_user_origin.py:27-29,234](../../scripts/fix_branch_user_origin.py#L234)) para no pisar ALIANZA/PROSPECCIÓN. Efecto colateral: cuando voltean canal a SUCURSAL dejan el `LEAD DIGITAL` viejo huérfano → los 28 (Patrón A). +2. En Tampico se usó el valor no canónico `REDES SOCIALES` como Fuente de Prospecto en 7 contactos digitales (social), que quedaron con canal=SUCURSAL (Patrón B). + +## ACCIÓN +Decisión del usuario (2 rondas AskUserQuestion): Patrón A → todos a SUCURSAL (createdBy manda sobre source de Marca); Patrón B → FACEBOOK + LEAD DIGITAL. + +Script nuevo `scripts/fix_origen_fuente_incoherencia.py` (idempotente, detecta por estado actual, dry-run default, `script_audit` reversible, resuelve field ids por FIELD_ALIASES). Plan por patrón: +- A: contacto Fuente→SUCURSAL; opps: Canal de Origen de la Oportunidad→Sucursal, Fuente→SUCURSAL. +- B: contacto Canal→FACEBOOK + Fuente→LEAD DIGITAL; opps: Canal Opp→Facebook, Tipo de Lead→Lead digital, Fuente→LEAD DIGITAL. + +Secuencia (RUN_ID `origen-fuente-3c50e43ec6a9`): +``` +# dry-run (las 7 cuentas) -> A=28, B=7, 35 opps +# piloto Tampico (Patrón B) +python scripts/fix_origen_fuente_incoherencia.py --location WCHyow6KpjLFYriQWdbJ --apply --run-id origen-fuente-3c50e43ec6a9 +# verificación live 7/7 OK -> batch resto: +for LOC in GbKkBpCmKu2QmloKFHy3 KEZ7dAhgwzK4uZfMvZuj uJEn2iuUficuml9zxAnt uZnMH5bO6MXTHcgHeyq9 nF1uEaYB3mCK5em9bPn2 jE41bVhhnb5T505BFm4F; do + python scripts/fix_origen_fuente_incoherencia.py --location $LOC --apply --run-id origen-fuente-3c50e43ec6a9; done +``` +Marca en el batch reportó B=1 (no 7): los otros 6 REDES SOCIALES de Marca ya se habían corregido solos por replicación n8n desde Tampico. 0 errores en todas. + +## VERIFICACIÓN +Cross-tab **en vivo** en Marca (re-GET de 1328+ contactos vía API, no cache): +- LEAD DIGITAL = 908 = FORMULARIO 612 + FACEBOOK 296 → **COHERENTE** (antes 929 vs 901). +- canal=SUCURSAL & fuente=LEAD DIGITAL: **0** (antes 28). +- REDES SOCIALES: **0** (antes 7). +- Canal SUCURSAL 419 (antes 426; −7 que pasaron a FACEBOOK). +Piloto Tampico verificado 7/7 (canal=FACEBOOK, fuente=LEAD DIGITAL sostenido; n8n [2004] no revirtió en el momento). + +## EDGE-CASES / TRAMPAS +- **NO confiar en `contact.source` de Marca** para clasificar origen: la replicación n8n lo estampa (Formulario/Sucursal) y diverge del lado sucursal. Usar `createdBy.source` en vivo (GET individual). +- Tampico tiene los campos con capitalización distinta (`CANAL DE ORIGEN`): resolver field ids **case-insensitive** / por FIELD_ALIASES, nunca por nombre exacto. +- Falso positivo en verificación: match difuso de nombre ("edgar") atrapó "edgar alejandro pozos" y "edgard radiadores" (PROSPECCIÓN/SUCURSAL, no eran target). Filtrar por id, no por substring de nombre. +- Riesgo de reversión del Patrón B: los 7 de Tampico son WEB_USER y n8n [2004] re-pone canal=SUCURSAL a WEB_USER. Si se re-editan, podrían revertir. Pendiente endurecer [2004]. + +## REUTILIZABLE +- `python scripts/audit_origen_fuente_incoherencia.py --all` → clasifica incoherentes con createdBy en vivo (read-only). +- `python scripts/fix_origen_fuente_incoherencia.py --all` (dry-run) / `--apply --run-id ` → corrige + propaga a opps. Idempotente: re-correr no re-toca. +- Rollback: dashboard → run `origen-fuente-3c50e43ec6a9`. + +## CAUSA RAÍZ — CIERRE EN TIEMPO REAL (2026-05-30) +Endurecido el workflow n8n **[2004]** (ddUEORBEtZLzsQF2) con `n8n/_add_fuente_reconcile.py` (backup→dry-run→apply→verify→E2E). En la rama "Creado por usuario" (esUsuario=true), tras `Tag- facebook-ads` se añadió: IF `fuenteEsLeadDigital` → PUT Fuente de Prospecto=SUCURSAL. El Code node se extendió para resolver el field `contact.fuente_de_prospecto` y leer su VALOR ACTUAL del GET del contacto (`contact.customFields[].value`), exponiendo `fuente`/`fuenteActual`/`fuenteEsLeadDigital`. Solo reconcilia si vale exactamente LEAD DIGITAL → preserva ALIANZA/PROSPECCIÓN/etc. versionId `11c7184b`→`e8f6f33f`, 19→21 nodos. Backup `n8n/backup_fuente_reconcile_ddUEORBEtZLzsQF2_20260530_190108.json`. + +**E2E en vivo (Cancún, esau sotelo do3ClHt57hfHj0hw4tKk, WEB_USER):** (1) ensuciado FORMULARIO/LEAD DIGITAL → webhook 8d574598 → **SUCURSAL/SUCURSAL** ✓. (2) ensuciado FORMULARIO/PROSPECCIÓN → webhook → **SUCURSAL/PROSPECCIÓN intacta** ✓ (preservación). Contacto restaurado a su estado real SUCURSAL/SUCURSAL. + +## CAUSA RAÍZ PATRÓN B — CIERRE EN TIEMPO REAL (2026-05-30) +Cerrado con `n8n/_add_redes_sociales_branch.py`. En la rama esUsuario, ANTES del PUT canal=SUCURSAL, se intercaló IF "Fuente es REDES SOCIALES": [true]→PUT Canal=FACEBOOK→PUT Fuente=LEAD DIGITAL (camino digital, igual que el fix de datos Patrón B); [false]→PUT Canal=SUCURSAL (camino existente intacto). Code node expone `fuenteEsRedesSociales`. versionId `e8f6f33f`→`37f780e1`, 21→24 nodos. Backup `n8n/backup_redes_sociales_ddUEORBEtZLzsQF2_20260530_192343.json`. +**E2E vivo (Cancún, esau):** T1 SUCURSAL/REDES SOCIALES→**FACEBOOK/LEAD DIGITAL** ✓; T2 regresión FORMULARIO/LEAD DIGITAL→**SUCURSAL/SUCURSAL** ✓ (la rama Patrón A sigue intacta por el false branch). Restaurado. + +## PENDIENTES +1. [HECHO 2026-05-30] Causa raíz Patrón A cerrada en [2004] (`_add_fuente_reconcile.py`). Nota: `fix_branch_user_origin.py` (batch) sigue sin reconciliar Fuente, pero `fix_origen_fuente_incoherencia.py` cubre el backlog y [2004] cubre lo nuevo en tiempo real. +2. [HECHO 2026-05-30] Causa raíz Patrón B (REDES SOCIALES) cerrada en [2004] (`_add_redes_sociales_branch.py`, ver sección arriba). +3. Re-sync de la cache local de las 7 cuentas (el panel GHL ya está bien; la cache SQLite quedó stale para los 35 contactos). + +## ENLACES +- Memorias: [[incoherencia_canal_fuente_lead_digital]], [[super_script_fix_branch_user_origin]], [[n8n_2004_canal_origen_complemento]], [[createdby_only_in_individual_get]], [[sucursal_tag_on_digital_leads]] +- Scripts: `scripts/fix_origen_fuente_incoherencia.py`, `scripts/audit_origen_fuente_incoherencia.py`, `n8n/_add_fuente_reconcile.py` (cierre raíz Patrón A [2004]), `n8n/_add_redes_sociales_branch.py` (cierre raíz Patrón B [2004]), `n8n/_add_canal_origen_branch.py` (rama previa), `scripts/fix_branch_user_origin.py`, `scripts/fuente_prospecto_workflow.py`, `scripts/tag_canal_origen_workflow.py`, `scripts/canal_origen_resolver.py` diff --git a/docs/casos/2026-05-30-opp-marca-cf-vacios-mapeo-contacto.md b/docs/casos/2026-05-30-opp-marca-cf-vacios-mapeo-contacto.md new file mode 100644 index 0000000..43da78c --- /dev/null +++ b/docs/casos/2026-05-30-opp-marca-cf-vacios-mapeo-contacto.md @@ -0,0 +1,63 @@ +--- +id: CASE-2026-05-30-opp-marca-cf-vacios-mapeo-contacto +fecha: 2026-05-30 +categoria: cascada_n8n +location_ids: ["GbKkBpCmKu2QmloKFHy3 (Monte Providencia / Marca)", "yjqKxoO02rsdwdJZSPmD (85950 - MP - Temixco)"] +run_ids: ["backfill-opp-cf-20260530-164821"] +snapshots: ["generated/migrations/backfill_opp_cf_8HITkGkOn3gN23Tl8LBr_backfill-opp-cf-20260530-164821.json"] +status: resuelto +memorias: ["[[n8n_opp_sync_match]]", "[[opp_multiplicity_replication_gap]]", "[[sucursal_to_marca_cf_drop_on_create]]", "[[sucursal_datatype_divergence_intentional]]"] +playbooks: ["docs/PLAYBOOK_ENLACE_OPORTUNIDADES.md"] +--- + +## TRIGGERS +- opp de Marca con `Sucursal` / `TIENDA` / `Canal de Origen` (CANAL DE ORIGEN) **vacíos** +- "desde sucursal no se están trayendo los datos de las oportunidades" +- opp de Marca con **solo** `opportunity.id_oportunidad_sucursal` y ningún otro CF (opp "solo-link") +- workflow `Cfgwp0bOtDW8zuKW` "Sincronizar Oportunidad - Nodos Nuevos (Create/Update)" +- `8HITkGkOn3gN23Tl8LBr` (opp Marca), `OQBrOQN9mNlybjlzB8Jk` (opp Temixco), Miguel Angel `mahernandez2282@gmail.com` +- `opportunity.fuente_de_posible_cliente` == CANAL DE ORIGEN (opp y contacto comparten el sufijo de fieldKey) + +## SÍNTOMA +La opp de Marca `8HITkGkOn3gN23Tl8LBr` (Miguel Angel, viene de Temixco) tenía vacíos Sucursal, TIENDA y Canal de Origen. Hipótesis del owner: la réplica n8n no mapea esos campos. + +## DIAGNÓSTICO (read-only) +- `get_opportunities(GbKkBpCmKu2QmloKFHy3)` agregado por CF: de **1342** opps de Marca, `opportunity.sucursal` poblado en **1341**, `tienda` 1295, `fuente_de_posible_cliente` 711. → el mapeo genérico SÍ funciona; `8HIT` era la **única** opp "solo-link". +- Los nodos `Armar Body - CREATE` y `Armar Body - UPDATE (v2)` ya copian *todos* los CF de la opp de sucursal → Marca por `fieldKey` (fallback `name`), enriquecidos por `Mapeo completo oportunidad origen - SUCURSAL`. +- La opp de sucursal `OQBrOQN9…` SÍ tiene `opportunity.sucursal`="Temixco, Morelos", `tienda`="TEMIXCO", pero **NO** tiene `fuente_de_posible_cliente` (CANAL DE ORIGEN). El **contacto** de Temixco (`eE6374FcwI7zlmQmTgGO`) sí: `contact.fuente_de_posible_cliente`="SUCURSAL". +- Schema en vivo: el `fieldKey` canónico (sufijo) es **idéntico** contact↔opportunity: `*.sucursal`, `*.tienda`, `*.fuente_de_posible_cliente`. `contact.sucursal` es SINGLE_OPTIONS pero su label = el texto-ciudad que `opportunity.sucursal` (TEXT) espera ([[sucursal_datatype_divergence_intentional]]). + +## CAUSA RAÍZ +1. **Caso puntual `8HIT`**: opp creada fuera del flujo normal (creación manual + `backfill_opp_sucursal_link.py`, que solo escribe el link) — por eso quedó solo-link. No es bug del mapeo genérico. +2. **Gap general**: cuando la opp de sucursal NO trae un CF (p.ej. CANAL DE ORIGEN, que vive solo en el contacto, o por carrera de tiempo intermitente [[sucursal_to_marca_cf_drop_on_create]]), Marca queda sin ese campo. El contacto es la fuente estable que faltaba consultar. + +## ACCIÓN +1. **Workflow `Cfgwp0bOtDW8zuKW`** (script `n8n/_add_contact_to_opp_mapping.py`, dry-run → confirmación → `--apply`): + - Nodo nuevo `Obtener Contacto - SUCURSAL` (GET `/contacts/{{ Datos de Lead.Cliente['Contact ID'] }}`, token sucursal, `onError=continue`) insertado entre `Obtener info de Oportunidad - SUCURSAL` y `Obtener Pipelines - SUCURSAL`. + - Code `Mapeo completo oportunidad origen - SUCURSAL` extendido: upsert de `opportunity.sucursal` / `tienda` / `fuente_de_posible_cliente` con prioridad **(a)** opp sucursal → **(b)** contacto → **(c)** webhook. El loop genérico de `Armar Body` lo propaga a Marca por `fieldKey` (sin race lectura/escritura). + - Aplicado y verificado (`verify_post`), workflow reactivado. Backups en `n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_*.json`. +2. **Backfill `8HIT`** (`scripts/backfill_brand_opp_cf_from_source.py --brand-opp-id 8HITkGkOn3gN23Tl8LBr --branch-location-id yjqKxoO02rsdwdJZSPmD --apply`): rellenó 5 CF vacíos desde la opp de sucursal + contacto. `run_id=backfill-opp-cf-20260530-164821` (reversible desde dashboard). + +## VERIFICACIÓN +`GET /opportunities/8HITkGkOn3gN23Tl8LBr` (Marca) — después: +- Sucursal = `Temixco, Morelos`, TIENDA = `TEMIXCO`, CANAL DE ORIGEN = `SUCURSAL` (del contacto), Fuente de Prospecto = `LEAD DIGITAL`, Vehículo = `March 2014`, link intacto. + +## EDGE-CASES / TRAMPAS +- **No confundir** `opportunity.fuente_de_prospecto` (LEAD DIGITAL / REFERIDO / REDES SOCIALES) con `opportunity.fuente_de_posible_cliente` (= CANAL DE ORIGEN: SUCURSAL / FACEBOOK / FORMULARIO / WHATSAPP). Mismo nombre "Fuente…" en GHL, fieldKeys distintos. +- **No** sourcear `8HIT` del contacto de **Marca** (`RwxMQr0`): por el lío de homónimos su `contact.sucursal`="Narvarte Oriente…" (incorrecto para Temixco). La opp de **sucursal** es la fuente limpia. +- El valor de CANAL DE ORIGEN debe ser opción válida del picklist de Marca (`SUCURSAL` lo es). [[custom_fields_picklist_alignment]] +- El backfill solo rellena campos **vacíos** (no sobreescribe). + +## REUTILIZABLE +- Agregado por CF para detectar opps "solo-link" en Marca: cargar el dump de `get_opportunities` y contar ids de CF (1342 vs 1341 reveló el outlier). +- `scripts/backfill_brand_opp_cf_from_source.py`: backfill genérico de CF descriptivos de una opp de Marca desde su opp de sucursal enlazada (+ contacto como respaldo), dry-run/snapshot/audit. + +## PENDIENTES +- Validar E2E el cambio del workflow en la **próxima sincronización real** de una opp de Temixco (o construir un E2E para `Cfgwp0bOtDW8zuKW`, hoy el harness `scripts/n8n_e2e_test.py` solo cubre los workflows de contactos). +- (Opcional 2ª iteración) Persistir también los 3 campos en la opp de **sucursal** vía el PUT de `Mapear ID Oportunidad Sucursal - SUCURSAL`. +- Re-sync de Marca para refrescar el cache SQLite del dashboard. + +## ENLACES +- [[n8n_opp_sync_match]] · [[opp_multiplicity_replication_gap]] · [[sucursal_to_marca_cf_drop_on_create]] · [[sucursal_datatype_divergence_intentional]] · [[custom_fields_picklist_alignment]] +- `n8n/_add_contact_to_opp_mapping.py` · `scripts/backfill_brand_opp_cf_from_source.py` +- docs/PLAYBOOK_ENLACE_OPORTUNIDADES.md diff --git a/docs/casos/INDEX.md b/docs/casos/INDEX.md index 118e47b..2314b24 100644 --- a/docs/casos/INDEX.md +++ b/docs/casos/INDEX.md @@ -34,6 +34,8 @@ No registres: lecturas triviales, consultas de un solo dato, o trabajo puramente | Fecha | ID | Categoría | Triggers (keywords para grep) | Status | Enlace | |---|---|---|---|---|---| +| 2026-05-30 | CASE-2026-05-30-incoherencia-canal-fuente-lead-digital | custom_field, cascada_n8n, descuadre | `LEAD DIGITAL no cuadra con FORMULARIO + FACEBOOK`, `929 vs 901 descuadre 28`, `Canal=SUCURSAL pero Fuente=LEAD DIGITAL`, `REDES SOCIALES fuente no canónica Tampico`, `fix_branch_user_origin NO toca Fuente`, `[2004] pone Canal=SUCURSAL a WEB_USER sin reconciliar Fuente`, `source de Marca lo estampa n8n no es fiable usar createdBy.source`, `fix_origen_fuente_incoherencia.py`, `audit_origen_fuente_incoherencia.py`, `run origen-fuente-3c50e43ec6a9` | resuelto | [caso](2026-05-30-incoherencia-canal-fuente-lead-digital.md) | +| 2026-05-30 | CASE-2026-05-30-opp-marca-cf-vacios-mapeo-contacto | cascada_n8n, custom_field | `opp Marca Sucursal TIENDA Canal de Origen vacíos`, `desde sucursal no se traen datos de oportunidades`, `opp solo-link solo id_oportunidad_sucursal`, `Cfgwp0bOtDW8zuKW mapear campos del contacto a la opp`, `Obtener Contacto - SUCURSAL nodo nuevo`, `Mapeo completo oportunidad origen - SUCURSAL`, `8HITkGkOn3gN23Tl8LBr OQBrOQN9mNlybjlzB8Jk Miguel mahernandez2282`, `fuente_de_posible_cliente CANAL DE ORIGEN del contacto`, `backfill_brand_opp_cf_from_source.py` | resuelto | [caso](2026-05-30-opp-marca-cf-vacios-mapeo-contacto.md) | | 2026-05-30 | CASE-2026-05-30-comparativa-auditoria-completa-buckets | descuadre, cascada_n8n, config_location | `Comparativa Marca vs Sucursales auditar todos los buckets`, `opps +1 opportunities_in_brand_duplicate_link`, `present_in_other_branch_not_assigned 84 falsos positivos`, `TIENDA=METEPEC vive en Pilares 85935`, `DIGITAL_HUB_BY_SHELL hub-map en código`, `audit lee Verificador CSV no Baserow`, `PATRICIA PARRA zzBzWC4adBrdTA8WhQph réplica abandoned $0`, `redirect Windows cp1252 no utf-8`, `re-sync Marca antes de re-auditar` | resuelto | [caso](2026-05-30-comparativa-auditoria-completa-buckets.md) | | 2026-05-30 | CASE-2026-05-30-descuadre-opp-deadlink | descuadre, cascada_n8n | `DIFERENCIA OPORTUNIDADES +2`, `descuadre positivo todos los buckets de opps en 0`, `opp Marca link muerto GET 400 Opportunity doesn't exist or is deleted`, `réplica obsoleta n8n no borró id rotado`, `contacto 2 opps Marca 1 sola viva sucursal`, `cleanup scripts no atrapan dead-link único`, `re-enlazar vs borrar opp 1:1` | resuelto | [caso](2026-05-30-descuadre-opp-deadlink.md) | | 2026-05-30 | CASE-2026-05-30-descuadre-opp-replica-duplicada-marca | descuadre, cascada_n8n, duplicado | `DIFERENCIA OPORTUNIDADES +10`, `descuadre positivo Marca > Sucursales`, `réplicas duplicadas mismo ID Oportunidad Sucursal`, `dos opps Marca misma opp sucursal`, `opportunities_in_brand_duplicate_link`, `Cfgwp0bOtDW8zuKW CREATE en vez de UPDATE`, `Decidir Match Create vs Update`, `cleanup_brand_orphan cero huérfanas pero descuadre`, `antelmo anselmo mismo teléfono distinto contacto`, `cleanup_brand_duplicate_replica_opps.py` | parcial | [caso](2026-05-30-descuadre-opp-replica-duplicada-marca.md) | diff --git a/n8n/_add_contact_to_opp_mapping.py b/n8n/_add_contact_to_opp_mapping.py new file mode 100644 index 0000000..12f7877 --- /dev/null +++ b/n8n/_add_contact_to_opp_mapping.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Garantiza que la opp sincronizada a Marca traiga Sucursal / TIENDA / Canal de +Origen, derivándolos del CONTACTO de sucursal cuando la opp de origen no los +trae (workflow `Cfgwp0bOtDW8zuKW` "Sincronizar Oportunidad - Nodos Nuevos"). + +Contexto: los nodos `Armar Body - CREATE/UPDATE` ya copian TODOS los custom +fields de la opp de sucursal → Marca por `fieldKey`. Pero si la opp de origen +llega sin esos CF (carrera de tiempo / creación fuera de flujo), la opp de Marca +queda vacía (caso `8HIT…` de Miguel Temixco). El CONTACTO siempre los tiene +(poblados por [1604]/[2004]) y usa el MISMO `fieldKey` canónico que la opp: + contact.sucursal -> opportunity.sucursal + contact.tienda -> opportunity.tienda + contact.fuente_de_posible_cliente-> opportunity.fuente_de_posible_cliente (CANAL DE ORIGEN) + +Cambios (solo AÑADE / enriquece, preserva el flujo actual): + 1. Nodo nuevo `Obtener Contacto - SUCURSAL` (GET /contacts/{id}) insertado + entre `Obtener info de Oportunidad - SUCURSAL` y `Obtener Pipelines - SUCURSAL`. + 2. Extiende el Code node `Mapeo completo oportunidad origen - SUCURSAL` para, + tras enriquecer los CF de la opp, hacer upsert de Sucursal/TIENDA/Canal con + prioridad (a) valor de la opp → (b) valor del contacto → (c) webhook. + El loop genérico de `Armar Body` los propaga a Marca por `fieldKey`. + No depende de leer-después-de-escribir (sin race). + +Uso: + python n8n/_add_contact_to_opp_mapping.py # dry-run (dumpea payload) + python n8n/_add_contact_to_opp_mapping.py --apply # aplica + reactiva +""" + +import argparse +import os +import sys + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if ROOT_DIR not in sys.path: + sys.path.insert(0, ROOT_DIR) + +from scripts.n8n_workflow_lib import N8NClient, load_credentials # noqa: E402 + +WID = "Cfgwp0bOtDW8zuKW" + +# Nodos existentes (referencias). +MAPEO_NODE = "Mapeo completo oportunidad origen - SUCURSAL" +OBTENER_INFO_NODE = "Obtener info de Oportunidad - SUCURSAL" +OBTENER_PIPELINES_NODE = "Obtener Pipelines - SUCURSAL" +DATOS_API_SUCURSAL = "DATOS API - SUCURSAL" +CF_DEFS_NODE = "Conseguir Custom Fields - Opportunity - SUCURSAL" + +# Nodo nuevo. +GET_CONTACT_NODE = "Obtener Contacto - SUCURSAL" + +# Marca de idempotencia dentro del jsCode extendido. +ENRICH_MARKER = "CONTACT->OPP ENRICH" + +CONTACT_ID_EXPR = "{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}" +TOKEN_EXPR = "{{ $('" + DATOS_API_SUCURSAL + "').item.json['Token/API'] }}" + +# Bloque que se inyecta en el Code node, justo antes de `const stageInfo`. +ENRICH_BLOCK = r""" +// ── CONTACT->OPP ENRICH: garantizar Sucursal / TIENDA / Canal de Origen ── +// Respaldo cuando la opp de sucursal no trae el CF: tomar el valor del CONTACTO +// (siempre poblado por [1604]/[2004]) y, en ultimo caso, del webhook. El fieldKey +// canonico (sufijo) es identico en contact y opportunity. +const contactResp = $('Obtener Contacto - SUCURSAL').first().json; +const contact = (contactResp && contactResp.contact) ? contactResp.contact : (contactResp || {}); +const webhookBody = ($('Webhook').first().json && $('Webhook').first().json.body) || {}; +const norm2 = (s) => (s == null ? '' : String(s)).trim(); + +// Valores del contacto por fieldKey (contacts traen {id, value}). +const contactValueByFieldKey = {}; +for (const cf of (contact.customFields || [])) { + const fk = fieldMap[cf.id] ? fieldMap[cf.id].fieldKey : null; + if (fk) contactValueByFieldKey[fk] = cf.value; +} + +const ENRICH_TARGETS = [ + { oppKey: 'opportunity.sucursal', contactKey: 'contact.sucursal', webhookKey: 'Sucursal' }, + { oppKey: 'opportunity.tienda', contactKey: 'contact.tienda', webhookKey: 'TIENDA' }, + { oppKey: 'opportunity.fuente_de_posible_cliente', contactKey: 'contact.fuente_de_posible_cliente', webhookKey: 'CANAL DE ORIGEN' }, +]; + +for (const t of ENRICH_TARGETS) { + const existing = enrichedCustomFields.find(cf => cf.fieldKey === t.oppKey); + if (existing && norm2(existing.fieldValue) !== '') continue; // (a) ya viene de la opp + let value = norm2(contactValueByFieldKey[t.contactKey]); // (b) contacto + if (value === '') value = norm2(webhookBody[t.webhookKey]); // (c) webhook + if (value === '') continue; + if (existing) { + existing.fieldValue = value; + } else { + const def = customFieldsDefs.find(d => d.fieldKey === t.oppKey); + enrichedCustomFields.push({ id: def ? def.id : null, name: def ? def.name : null, fieldKey: t.oppKey, fieldValue: value }); + } +} +// ── fin CONTACT->OPP ENRICH ── +""" + + +def ensure_model_all(cf_node): + """Asegura que `Conseguir Custom Fields…` pase ?model=all (sin esto el + endpoint /locations/{id}/customFields solo devuelve campos de CONTACTO, y el + mapeo genérico descarta TODOS los CF nativos de la opp). Devuelve True si + cambió algo.""" + p = cf_node.setdefault("parameters", {}) + p["sendQuery"] = True + qp = p.setdefault("queryParameters", {}) + params = qp.setdefault("parameters", []) + for entry in params: + if entry.get("name") == "model": + if entry.get("value") == "all": + return False + entry["value"] = "all" + return True + params.append({"name": "model", "value": "all"}) + return True + + +def build_get_contact_node(): + return { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/" + CONTACT_ID_EXPR, + "sendHeaders": True, + "headerParameters": { + "parameters": [ + {"name": "Accept", "value": "application/json"}, + {"name": "Version", "value": "2021-07-28"}, + {"name": "Authorization", "value": "=Bearer " + TOKEN_EXPR}, + ] + }, + "options": {"redirect": {"redirect": {}}}, + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [1750, -32], + "name": GET_CONTACT_NODE, + "onError": "continueRegularOutput", + "notes": "Trae el contacto de sucursal para derivar Sucursal/TIENDA/Canal de Origen de la opp cuando la opp de origen no los trae. onError=continue para no romper el sync.", + } + + +def inject_enrich(js_code): + """Inserta ENRICH_BLOCK antes de `const stageInfo` (anchor estable).""" + anchor_candidates = [ + "// Resolve pipeline and stage info", + "const stageInfo =", + ] + for anchor in anchor_candidates: + idx = js_code.find(anchor) + if idx != -1: + return js_code[:idx] + ENRICH_BLOCK + "\n" + js_code[idx:] + raise SystemExit( + "No se encontró un anchor para inyectar el bloque de enriquecimiento " + "en el jsCode del nodo Mapeo. Revisar el Code node manualmente." + ) + + +def main(): + parser = argparse.ArgumentParser( + description="Mapea Sucursal/TIENDA/Canal de Origen del contacto a la opp en el workflow Cfgwp0bOtDW8zuKW." + ) + parser.add_argument("--apply", action="store_true", help="Aplica el PUT real (sin esto: dry-run).") + args = parser.parse_args() + + client = N8NClient(*load_credentials()) + wf, backup_path = client.backup_workflow(WID, label="contact_to_opp_mapping") + prev_version = wf.get("versionId") + print(f"Workflow: {wf.get('name')}") + print(f" active={wf.get('active')} nodes={len(wf.get('nodes') or [])} versionId={prev_version}") + print(f" backup -> {backup_path}") + + # Validar que existen los nodos de referencia. + for nm in (MAPEO_NODE, OBTENER_INFO_NODE, OBTENER_PIPELINES_NODE, DATOS_API_SUCURSAL, CF_DEFS_NODE): + if client.find_node(wf, nm) is None: + raise SystemExit(f"No se encontró el nodo esperado {nm!r}.") + + # Idempotencia. + code_node = client.find_node(wf, MAPEO_NODE) + cf_node = client.find_node(wf, CF_DEFS_NODE) + already_node = client.find_node(wf, GET_CONTACT_NODE) is not None + already_code = ENRICH_MARKER in (code_node["parameters"].get("jsCode") or "") + cf_params = (cf_node.get("parameters") or {}).get("queryParameters", {}).get("parameters", []) + already_query = any(e.get("name") == "model" and e.get("value") == "all" for e in cf_params) + if already_node and already_code and already_query: + raise SystemExit("El complemento ya fue aplicado (nodo + jsCode + model=all). Nada que hacer.") + + # 0. Asegurar ?model=all en el fetch de custom fields (trae defs de opp). + if ensure_model_all(cf_node): + print(f" Nodo {CF_DEFS_NODE!r}: query 'model=all' añadido (ahora trae defs de opportunity).") + else: + print(f" Nodo {CF_DEFS_NODE!r} ya tenía 'model=all'.") + + # 1. Nodo nuevo GET contacto + cableado. + if not already_node: + client.add_node(wf, build_get_contact_node()) + client.insert_between(wf, OBTENER_INFO_NODE, GET_CONTACT_NODE, OBTENER_PIPELINES_NODE) + print(f" Nodo {GET_CONTACT_NODE!r} insertado entre " + f"{OBTENER_INFO_NODE!r} y {OBTENER_PIPELINES_NODE!r}.") + else: + print(f" Nodo {GET_CONTACT_NODE!r} ya existía (no se re-inserta).") + + # 2. Enriquecer el Code node. + if not already_code: + code_node["parameters"]["jsCode"] = inject_enrich(code_node["parameters"]["jsCode"]) + print(f" Code node {MAPEO_NODE!r}: bloque de enriquecimiento inyectado.") + else: + print(f" Code node {MAPEO_NODE!r} ya tenía el bloque (no se re-inyecta).") + + expected = [GET_CONTACT_NODE] + + if not args.apply: + res = client.put_workflow(WID, wf, dry_run=True) + print(f"\nDRY-RUN. Payload -> {res['path']} ({res['node_count']} nodos).") + print("Revisa el JSON y vuelve a correr con --apply para aplicar.") + return + + was_active = bool(wf.get("active")) + if was_active: + try: + client.deactivate(WID) + print(" Workflow desactivado para PUT estructural.") + except Exception as exc: + print(f" WARN al desactivar: {exc}") + client.put_workflow(WID, wf, dry_run=False) + print(" PUT aplicado.") + if was_active: + client.activate(WID) + print(" Workflow reactivado.") + client.verify_post(WID, expected_node_names=expected, prev_version_id=prev_version) + print("\nOK: complemento aplicado y verificado. Backup en:", backup_path) + + +if __name__ == "__main__": + main() diff --git a/n8n/_add_fuente_reconcile.py b/n8n/_add_fuente_reconcile.py new file mode 100644 index 0000000..9f5fd15 --- /dev/null +++ b/n8n/_add_fuente_reconcile.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Endurece el workflow n8n [2004] (ddUEORBEtZLzsQF2) para CERRAR LA CAUSA RAÍZ +del Patrón A (Canal=SUCURSAL & Fuente=LEAD DIGITAL). + +Contexto: la rama "Creado por usuario" (añadida por `_add_canal_origen_branch.py`) +pone Canal de Origen=SUCURSAL a los contactos WEB_USER/MOBILE_USER pero NO toca +Fuente de Prospecto, dejando huérfano un `LEAD DIGITAL` previo -> incoherencia. + +Este complemento (solo AÑADE, preserva el flujo) reconcilia la Fuente SOLO cuando +vale exactamente "LEAD DIGITAL" (preserva ALIANZA / PROSPECCIÓN / CLIENTE CONOCIDO +/ etc., que con Canal=SUCURSAL son válidas): + + 1. Extiende el Code node para resolver el field "Fuente de Prospecto" + (fieldKey `contact.fuente_de_prospecto`, fallback por nombre) y leer su VALOR + ACTUAL del GET individual del contacto -> expone `fuente`, `fuenteActual`, + `fuenteEsLeadDigital`. + 2. Tras `Tag- facebook-ads` añade: + IF "Fuente = LEAD DIGITAL (reconciliar)" -> [true] PUT Fuente=SUCURSAL + [false] (fin: no se toca). + +Solo corre dentro de la rama esUsuario=true (ya gateada aguas arriba por el IF +"Creado por usuario"). El [2004] dispara al CREAR contacto, así que no revierte +ediciones manuales posteriores. + +Uso: + python n8n/_add_fuente_reconcile.py # dry-run (dumpea payload) + python n8n/_add_fuente_reconcile.py --apply # aplica + reactiva +""" + +import argparse +import os +import sys + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if ROOT_DIR not in sys.path: + sys.path.insert(0, ROOT_DIR) + +from scripts.n8n_workflow_lib import N8NClient, load_credentials # noqa: E402 + +WID = "ddUEORBEtZLzsQF2" + +# Nodos existentes (referencias). +CODE_NODE = 'Buscar "contact.sucursal" y "contact.tienda"' +GET_CONTACT_NODE = "Obtener Contacto Cuenta Origen - SUCURSAL" +VERIFICADOR_NODE = "Buscar Sucursal en Verificador de Sucursales" +TAG_RM_FB_NODE = "Tag- facebook-ads" # último nodo de la rama canal de origen + +# Nodos nuevos. +IF_FUENTE_NODE = "Fuente = LEAD DIGITAL (reconciliar)" +PUT_FUENTE_NODE = "PUT Fuente de Prospecto = SUCURSAL" + +CONTACT_ID_EXPR = "{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}" +TOKEN_EXPR = "{{ $('" + VERIFICADOR_NODE + "').item.json['SC TOKEN BUCEFALO'] }}" +CODE_REF = "$('" + CODE_NODE + "').item.json" + +HEADERS = [ + {"name": "Accept", "value": "application/json"}, + {"name": "Version", "value": "2021-07-28"}, + {"name": "Authorization", "value": "=Bearer " + TOKEN_EXPR}, +] + +# Code node extendido: preserva VERBATIM la lógica sucursal/tienda/canal/esUsuario +# y AÑADE la resolución de Fuente + su valor actual + flag fuenteEsLeadDigital. +NEW_JSCODE = r"""const customFields = $input.first().json.customFields; + +function findField(key, names) { + let f = customFields.find(x => x.fieldKey === key); + if (!f) { + const wanted = names.map(n => n.toLowerCase().trim()); + f = customFields.find(x => wanted.includes((x.name || "").toLowerCase().trim())); + } + return f || null; +} + +const sucursalField = findField("contact.sucursal", ["Sucursal"]); +const tiendaField = findField("contact.tienda", ["TIENDA", "Tienda"]); +const canalField = findField("contact.fuente_de_posible_cliente", ["CANAL DE ORIGEN", "Canal de Origen"]); +const fuenteField = findField("contact.fuente_de_prospecto", ["Fuente de Prospecto", "FUENTE DE PROSPECTO"]); + +// createdBy.source SOLO viene del GET individual del contacto. +const contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json; +const createdBySource = + (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) || + (contactResp && contactResp.createdBy && contactResp.createdBy.source) || + null; +const esUsuario = createdBySource === "WEB_USER" || createdBySource === "MOBILE_USER"; + +// Valor ACTUAL de "Fuente de Prospecto" en el contacto (para reconciliar SOLO si = LEAD DIGITAL). +const contactCFs = + (contactResp && contactResp.contact && contactResp.contact.customFields) || + (contactResp && contactResp.customFields) || + []; +let fuenteActual = null; +if (fuenteField && fuenteField.id) { + const hit = contactCFs.find(cf => cf.id === fuenteField.id); + fuenteActual = hit ? (hit.value != null ? hit.value : (hit.fieldValue != null ? hit.fieldValue : null)) : null; +} +const fuenteEsLeadDigital = String(fuenteActual || "").trim().toUpperCase() === "LEAD DIGITAL"; + +return [ + { + json: { + sucursal: { + id: sucursalField?.id ?? null, + name: sucursalField?.name ?? null, + fieldKey: sucursalField?.fieldKey ?? null, + picklistOptions: sucursalField?.picklistOptions ?? [], + }, + tienda: { + id: tiendaField?.id ?? null, + name: tiendaField?.name ?? null, + fieldKey: tiendaField?.fieldKey ?? null, + picklistOptions: tiendaField?.picklistOptions ?? [], + }, + canal: { + id: canalField?.id ?? null, + name: canalField?.name ?? null, + fieldKey: canalField?.fieldKey ?? null, + picklistOptions: canalField?.picklistOptions ?? [], + }, + fuente: { + id: fuenteField?.id ?? null, + name: fuenteField?.name ?? null, + fieldKey: fuenteField?.fieldKey ?? null, + picklistOptions: fuenteField?.picklistOptions ?? [], + }, + createdBySource: createdBySource, + esUsuario: esUsuario, + fuenteActual: fuenteActual, + fuenteEsLeadDigital: fuenteEsLeadDigital, + }, + }, +];""" + + +def build_nodes(): + if_node = { + "parameters": { + "conditions": { + "options": {"caseSensitive": True, "leftValue": "", "typeValidation": "loose", "version": 3}, + "conditions": [ + { + "id": "fuente-es-lead-digital", + "leftValue": "={{ " + CODE_REF + ".fuenteEsLeadDigital }}", + "rightValue": "", + "operator": {"type": "boolean", "operation": "true", "singleValue": True}, + } + ], + "combinator": "and", + }, + "options": {}, + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [1820, -120], + "name": IF_FUENTE_NODE, + } + + fuente_body = ( + "={\n" + ' "customFields": [\n' + " {\n" + ' "id": "{{ ' + CODE_REF + '.fuente.id }}",\n' + ' "key": "{{ ' + CODE_REF + '.fuente.fieldKey }}",\n' + ' "field_value": "SUCURSAL"\n' + " }\n" + " ]\n" + "}" + ) + put_fuente = { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/" + CONTACT_ID_EXPR, + "sendHeaders": True, + "headerParameters": {"parameters": [dict(h) for h in HEADERS]}, + "sendBody": True, + "specifyBody": "json", + "jsonBody": fuente_body, + "options": {"redirect": {"redirect": {}}}, + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [2040, -120], + "name": PUT_FUENTE_NODE, + } + return if_node, put_fuente + + +def main(): + parser = argparse.ArgumentParser(description="Reconcilia Fuente=LEAD DIGITAL->SUCURSAL en [2004].") + parser.add_argument("--apply", action="store_true", help="Aplica el PUT real (sin esto: dry-run).") + args = parser.parse_args() + + client = N8NClient(*load_credentials()) + wf, backup_path = client.backup_workflow(WID, label="fuente_reconcile") + prev_version = wf.get("versionId") + print(f"Workflow: {wf.get('name')}") + print(f" active={wf.get('active')} nodes={len(wf.get('nodes') or [])} versionId={prev_version}") + print(f" backup -> {backup_path}") + + # Idempotencia. + for nm in (IF_FUENTE_NODE, PUT_FUENTE_NODE): + if client.find_node(wf, nm) is not None: + raise SystemExit(f"El nodo {nm!r} ya existe; el complemento ya fue aplicado. Nada que hacer.") + + # Pre-requisito: la rama canal de origen debe existir (TAG_RM_FB_NODE es su cola). + if client.find_node(wf, TAG_RM_FB_NODE) is None: + raise SystemExit(f"No existe {TAG_RM_FB_NODE!r}; corre antes _add_canal_origen_branch.py.") + + # 1. Extender el Code node (preserva lógica previa, añade fuente). + code_node = client.find_node(wf, CODE_NODE) + if code_node is None: + raise SystemExit(f"No se encontró el Code node {CODE_NODE!r}.") + code_node["parameters"]["jsCode"] = NEW_JSCODE + print(f" Code node {CODE_NODE!r}: jsCode extendido (fuente + fuenteEsLeadDigital).") + + # 2. Añadir nodos. + if_node, put_fuente = build_nodes() + for n in (if_node, put_fuente): + client.assert_idempotent(wf, n["name"]) + client.add_node(wf, n) + print(" Nodos añadidos: IF Fuente + PUT Fuente=SUCURSAL.") + + # 3. Conexiones: Tag- facebook-ads -> IF -> [true] PUT Fuente. [false] fin. + client.set_connection(wf, TAG_RM_FB_NODE, IF_FUENTE_NODE) + client.set_connection(wf, IF_FUENTE_NODE, PUT_FUENTE_NODE, output_index=0) + print(" Conexiones cableadas.") + + expected = [IF_FUENTE_NODE, PUT_FUENTE_NODE] + + if not args.apply: + res = client.put_workflow(WID, wf, dry_run=True) + print(f"\nDRY-RUN. Payload -> {res['path']} ({res['node_count']} nodos).") + print("Revisa el JSON y vuelve a correr con --apply para aplicar.") + return + + was_active = bool(wf.get("active")) + if was_active: + try: + client.deactivate(WID) + print(" Workflow desactivado para PUT estructural.") + except Exception as exc: + print(f" WARN al desactivar: {exc}") + client.put_workflow(WID, wf, dry_run=False) + print(" PUT aplicado.") + if was_active: + client.activate(WID) + print(" Workflow reactivado.") + client.verify_post(WID, expected_node_names=expected, prev_version_id=prev_version) + print("\nOK: complemento aplicado y verificado. Backup en:", backup_path) + + +if __name__ == "__main__": + main() diff --git a/n8n/_add_redes_sociales_branch.py b/n8n/_add_redes_sociales_branch.py new file mode 100644 index 0000000..8fa404c --- /dev/null +++ b/n8n/_add_redes_sociales_branch.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Cierra el Patrón B (Fuente=REDES SOCIALES) en el workflow n8n [2004] +(ddUEORBEtZLzsQF2). + +Contexto: la rama "Creado por usuario" pone Canal=SUCURSAL a los WEB_USER/ +MOBILE_USER. Para un contacto con Fuente=REDES SOCIALES eso es incoherente +(redes sociales = canal digital). Decisión de negocio (2026-05-30): tratarlos +como digital -> Canal=FACEBOOK + Fuente=LEAD DIGITAL (igual que hizo el fix de +datos `scripts/fix_origen_fuente_incoherencia.py` Patrón B). + +Cambio (solo AÑADE / re-cablea, preserva el camino SUCURSAL existente): + 1. Extiende el Code node para exponer `fuenteEsRedesSociales`. + 2. Intercala, en la rama true del IF "Creado por usuario", un IF + "Fuente es REDES SOCIALES": + [true] -> PUT Canal=FACEBOOK -> PUT Fuente=LEAD DIGITAL (camino digital) + [false] -> PUT Canal de Origen = SUCURSAL (camino existente intacto) + + Antes: Creado por usuario --true--> [PUT Canal=SUCURSAL -> tags -> IF LEAD DIGITAL -> PUT Fuente=SUCURSAL] + Ahora: Creado por usuario --true--> IF REDES SOCIALES + --true--> PUT Canal=FACEBOOK -> PUT Fuente=LEAD DIGITAL + --false-> [PUT Canal=SUCURSAL -> ... (igual que antes)] + +Solo toca custom fields del contacto (no tags ni opps), igual que el fix de datos. +[2004] dispara al CREAR contacto. + +Uso: + python n8n/_add_redes_sociales_branch.py # dry-run (dumpea payload) + python n8n/_add_redes_sociales_branch.py --apply # aplica + reactiva +""" + +import argparse +import os +import sys + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if ROOT_DIR not in sys.path: + sys.path.insert(0, ROOT_DIR) + +from scripts.n8n_workflow_lib import N8NClient, load_credentials # noqa: E402 + +WID = "ddUEORBEtZLzsQF2" + +# Nodos existentes. +CODE_NODE = 'Buscar "contact.sucursal" y "contact.tienda"' +VERIFICADOR_NODE = "Buscar Sucursal en Verificador de Sucursales" +IF_USUARIO_NODE = "Creado por usuario (Canal de Origen)" +PUT_CANAL_SUCURSAL_NODE = "PUT Canal de Origen = SUCURSAL" + +# Nodos nuevos. +IF_REDES_NODE = "Fuente es REDES SOCIALES" +PUT_CANAL_FB_NODE = "PUT Canal de Origen = FACEBOOK" +PUT_FUENTE_LD_NODE = "PUT Fuente de Prospecto = LEAD DIGITAL" + +CONTACT_ID_EXPR = "{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}" +TOKEN_EXPR = "{{ $('" + VERIFICADOR_NODE + "').item.json['SC TOKEN BUCEFALO'] }}" +CODE_REF = "$('" + CODE_NODE + "').item.json" + +HEADERS = [ + {"name": "Accept", "value": "application/json"}, + {"name": "Version", "value": "2021-07-28"}, + {"name": "Authorization", "value": "=Bearer " + TOKEN_EXPR}, +] + +# Code node: versión con fuente (de _add_fuente_reconcile.py) + flag fuenteEsRedesSociales. +NEW_JSCODE = r"""const customFields = $input.first().json.customFields; + +function findField(key, names) { + let f = customFields.find(x => x.fieldKey === key); + if (!f) { + const wanted = names.map(n => n.toLowerCase().trim()); + f = customFields.find(x => wanted.includes((x.name || "").toLowerCase().trim())); + } + return f || null; +} + +const sucursalField = findField("contact.sucursal", ["Sucursal"]); +const tiendaField = findField("contact.tienda", ["TIENDA", "Tienda"]); +const canalField = findField("contact.fuente_de_posible_cliente", ["CANAL DE ORIGEN", "Canal de Origen"]); +const fuenteField = findField("contact.fuente_de_prospecto", ["Fuente de Prospecto", "FUENTE DE PROSPECTO"]); + +// createdBy.source SOLO viene del GET individual del contacto. +const contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json; +const createdBySource = + (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) || + (contactResp && contactResp.createdBy && contactResp.createdBy.source) || + null; +const esUsuario = createdBySource === "WEB_USER" || createdBySource === "MOBILE_USER"; + +// Valor ACTUAL de "Fuente de Prospecto" en el contacto. +const contactCFs = + (contactResp && contactResp.contact && contactResp.contact.customFields) || + (contactResp && contactResp.customFields) || + []; +let fuenteActual = null; +if (fuenteField && fuenteField.id) { + const hit = contactCFs.find(cf => cf.id === fuenteField.id); + fuenteActual = hit ? (hit.value != null ? hit.value : (hit.fieldValue != null ? hit.fieldValue : null)) : null; +} +const fuenteNorm = String(fuenteActual || "").trim().toUpperCase(); +const fuenteEsLeadDigital = fuenteNorm === "LEAD DIGITAL"; +const fuenteEsRedesSociales = fuenteNorm === "REDES SOCIALES"; + +return [ + { + json: { + sucursal: { + id: sucursalField?.id ?? null, + name: sucursalField?.name ?? null, + fieldKey: sucursalField?.fieldKey ?? null, + picklistOptions: sucursalField?.picklistOptions ?? [], + }, + tienda: { + id: tiendaField?.id ?? null, + name: tiendaField?.name ?? null, + fieldKey: tiendaField?.fieldKey ?? null, + picklistOptions: tiendaField?.picklistOptions ?? [], + }, + canal: { + id: canalField?.id ?? null, + name: canalField?.name ?? null, + fieldKey: canalField?.fieldKey ?? null, + picklistOptions: canalField?.picklistOptions ?? [], + }, + fuente: { + id: fuenteField?.id ?? null, + name: fuenteField?.name ?? null, + fieldKey: fuenteField?.fieldKey ?? null, + picklistOptions: fuenteField?.picklistOptions ?? [], + }, + createdBySource: createdBySource, + esUsuario: esUsuario, + fuenteActual: fuenteActual, + fuenteEsLeadDigital: fuenteEsLeadDigital, + fuenteEsRedesSociales: fuenteEsRedesSociales, + }, + }, +];""" + + +def put_cf_node(name, position, *, code_field, value): + """Nodo httpRequest PUT /contacts/{id} que setea un custom field por id+key.""" + body = ( + "={\n" + ' "customFields": [\n' + " {\n" + ' "id": "{{ ' + CODE_REF + "." + code_field + '.id }}",\n' + ' "key": "{{ ' + CODE_REF + "." + code_field + '.fieldKey }}",\n' + ' "field_value": "' + value + '"\n' + " }\n" + " ]\n" + "}" + ) + return { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/" + CONTACT_ID_EXPR, + "sendHeaders": True, + "headerParameters": {"parameters": [dict(h) for h in HEADERS]}, + "sendBody": True, + "specifyBody": "json", + "jsonBody": body, + "options": {"redirect": {"redirect": {}}}, + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": position, + "name": name, + } + + +def build_nodes(): + if_redes = { + "parameters": { + "conditions": { + "options": {"caseSensitive": True, "leftValue": "", "typeValidation": "loose", "version": 3}, + "conditions": [ + { + "id": "fuente-es-redes-sociales", + "leftValue": "={{ " + CODE_REF + ".fuenteEsRedesSociales }}", + "rightValue": "", + "operator": {"type": "boolean", "operation": "true", "singleValue": True}, + } + ], + "combinator": "and", + }, + "options": {}, + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [820, -320], + "name": IF_REDES_NODE, + } + put_canal_fb = put_cf_node(PUT_CANAL_FB_NODE, [1040, -320], code_field="canal", value="FACEBOOK") + put_fuente_ld = put_cf_node(PUT_FUENTE_LD_NODE, [1260, -320], code_field="fuente", value="LEAD DIGITAL") + return if_redes, put_canal_fb, put_fuente_ld + + +def main(): + parser = argparse.ArgumentParser(description="Cierra Patrón B (REDES SOCIALES) en [2004].") + parser.add_argument("--apply", action="store_true", help="Aplica el PUT real (sin esto: dry-run).") + args = parser.parse_args() + + client = N8NClient(*load_credentials()) + wf, backup_path = client.backup_workflow(WID, label="redes_sociales") + prev_version = wf.get("versionId") + print(f"Workflow: {wf.get('name')}") + print(f" active={wf.get('active')} nodes={len(wf.get('nodes') or [])} versionId={prev_version}") + print(f" backup -> {backup_path}") + + # Idempotencia. + for nm in (IF_REDES_NODE, PUT_CANAL_FB_NODE, PUT_FUENTE_LD_NODE): + if client.find_node(wf, nm) is not None: + raise SystemExit(f"El nodo {nm!r} ya existe; ya fue aplicado. Nada que hacer.") + + # Pre-requisitos. + for nm in (IF_USUARIO_NODE, PUT_CANAL_SUCURSAL_NODE): + if client.find_node(wf, nm) is None: + raise SystemExit(f"No existe {nm!r}; corre antes _add_canal_origen_branch.py.") + + # Verifica que el IF "Creado por usuario" hoy conecta su rama true a PUT Canal=SUCURSAL. + cur = client.get_connection_targets(wf, IF_USUARIO_NODE, output_index=0) + cur_names = [t.get("node") for t in cur] + if cur_names != [PUT_CANAL_SUCURSAL_NODE]: + raise SystemExit(f"Topología inesperada: {IF_USUARIO_NODE!r} true -> {cur_names} (esperaba [{PUT_CANAL_SUCURSAL_NODE!r}]).") + + # 1. Extender Code node. + code_node = client.find_node(wf, CODE_NODE) + code_node["parameters"]["jsCode"] = NEW_JSCODE + print(f" Code node {CODE_NODE!r}: jsCode extendido (fuenteEsRedesSociales).") + + # 2. Añadir nodos. + if_redes, put_canal_fb, put_fuente_ld = build_nodes() + for n in (if_redes, put_canal_fb, put_fuente_ld): + client.assert_idempotent(wf, n["name"]) + client.add_node(wf, n) + print(" Nodos añadidos: IF REDES + PUT Canal=FACEBOOK + PUT Fuente=LEAD DIGITAL.") + + # 3. Re-cablear: Creado por usuario[true] -> IF REDES; + # IF REDES[true] -> PUT Canal=FACEBOOK -> PUT Fuente=LEAD DIGITAL; + # IF REDES[false] -> PUT Canal=SUCURSAL (camino existente). + client.set_connection(wf, IF_USUARIO_NODE, IF_REDES_NODE, output_index=0) + client.set_connection(wf, IF_REDES_NODE, PUT_CANAL_FB_NODE, output_index=0) + client.set_connection(wf, IF_REDES_NODE, PUT_CANAL_SUCURSAL_NODE, output_index=1) + client.set_connection(wf, PUT_CANAL_FB_NODE, PUT_FUENTE_LD_NODE) + print(" Conexiones re-cableadas.") + + expected = [IF_REDES_NODE, PUT_CANAL_FB_NODE, PUT_FUENTE_LD_NODE] + + if not args.apply: + res = client.put_workflow(WID, wf, dry_run=True) + print(f"\nDRY-RUN. Payload -> {res['path']} ({res['node_count']} nodos).") + print("Revisa el JSON y vuelve a correr con --apply para aplicar.") + return + + was_active = bool(wf.get("active")) + if was_active: + try: + client.deactivate(WID) + print(" Workflow desactivado para PUT estructural.") + except Exception as exc: + print(f" WARN al desactivar: {exc}") + client.put_workflow(WID, wf, dry_run=False) + print(" PUT aplicado.") + if was_active: + client.activate(WID) + print(" Workflow reactivado.") + client.verify_post(WID, expected_node_names=expected, prev_version_id=prev_version) + print("\nOK: Patrón B cerrado y verificado. Backup en:", backup_path) + + +if __name__ == "__main__": + main() diff --git a/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_163623.json b/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_163623.json new file mode 100644 index 0000000..abbee52 --- /dev/null +++ b/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_163623.json @@ -0,0 +1,4148 @@ +{ + "updatedAt": "2026-05-30T22:21:10.460Z", + "createdAt": "2026-03-02T01:12:56.387Z", + "id": "Cfgwp0bOtDW8zuKW", + "name": "Sincronizar Oportunidad - Nodos Nuevos (Create/Update)", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "3eecb45a-e24c-49ac-aef7-99843f485819", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -336, + -16 + ], + "id": "dd424583-8295-4013-96ba-c7e29ea944c6", + "name": "Webhook", + "webhookId": "3eecb45a-e24c-49ac-aef7-99843f485819" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "b02bc821-6121-452d-a042-623fac6e443c", + "name": "Oportunidad.opportunity_name", + "value": "={{ $json.body.opportunity_name }}", + "type": "string" + }, + { + "id": "898817a2-7926-4ddf-b785-cac8e28afc58", + "name": "Oportunidad.pipleline_stage (new)", + "value": "={{ $json.body.pipleline_stage }}", + "type": "string" + }, + { + "id": "opp-id-sucursal-capture", + "name": "Oportunidad.opportunity_id", + "value": "={{ $json.body.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -96, + -16 + ], + "id": "2b3ec130-27ce-4988-82db-46e4f12c3f7d", + "name": "Datos de Lead" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE SUCURSAL", + "height": 352, + "width": 1152 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1072, + -144 + ], + "typeVersion": 1, + "id": "19023e33-a9f1-4c60-b87c-957e751e3dc2", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE MARCA", + "height": 208, + "width": 1188, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2240, + -80 + ], + "typeVersion": 1, + "id": "3a3d4f18-1a08-4dbc-a00b-7ae6bc4d49d1", + "name": "Sticky Note1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2480, + -32 + ], + "id": "a46df506-6a9a-420d-9cdd-6fc51d1a691e", + "name": "DATOS API" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 80, + -16 + ], + "id": "25db982f-7bc7-4318-a7b0-e94326abc437", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3952, + -32 + ], + "id": "2320b102-816e-4a0c-ae9c-e305d25df6df", + "name": "Actualizar Oportunidad - MARCA2", + "notes": "El código mapea los custom fields de una oportunidad de una sucursal hacia los IDs equivalentes de una marca. Toma tres fuentes: los metadatos de campos de la sucursal, los valores reales de esos campos en la oportunidad, y los metadatos de campos de la marca. Para cada campo con valor en la oportunidad, primero busca su `fieldKey` en los metadatos de la sucursal usando el `id`, luego usa ese `fieldKey` para encontrar el campo equivalente en la marca, y construye un array con el `id` y `key` de la marca pero con el valor original de la sucursal. Finalmente arma el body de una petición para crear/actualizar una oportunidad en la marca, reutilizando pipeline, nombre, stage y valor monetario de otros nodos, con los custom fields ya traducidos al contexto de la marca." + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1536, + -32 + ], + "id": "4e89658b-7ffd-4a3b-b91e-9ed3223a1a73", + "name": "Buscar Oportunidad - SUCURSAL", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 528, + -32 + ], + "id": "b721a65d-bcbb-44ab-bb7f-8f200b34fcdd", + "name": "DATOS API - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1136, + -32 + ], + "id": "39ef9dcc-ac20-4ba2-b891-2a2baa6ea4f5", + "name": "Conseguir Custom Fields - Opportunity - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1712, + -32 + ], + "id": "fbd46ae4-792b-451b-b99e-9816dc175d52", + "name": "Obtener info de Oportunidad - SUCURSAL" + }, + { + "parameters": { + "content": "# ACTUALIZAR OPORTUNIDAD - MARCA", + "height": 352, + "width": 928 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3456, + -144 + ], + "typeVersion": 1, + "id": "f3ca1cfe-08e2-485b-b238-9240f5753809", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "", + "height": 208, + "width": 992, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2768, + -64 + ], + "typeVersion": 1, + "id": "45066721-8404-48ed-94c1-c88edf48bca5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "", + "height": 192, + "width": 928, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1104, + -64 + ], + "typeVersion": 1, + "id": "b1646bb4-8b64-4bf4-b380-189cc7902741", + "name": "Sticky Note7" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Webhook').item.json.body.location.name }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 320, + -32 + ], + "id": "12088f29-8bb4-475d-ae0e-2cbdf8033138", + "name": "API de Sucursal", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "=Monte Providencia" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 2288, + -32 + ], + "id": "8c8fa238-6ce4-4adf-8926-b603987a55cf", + "name": "API de MARCA", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "jsCode": "const opportunityData = $('Obtener info de Oportunidad - SUCURSAL').first().json;\nconst opportunity = opportunityData.opportunity;\nconst customFieldsDefs = $('Conseguir Custom Fields - Opportunity - SUCURSAL').first().json.customFields;\nconst pipelines = $input.first().json.pipelines;\n\n// Map custom fields: id -> { fieldKey, name }\nconst fieldMap = {};\nfor (const def of customFieldsDefs) {\n fieldMap[def.id] = { fieldKey: def.fieldKey, name: def.name };\n}\n\n// Map pipelines: id -> name, stages: id -> { name, position, stageWinProbability }\nconst pipelineMap = {};\nconst stageMap = {};\nfor (const pipeline of pipelines) {\n pipelineMap[pipeline.id] = pipeline.name;\n for (const stage of pipeline.stages) {\n stageMap[stage.id] = {\n name: stage.name,\n position: stage.position,\n stageWinProbability: stage.stageWinProbability,\n color: stage.color || null\n };\n }\n}\n\n// Enrich custom fields with fieldKey and name\nconst enrichedCustomFields = opportunity.customFields.map(cf => ({\n id: cf.id,\n name: fieldMap[cf.id]?.name || null,\n fieldKey: fieldMap[cf.id]?.fieldKey || null,\n fieldValue: cf.fieldValue\n}));\n\n// Resolve pipeline and stage info\nconst stageInfo = stageMap[opportunity.pipelineStageId] || {};\n\n// Build enriched opportunity\nconst enrichedOpportunity = {\n ...opportunity,\n pipelineName: pipelineMap[opportunity.pipelineId] || null,\n pipelineStageName: stageInfo.name || null,\n pipelineStagePosition: stageInfo.position ?? null,\n pipelineStageWinProbability: stageInfo.stageWinProbability ?? null,\n pipelineStageColor: stageInfo.color || null,\n customFields: enrichedCustomFields\n};\n\nreturn [{ json: { opportunity: enrichedOpportunity, traceId: opportunityData.traceId } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + -32 + ], + "id": "d9609dd9-9493-4f3d-b356-99438cedada6", + "name": "Mapeo completo oportunidad origen - SUCURSAL" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('Webhook').item.json.body.location.id }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de Sucursal').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1872, + -32 + ], + "id": "2d799ae3-b6b1-4e4f-8774-714ebe4f009f", + "name": "Obtener Pipelines - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Buscar Oportunidad - MARCA').item.json.opportunities[0].id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3216, + -32 + ], + "id": "1eee43d2-c6a8-414b-bebf-4b31d6fb34d7", + "name": "Obtener info de Oportunidad - MARCA" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('API de MARCA').item.json.Location_ID }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de MARCA').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3424, + -32 + ], + "id": "20a1a288-2cd8-471b-9203-5ba88770edb0", + "name": "Obtener Pipelines - MARCA" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2800, + -32 + ], + "id": "798a7e51-b8ed-4364-947a-7396fab47940", + "name": "Buscar Oportunidad - MARCA", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3008, + -32 + ], + "id": "cc37ea9b-0a9f-44ce-8ce2-ee2444822ecf", + "name": "Conseguir Custom Fields - Opportunity - MARCA" + }, + { + "parameters": { + "jsCode": "// ── DATOS MARCA ──────────────────────────────────────────────────────────────\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA').first().json.customFields;\nconst marcaPipelines = $input.first().json.pipelines;\n\n// ── DATOS SUCURSAL (ya enriquecidos) ─────────────────────────────────────────\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\n\n// ── MAP PIPELINES MARCA: name -> { id, stages } ───────────────────────────────\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[pipeline.name.trim()] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[stage.name.trim()] = stage.id;\n }\n}\n\n// ── RESOLVER PIPELINE Y STAGE POR NAME DESDE SUCURSAL ────────────────────────\nconst resolvedPipelineId = marcaPipelineByName[sucursalOpp.pipelineName?.trim()] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[sucursalOpp.pipelineStageName?.trim()] || marcaOpp.pipelineStageId;\n\n// ── MAP CUSTOM FIELDS SUCURSAL: name -> fieldValue ────────────────────────────\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields) {\n if (cf.name) {\n sucursalValueByName[cf.name.trim()] = cf.fieldValue;\n }\n}\n\n// ── CONSTRUIR CUSTOM FIELDS PARA EL BODY ─────────────────────────────────────\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n const nameKey = def.name.trim();\n const sucursalValue = sucursalValueByName[nameKey];\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n const fieldValue = sucursalValue !== undefined ? sucursalValue : (marcaOriginal?.fieldValue ?? null);\n\n if (fieldValue === null) continue;\n\n customFields.push({\n id: def.id,\n key: def.fieldKey,\n field_value: fieldValue\n });\n}\n\n// ── CONSTRUIR BODY VÁLIDO PARA UPDATE OPPORTUNITY ────────────────────────────\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source,\n customFields: customFields\n};\n\nreturn [{ json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body)\n} }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3632, + -32 + ], + "id": "3b547c2a-a541-4b48-8d97-ea2d5df52010", + "name": "Code in JavaScript" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"pageLimit\": 10,\n \"query\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono || $('Datos de Lead').item.json.Cliente.Email }}\"\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3360, + 960 + ], + "id": "02cd0fd6-6bb8-4faa-b85f-5d42663fba29", + "name": "Buscar Contacto en MARCA", + "notes": "Busca contacto en la location de marca por email.\nSi no encuentra, la siguiente IF deriva a creación." + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "exist-contact-check", + "leftValue": "={{ $json.contacts ? $json.contacts.length : 0 }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3600, + 960 + ], + "id": "aa2644dc-34b4-4159-8afb-89147b12f5ee", + "name": "¿Existe contacto en MARCA?" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/upsert", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"firstName\": \"{{ $('Datos de Lead').item.json.Cliente.Apellido }}\",\n \"name\": \"{{ $('Datos de Lead').item.json.Cliente['Nombre Completo'] }}\",\n \"email\": \"{{ $('Datos de Lead').item.json.Cliente.Email }}\",\n \"phone\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono }}\",\n \"source\": \"Sincronización Sucursal\",\n \"tags\": [\"sincronizado-sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3840, + 1104 + ], + "id": "f6c75352-3b9e-4fc9-b8e4-063c2792d190", + "name": "Crear Contacto en MARCA", + "notes": "Crea contacto en la marca solo si no existía.\nIMPORTANTE: revisa el mapeo de Nombre/Apellido, en el workflow original están invertidos." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "contact-id-resolved", + "name": "contactId", + "value": "={{ $json.contact ? $json.contact.id : $json.contacts[0].id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 4080, + 960 + ], + "id": "2a49d01e-6679-4584-9d9b-5778dfc4431e", + "name": "Set Contact ID Resuelto", + "notes": "Unifica el contactId tanto si vino de la búsqueda (contacts[0].id) como de la creación (contact.id)." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "location_id", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + }, + { + "name": "contact_id", + "value": "={{ $('Set Contact ID Resuelto').item.json.contactId }}" + }, + { + "name": "limit", + "value": "100" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 4320, + 960 + ], + "id": "8e25f660-3ed2-4597-9c2a-7480ac7d150f", + "name": "Buscar Oportunidades del Contacto - MARCA", + "notes": "Busca TODAS las oportunidades del contacto en la marca.\nDespués el Code de decisión elige cuál (o crea nueva)." + }, + { + "parameters": { + "jsCode": "// DECISION (Create vs Update) con idempotencia GLOBAL via mapeo Baserow (tabla 754).\n// (1) Si el mapeo id_opp_sucursal -> id_opp_marca existe -> UPDATE esa opp de Marca,\n// independiente del contacto resuelto (evita duplicados por contacto ambiguo).\n// (2) Si no hay mapeo, match por CF entre las opps del contacto resuelto.\n// (3) Si nada -> CREATE.\nconst result = $input.first().json;\nconst opportunities = result.opportunities || [];\nconst sucursalOppId = $('Datos de Lead').first().json.Oportunidad.opportunity_id;\n\nlet action = 'CREATE';\nlet opportunityId = null;\nlet matchReason = '';\n\n// (1) Mapeo Baserow global\nlet baserowRows = [];\ntry { baserowRows = $('Buscar Mapeo Opp - Baserow').all().map(i => i.json); } catch (e) { baserowRows = []; }\nconst mapped = baserowRows.find(r =>\n String(r.id_opp_sucursal || '') === String(sucursalOppId) && String(r.id_opp_marca || '').trim()\n);\n\nif (mapped) {\n action = 'UPDATE';\n opportunityId = String(mapped.id_opp_marca).trim();\n matchReason = 'Match por mapeo Baserow (global, tabla 754)';\n} else {\n // (2) match por CF entre las opps del contacto resuelto\n const match = opportunities.find(o =>\n (o.customFields || []).some(cf => {\n const v = cf.fieldValueString ?? cf.fieldValue ?? '';\n return String(v) === String(sucursalOppId);\n })\n );\n if (match) {\n action = 'UPDATE';\n opportunityId = match.id;\n matchReason = 'Match por ID Oportunidad Sucursal (contacto)';\n } else {\n action = 'CREATE';\n matchReason = 'Sin match (Baserow ni contacto) -> CREATE (contacto tiene ' + opportunities.length + ' opps)';\n }\n}\n\nreturn [{\n json: {\n action,\n opportunityId,\n matchReason,\n sucursalOppId,\n contactId: $('Set Contact ID Resuelto').first().json.contactId,\n totalOppsContacto: opportunities.length\n }\n}];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4560, + 960 + ], + "id": "6ca30f13-8503-427f-95ec-b243d30688ec", + "name": "Decidir Match (Create vs Update)" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-update", + "leftValue": "={{ $json.action }}", + "rightValue": "UPDATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "UPDATE" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-create", + "leftValue": "={{ $json.action }}", + "rightValue": "CREATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "CREATE" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 4800, + 960 + ], + "id": "bff7a5dc-b4af-4033-83ea-c508aa92264a", + "name": "Switch UPDATE vs CREATE" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Decidir Match (Create vs Update)').item.json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5040, + 816 + ], + "id": "83cd0fd7-1ee9-4896-926e-a0b752e216fe", + "name": "Obtener info de Oportunidad - MARCA (v2)", + "notes": "Sólo se ejecuta en rama UPDATE.\nReemplaza el nodo original 'Obtener info de Oportunidad - MARCA' que dependía de búsqueda por nombre." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 1104 + ], + "id": "0fb2fdea-cf40-4dfb-abfa-c422282e5d2a", + "name": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 1104 + ], + "id": "58114758-f0fb-483a-bd5b-d28188880a02", + "name": "Obtener Pipelines - MARCA (CREATE)" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA CREAR OPORTUNIDAD EN MARCA ──────────────────────\n// Mapea custom fields por fieldKey (fallback name) y FIJA el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia futura).\n\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (CREATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (CREATE)').first().json.pipelines || [];\nconst locationId = $('DATOS API').first().json['Location ID'];\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst contactId = decision.contactId;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\n// Map pipelines/stages de la marca por nombre normalizado\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)];\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)];\n\nif (!resolvedPipelineId) {\n throw new Error(`No se encontró pipeline en marca: \"${sucursalOpp.pipelineName}\". Revisa que el nombre coincida.`);\n}\nif (!resolvedStageId) {\n throw new Error(`No se encontró stage en marca: \"${sucursalOpp.pipelineStageName}\". Revisa que el nombre coincida.`);\n}\n\n// Mapear custom fields: preferir fieldKey, fallback a name normalizado\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null || value === '') continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// FIJAR el campo de enlace \"ID Oportunidad Sucursal\" = id de la opp de sucursal\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n locationId: locationId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || 'open',\n contactId: contactId,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source || 'Sincronización Sucursal',\n customFields: customFields\n};\n\nreturn [{\n json: {\n body: JSON.stringify(body),\n debug: { resolvedPipelineId, resolvedStageId, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5520, + 1104 + ], + "id": "2f9a63e7-277d-49de-8838-a00922c993b2", + "name": "Armar Body - CREATE" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5760, + 1104 + ], + "id": "fc4b3398-91bb-4dd9-b406-96c8d75a17be", + "name": "Crear Oportunidad - MARCA" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA ACTUALIZAR OPORTUNIDAD EN MARCA (v2) ────────────\n// Mapea CF por fieldKey (fallback name) y reafirma el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia).\n\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA (v2)').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (UPDATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (UPDATE)').first().json.pipelines || [];\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)] || marcaOpp.pipelineStageId;\n\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null) {\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n value = marcaOriginal?.fieldValue;\n }\n if (value === undefined || value === null) continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// Reafirmar el campo de enlace\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || marcaOpp.status,\n monetaryValue: sucursalOpp.monetaryValue ?? marcaOpp.monetaryValue,\n source: sucursalOpp.source || marcaOpp.source,\n customFields: customFields\n};\n\nreturn [{\n json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body),\n debug: { matchReason: decision.matchReason, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5280, + 816 + ], + "id": "969e46cd-bbae-4372-9808-c939f63e3c02", + "name": "Armar Body - UPDATE (v2)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5520, + 816 + ], + "id": "83929e08-7afd-4670-9c18-be22b8c3656f", + "name": "Actualizar Oportunidad - MARCA (v2)" + }, + { + "parameters": { + "content": "# RAMA UPDATE\n## Oportunidad ya existe en marca → se actualiza", + "height": 280, + "width": 880, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 720 + ], + "typeVersion": 1, + "id": "1c285234-8d90-49ef-8dac-c98cb379299a", + "name": "Sticky Update" + }, + { + "parameters": { + "content": "# RAMA CREATE\n## Oportunidad no existe en marca → se crea desde cero\n### Incluye contacto + custom fields + tag origin-id", + "height": 280, + "width": 1120, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 1024 + ], + "typeVersion": 1, + "id": "7e8f350c-9562-485b-8ce5-1b13b4086856", + "name": "Sticky Create" + }, + { + "parameters": { + "content": "# MATCHING ROBUSTO\n## 1. Buscar/crear contacto en marca\n## 2. Buscar oportunidades DEL CONTACTO\n## 3. Decidir match por cantidad + tag origin-id", + "height": 320, + "width": 1500, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3328, + 864 + ], + "typeVersion": 1, + "id": "8a583e46-41b6-44e0-9313-b70ee085b740", + "name": "Sticky Matching" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 560 + ], + "id": "45f0a6b0-f44d-4e83-b2ab-cdd3fbfe7911", + "name": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 560 + ], + "id": "5ca962a9-9e56-42d1-af05-ce05d39ed77f", + "name": "Obtener Pipelines - MARCA (UPDATE)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ customFields: [ { id: $('Conseguir Custom Fields - Opportunity - SUCURSAL').item.json.customFields.find(f => f.fieldKey === 'opportunity.id_oportunidad_sucursal')?.id, key: 'opportunity.id_oportunidad_sucursal', field_value: $('Datos de Lead').item.json.Oportunidad.opportunity_id } ] }) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1328, + -32 + ], + "id": "a9572dee-ed57-4104-966a-4b22721e75f1", + "name": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Auto-mapeo de seguridad: setea 'ID Oportunidad Sucursal' = id nativo de la propia opp de sucursal (del webhook) antes de procesar. onError=continue para no romper el sync si falla." + }, + { + "parameters": { + "databaseId": 63, + "tableId": 754, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7280, + "value": "={{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 4208, + 1152 + ], + "id": "a64fdffd-ab8f-4ddd-a4d4-4d683a05132d", + "name": "Buscar Mapeo Opp - Baserow", + "alwaysOutputData": true, + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Idempotencia global: busca en la tabla 754 el mapeo id_opp_sucursal->id_opp_marca. Si existe, 'Decidir Match' hara UPDATE de esa opp aunque el contacto resuelto sea otro. alwaysOutputData + onError para no romper el flujo si no hay match o si Baserow falla." + }, + { + "parameters": { + "jsCode": "// Prepara el UPSERT del mapeo Baserow (tabla 754) tras CREATE/UPDATE de la opp en Marca.\n// Solo escribe cuando el mapeo NO existe aun:\n// - action CREATE -> la fila no existe (el lookup Baserow fallo) -> crear.\n// - action UPDATE por contacto -> Baserow no la tenia -> crear (idempotencia futura).\n// - action UPDATE por Baserow -> la fila YA existe -> NO escribir (no duplicar).\n// Asi una re-ejecucion del mismo opp encuentra el mapeo y hace UPDATE, no CREATE duplicado.\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst action = decision.action;\nconst matchReason = decision.matchReason || '';\nconst sucursalOppId = decision.sucursalOppId;\n\nconst matchedByBaserow = matchReason.indexOf('Baserow') !== -1;\nconst needWrite = (action === 'CREATE') || (action === 'UPDATE' && !matchedByBaserow);\nif (!needWrite) return [];\n\n// id de la opp en Marca segun el camino\nlet marcaOppId = decision.opportunityId;\nif (action === 'CREATE') {\n try {\n const co = $('Crear Oportunidad - MARCA').first().json;\n marcaOppId = (co.opportunity && co.opportunity.id) || co.id || marcaOppId;\n } catch (e) {}\n}\nif (!marcaOppId || !sucursalOppId) return [];\n\nlet locationSucursal = '';\ntry { locationSucursal = $('DATOS API - SUCURSAL').first().json['Location ID'] || ''; } catch (e) {}\n\nreturn [{ json: { sucursalOppId, marcaOppId, locationSucursal } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 6048, + 960 + ], + "id": "6542723e-f7b2-41de-b371-9e793d48f560", + "name": "Preparar Upsert Mapeo", + "onError": "continueRegularOutput", + "notes": "Decide si escribir el mapeo Baserow tras CREATE/UPDATE. Solo cuando la fila no existe (CREATE o UPDATE-por-contacto). onError continue para no romper la replicacion." + }, + { + "parameters": { + "operation": "create", + "databaseId": 63, + "tableId": 754, + "fieldsUi": { + "fieldValues": [ + { + "fieldId": 7280, + "fieldValue": "={{ $json.sucursalOppId }}" + }, + { + "fieldId": 7283, + "fieldValue": "={{ $json.marcaOppId }}" + }, + { + "fieldId": 7284, + "fieldValue": "={{ $json.locationSucursal }}" + }, + { + "fieldId": 7285, + "fieldValue": "={{ $now.toISO() }}" + } + ] + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + 6288, + 960 + ], + "id": "c69036dd-3914-4424-a165-6e0f3b110ebd", + "name": "Crear Mapeo - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Inserta el mapeo id_opp_sucursal->id_opp_marca en la tabla 754 en tiempo real. Idempotente con el backfill (verifica por id_opp_sucursal)." + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API": { + "main": [ + [ + { + "node": "Buscar Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "API de Sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - SUCURSAL": { + "main": [ + [ + { + "node": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Pipelines - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de Sucursal": { + "main": [ + [ + { + "node": "DATOS API - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de MARCA": { + "main": [ + [ + { + "node": "DATOS API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapeo completo oportunidad origen - SUCURSAL": { + "main": [ + [ + { + "node": "API de MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - SUCURSAL": { + "main": [ + [ + { + "node": "Mapeo completo oportunidad origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contacto en MARCA": { + "main": [ + [ + { + "node": "¿Existe contacto en MARCA?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Existe contacto en MARCA?": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Crear Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Contacto en MARCA": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Contact ID Resuelto": { + "main": [ + [ + { + "node": "Buscar Mapeo Opp - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidades del Contacto - MARCA": { + "main": [ + [ + { + "node": "Decidir Match (Create vs Update)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Decidir Match (Create vs Update)": { + "main": [ + [ + { + "node": "Switch UPDATE vs CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch UPDATE vs CREATE": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - UPDATE (v2)": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Armar Body - CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - CREATE": { + "main": [ + [ + { + "node": "Crear Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Armar Body - UPDATE (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapear ID Oportunidad Sucursal - SUCURSAL": { + "main": [ + [ + { + "node": "Buscar Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Mapeo Opp - Baserow": { + "main": [ + [ + { + "node": "Buscar Oportunidades del Contacto - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Upsert Mapeo": { + "main": [ + [ + { + "node": "Crear Mapeo - Baserow", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false + }, + "staticData": null, + "meta": { + "templateCredsSetupCompleted": true + }, + "pinData": { + "Webhook": [ + { + "json": { + "headers": { + "host": "webhookn8.consultoriae3.com", + "user-agent": "axios/1.13.2", + "content-length": "1839", + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, compress, deflate, br", + "content-type": "application/json", + "traceparent": "00-00e6586cc6967f6985b8aa87332de4e0-932bdb81faeff7c4-01", + "x-forwarded-for": "34.55.150.6", + "x-forwarded-host": "webhookn8.consultoriae3.com", + "x-forwarded-port": "443", + "x-forwarded-proto": "https", + "x-forwarded-server": "f569f6e85d30", + "x-real-ip": "34.55.150.6" + }, + "params": {}, + "query": {}, + "body": { + "¿Cuándo necesitas el dinero?": "", + "[Número de Teléfono para Atender]": "", + "Información Adicional": "", + "Presupuesto": "", + "CANAL DE ORIGEN": "", + "[Número de WhatsApp para Atender]": "", + "[Dirección de la Empresa]": "", + "Año del Vehículo": "2015", + "¿Qué modalidad prefieres?": "Tradicional (Resguardo)", + "Marca del Vehiculo": "RENAULT", + "TIENDA": "TAMPICO", + "Sucursal": "Tampico, Tamaulipas", + "Descripción": "", + "[Correo_Empresa]": "", + "ID Contacto Monte Providencia": "", + "Fuente de Prospecto": "REDES SOCIALES", + "[Correo_Canalizados]": "", + "Version del Vehiculo": "LOGAN", + "Acepta los terminos para tu cotización": "", + "[Nombre de la Empresa]": "", + "Check Comunicaciones de Marketing": "", + "Archivos Adicionales": "", + "contact_id": "B9oJWsFXy6Ol9NdHL5Jh", + "first_name": "RAUL", + "last_name": "LOGAN", + "full_name": "RAUL LOGAN", + "email": "raulsincorreo@gmail.com", + "phone": "+528332937527", + "tags": "sucursal", + "country": "MX", + "date_created": "2026-05-27T21:16:44.369Z", + "full_address": "", + "contact_type": "lead", + "opportunity_name": "RAUL LOGAN", + "status": "open", + "lead_value": 40000, + "opportunity_source": "REDES SOCIALES", + "source": "REDES SOCIALES", + "pipleline_stage": "Cliente interesado", + "pipeline_id": "ep1d4VpzRezVqWayFbBf", + "id": "NuFArKAQL72I8QC69MDa", + "pipeline_name": "Standar", + "user": { + "firstName": "Tampico - 85969", + "lastName": "Tampico", + "email": "ezstore85969@ezcorp.com" + }, + "owner": "Tampico - 85969 Tampico", + "location": { + "name": "85969 - MP - Tampico", + "address": "Tampico", + "city": "Tampico", + "state": "Tamaulipas", + "country": "MX", + "postalCode": "10000", + "fullAddress": "Tampico, Tampico Tamaulipas 10000", + "id": "WCHyow6KpjLFYriQWdbJ" + }, + "workflow": { + "id": "8ba8fdf1-85f2-4f1f-bf28-56400e1adcd6", + "name": "Sincronizar Oportunidad V2" + }, + "triggerData": {}, + "contact": { + "attributionSource": { + "sessionSource": "CRM UI", + "mediumId": null, + "medium": "manual" + }, + "lastAttributionSource": {} + }, + "attributionSource": {}, + "customData": {} + }, + "webhookUrl": "https://webhookn8.consultoriae3.com/webhook/3eecb45a-e24c-49ac-aef7-99843f485819", + "executionMode": "production" + }, + "pairedItem": { + "item": 0 + } + } + ] + }, + "versionId": "23dedba0-39b4-4edc-8751-03808c7af4d7", + "activeVersionId": "65decdcb-f97e-493d-8eb9-b872905b3d35", + "versionCounter": 433, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-03-02T01:12:56.387Z", + "createdAt": "2026-03-02T01:12:56.387Z", + "role": "workflow:owner", + "workflowId": "Cfgwp0bOtDW8zuKW", + "projectId": "aycNlMpnzsxvXbfG", + "project": { + "updatedAt": "2025-11-27T04:06:26.313Z", + "createdAt": "2025-11-27T03:54:24.410Z", + "id": "aycNlMpnzsxvXbfG", + "name": "Consultoria E3 ® ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + } + ], + "tags": [ + { + "updatedAt": "2025-11-27T05:48:12.272Z", + "createdAt": "2025-11-27T05:48:12.272Z", + "id": "pHkPpq3Httzx4hnb", + "name": "Bucefalo" + } + ], + "activeVersion": { + "updatedAt": "2026-05-30T17:03:07.471Z", + "createdAt": "2026-05-30T17:03:07.471Z", + "versionId": "65decdcb-f97e-493d-8eb9-b872905b3d35", + "workflowId": "Cfgwp0bOtDW8zuKW", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "3eecb45a-e24c-49ac-aef7-99843f485819", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -336, + -16 + ], + "id": "dd424583-8295-4013-96ba-c7e29ea944c6", + "name": "Webhook", + "webhookId": "3eecb45a-e24c-49ac-aef7-99843f485819" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "b02bc821-6121-452d-a042-623fac6e443c", + "name": "Oportunidad.opportunity_name", + "value": "={{ $json.body.opportunity_name }}", + "type": "string" + }, + { + "id": "898817a2-7926-4ddf-b785-cac8e28afc58", + "name": "Oportunidad.pipleline_stage (new)", + "value": "={{ $json.body.pipleline_stage }}", + "type": "string" + }, + { + "id": "opp-id-sucursal-capture", + "name": "Oportunidad.opportunity_id", + "value": "={{ $json.body.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -96, + -16 + ], + "id": "2b3ec130-27ce-4988-82db-46e4f12c3f7d", + "name": "Datos de Lead" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE SUCURSAL", + "height": 352, + "width": 1152 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1072, + -144 + ], + "typeVersion": 1, + "id": "19023e33-a9f1-4c60-b87c-957e751e3dc2", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE MARCA", + "height": 208, + "width": 1188, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2240, + -80 + ], + "typeVersion": 1, + "id": "3a3d4f18-1a08-4dbc-a00b-7ae6bc4d49d1", + "name": "Sticky Note1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2480, + -32 + ], + "id": "a46df506-6a9a-420d-9cdd-6fc51d1a691e", + "name": "DATOS API" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 80, + -16 + ], + "id": "25db982f-7bc7-4318-a7b0-e94326abc437", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3952, + -32 + ], + "id": "2320b102-816e-4a0c-ae9c-e305d25df6df", + "name": "Actualizar Oportunidad - MARCA2", + "notes": "El código mapea los custom fields de una oportunidad de una sucursal hacia los IDs equivalentes de una marca. Toma tres fuentes: los metadatos de campos de la sucursal, los valores reales de esos campos en la oportunidad, y los metadatos de campos de la marca. Para cada campo con valor en la oportunidad, primero busca su `fieldKey` en los metadatos de la sucursal usando el `id`, luego usa ese `fieldKey` para encontrar el campo equivalente en la marca, y construye un array con el `id` y `key` de la marca pero con el valor original de la sucursal. Finalmente arma el body de una petición para crear/actualizar una oportunidad en la marca, reutilizando pipeline, nombre, stage y valor monetario de otros nodos, con los custom fields ya traducidos al contexto de la marca." + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1536, + -32 + ], + "id": "4e89658b-7ffd-4a3b-b91e-9ed3223a1a73", + "name": "Buscar Oportunidad - SUCURSAL", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 528, + -32 + ], + "id": "b721a65d-bcbb-44ab-bb7f-8f200b34fcdd", + "name": "DATOS API - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1136, + -32 + ], + "id": "39ef9dcc-ac20-4ba2-b891-2a2baa6ea4f5", + "name": "Conseguir Custom Fields - Opportunity - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1712, + -32 + ], + "id": "fbd46ae4-792b-451b-b99e-9816dc175d52", + "name": "Obtener info de Oportunidad - SUCURSAL" + }, + { + "parameters": { + "content": "# ACTUALIZAR OPORTUNIDAD - MARCA", + "height": 352, + "width": 928 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3456, + -144 + ], + "typeVersion": 1, + "id": "f3ca1cfe-08e2-485b-b238-9240f5753809", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "", + "height": 208, + "width": 992, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2768, + -64 + ], + "typeVersion": 1, + "id": "45066721-8404-48ed-94c1-c88edf48bca5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "", + "height": 192, + "width": 928, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1104, + -64 + ], + "typeVersion": 1, + "id": "b1646bb4-8b64-4bf4-b380-189cc7902741", + "name": "Sticky Note7" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Webhook').item.json.body.location.name }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 320, + -32 + ], + "id": "12088f29-8bb4-475d-ae0e-2cbdf8033138", + "name": "API de Sucursal", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "=Monte Providencia" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 2288, + -32 + ], + "id": "8c8fa238-6ce4-4adf-8926-b603987a55cf", + "name": "API de MARCA", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "jsCode": "const opportunityData = $('Obtener info de Oportunidad - SUCURSAL').first().json;\nconst opportunity = opportunityData.opportunity;\nconst customFieldsDefs = $('Conseguir Custom Fields - Opportunity - SUCURSAL').first().json.customFields;\nconst pipelines = $input.first().json.pipelines;\n\n// Map custom fields: id -> { fieldKey, name }\nconst fieldMap = {};\nfor (const def of customFieldsDefs) {\n fieldMap[def.id] = { fieldKey: def.fieldKey, name: def.name };\n}\n\n// Map pipelines: id -> name, stages: id -> { name, position, stageWinProbability }\nconst pipelineMap = {};\nconst stageMap = {};\nfor (const pipeline of pipelines) {\n pipelineMap[pipeline.id] = pipeline.name;\n for (const stage of pipeline.stages) {\n stageMap[stage.id] = {\n name: stage.name,\n position: stage.position,\n stageWinProbability: stage.stageWinProbability,\n color: stage.color || null\n };\n }\n}\n\n// Enrich custom fields with fieldKey and name\nconst enrichedCustomFields = opportunity.customFields.map(cf => ({\n id: cf.id,\n name: fieldMap[cf.id]?.name || null,\n fieldKey: fieldMap[cf.id]?.fieldKey || null,\n fieldValue: cf.fieldValue\n}));\n\n// Resolve pipeline and stage info\nconst stageInfo = stageMap[opportunity.pipelineStageId] || {};\n\n// Build enriched opportunity\nconst enrichedOpportunity = {\n ...opportunity,\n pipelineName: pipelineMap[opportunity.pipelineId] || null,\n pipelineStageName: stageInfo.name || null,\n pipelineStagePosition: stageInfo.position ?? null,\n pipelineStageWinProbability: stageInfo.stageWinProbability ?? null,\n pipelineStageColor: stageInfo.color || null,\n customFields: enrichedCustomFields\n};\n\nreturn [{ json: { opportunity: enrichedOpportunity, traceId: opportunityData.traceId } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + -32 + ], + "id": "d9609dd9-9493-4f3d-b356-99438cedada6", + "name": "Mapeo completo oportunidad origen - SUCURSAL" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('Webhook').item.json.body.location.id }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de Sucursal').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1872, + -32 + ], + "id": "2d799ae3-b6b1-4e4f-8774-714ebe4f009f", + "name": "Obtener Pipelines - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Buscar Oportunidad - MARCA').item.json.opportunities[0].id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3216, + -32 + ], + "id": "1eee43d2-c6a8-414b-bebf-4b31d6fb34d7", + "name": "Obtener info de Oportunidad - MARCA" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('API de MARCA').item.json.Location_ID }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de MARCA').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3424, + -32 + ], + "id": "20a1a288-2cd8-471b-9203-5ba88770edb0", + "name": "Obtener Pipelines - MARCA" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2800, + -32 + ], + "id": "798a7e51-b8ed-4364-947a-7396fab47940", + "name": "Buscar Oportunidad - MARCA", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3008, + -32 + ], + "id": "cc37ea9b-0a9f-44ce-8ce2-ee2444822ecf", + "name": "Conseguir Custom Fields - Opportunity - MARCA" + }, + { + "parameters": { + "jsCode": "// ── DATOS MARCA ──────────────────────────────────────────────────────────────\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA').first().json.customFields;\nconst marcaPipelines = $input.first().json.pipelines;\n\n// ── DATOS SUCURSAL (ya enriquecidos) ─────────────────────────────────────────\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\n\n// ── MAP PIPELINES MARCA: name -> { id, stages } ───────────────────────────────\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[pipeline.name.trim()] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[stage.name.trim()] = stage.id;\n }\n}\n\n// ── RESOLVER PIPELINE Y STAGE POR NAME DESDE SUCURSAL ────────────────────────\nconst resolvedPipelineId = marcaPipelineByName[sucursalOpp.pipelineName?.trim()] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[sucursalOpp.pipelineStageName?.trim()] || marcaOpp.pipelineStageId;\n\n// ── MAP CUSTOM FIELDS SUCURSAL: name -> fieldValue ────────────────────────────\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields) {\n if (cf.name) {\n sucursalValueByName[cf.name.trim()] = cf.fieldValue;\n }\n}\n\n// ── CONSTRUIR CUSTOM FIELDS PARA EL BODY ─────────────────────────────────────\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n const nameKey = def.name.trim();\n const sucursalValue = sucursalValueByName[nameKey];\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n const fieldValue = sucursalValue !== undefined ? sucursalValue : (marcaOriginal?.fieldValue ?? null);\n\n if (fieldValue === null) continue;\n\n customFields.push({\n id: def.id,\n key: def.fieldKey,\n field_value: fieldValue\n });\n}\n\n// ── CONSTRUIR BODY VÁLIDO PARA UPDATE OPPORTUNITY ────────────────────────────\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source,\n customFields: customFields\n};\n\nreturn [{ json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body)\n} }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3632, + -32 + ], + "id": "3b547c2a-a541-4b48-8d97-ea2d5df52010", + "name": "Code in JavaScript" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"pageLimit\": 10,\n \"query\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono || $('Datos de Lead').item.json.Cliente.Email }}\"\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3360, + 960 + ], + "id": "02cd0fd6-6bb8-4faa-b85f-5d42663fba29", + "name": "Buscar Contacto en MARCA", + "notes": "Busca contacto en la location de marca por email.\nSi no encuentra, la siguiente IF deriva a creación." + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "exist-contact-check", + "leftValue": "={{ $json.contacts ? $json.contacts.length : 0 }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3600, + 960 + ], + "id": "aa2644dc-34b4-4159-8afb-89147b12f5ee", + "name": "¿Existe contacto en MARCA?" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/upsert", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"firstName\": \"{{ $('Datos de Lead').item.json.Cliente.Apellido }}\",\n \"name\": \"{{ $('Datos de Lead').item.json.Cliente['Nombre Completo'] }}\",\n \"email\": \"{{ $('Datos de Lead').item.json.Cliente.Email }}\",\n \"phone\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono }}\",\n \"source\": \"Sincronización Sucursal\",\n \"tags\": [\"sincronizado-sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3840, + 1104 + ], + "id": "f6c75352-3b9e-4fc9-b8e4-063c2792d190", + "name": "Crear Contacto en MARCA", + "notes": "Crea contacto en la marca solo si no existía.\nIMPORTANTE: revisa el mapeo de Nombre/Apellido, en el workflow original están invertidos." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "contact-id-resolved", + "name": "contactId", + "value": "={{ $json.contact ? $json.contact.id : $json.contacts[0].id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 4080, + 960 + ], + "id": "2a49d01e-6679-4584-9d9b-5778dfc4431e", + "name": "Set Contact ID Resuelto", + "notes": "Unifica el contactId tanto si vino de la búsqueda (contacts[0].id) como de la creación (contact.id)." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "location_id", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + }, + { + "name": "contact_id", + "value": "={{ $('Set Contact ID Resuelto').item.json.contactId }}" + }, + { + "name": "limit", + "value": "100" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 4320, + 960 + ], + "id": "8e25f660-3ed2-4597-9c2a-7480ac7d150f", + "name": "Buscar Oportunidades del Contacto - MARCA", + "notes": "Busca TODAS las oportunidades del contacto en la marca.\nDespués el Code de decisión elige cuál (o crea nueva)." + }, + { + "parameters": { + "jsCode": "// DECISION (Create vs Update) con idempotencia GLOBAL via mapeo Baserow (tabla 754).\n// (1) Si el mapeo id_opp_sucursal -> id_opp_marca existe -> UPDATE esa opp de Marca,\n// independiente del contacto resuelto (evita duplicados por contacto ambiguo).\n// (2) Si no hay mapeo, match por CF entre las opps del contacto resuelto.\n// (3) Si nada -> CREATE.\nconst result = $input.first().json;\nconst opportunities = result.opportunities || [];\nconst sucursalOppId = $('Datos de Lead').first().json.Oportunidad.opportunity_id;\n\nlet action = 'CREATE';\nlet opportunityId = null;\nlet matchReason = '';\n\n// (1) Mapeo Baserow global\nlet baserowRows = [];\ntry { baserowRows = $('Buscar Mapeo Opp - Baserow').all().map(i => i.json); } catch (e) { baserowRows = []; }\nconst mapped = baserowRows.find(r =>\n String(r.id_opp_sucursal || '') === String(sucursalOppId) && String(r.id_opp_marca || '').trim()\n);\n\nif (mapped) {\n action = 'UPDATE';\n opportunityId = String(mapped.id_opp_marca).trim();\n matchReason = 'Match por mapeo Baserow (global, tabla 754)';\n} else {\n // (2) match por CF entre las opps del contacto resuelto\n const match = opportunities.find(o =>\n (o.customFields || []).some(cf => {\n const v = cf.fieldValueString ?? cf.fieldValue ?? '';\n return String(v) === String(sucursalOppId);\n })\n );\n if (match) {\n action = 'UPDATE';\n opportunityId = match.id;\n matchReason = 'Match por ID Oportunidad Sucursal (contacto)';\n } else {\n action = 'CREATE';\n matchReason = 'Sin match (Baserow ni contacto) -> CREATE (contacto tiene ' + opportunities.length + ' opps)';\n }\n}\n\nreturn [{\n json: {\n action,\n opportunityId,\n matchReason,\n sucursalOppId,\n contactId: $('Set Contact ID Resuelto').first().json.contactId,\n totalOppsContacto: opportunities.length\n }\n}];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4560, + 960 + ], + "id": "6ca30f13-8503-427f-95ec-b243d30688ec", + "name": "Decidir Match (Create vs Update)" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-update", + "leftValue": "={{ $json.action }}", + "rightValue": "UPDATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "UPDATE" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-create", + "leftValue": "={{ $json.action }}", + "rightValue": "CREATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "CREATE" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 4800, + 960 + ], + "id": "bff7a5dc-b4af-4033-83ea-c508aa92264a", + "name": "Switch UPDATE vs CREATE" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Decidir Match (Create vs Update)').item.json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5040, + 816 + ], + "id": "83cd0fd7-1ee9-4896-926e-a0b752e216fe", + "name": "Obtener info de Oportunidad - MARCA (v2)", + "notes": "Sólo se ejecuta en rama UPDATE.\nReemplaza el nodo original 'Obtener info de Oportunidad - MARCA' que dependía de búsqueda por nombre." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 1104 + ], + "id": "0fb2fdea-cf40-4dfb-abfa-c422282e5d2a", + "name": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 1104 + ], + "id": "58114758-f0fb-483a-bd5b-d28188880a02", + "name": "Obtener Pipelines - MARCA (CREATE)" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA CREAR OPORTUNIDAD EN MARCA ──────────────────────\n// Mapea custom fields por fieldKey (fallback name) y FIJA el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia futura).\n\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (CREATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (CREATE)').first().json.pipelines || [];\nconst locationId = $('DATOS API').first().json['Location ID'];\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst contactId = decision.contactId;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\n// Map pipelines/stages de la marca por nombre normalizado\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)];\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)];\n\nif (!resolvedPipelineId) {\n throw new Error(`No se encontró pipeline en marca: \"${sucursalOpp.pipelineName}\". Revisa que el nombre coincida.`);\n}\nif (!resolvedStageId) {\n throw new Error(`No se encontró stage en marca: \"${sucursalOpp.pipelineStageName}\". Revisa que el nombre coincida.`);\n}\n\n// Mapear custom fields: preferir fieldKey, fallback a name normalizado\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null || value === '') continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// FIJAR el campo de enlace \"ID Oportunidad Sucursal\" = id de la opp de sucursal\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n locationId: locationId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || 'open',\n contactId: contactId,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source || 'Sincronización Sucursal',\n customFields: customFields\n};\n\nreturn [{\n json: {\n body: JSON.stringify(body),\n debug: { resolvedPipelineId, resolvedStageId, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5520, + 1104 + ], + "id": "2f9a63e7-277d-49de-8838-a00922c993b2", + "name": "Armar Body - CREATE" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5760, + 1104 + ], + "id": "fc4b3398-91bb-4dd9-b406-96c8d75a17be", + "name": "Crear Oportunidad - MARCA" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA ACTUALIZAR OPORTUNIDAD EN MARCA (v2) ────────────\n// Mapea CF por fieldKey (fallback name) y reafirma el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia).\n\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA (v2)').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (UPDATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (UPDATE)').first().json.pipelines || [];\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)] || marcaOpp.pipelineStageId;\n\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null) {\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n value = marcaOriginal?.fieldValue;\n }\n if (value === undefined || value === null) continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// Reafirmar el campo de enlace\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || marcaOpp.status,\n monetaryValue: sucursalOpp.monetaryValue ?? marcaOpp.monetaryValue,\n source: sucursalOpp.source || marcaOpp.source,\n customFields: customFields\n};\n\nreturn [{\n json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body),\n debug: { matchReason: decision.matchReason, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5280, + 816 + ], + "id": "969e46cd-bbae-4372-9808-c939f63e3c02", + "name": "Armar Body - UPDATE (v2)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5520, + 816 + ], + "id": "83929e08-7afd-4670-9c18-be22b8c3656f", + "name": "Actualizar Oportunidad - MARCA (v2)" + }, + { + "parameters": { + "content": "# RAMA UPDATE\n## Oportunidad ya existe en marca → se actualiza", + "height": 280, + "width": 880, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 720 + ], + "typeVersion": 1, + "id": "1c285234-8d90-49ef-8dac-c98cb379299a", + "name": "Sticky Update" + }, + { + "parameters": { + "content": "# RAMA CREATE\n## Oportunidad no existe en marca → se crea desde cero\n### Incluye contacto + custom fields + tag origin-id", + "height": 280, + "width": 1120, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 1024 + ], + "typeVersion": 1, + "id": "7e8f350c-9562-485b-8ce5-1b13b4086856", + "name": "Sticky Create" + }, + { + "parameters": { + "content": "# MATCHING ROBUSTO\n## 1. Buscar/crear contacto en marca\n## 2. Buscar oportunidades DEL CONTACTO\n## 3. Decidir match por cantidad + tag origin-id", + "height": 320, + "width": 1500, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3328, + 864 + ], + "typeVersion": 1, + "id": "8a583e46-41b6-44e0-9313-b70ee085b740", + "name": "Sticky Matching" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 560 + ], + "id": "45f0a6b0-f44d-4e83-b2ab-cdd3fbfe7911", + "name": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 560 + ], + "id": "5ca962a9-9e56-42d1-af05-ce05d39ed77f", + "name": "Obtener Pipelines - MARCA (UPDATE)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ customFields: [ { id: $('Conseguir Custom Fields - Opportunity - SUCURSAL').item.json.customFields.find(f => f.fieldKey === 'opportunity.id_oportunidad_sucursal')?.id, key: 'opportunity.id_oportunidad_sucursal', field_value: $('Datos de Lead').item.json.Oportunidad.opportunity_id } ] }) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1328, + -32 + ], + "id": "a9572dee-ed57-4104-966a-4b22721e75f1", + "name": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Auto-mapeo de seguridad: setea 'ID Oportunidad Sucursal' = id nativo de la propia opp de sucursal (del webhook) antes de procesar. onError=continue para no romper el sync si falla." + }, + { + "parameters": { + "databaseId": 63, + "tableId": 754, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7280, + "value": "={{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 4200, + 1140 + ], + "id": "a64fdffd-ab8f-4ddd-a4d4-4d683a05132d", + "name": "Buscar Mapeo Opp - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "alwaysOutputData": true, + "onError": "continueRegularOutput", + "notes": "Idempotencia global: busca en la tabla 754 el mapeo id_opp_sucursal->id_opp_marca. Si existe, 'Decidir Match' hara UPDATE de esa opp aunque el contacto resuelto sea otro. alwaysOutputData + onError para no romper el flujo si no hay match o si Baserow falla." + }, + { + "parameters": { + "jsCode": "// Prepara el UPSERT del mapeo Baserow (tabla 754) tras CREATE/UPDATE de la opp en Marca.\n// Solo escribe cuando el mapeo NO existe aun:\n// - action CREATE -> la fila no existe (el lookup Baserow fallo) -> crear.\n// - action UPDATE por contacto -> Baserow no la tenia -> crear (idempotencia futura).\n// - action UPDATE por Baserow -> la fila YA existe -> NO escribir (no duplicar).\n// Asi una re-ejecucion del mismo opp encuentra el mapeo y hace UPDATE, no CREATE duplicado.\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst action = decision.action;\nconst matchReason = decision.matchReason || '';\nconst sucursalOppId = decision.sucursalOppId;\n\nconst matchedByBaserow = matchReason.indexOf('Baserow') !== -1;\nconst needWrite = (action === 'CREATE') || (action === 'UPDATE' && !matchedByBaserow);\nif (!needWrite) return [];\n\n// id de la opp en Marca segun el camino\nlet marcaOppId = decision.opportunityId;\nif (action === 'CREATE') {\n try {\n const co = $('Crear Oportunidad - MARCA').first().json;\n marcaOppId = (co.opportunity && co.opportunity.id) || co.id || marcaOppId;\n } catch (e) {}\n}\nif (!marcaOppId || !sucursalOppId) return [];\n\nlet locationSucursal = '';\ntry { locationSucursal = $('DATOS API - SUCURSAL').first().json['Location ID'] || ''; } catch (e) {}\n\nreturn [{ json: { sucursalOppId, marcaOppId, locationSucursal } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 6040, + 960 + ], + "id": "6542723e-f7b2-41de-b371-9e793d48f560", + "name": "Preparar Upsert Mapeo", + "onError": "continueRegularOutput", + "notes": "Decide si escribir el mapeo Baserow tras CREATE/UPDATE. Solo cuando la fila no existe (CREATE o UPDATE-por-contacto). onError continue para no romper la replicacion." + }, + { + "parameters": { + "operation": "create", + "databaseId": 63, + "tableId": 754, + "fieldsUi": { + "fieldValues": [ + { + "fieldId": 7280, + "fieldValue": "={{ $json.sucursalOppId }}" + }, + { + "fieldId": 7283, + "fieldValue": "={{ $json.marcaOppId }}" + }, + { + "fieldId": 7284, + "fieldValue": "={{ $json.locationSucursal }}" + }, + { + "fieldId": 7285, + "fieldValue": "={{ $now.toISO() }}" + } + ] + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + 6280, + 960 + ], + "id": "c69036dd-3914-4424-a165-6e0f3b110ebd", + "name": "Crear Mapeo - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Inserta el mapeo id_opp_sucursal->id_opp_marca en la tabla 754 en tiempo real. Idempotente con el backfill (verifica por id_opp_sucursal)." + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API": { + "main": [ + [ + { + "node": "Buscar Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "API de Sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - SUCURSAL": { + "main": [ + [ + { + "node": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Pipelines - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de Sucursal": { + "main": [ + [ + { + "node": "DATOS API - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de MARCA": { + "main": [ + [ + { + "node": "DATOS API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapeo completo oportunidad origen - SUCURSAL": { + "main": [ + [ + { + "node": "API de MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - SUCURSAL": { + "main": [ + [ + { + "node": "Mapeo completo oportunidad origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contacto en MARCA": { + "main": [ + [ + { + "node": "¿Existe contacto en MARCA?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Existe contacto en MARCA?": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Crear Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Contacto en MARCA": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Contact ID Resuelto": { + "main": [ + [ + { + "node": "Buscar Mapeo Opp - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidades del Contacto - MARCA": { + "main": [ + [ + { + "node": "Decidir Match (Create vs Update)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Decidir Match (Create vs Update)": { + "main": [ + [ + { + "node": "Switch UPDATE vs CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch UPDATE vs CREATE": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - UPDATE (v2)": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Armar Body - CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - CREATE": { + "main": [ + [ + { + "node": "Crear Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Armar Body - UPDATE (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapear ID Oportunidad Sucursal - SUCURSAL": { + "main": [ + [ + { + "node": "Buscar Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Mapeo Opp - Baserow": { + "main": [ + [ + { + "node": "Buscar Oportunidades del Contacto - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Upsert Mapeo": { + "main": [ + [ + { + "node": "Crear Mapeo - Baserow", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "Consultoria E3 ®", + "name": null, + "description": null, + "autosaved": false, + "workflowPublishHistory": [ + { + "createdAt": "2026-05-30T17:03:07.625Z", + "id": 674, + "workflowId": "Cfgwp0bOtDW8zuKW", + "versionId": "65decdcb-f97e-493d-8eb9-b872905b3d35", + "event": "deactivated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + }, + { + "createdAt": "2026-05-30T17:03:07.684Z", + "id": 675, + "workflowId": "Cfgwp0bOtDW8zuKW", + "versionId": "65decdcb-f97e-493d-8eb9-b872905b3d35", + "event": "activated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + ] + } +} \ No newline at end of file diff --git a/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_164248.json b/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_164248.json new file mode 100644 index 0000000..abbee52 --- /dev/null +++ b/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_164248.json @@ -0,0 +1,4148 @@ +{ + "updatedAt": "2026-05-30T22:21:10.460Z", + "createdAt": "2026-03-02T01:12:56.387Z", + "id": "Cfgwp0bOtDW8zuKW", + "name": "Sincronizar Oportunidad - Nodos Nuevos (Create/Update)", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "3eecb45a-e24c-49ac-aef7-99843f485819", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -336, + -16 + ], + "id": "dd424583-8295-4013-96ba-c7e29ea944c6", + "name": "Webhook", + "webhookId": "3eecb45a-e24c-49ac-aef7-99843f485819" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "b02bc821-6121-452d-a042-623fac6e443c", + "name": "Oportunidad.opportunity_name", + "value": "={{ $json.body.opportunity_name }}", + "type": "string" + }, + { + "id": "898817a2-7926-4ddf-b785-cac8e28afc58", + "name": "Oportunidad.pipleline_stage (new)", + "value": "={{ $json.body.pipleline_stage }}", + "type": "string" + }, + { + "id": "opp-id-sucursal-capture", + "name": "Oportunidad.opportunity_id", + "value": "={{ $json.body.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -96, + -16 + ], + "id": "2b3ec130-27ce-4988-82db-46e4f12c3f7d", + "name": "Datos de Lead" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE SUCURSAL", + "height": 352, + "width": 1152 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1072, + -144 + ], + "typeVersion": 1, + "id": "19023e33-a9f1-4c60-b87c-957e751e3dc2", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE MARCA", + "height": 208, + "width": 1188, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2240, + -80 + ], + "typeVersion": 1, + "id": "3a3d4f18-1a08-4dbc-a00b-7ae6bc4d49d1", + "name": "Sticky Note1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2480, + -32 + ], + "id": "a46df506-6a9a-420d-9cdd-6fc51d1a691e", + "name": "DATOS API" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 80, + -16 + ], + "id": "25db982f-7bc7-4318-a7b0-e94326abc437", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3952, + -32 + ], + "id": "2320b102-816e-4a0c-ae9c-e305d25df6df", + "name": "Actualizar Oportunidad - MARCA2", + "notes": "El código mapea los custom fields de una oportunidad de una sucursal hacia los IDs equivalentes de una marca. Toma tres fuentes: los metadatos de campos de la sucursal, los valores reales de esos campos en la oportunidad, y los metadatos de campos de la marca. Para cada campo con valor en la oportunidad, primero busca su `fieldKey` en los metadatos de la sucursal usando el `id`, luego usa ese `fieldKey` para encontrar el campo equivalente en la marca, y construye un array con el `id` y `key` de la marca pero con el valor original de la sucursal. Finalmente arma el body de una petición para crear/actualizar una oportunidad en la marca, reutilizando pipeline, nombre, stage y valor monetario de otros nodos, con los custom fields ya traducidos al contexto de la marca." + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1536, + -32 + ], + "id": "4e89658b-7ffd-4a3b-b91e-9ed3223a1a73", + "name": "Buscar Oportunidad - SUCURSAL", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 528, + -32 + ], + "id": "b721a65d-bcbb-44ab-bb7f-8f200b34fcdd", + "name": "DATOS API - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1136, + -32 + ], + "id": "39ef9dcc-ac20-4ba2-b891-2a2baa6ea4f5", + "name": "Conseguir Custom Fields - Opportunity - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1712, + -32 + ], + "id": "fbd46ae4-792b-451b-b99e-9816dc175d52", + "name": "Obtener info de Oportunidad - SUCURSAL" + }, + { + "parameters": { + "content": "# ACTUALIZAR OPORTUNIDAD - MARCA", + "height": 352, + "width": 928 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3456, + -144 + ], + "typeVersion": 1, + "id": "f3ca1cfe-08e2-485b-b238-9240f5753809", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "", + "height": 208, + "width": 992, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2768, + -64 + ], + "typeVersion": 1, + "id": "45066721-8404-48ed-94c1-c88edf48bca5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "", + "height": 192, + "width": 928, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1104, + -64 + ], + "typeVersion": 1, + "id": "b1646bb4-8b64-4bf4-b380-189cc7902741", + "name": "Sticky Note7" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Webhook').item.json.body.location.name }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 320, + -32 + ], + "id": "12088f29-8bb4-475d-ae0e-2cbdf8033138", + "name": "API de Sucursal", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "=Monte Providencia" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 2288, + -32 + ], + "id": "8c8fa238-6ce4-4adf-8926-b603987a55cf", + "name": "API de MARCA", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "jsCode": "const opportunityData = $('Obtener info de Oportunidad - SUCURSAL').first().json;\nconst opportunity = opportunityData.opportunity;\nconst customFieldsDefs = $('Conseguir Custom Fields - Opportunity - SUCURSAL').first().json.customFields;\nconst pipelines = $input.first().json.pipelines;\n\n// Map custom fields: id -> { fieldKey, name }\nconst fieldMap = {};\nfor (const def of customFieldsDefs) {\n fieldMap[def.id] = { fieldKey: def.fieldKey, name: def.name };\n}\n\n// Map pipelines: id -> name, stages: id -> { name, position, stageWinProbability }\nconst pipelineMap = {};\nconst stageMap = {};\nfor (const pipeline of pipelines) {\n pipelineMap[pipeline.id] = pipeline.name;\n for (const stage of pipeline.stages) {\n stageMap[stage.id] = {\n name: stage.name,\n position: stage.position,\n stageWinProbability: stage.stageWinProbability,\n color: stage.color || null\n };\n }\n}\n\n// Enrich custom fields with fieldKey and name\nconst enrichedCustomFields = opportunity.customFields.map(cf => ({\n id: cf.id,\n name: fieldMap[cf.id]?.name || null,\n fieldKey: fieldMap[cf.id]?.fieldKey || null,\n fieldValue: cf.fieldValue\n}));\n\n// Resolve pipeline and stage info\nconst stageInfo = stageMap[opportunity.pipelineStageId] || {};\n\n// Build enriched opportunity\nconst enrichedOpportunity = {\n ...opportunity,\n pipelineName: pipelineMap[opportunity.pipelineId] || null,\n pipelineStageName: stageInfo.name || null,\n pipelineStagePosition: stageInfo.position ?? null,\n pipelineStageWinProbability: stageInfo.stageWinProbability ?? null,\n pipelineStageColor: stageInfo.color || null,\n customFields: enrichedCustomFields\n};\n\nreturn [{ json: { opportunity: enrichedOpportunity, traceId: opportunityData.traceId } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + -32 + ], + "id": "d9609dd9-9493-4f3d-b356-99438cedada6", + "name": "Mapeo completo oportunidad origen - SUCURSAL" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('Webhook').item.json.body.location.id }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de Sucursal').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1872, + -32 + ], + "id": "2d799ae3-b6b1-4e4f-8774-714ebe4f009f", + "name": "Obtener Pipelines - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Buscar Oportunidad - MARCA').item.json.opportunities[0].id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3216, + -32 + ], + "id": "1eee43d2-c6a8-414b-bebf-4b31d6fb34d7", + "name": "Obtener info de Oportunidad - MARCA" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('API de MARCA').item.json.Location_ID }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de MARCA').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3424, + -32 + ], + "id": "20a1a288-2cd8-471b-9203-5ba88770edb0", + "name": "Obtener Pipelines - MARCA" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2800, + -32 + ], + "id": "798a7e51-b8ed-4364-947a-7396fab47940", + "name": "Buscar Oportunidad - MARCA", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3008, + -32 + ], + "id": "cc37ea9b-0a9f-44ce-8ce2-ee2444822ecf", + "name": "Conseguir Custom Fields - Opportunity - MARCA" + }, + { + "parameters": { + "jsCode": "// ── DATOS MARCA ──────────────────────────────────────────────────────────────\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA').first().json.customFields;\nconst marcaPipelines = $input.first().json.pipelines;\n\n// ── DATOS SUCURSAL (ya enriquecidos) ─────────────────────────────────────────\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\n\n// ── MAP PIPELINES MARCA: name -> { id, stages } ───────────────────────────────\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[pipeline.name.trim()] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[stage.name.trim()] = stage.id;\n }\n}\n\n// ── RESOLVER PIPELINE Y STAGE POR NAME DESDE SUCURSAL ────────────────────────\nconst resolvedPipelineId = marcaPipelineByName[sucursalOpp.pipelineName?.trim()] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[sucursalOpp.pipelineStageName?.trim()] || marcaOpp.pipelineStageId;\n\n// ── MAP CUSTOM FIELDS SUCURSAL: name -> fieldValue ────────────────────────────\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields) {\n if (cf.name) {\n sucursalValueByName[cf.name.trim()] = cf.fieldValue;\n }\n}\n\n// ── CONSTRUIR CUSTOM FIELDS PARA EL BODY ─────────────────────────────────────\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n const nameKey = def.name.trim();\n const sucursalValue = sucursalValueByName[nameKey];\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n const fieldValue = sucursalValue !== undefined ? sucursalValue : (marcaOriginal?.fieldValue ?? null);\n\n if (fieldValue === null) continue;\n\n customFields.push({\n id: def.id,\n key: def.fieldKey,\n field_value: fieldValue\n });\n}\n\n// ── CONSTRUIR BODY VÁLIDO PARA UPDATE OPPORTUNITY ────────────────────────────\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source,\n customFields: customFields\n};\n\nreturn [{ json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body)\n} }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3632, + -32 + ], + "id": "3b547c2a-a541-4b48-8d97-ea2d5df52010", + "name": "Code in JavaScript" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"pageLimit\": 10,\n \"query\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono || $('Datos de Lead').item.json.Cliente.Email }}\"\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3360, + 960 + ], + "id": "02cd0fd6-6bb8-4faa-b85f-5d42663fba29", + "name": "Buscar Contacto en MARCA", + "notes": "Busca contacto en la location de marca por email.\nSi no encuentra, la siguiente IF deriva a creación." + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "exist-contact-check", + "leftValue": "={{ $json.contacts ? $json.contacts.length : 0 }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3600, + 960 + ], + "id": "aa2644dc-34b4-4159-8afb-89147b12f5ee", + "name": "¿Existe contacto en MARCA?" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/upsert", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"firstName\": \"{{ $('Datos de Lead').item.json.Cliente.Apellido }}\",\n \"name\": \"{{ $('Datos de Lead').item.json.Cliente['Nombre Completo'] }}\",\n \"email\": \"{{ $('Datos de Lead').item.json.Cliente.Email }}\",\n \"phone\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono }}\",\n \"source\": \"Sincronización Sucursal\",\n \"tags\": [\"sincronizado-sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3840, + 1104 + ], + "id": "f6c75352-3b9e-4fc9-b8e4-063c2792d190", + "name": "Crear Contacto en MARCA", + "notes": "Crea contacto en la marca solo si no existía.\nIMPORTANTE: revisa el mapeo de Nombre/Apellido, en el workflow original están invertidos." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "contact-id-resolved", + "name": "contactId", + "value": "={{ $json.contact ? $json.contact.id : $json.contacts[0].id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 4080, + 960 + ], + "id": "2a49d01e-6679-4584-9d9b-5778dfc4431e", + "name": "Set Contact ID Resuelto", + "notes": "Unifica el contactId tanto si vino de la búsqueda (contacts[0].id) como de la creación (contact.id)." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "location_id", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + }, + { + "name": "contact_id", + "value": "={{ $('Set Contact ID Resuelto').item.json.contactId }}" + }, + { + "name": "limit", + "value": "100" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 4320, + 960 + ], + "id": "8e25f660-3ed2-4597-9c2a-7480ac7d150f", + "name": "Buscar Oportunidades del Contacto - MARCA", + "notes": "Busca TODAS las oportunidades del contacto en la marca.\nDespués el Code de decisión elige cuál (o crea nueva)." + }, + { + "parameters": { + "jsCode": "// DECISION (Create vs Update) con idempotencia GLOBAL via mapeo Baserow (tabla 754).\n// (1) Si el mapeo id_opp_sucursal -> id_opp_marca existe -> UPDATE esa opp de Marca,\n// independiente del contacto resuelto (evita duplicados por contacto ambiguo).\n// (2) Si no hay mapeo, match por CF entre las opps del contacto resuelto.\n// (3) Si nada -> CREATE.\nconst result = $input.first().json;\nconst opportunities = result.opportunities || [];\nconst sucursalOppId = $('Datos de Lead').first().json.Oportunidad.opportunity_id;\n\nlet action = 'CREATE';\nlet opportunityId = null;\nlet matchReason = '';\n\n// (1) Mapeo Baserow global\nlet baserowRows = [];\ntry { baserowRows = $('Buscar Mapeo Opp - Baserow').all().map(i => i.json); } catch (e) { baserowRows = []; }\nconst mapped = baserowRows.find(r =>\n String(r.id_opp_sucursal || '') === String(sucursalOppId) && String(r.id_opp_marca || '').trim()\n);\n\nif (mapped) {\n action = 'UPDATE';\n opportunityId = String(mapped.id_opp_marca).trim();\n matchReason = 'Match por mapeo Baserow (global, tabla 754)';\n} else {\n // (2) match por CF entre las opps del contacto resuelto\n const match = opportunities.find(o =>\n (o.customFields || []).some(cf => {\n const v = cf.fieldValueString ?? cf.fieldValue ?? '';\n return String(v) === String(sucursalOppId);\n })\n );\n if (match) {\n action = 'UPDATE';\n opportunityId = match.id;\n matchReason = 'Match por ID Oportunidad Sucursal (contacto)';\n } else {\n action = 'CREATE';\n matchReason = 'Sin match (Baserow ni contacto) -> CREATE (contacto tiene ' + opportunities.length + ' opps)';\n }\n}\n\nreturn [{\n json: {\n action,\n opportunityId,\n matchReason,\n sucursalOppId,\n contactId: $('Set Contact ID Resuelto').first().json.contactId,\n totalOppsContacto: opportunities.length\n }\n}];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4560, + 960 + ], + "id": "6ca30f13-8503-427f-95ec-b243d30688ec", + "name": "Decidir Match (Create vs Update)" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-update", + "leftValue": "={{ $json.action }}", + "rightValue": "UPDATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "UPDATE" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-create", + "leftValue": "={{ $json.action }}", + "rightValue": "CREATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "CREATE" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 4800, + 960 + ], + "id": "bff7a5dc-b4af-4033-83ea-c508aa92264a", + "name": "Switch UPDATE vs CREATE" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Decidir Match (Create vs Update)').item.json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5040, + 816 + ], + "id": "83cd0fd7-1ee9-4896-926e-a0b752e216fe", + "name": "Obtener info de Oportunidad - MARCA (v2)", + "notes": "Sólo se ejecuta en rama UPDATE.\nReemplaza el nodo original 'Obtener info de Oportunidad - MARCA' que dependía de búsqueda por nombre." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 1104 + ], + "id": "0fb2fdea-cf40-4dfb-abfa-c422282e5d2a", + "name": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 1104 + ], + "id": "58114758-f0fb-483a-bd5b-d28188880a02", + "name": "Obtener Pipelines - MARCA (CREATE)" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA CREAR OPORTUNIDAD EN MARCA ──────────────────────\n// Mapea custom fields por fieldKey (fallback name) y FIJA el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia futura).\n\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (CREATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (CREATE)').first().json.pipelines || [];\nconst locationId = $('DATOS API').first().json['Location ID'];\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst contactId = decision.contactId;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\n// Map pipelines/stages de la marca por nombre normalizado\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)];\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)];\n\nif (!resolvedPipelineId) {\n throw new Error(`No se encontró pipeline en marca: \"${sucursalOpp.pipelineName}\". Revisa que el nombre coincida.`);\n}\nif (!resolvedStageId) {\n throw new Error(`No se encontró stage en marca: \"${sucursalOpp.pipelineStageName}\". Revisa que el nombre coincida.`);\n}\n\n// Mapear custom fields: preferir fieldKey, fallback a name normalizado\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null || value === '') continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// FIJAR el campo de enlace \"ID Oportunidad Sucursal\" = id de la opp de sucursal\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n locationId: locationId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || 'open',\n contactId: contactId,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source || 'Sincronización Sucursal',\n customFields: customFields\n};\n\nreturn [{\n json: {\n body: JSON.stringify(body),\n debug: { resolvedPipelineId, resolvedStageId, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5520, + 1104 + ], + "id": "2f9a63e7-277d-49de-8838-a00922c993b2", + "name": "Armar Body - CREATE" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5760, + 1104 + ], + "id": "fc4b3398-91bb-4dd9-b406-96c8d75a17be", + "name": "Crear Oportunidad - MARCA" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA ACTUALIZAR OPORTUNIDAD EN MARCA (v2) ────────────\n// Mapea CF por fieldKey (fallback name) y reafirma el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia).\n\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA (v2)').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (UPDATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (UPDATE)').first().json.pipelines || [];\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)] || marcaOpp.pipelineStageId;\n\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null) {\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n value = marcaOriginal?.fieldValue;\n }\n if (value === undefined || value === null) continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// Reafirmar el campo de enlace\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || marcaOpp.status,\n monetaryValue: sucursalOpp.monetaryValue ?? marcaOpp.monetaryValue,\n source: sucursalOpp.source || marcaOpp.source,\n customFields: customFields\n};\n\nreturn [{\n json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body),\n debug: { matchReason: decision.matchReason, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5280, + 816 + ], + "id": "969e46cd-bbae-4372-9808-c939f63e3c02", + "name": "Armar Body - UPDATE (v2)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5520, + 816 + ], + "id": "83929e08-7afd-4670-9c18-be22b8c3656f", + "name": "Actualizar Oportunidad - MARCA (v2)" + }, + { + "parameters": { + "content": "# RAMA UPDATE\n## Oportunidad ya existe en marca → se actualiza", + "height": 280, + "width": 880, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 720 + ], + "typeVersion": 1, + "id": "1c285234-8d90-49ef-8dac-c98cb379299a", + "name": "Sticky Update" + }, + { + "parameters": { + "content": "# RAMA CREATE\n## Oportunidad no existe en marca → se crea desde cero\n### Incluye contacto + custom fields + tag origin-id", + "height": 280, + "width": 1120, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 1024 + ], + "typeVersion": 1, + "id": "7e8f350c-9562-485b-8ce5-1b13b4086856", + "name": "Sticky Create" + }, + { + "parameters": { + "content": "# MATCHING ROBUSTO\n## 1. Buscar/crear contacto en marca\n## 2. Buscar oportunidades DEL CONTACTO\n## 3. Decidir match por cantidad + tag origin-id", + "height": 320, + "width": 1500, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3328, + 864 + ], + "typeVersion": 1, + "id": "8a583e46-41b6-44e0-9313-b70ee085b740", + "name": "Sticky Matching" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 560 + ], + "id": "45f0a6b0-f44d-4e83-b2ab-cdd3fbfe7911", + "name": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 560 + ], + "id": "5ca962a9-9e56-42d1-af05-ce05d39ed77f", + "name": "Obtener Pipelines - MARCA (UPDATE)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ customFields: [ { id: $('Conseguir Custom Fields - Opportunity - SUCURSAL').item.json.customFields.find(f => f.fieldKey === 'opportunity.id_oportunidad_sucursal')?.id, key: 'opportunity.id_oportunidad_sucursal', field_value: $('Datos de Lead').item.json.Oportunidad.opportunity_id } ] }) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1328, + -32 + ], + "id": "a9572dee-ed57-4104-966a-4b22721e75f1", + "name": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Auto-mapeo de seguridad: setea 'ID Oportunidad Sucursal' = id nativo de la propia opp de sucursal (del webhook) antes de procesar. onError=continue para no romper el sync si falla." + }, + { + "parameters": { + "databaseId": 63, + "tableId": 754, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7280, + "value": "={{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 4208, + 1152 + ], + "id": "a64fdffd-ab8f-4ddd-a4d4-4d683a05132d", + "name": "Buscar Mapeo Opp - Baserow", + "alwaysOutputData": true, + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Idempotencia global: busca en la tabla 754 el mapeo id_opp_sucursal->id_opp_marca. Si existe, 'Decidir Match' hara UPDATE de esa opp aunque el contacto resuelto sea otro. alwaysOutputData + onError para no romper el flujo si no hay match o si Baserow falla." + }, + { + "parameters": { + "jsCode": "// Prepara el UPSERT del mapeo Baserow (tabla 754) tras CREATE/UPDATE de la opp en Marca.\n// Solo escribe cuando el mapeo NO existe aun:\n// - action CREATE -> la fila no existe (el lookup Baserow fallo) -> crear.\n// - action UPDATE por contacto -> Baserow no la tenia -> crear (idempotencia futura).\n// - action UPDATE por Baserow -> la fila YA existe -> NO escribir (no duplicar).\n// Asi una re-ejecucion del mismo opp encuentra el mapeo y hace UPDATE, no CREATE duplicado.\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst action = decision.action;\nconst matchReason = decision.matchReason || '';\nconst sucursalOppId = decision.sucursalOppId;\n\nconst matchedByBaserow = matchReason.indexOf('Baserow') !== -1;\nconst needWrite = (action === 'CREATE') || (action === 'UPDATE' && !matchedByBaserow);\nif (!needWrite) return [];\n\n// id de la opp en Marca segun el camino\nlet marcaOppId = decision.opportunityId;\nif (action === 'CREATE') {\n try {\n const co = $('Crear Oportunidad - MARCA').first().json;\n marcaOppId = (co.opportunity && co.opportunity.id) || co.id || marcaOppId;\n } catch (e) {}\n}\nif (!marcaOppId || !sucursalOppId) return [];\n\nlet locationSucursal = '';\ntry { locationSucursal = $('DATOS API - SUCURSAL').first().json['Location ID'] || ''; } catch (e) {}\n\nreturn [{ json: { sucursalOppId, marcaOppId, locationSucursal } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 6048, + 960 + ], + "id": "6542723e-f7b2-41de-b371-9e793d48f560", + "name": "Preparar Upsert Mapeo", + "onError": "continueRegularOutput", + "notes": "Decide si escribir el mapeo Baserow tras CREATE/UPDATE. Solo cuando la fila no existe (CREATE o UPDATE-por-contacto). onError continue para no romper la replicacion." + }, + { + "parameters": { + "operation": "create", + "databaseId": 63, + "tableId": 754, + "fieldsUi": { + "fieldValues": [ + { + "fieldId": 7280, + "fieldValue": "={{ $json.sucursalOppId }}" + }, + { + "fieldId": 7283, + "fieldValue": "={{ $json.marcaOppId }}" + }, + { + "fieldId": 7284, + "fieldValue": "={{ $json.locationSucursal }}" + }, + { + "fieldId": 7285, + "fieldValue": "={{ $now.toISO() }}" + } + ] + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + 6288, + 960 + ], + "id": "c69036dd-3914-4424-a165-6e0f3b110ebd", + "name": "Crear Mapeo - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Inserta el mapeo id_opp_sucursal->id_opp_marca en la tabla 754 en tiempo real. Idempotente con el backfill (verifica por id_opp_sucursal)." + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API": { + "main": [ + [ + { + "node": "Buscar Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "API de Sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - SUCURSAL": { + "main": [ + [ + { + "node": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Pipelines - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de Sucursal": { + "main": [ + [ + { + "node": "DATOS API - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de MARCA": { + "main": [ + [ + { + "node": "DATOS API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapeo completo oportunidad origen - SUCURSAL": { + "main": [ + [ + { + "node": "API de MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - SUCURSAL": { + "main": [ + [ + { + "node": "Mapeo completo oportunidad origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contacto en MARCA": { + "main": [ + [ + { + "node": "¿Existe contacto en MARCA?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Existe contacto en MARCA?": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Crear Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Contacto en MARCA": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Contact ID Resuelto": { + "main": [ + [ + { + "node": "Buscar Mapeo Opp - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidades del Contacto - MARCA": { + "main": [ + [ + { + "node": "Decidir Match (Create vs Update)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Decidir Match (Create vs Update)": { + "main": [ + [ + { + "node": "Switch UPDATE vs CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch UPDATE vs CREATE": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - UPDATE (v2)": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Armar Body - CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - CREATE": { + "main": [ + [ + { + "node": "Crear Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Armar Body - UPDATE (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapear ID Oportunidad Sucursal - SUCURSAL": { + "main": [ + [ + { + "node": "Buscar Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Mapeo Opp - Baserow": { + "main": [ + [ + { + "node": "Buscar Oportunidades del Contacto - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Upsert Mapeo": { + "main": [ + [ + { + "node": "Crear Mapeo - Baserow", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false + }, + "staticData": null, + "meta": { + "templateCredsSetupCompleted": true + }, + "pinData": { + "Webhook": [ + { + "json": { + "headers": { + "host": "webhookn8.consultoriae3.com", + "user-agent": "axios/1.13.2", + "content-length": "1839", + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, compress, deflate, br", + "content-type": "application/json", + "traceparent": "00-00e6586cc6967f6985b8aa87332de4e0-932bdb81faeff7c4-01", + "x-forwarded-for": "34.55.150.6", + "x-forwarded-host": "webhookn8.consultoriae3.com", + "x-forwarded-port": "443", + "x-forwarded-proto": "https", + "x-forwarded-server": "f569f6e85d30", + "x-real-ip": "34.55.150.6" + }, + "params": {}, + "query": {}, + "body": { + "¿Cuándo necesitas el dinero?": "", + "[Número de Teléfono para Atender]": "", + "Información Adicional": "", + "Presupuesto": "", + "CANAL DE ORIGEN": "", + "[Número de WhatsApp para Atender]": "", + "[Dirección de la Empresa]": "", + "Año del Vehículo": "2015", + "¿Qué modalidad prefieres?": "Tradicional (Resguardo)", + "Marca del Vehiculo": "RENAULT", + "TIENDA": "TAMPICO", + "Sucursal": "Tampico, Tamaulipas", + "Descripción": "", + "[Correo_Empresa]": "", + "ID Contacto Monte Providencia": "", + "Fuente de Prospecto": "REDES SOCIALES", + "[Correo_Canalizados]": "", + "Version del Vehiculo": "LOGAN", + "Acepta los terminos para tu cotización": "", + "[Nombre de la Empresa]": "", + "Check Comunicaciones de Marketing": "", + "Archivos Adicionales": "", + "contact_id": "B9oJWsFXy6Ol9NdHL5Jh", + "first_name": "RAUL", + "last_name": "LOGAN", + "full_name": "RAUL LOGAN", + "email": "raulsincorreo@gmail.com", + "phone": "+528332937527", + "tags": "sucursal", + "country": "MX", + "date_created": "2026-05-27T21:16:44.369Z", + "full_address": "", + "contact_type": "lead", + "opportunity_name": "RAUL LOGAN", + "status": "open", + "lead_value": 40000, + "opportunity_source": "REDES SOCIALES", + "source": "REDES SOCIALES", + "pipleline_stage": "Cliente interesado", + "pipeline_id": "ep1d4VpzRezVqWayFbBf", + "id": "NuFArKAQL72I8QC69MDa", + "pipeline_name": "Standar", + "user": { + "firstName": "Tampico - 85969", + "lastName": "Tampico", + "email": "ezstore85969@ezcorp.com" + }, + "owner": "Tampico - 85969 Tampico", + "location": { + "name": "85969 - MP - Tampico", + "address": "Tampico", + "city": "Tampico", + "state": "Tamaulipas", + "country": "MX", + "postalCode": "10000", + "fullAddress": "Tampico, Tampico Tamaulipas 10000", + "id": "WCHyow6KpjLFYriQWdbJ" + }, + "workflow": { + "id": "8ba8fdf1-85f2-4f1f-bf28-56400e1adcd6", + "name": "Sincronizar Oportunidad V2" + }, + "triggerData": {}, + "contact": { + "attributionSource": { + "sessionSource": "CRM UI", + "mediumId": null, + "medium": "manual" + }, + "lastAttributionSource": {} + }, + "attributionSource": {}, + "customData": {} + }, + "webhookUrl": "https://webhookn8.consultoriae3.com/webhook/3eecb45a-e24c-49ac-aef7-99843f485819", + "executionMode": "production" + }, + "pairedItem": { + "item": 0 + } + } + ] + }, + "versionId": "23dedba0-39b4-4edc-8751-03808c7af4d7", + "activeVersionId": "65decdcb-f97e-493d-8eb9-b872905b3d35", + "versionCounter": 433, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-03-02T01:12:56.387Z", + "createdAt": "2026-03-02T01:12:56.387Z", + "role": "workflow:owner", + "workflowId": "Cfgwp0bOtDW8zuKW", + "projectId": "aycNlMpnzsxvXbfG", + "project": { + "updatedAt": "2025-11-27T04:06:26.313Z", + "createdAt": "2025-11-27T03:54:24.410Z", + "id": "aycNlMpnzsxvXbfG", + "name": "Consultoria E3 ® ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + } + ], + "tags": [ + { + "updatedAt": "2025-11-27T05:48:12.272Z", + "createdAt": "2025-11-27T05:48:12.272Z", + "id": "pHkPpq3Httzx4hnb", + "name": "Bucefalo" + } + ], + "activeVersion": { + "updatedAt": "2026-05-30T17:03:07.471Z", + "createdAt": "2026-05-30T17:03:07.471Z", + "versionId": "65decdcb-f97e-493d-8eb9-b872905b3d35", + "workflowId": "Cfgwp0bOtDW8zuKW", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "3eecb45a-e24c-49ac-aef7-99843f485819", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -336, + -16 + ], + "id": "dd424583-8295-4013-96ba-c7e29ea944c6", + "name": "Webhook", + "webhookId": "3eecb45a-e24c-49ac-aef7-99843f485819" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "b02bc821-6121-452d-a042-623fac6e443c", + "name": "Oportunidad.opportunity_name", + "value": "={{ $json.body.opportunity_name }}", + "type": "string" + }, + { + "id": "898817a2-7926-4ddf-b785-cac8e28afc58", + "name": "Oportunidad.pipleline_stage (new)", + "value": "={{ $json.body.pipleline_stage }}", + "type": "string" + }, + { + "id": "opp-id-sucursal-capture", + "name": "Oportunidad.opportunity_id", + "value": "={{ $json.body.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -96, + -16 + ], + "id": "2b3ec130-27ce-4988-82db-46e4f12c3f7d", + "name": "Datos de Lead" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE SUCURSAL", + "height": 352, + "width": 1152 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1072, + -144 + ], + "typeVersion": 1, + "id": "19023e33-a9f1-4c60-b87c-957e751e3dc2", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE MARCA", + "height": 208, + "width": 1188, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2240, + -80 + ], + "typeVersion": 1, + "id": "3a3d4f18-1a08-4dbc-a00b-7ae6bc4d49d1", + "name": "Sticky Note1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2480, + -32 + ], + "id": "a46df506-6a9a-420d-9cdd-6fc51d1a691e", + "name": "DATOS API" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 80, + -16 + ], + "id": "25db982f-7bc7-4318-a7b0-e94326abc437", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3952, + -32 + ], + "id": "2320b102-816e-4a0c-ae9c-e305d25df6df", + "name": "Actualizar Oportunidad - MARCA2", + "notes": "El código mapea los custom fields de una oportunidad de una sucursal hacia los IDs equivalentes de una marca. Toma tres fuentes: los metadatos de campos de la sucursal, los valores reales de esos campos en la oportunidad, y los metadatos de campos de la marca. Para cada campo con valor en la oportunidad, primero busca su `fieldKey` en los metadatos de la sucursal usando el `id`, luego usa ese `fieldKey` para encontrar el campo equivalente en la marca, y construye un array con el `id` y `key` de la marca pero con el valor original de la sucursal. Finalmente arma el body de una petición para crear/actualizar una oportunidad en la marca, reutilizando pipeline, nombre, stage y valor monetario de otros nodos, con los custom fields ya traducidos al contexto de la marca." + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1536, + -32 + ], + "id": "4e89658b-7ffd-4a3b-b91e-9ed3223a1a73", + "name": "Buscar Oportunidad - SUCURSAL", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 528, + -32 + ], + "id": "b721a65d-bcbb-44ab-bb7f-8f200b34fcdd", + "name": "DATOS API - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1136, + -32 + ], + "id": "39ef9dcc-ac20-4ba2-b891-2a2baa6ea4f5", + "name": "Conseguir Custom Fields - Opportunity - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1712, + -32 + ], + "id": "fbd46ae4-792b-451b-b99e-9816dc175d52", + "name": "Obtener info de Oportunidad - SUCURSAL" + }, + { + "parameters": { + "content": "# ACTUALIZAR OPORTUNIDAD - MARCA", + "height": 352, + "width": 928 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3456, + -144 + ], + "typeVersion": 1, + "id": "f3ca1cfe-08e2-485b-b238-9240f5753809", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "", + "height": 208, + "width": 992, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2768, + -64 + ], + "typeVersion": 1, + "id": "45066721-8404-48ed-94c1-c88edf48bca5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "", + "height": 192, + "width": 928, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1104, + -64 + ], + "typeVersion": 1, + "id": "b1646bb4-8b64-4bf4-b380-189cc7902741", + "name": "Sticky Note7" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Webhook').item.json.body.location.name }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 320, + -32 + ], + "id": "12088f29-8bb4-475d-ae0e-2cbdf8033138", + "name": "API de Sucursal", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "=Monte Providencia" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 2288, + -32 + ], + "id": "8c8fa238-6ce4-4adf-8926-b603987a55cf", + "name": "API de MARCA", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "jsCode": "const opportunityData = $('Obtener info de Oportunidad - SUCURSAL').first().json;\nconst opportunity = opportunityData.opportunity;\nconst customFieldsDefs = $('Conseguir Custom Fields - Opportunity - SUCURSAL').first().json.customFields;\nconst pipelines = $input.first().json.pipelines;\n\n// Map custom fields: id -> { fieldKey, name }\nconst fieldMap = {};\nfor (const def of customFieldsDefs) {\n fieldMap[def.id] = { fieldKey: def.fieldKey, name: def.name };\n}\n\n// Map pipelines: id -> name, stages: id -> { name, position, stageWinProbability }\nconst pipelineMap = {};\nconst stageMap = {};\nfor (const pipeline of pipelines) {\n pipelineMap[pipeline.id] = pipeline.name;\n for (const stage of pipeline.stages) {\n stageMap[stage.id] = {\n name: stage.name,\n position: stage.position,\n stageWinProbability: stage.stageWinProbability,\n color: stage.color || null\n };\n }\n}\n\n// Enrich custom fields with fieldKey and name\nconst enrichedCustomFields = opportunity.customFields.map(cf => ({\n id: cf.id,\n name: fieldMap[cf.id]?.name || null,\n fieldKey: fieldMap[cf.id]?.fieldKey || null,\n fieldValue: cf.fieldValue\n}));\n\n// Resolve pipeline and stage info\nconst stageInfo = stageMap[opportunity.pipelineStageId] || {};\n\n// Build enriched opportunity\nconst enrichedOpportunity = {\n ...opportunity,\n pipelineName: pipelineMap[opportunity.pipelineId] || null,\n pipelineStageName: stageInfo.name || null,\n pipelineStagePosition: stageInfo.position ?? null,\n pipelineStageWinProbability: stageInfo.stageWinProbability ?? null,\n pipelineStageColor: stageInfo.color || null,\n customFields: enrichedCustomFields\n};\n\nreturn [{ json: { opportunity: enrichedOpportunity, traceId: opportunityData.traceId } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + -32 + ], + "id": "d9609dd9-9493-4f3d-b356-99438cedada6", + "name": "Mapeo completo oportunidad origen - SUCURSAL" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('Webhook').item.json.body.location.id }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de Sucursal').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1872, + -32 + ], + "id": "2d799ae3-b6b1-4e4f-8774-714ebe4f009f", + "name": "Obtener Pipelines - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Buscar Oportunidad - MARCA').item.json.opportunities[0].id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3216, + -32 + ], + "id": "1eee43d2-c6a8-414b-bebf-4b31d6fb34d7", + "name": "Obtener info de Oportunidad - MARCA" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('API de MARCA').item.json.Location_ID }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de MARCA').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3424, + -32 + ], + "id": "20a1a288-2cd8-471b-9203-5ba88770edb0", + "name": "Obtener Pipelines - MARCA" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2800, + -32 + ], + "id": "798a7e51-b8ed-4364-947a-7396fab47940", + "name": "Buscar Oportunidad - MARCA", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3008, + -32 + ], + "id": "cc37ea9b-0a9f-44ce-8ce2-ee2444822ecf", + "name": "Conseguir Custom Fields - Opportunity - MARCA" + }, + { + "parameters": { + "jsCode": "// ── DATOS MARCA ──────────────────────────────────────────────────────────────\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA').first().json.customFields;\nconst marcaPipelines = $input.first().json.pipelines;\n\n// ── DATOS SUCURSAL (ya enriquecidos) ─────────────────────────────────────────\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\n\n// ── MAP PIPELINES MARCA: name -> { id, stages } ───────────────────────────────\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[pipeline.name.trim()] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[stage.name.trim()] = stage.id;\n }\n}\n\n// ── RESOLVER PIPELINE Y STAGE POR NAME DESDE SUCURSAL ────────────────────────\nconst resolvedPipelineId = marcaPipelineByName[sucursalOpp.pipelineName?.trim()] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[sucursalOpp.pipelineStageName?.trim()] || marcaOpp.pipelineStageId;\n\n// ── MAP CUSTOM FIELDS SUCURSAL: name -> fieldValue ────────────────────────────\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields) {\n if (cf.name) {\n sucursalValueByName[cf.name.trim()] = cf.fieldValue;\n }\n}\n\n// ── CONSTRUIR CUSTOM FIELDS PARA EL BODY ─────────────────────────────────────\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n const nameKey = def.name.trim();\n const sucursalValue = sucursalValueByName[nameKey];\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n const fieldValue = sucursalValue !== undefined ? sucursalValue : (marcaOriginal?.fieldValue ?? null);\n\n if (fieldValue === null) continue;\n\n customFields.push({\n id: def.id,\n key: def.fieldKey,\n field_value: fieldValue\n });\n}\n\n// ── CONSTRUIR BODY VÁLIDO PARA UPDATE OPPORTUNITY ────────────────────────────\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source,\n customFields: customFields\n};\n\nreturn [{ json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body)\n} }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3632, + -32 + ], + "id": "3b547c2a-a541-4b48-8d97-ea2d5df52010", + "name": "Code in JavaScript" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"pageLimit\": 10,\n \"query\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono || $('Datos de Lead').item.json.Cliente.Email }}\"\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3360, + 960 + ], + "id": "02cd0fd6-6bb8-4faa-b85f-5d42663fba29", + "name": "Buscar Contacto en MARCA", + "notes": "Busca contacto en la location de marca por email.\nSi no encuentra, la siguiente IF deriva a creación." + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "exist-contact-check", + "leftValue": "={{ $json.contacts ? $json.contacts.length : 0 }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3600, + 960 + ], + "id": "aa2644dc-34b4-4159-8afb-89147b12f5ee", + "name": "¿Existe contacto en MARCA?" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/upsert", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"firstName\": \"{{ $('Datos de Lead').item.json.Cliente.Apellido }}\",\n \"name\": \"{{ $('Datos de Lead').item.json.Cliente['Nombre Completo'] }}\",\n \"email\": \"{{ $('Datos de Lead').item.json.Cliente.Email }}\",\n \"phone\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono }}\",\n \"source\": \"Sincronización Sucursal\",\n \"tags\": [\"sincronizado-sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3840, + 1104 + ], + "id": "f6c75352-3b9e-4fc9-b8e4-063c2792d190", + "name": "Crear Contacto en MARCA", + "notes": "Crea contacto en la marca solo si no existía.\nIMPORTANTE: revisa el mapeo de Nombre/Apellido, en el workflow original están invertidos." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "contact-id-resolved", + "name": "contactId", + "value": "={{ $json.contact ? $json.contact.id : $json.contacts[0].id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 4080, + 960 + ], + "id": "2a49d01e-6679-4584-9d9b-5778dfc4431e", + "name": "Set Contact ID Resuelto", + "notes": "Unifica el contactId tanto si vino de la búsqueda (contacts[0].id) como de la creación (contact.id)." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "location_id", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + }, + { + "name": "contact_id", + "value": "={{ $('Set Contact ID Resuelto').item.json.contactId }}" + }, + { + "name": "limit", + "value": "100" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 4320, + 960 + ], + "id": "8e25f660-3ed2-4597-9c2a-7480ac7d150f", + "name": "Buscar Oportunidades del Contacto - MARCA", + "notes": "Busca TODAS las oportunidades del contacto en la marca.\nDespués el Code de decisión elige cuál (o crea nueva)." + }, + { + "parameters": { + "jsCode": "// DECISION (Create vs Update) con idempotencia GLOBAL via mapeo Baserow (tabla 754).\n// (1) Si el mapeo id_opp_sucursal -> id_opp_marca existe -> UPDATE esa opp de Marca,\n// independiente del contacto resuelto (evita duplicados por contacto ambiguo).\n// (2) Si no hay mapeo, match por CF entre las opps del contacto resuelto.\n// (3) Si nada -> CREATE.\nconst result = $input.first().json;\nconst opportunities = result.opportunities || [];\nconst sucursalOppId = $('Datos de Lead').first().json.Oportunidad.opportunity_id;\n\nlet action = 'CREATE';\nlet opportunityId = null;\nlet matchReason = '';\n\n// (1) Mapeo Baserow global\nlet baserowRows = [];\ntry { baserowRows = $('Buscar Mapeo Opp - Baserow').all().map(i => i.json); } catch (e) { baserowRows = []; }\nconst mapped = baserowRows.find(r =>\n String(r.id_opp_sucursal || '') === String(sucursalOppId) && String(r.id_opp_marca || '').trim()\n);\n\nif (mapped) {\n action = 'UPDATE';\n opportunityId = String(mapped.id_opp_marca).trim();\n matchReason = 'Match por mapeo Baserow (global, tabla 754)';\n} else {\n // (2) match por CF entre las opps del contacto resuelto\n const match = opportunities.find(o =>\n (o.customFields || []).some(cf => {\n const v = cf.fieldValueString ?? cf.fieldValue ?? '';\n return String(v) === String(sucursalOppId);\n })\n );\n if (match) {\n action = 'UPDATE';\n opportunityId = match.id;\n matchReason = 'Match por ID Oportunidad Sucursal (contacto)';\n } else {\n action = 'CREATE';\n matchReason = 'Sin match (Baserow ni contacto) -> CREATE (contacto tiene ' + opportunities.length + ' opps)';\n }\n}\n\nreturn [{\n json: {\n action,\n opportunityId,\n matchReason,\n sucursalOppId,\n contactId: $('Set Contact ID Resuelto').first().json.contactId,\n totalOppsContacto: opportunities.length\n }\n}];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4560, + 960 + ], + "id": "6ca30f13-8503-427f-95ec-b243d30688ec", + "name": "Decidir Match (Create vs Update)" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-update", + "leftValue": "={{ $json.action }}", + "rightValue": "UPDATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "UPDATE" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-create", + "leftValue": "={{ $json.action }}", + "rightValue": "CREATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "CREATE" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 4800, + 960 + ], + "id": "bff7a5dc-b4af-4033-83ea-c508aa92264a", + "name": "Switch UPDATE vs CREATE" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Decidir Match (Create vs Update)').item.json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5040, + 816 + ], + "id": "83cd0fd7-1ee9-4896-926e-a0b752e216fe", + "name": "Obtener info de Oportunidad - MARCA (v2)", + "notes": "Sólo se ejecuta en rama UPDATE.\nReemplaza el nodo original 'Obtener info de Oportunidad - MARCA' que dependía de búsqueda por nombre." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 1104 + ], + "id": "0fb2fdea-cf40-4dfb-abfa-c422282e5d2a", + "name": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 1104 + ], + "id": "58114758-f0fb-483a-bd5b-d28188880a02", + "name": "Obtener Pipelines - MARCA (CREATE)" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA CREAR OPORTUNIDAD EN MARCA ──────────────────────\n// Mapea custom fields por fieldKey (fallback name) y FIJA el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia futura).\n\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (CREATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (CREATE)').first().json.pipelines || [];\nconst locationId = $('DATOS API').first().json['Location ID'];\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst contactId = decision.contactId;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\n// Map pipelines/stages de la marca por nombre normalizado\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)];\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)];\n\nif (!resolvedPipelineId) {\n throw new Error(`No se encontró pipeline en marca: \"${sucursalOpp.pipelineName}\". Revisa que el nombre coincida.`);\n}\nif (!resolvedStageId) {\n throw new Error(`No se encontró stage en marca: \"${sucursalOpp.pipelineStageName}\". Revisa que el nombre coincida.`);\n}\n\n// Mapear custom fields: preferir fieldKey, fallback a name normalizado\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null || value === '') continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// FIJAR el campo de enlace \"ID Oportunidad Sucursal\" = id de la opp de sucursal\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n locationId: locationId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || 'open',\n contactId: contactId,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source || 'Sincronización Sucursal',\n customFields: customFields\n};\n\nreturn [{\n json: {\n body: JSON.stringify(body),\n debug: { resolvedPipelineId, resolvedStageId, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5520, + 1104 + ], + "id": "2f9a63e7-277d-49de-8838-a00922c993b2", + "name": "Armar Body - CREATE" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5760, + 1104 + ], + "id": "fc4b3398-91bb-4dd9-b406-96c8d75a17be", + "name": "Crear Oportunidad - MARCA" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA ACTUALIZAR OPORTUNIDAD EN MARCA (v2) ────────────\n// Mapea CF por fieldKey (fallback name) y reafirma el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia).\n\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA (v2)').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (UPDATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (UPDATE)').first().json.pipelines || [];\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)] || marcaOpp.pipelineStageId;\n\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null) {\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n value = marcaOriginal?.fieldValue;\n }\n if (value === undefined || value === null) continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// Reafirmar el campo de enlace\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || marcaOpp.status,\n monetaryValue: sucursalOpp.monetaryValue ?? marcaOpp.monetaryValue,\n source: sucursalOpp.source || marcaOpp.source,\n customFields: customFields\n};\n\nreturn [{\n json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body),\n debug: { matchReason: decision.matchReason, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5280, + 816 + ], + "id": "969e46cd-bbae-4372-9808-c939f63e3c02", + "name": "Armar Body - UPDATE (v2)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5520, + 816 + ], + "id": "83929e08-7afd-4670-9c18-be22b8c3656f", + "name": "Actualizar Oportunidad - MARCA (v2)" + }, + { + "parameters": { + "content": "# RAMA UPDATE\n## Oportunidad ya existe en marca → se actualiza", + "height": 280, + "width": 880, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 720 + ], + "typeVersion": 1, + "id": "1c285234-8d90-49ef-8dac-c98cb379299a", + "name": "Sticky Update" + }, + { + "parameters": { + "content": "# RAMA CREATE\n## Oportunidad no existe en marca → se crea desde cero\n### Incluye contacto + custom fields + tag origin-id", + "height": 280, + "width": 1120, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 1024 + ], + "typeVersion": 1, + "id": "7e8f350c-9562-485b-8ce5-1b13b4086856", + "name": "Sticky Create" + }, + { + "parameters": { + "content": "# MATCHING ROBUSTO\n## 1. Buscar/crear contacto en marca\n## 2. Buscar oportunidades DEL CONTACTO\n## 3. Decidir match por cantidad + tag origin-id", + "height": 320, + "width": 1500, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3328, + 864 + ], + "typeVersion": 1, + "id": "8a583e46-41b6-44e0-9313-b70ee085b740", + "name": "Sticky Matching" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 560 + ], + "id": "45f0a6b0-f44d-4e83-b2ab-cdd3fbfe7911", + "name": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 560 + ], + "id": "5ca962a9-9e56-42d1-af05-ce05d39ed77f", + "name": "Obtener Pipelines - MARCA (UPDATE)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ customFields: [ { id: $('Conseguir Custom Fields - Opportunity - SUCURSAL').item.json.customFields.find(f => f.fieldKey === 'opportunity.id_oportunidad_sucursal')?.id, key: 'opportunity.id_oportunidad_sucursal', field_value: $('Datos de Lead').item.json.Oportunidad.opportunity_id } ] }) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1328, + -32 + ], + "id": "a9572dee-ed57-4104-966a-4b22721e75f1", + "name": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Auto-mapeo de seguridad: setea 'ID Oportunidad Sucursal' = id nativo de la propia opp de sucursal (del webhook) antes de procesar. onError=continue para no romper el sync si falla." + }, + { + "parameters": { + "databaseId": 63, + "tableId": 754, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7280, + "value": "={{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 4200, + 1140 + ], + "id": "a64fdffd-ab8f-4ddd-a4d4-4d683a05132d", + "name": "Buscar Mapeo Opp - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "alwaysOutputData": true, + "onError": "continueRegularOutput", + "notes": "Idempotencia global: busca en la tabla 754 el mapeo id_opp_sucursal->id_opp_marca. Si existe, 'Decidir Match' hara UPDATE de esa opp aunque el contacto resuelto sea otro. alwaysOutputData + onError para no romper el flujo si no hay match o si Baserow falla." + }, + { + "parameters": { + "jsCode": "// Prepara el UPSERT del mapeo Baserow (tabla 754) tras CREATE/UPDATE de la opp en Marca.\n// Solo escribe cuando el mapeo NO existe aun:\n// - action CREATE -> la fila no existe (el lookup Baserow fallo) -> crear.\n// - action UPDATE por contacto -> Baserow no la tenia -> crear (idempotencia futura).\n// - action UPDATE por Baserow -> la fila YA existe -> NO escribir (no duplicar).\n// Asi una re-ejecucion del mismo opp encuentra el mapeo y hace UPDATE, no CREATE duplicado.\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst action = decision.action;\nconst matchReason = decision.matchReason || '';\nconst sucursalOppId = decision.sucursalOppId;\n\nconst matchedByBaserow = matchReason.indexOf('Baserow') !== -1;\nconst needWrite = (action === 'CREATE') || (action === 'UPDATE' && !matchedByBaserow);\nif (!needWrite) return [];\n\n// id de la opp en Marca segun el camino\nlet marcaOppId = decision.opportunityId;\nif (action === 'CREATE') {\n try {\n const co = $('Crear Oportunidad - MARCA').first().json;\n marcaOppId = (co.opportunity && co.opportunity.id) || co.id || marcaOppId;\n } catch (e) {}\n}\nif (!marcaOppId || !sucursalOppId) return [];\n\nlet locationSucursal = '';\ntry { locationSucursal = $('DATOS API - SUCURSAL').first().json['Location ID'] || ''; } catch (e) {}\n\nreturn [{ json: { sucursalOppId, marcaOppId, locationSucursal } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 6040, + 960 + ], + "id": "6542723e-f7b2-41de-b371-9e793d48f560", + "name": "Preparar Upsert Mapeo", + "onError": "continueRegularOutput", + "notes": "Decide si escribir el mapeo Baserow tras CREATE/UPDATE. Solo cuando la fila no existe (CREATE o UPDATE-por-contacto). onError continue para no romper la replicacion." + }, + { + "parameters": { + "operation": "create", + "databaseId": 63, + "tableId": 754, + "fieldsUi": { + "fieldValues": [ + { + "fieldId": 7280, + "fieldValue": "={{ $json.sucursalOppId }}" + }, + { + "fieldId": 7283, + "fieldValue": "={{ $json.marcaOppId }}" + }, + { + "fieldId": 7284, + "fieldValue": "={{ $json.locationSucursal }}" + }, + { + "fieldId": 7285, + "fieldValue": "={{ $now.toISO() }}" + } + ] + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + 6280, + 960 + ], + "id": "c69036dd-3914-4424-a165-6e0f3b110ebd", + "name": "Crear Mapeo - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Inserta el mapeo id_opp_sucursal->id_opp_marca en la tabla 754 en tiempo real. Idempotente con el backfill (verifica por id_opp_sucursal)." + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API": { + "main": [ + [ + { + "node": "Buscar Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "API de Sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - SUCURSAL": { + "main": [ + [ + { + "node": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Pipelines - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de Sucursal": { + "main": [ + [ + { + "node": "DATOS API - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de MARCA": { + "main": [ + [ + { + "node": "DATOS API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapeo completo oportunidad origen - SUCURSAL": { + "main": [ + [ + { + "node": "API de MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - SUCURSAL": { + "main": [ + [ + { + "node": "Mapeo completo oportunidad origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contacto en MARCA": { + "main": [ + [ + { + "node": "¿Existe contacto en MARCA?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Existe contacto en MARCA?": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Crear Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Contacto en MARCA": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Contact ID Resuelto": { + "main": [ + [ + { + "node": "Buscar Mapeo Opp - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidades del Contacto - MARCA": { + "main": [ + [ + { + "node": "Decidir Match (Create vs Update)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Decidir Match (Create vs Update)": { + "main": [ + [ + { + "node": "Switch UPDATE vs CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch UPDATE vs CREATE": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - UPDATE (v2)": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Armar Body - CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - CREATE": { + "main": [ + [ + { + "node": "Crear Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Armar Body - UPDATE (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapear ID Oportunidad Sucursal - SUCURSAL": { + "main": [ + [ + { + "node": "Buscar Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Mapeo Opp - Baserow": { + "main": [ + [ + { + "node": "Buscar Oportunidades del Contacto - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Upsert Mapeo": { + "main": [ + [ + { + "node": "Crear Mapeo - Baserow", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "Consultoria E3 ®", + "name": null, + "description": null, + "autosaved": false, + "workflowPublishHistory": [ + { + "createdAt": "2026-05-30T17:03:07.625Z", + "id": 674, + "workflowId": "Cfgwp0bOtDW8zuKW", + "versionId": "65decdcb-f97e-493d-8eb9-b872905b3d35", + "event": "deactivated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + }, + { + "createdAt": "2026-05-30T17:03:07.684Z", + "id": 675, + "workflowId": "Cfgwp0bOtDW8zuKW", + "versionId": "65decdcb-f97e-493d-8eb9-b872905b3d35", + "event": "activated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + ] + } +} \ No newline at end of file diff --git a/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_165900.json b/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_165900.json new file mode 100644 index 0000000..fb3de80 --- /dev/null +++ b/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_165900.json @@ -0,0 +1,4228 @@ +{ + "updatedAt": "2026-05-30T22:42:49.703Z", + "createdAt": "2026-03-02T01:12:56.387Z", + "id": "Cfgwp0bOtDW8zuKW", + "name": "Sincronizar Oportunidad - Nodos Nuevos (Create/Update)", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "3eecb45a-e24c-49ac-aef7-99843f485819", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -336, + -16 + ], + "id": "dd424583-8295-4013-96ba-c7e29ea944c6", + "name": "Webhook", + "webhookId": "3eecb45a-e24c-49ac-aef7-99843f485819" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "b02bc821-6121-452d-a042-623fac6e443c", + "name": "Oportunidad.opportunity_name", + "value": "={{ $json.body.opportunity_name }}", + "type": "string" + }, + { + "id": "898817a2-7926-4ddf-b785-cac8e28afc58", + "name": "Oportunidad.pipleline_stage (new)", + "value": "={{ $json.body.pipleline_stage }}", + "type": "string" + }, + { + "id": "opp-id-sucursal-capture", + "name": "Oportunidad.opportunity_id", + "value": "={{ $json.body.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -96, + -16 + ], + "id": "2b3ec130-27ce-4988-82db-46e4f12c3f7d", + "name": "Datos de Lead" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE SUCURSAL", + "height": 352, + "width": 1152 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1072, + -144 + ], + "typeVersion": 1, + "id": "19023e33-a9f1-4c60-b87c-957e751e3dc2", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE MARCA", + "height": 208, + "width": 1188, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2240, + -80 + ], + "typeVersion": 1, + "id": "3a3d4f18-1a08-4dbc-a00b-7ae6bc4d49d1", + "name": "Sticky Note1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2480, + -32 + ], + "id": "a46df506-6a9a-420d-9cdd-6fc51d1a691e", + "name": "DATOS API" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 80, + -16 + ], + "id": "25db982f-7bc7-4318-a7b0-e94326abc437", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3952, + -32 + ], + "id": "2320b102-816e-4a0c-ae9c-e305d25df6df", + "name": "Actualizar Oportunidad - MARCA2", + "notes": "El código mapea los custom fields de una oportunidad de una sucursal hacia los IDs equivalentes de una marca. Toma tres fuentes: los metadatos de campos de la sucursal, los valores reales de esos campos en la oportunidad, y los metadatos de campos de la marca. Para cada campo con valor en la oportunidad, primero busca su `fieldKey` en los metadatos de la sucursal usando el `id`, luego usa ese `fieldKey` para encontrar el campo equivalente en la marca, y construye un array con el `id` y `key` de la marca pero con el valor original de la sucursal. Finalmente arma el body de una petición para crear/actualizar una oportunidad en la marca, reutilizando pipeline, nombre, stage y valor monetario de otros nodos, con los custom fields ya traducidos al contexto de la marca." + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1536, + -32 + ], + "id": "4e89658b-7ffd-4a3b-b91e-9ed3223a1a73", + "name": "Buscar Oportunidad - SUCURSAL", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 528, + -32 + ], + "id": "b721a65d-bcbb-44ab-bb7f-8f200b34fcdd", + "name": "DATOS API - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1136, + -32 + ], + "id": "39ef9dcc-ac20-4ba2-b891-2a2baa6ea4f5", + "name": "Conseguir Custom Fields - Opportunity - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1712, + -32 + ], + "id": "fbd46ae4-792b-451b-b99e-9816dc175d52", + "name": "Obtener info de Oportunidad - SUCURSAL" + }, + { + "parameters": { + "content": "# ACTUALIZAR OPORTUNIDAD - MARCA", + "height": 352, + "width": 928 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3456, + -144 + ], + "typeVersion": 1, + "id": "f3ca1cfe-08e2-485b-b238-9240f5753809", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "", + "height": 208, + "width": 992, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2768, + -64 + ], + "typeVersion": 1, + "id": "45066721-8404-48ed-94c1-c88edf48bca5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "", + "height": 192, + "width": 928, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1104, + -64 + ], + "typeVersion": 1, + "id": "b1646bb4-8b64-4bf4-b380-189cc7902741", + "name": "Sticky Note7" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Webhook').item.json.body.location.name }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 320, + -32 + ], + "id": "12088f29-8bb4-475d-ae0e-2cbdf8033138", + "name": "API de Sucursal", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "=Monte Providencia" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 2288, + -32 + ], + "id": "8c8fa238-6ce4-4adf-8926-b603987a55cf", + "name": "API de MARCA", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "jsCode": "const opportunityData = $('Obtener info de Oportunidad - SUCURSAL').first().json;\nconst opportunity = opportunityData.opportunity;\nconst customFieldsDefs = $('Conseguir Custom Fields - Opportunity - SUCURSAL').first().json.customFields;\nconst pipelines = $input.first().json.pipelines;\n\n// Map custom fields: id -> { fieldKey, name }\nconst fieldMap = {};\nfor (const def of customFieldsDefs) {\n fieldMap[def.id] = { fieldKey: def.fieldKey, name: def.name };\n}\n\n// Map pipelines: id -> name, stages: id -> { name, position, stageWinProbability }\nconst pipelineMap = {};\nconst stageMap = {};\nfor (const pipeline of pipelines) {\n pipelineMap[pipeline.id] = pipeline.name;\n for (const stage of pipeline.stages) {\n stageMap[stage.id] = {\n name: stage.name,\n position: stage.position,\n stageWinProbability: stage.stageWinProbability,\n color: stage.color || null\n };\n }\n}\n\n// Enrich custom fields with fieldKey and name\nconst enrichedCustomFields = opportunity.customFields.map(cf => ({\n id: cf.id,\n name: fieldMap[cf.id]?.name || null,\n fieldKey: fieldMap[cf.id]?.fieldKey || null,\n fieldValue: cf.fieldValue\n}));\n\n\n// ── CONTACT->OPP ENRICH: garantizar Sucursal / TIENDA / Canal de Origen ──\n// Respaldo cuando la opp de sucursal no trae el CF: tomar el valor del CONTACTO\n// (siempre poblado por [1604]/[2004]) y, en ultimo caso, del webhook. El fieldKey\n// canonico (sufijo) es identico en contact y opportunity.\nconst contactResp = $('Obtener Contacto - SUCURSAL').first().json;\nconst contact = (contactResp && contactResp.contact) ? contactResp.contact : (contactResp || {});\nconst webhookBody = ($('Webhook').first().json && $('Webhook').first().json.body) || {};\nconst norm2 = (s) => (s == null ? '' : String(s)).trim();\n\n// Valores del contacto por fieldKey (contacts traen {id, value}).\nconst contactValueByFieldKey = {};\nfor (const cf of (contact.customFields || [])) {\n const fk = fieldMap[cf.id] ? fieldMap[cf.id].fieldKey : null;\n if (fk) contactValueByFieldKey[fk] = cf.value;\n}\n\nconst ENRICH_TARGETS = [\n { oppKey: 'opportunity.sucursal', contactKey: 'contact.sucursal', webhookKey: 'Sucursal' },\n { oppKey: 'opportunity.tienda', contactKey: 'contact.tienda', webhookKey: 'TIENDA' },\n { oppKey: 'opportunity.fuente_de_posible_cliente', contactKey: 'contact.fuente_de_posible_cliente', webhookKey: 'CANAL DE ORIGEN' },\n];\n\nfor (const t of ENRICH_TARGETS) {\n const existing = enrichedCustomFields.find(cf => cf.fieldKey === t.oppKey);\n if (existing && norm2(existing.fieldValue) !== '') continue; // (a) ya viene de la opp\n let value = norm2(contactValueByFieldKey[t.contactKey]); // (b) contacto\n if (value === '') value = norm2(webhookBody[t.webhookKey]); // (c) webhook\n if (value === '') continue;\n if (existing) {\n existing.fieldValue = value;\n } else {\n const def = customFieldsDefs.find(d => d.fieldKey === t.oppKey);\n enrichedCustomFields.push({ id: def ? def.id : null, name: def ? def.name : null, fieldKey: t.oppKey, fieldValue: value });\n }\n}\n// ── fin CONTACT->OPP ENRICH ──\n\n// Resolve pipeline and stage info\nconst stageInfo = stageMap[opportunity.pipelineStageId] || {};\n\n// Build enriched opportunity\nconst enrichedOpportunity = {\n ...opportunity,\n pipelineName: pipelineMap[opportunity.pipelineId] || null,\n pipelineStageName: stageInfo.name || null,\n pipelineStagePosition: stageInfo.position ?? null,\n pipelineStageWinProbability: stageInfo.stageWinProbability ?? null,\n pipelineStageColor: stageInfo.color || null,\n customFields: enrichedCustomFields\n};\n\nreturn [{ json: { opportunity: enrichedOpportunity, traceId: opportunityData.traceId } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + -32 + ], + "id": "d9609dd9-9493-4f3d-b356-99438cedada6", + "name": "Mapeo completo oportunidad origen - SUCURSAL" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('Webhook').item.json.body.location.id }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de Sucursal').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1872, + -32 + ], + "id": "2d799ae3-b6b1-4e4f-8774-714ebe4f009f", + "name": "Obtener Pipelines - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Buscar Oportunidad - MARCA').item.json.opportunities[0].id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3216, + -32 + ], + "id": "1eee43d2-c6a8-414b-bebf-4b31d6fb34d7", + "name": "Obtener info de Oportunidad - MARCA" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('API de MARCA').item.json.Location_ID }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de MARCA').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3424, + -32 + ], + "id": "20a1a288-2cd8-471b-9203-5ba88770edb0", + "name": "Obtener Pipelines - MARCA" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2800, + -32 + ], + "id": "798a7e51-b8ed-4364-947a-7396fab47940", + "name": "Buscar Oportunidad - MARCA", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3008, + -32 + ], + "id": "cc37ea9b-0a9f-44ce-8ce2-ee2444822ecf", + "name": "Conseguir Custom Fields - Opportunity - MARCA" + }, + { + "parameters": { + "jsCode": "// ── DATOS MARCA ──────────────────────────────────────────────────────────────\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA').first().json.customFields;\nconst marcaPipelines = $input.first().json.pipelines;\n\n// ── DATOS SUCURSAL (ya enriquecidos) ─────────────────────────────────────────\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\n\n// ── MAP PIPELINES MARCA: name -> { id, stages } ───────────────────────────────\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[pipeline.name.trim()] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[stage.name.trim()] = stage.id;\n }\n}\n\n// ── RESOLVER PIPELINE Y STAGE POR NAME DESDE SUCURSAL ────────────────────────\nconst resolvedPipelineId = marcaPipelineByName[sucursalOpp.pipelineName?.trim()] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[sucursalOpp.pipelineStageName?.trim()] || marcaOpp.pipelineStageId;\n\n// ── MAP CUSTOM FIELDS SUCURSAL: name -> fieldValue ────────────────────────────\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields) {\n if (cf.name) {\n sucursalValueByName[cf.name.trim()] = cf.fieldValue;\n }\n}\n\n// ── CONSTRUIR CUSTOM FIELDS PARA EL BODY ─────────────────────────────────────\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n const nameKey = def.name.trim();\n const sucursalValue = sucursalValueByName[nameKey];\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n const fieldValue = sucursalValue !== undefined ? sucursalValue : (marcaOriginal?.fieldValue ?? null);\n\n if (fieldValue === null) continue;\n\n customFields.push({\n id: def.id,\n key: def.fieldKey,\n field_value: fieldValue\n });\n}\n\n// ── CONSTRUIR BODY VÁLIDO PARA UPDATE OPPORTUNITY ────────────────────────────\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source,\n customFields: customFields\n};\n\nreturn [{ json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body)\n} }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3632, + -32 + ], + "id": "3b547c2a-a541-4b48-8d97-ea2d5df52010", + "name": "Code in JavaScript" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"pageLimit\": 10,\n \"query\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono || $('Datos de Lead').item.json.Cliente.Email }}\"\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3360, + 960 + ], + "id": "02cd0fd6-6bb8-4faa-b85f-5d42663fba29", + "name": "Buscar Contacto en MARCA", + "notes": "Busca contacto en la location de marca por email.\nSi no encuentra, la siguiente IF deriva a creación." + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "exist-contact-check", + "leftValue": "={{ $json.contacts ? $json.contacts.length : 0 }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3600, + 960 + ], + "id": "aa2644dc-34b4-4159-8afb-89147b12f5ee", + "name": "¿Existe contacto en MARCA?" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/upsert", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"firstName\": \"{{ $('Datos de Lead').item.json.Cliente.Apellido }}\",\n \"name\": \"{{ $('Datos de Lead').item.json.Cliente['Nombre Completo'] }}\",\n \"email\": \"{{ $('Datos de Lead').item.json.Cliente.Email }}\",\n \"phone\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono }}\",\n \"source\": \"Sincronización Sucursal\",\n \"tags\": [\"sincronizado-sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3840, + 1104 + ], + "id": "f6c75352-3b9e-4fc9-b8e4-063c2792d190", + "name": "Crear Contacto en MARCA", + "notes": "Crea contacto en la marca solo si no existía.\nIMPORTANTE: revisa el mapeo de Nombre/Apellido, en el workflow original están invertidos." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "contact-id-resolved", + "name": "contactId", + "value": "={{ $json.contact ? $json.contact.id : $json.contacts[0].id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 4080, + 960 + ], + "id": "2a49d01e-6679-4584-9d9b-5778dfc4431e", + "name": "Set Contact ID Resuelto", + "notes": "Unifica el contactId tanto si vino de la búsqueda (contacts[0].id) como de la creación (contact.id)." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "location_id", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + }, + { + "name": "contact_id", + "value": "={{ $('Set Contact ID Resuelto').item.json.contactId }}" + }, + { + "name": "limit", + "value": "100" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 4320, + 960 + ], + "id": "8e25f660-3ed2-4597-9c2a-7480ac7d150f", + "name": "Buscar Oportunidades del Contacto - MARCA", + "notes": "Busca TODAS las oportunidades del contacto en la marca.\nDespués el Code de decisión elige cuál (o crea nueva)." + }, + { + "parameters": { + "jsCode": "// DECISION (Create vs Update) con idempotencia GLOBAL via mapeo Baserow (tabla 754).\n// (1) Si el mapeo id_opp_sucursal -> id_opp_marca existe -> UPDATE esa opp de Marca,\n// independiente del contacto resuelto (evita duplicados por contacto ambiguo).\n// (2) Si no hay mapeo, match por CF entre las opps del contacto resuelto.\n// (3) Si nada -> CREATE.\nconst result = $input.first().json;\nconst opportunities = result.opportunities || [];\nconst sucursalOppId = $('Datos de Lead').first().json.Oportunidad.opportunity_id;\n\nlet action = 'CREATE';\nlet opportunityId = null;\nlet matchReason = '';\n\n// (1) Mapeo Baserow global\nlet baserowRows = [];\ntry { baserowRows = $('Buscar Mapeo Opp - Baserow').all().map(i => i.json); } catch (e) { baserowRows = []; }\nconst mapped = baserowRows.find(r =>\n String(r.id_opp_sucursal || '') === String(sucursalOppId) && String(r.id_opp_marca || '').trim()\n);\n\nif (mapped) {\n action = 'UPDATE';\n opportunityId = String(mapped.id_opp_marca).trim();\n matchReason = 'Match por mapeo Baserow (global, tabla 754)';\n} else {\n // (2) match por CF entre las opps del contacto resuelto\n const match = opportunities.find(o =>\n (o.customFields || []).some(cf => {\n const v = cf.fieldValueString ?? cf.fieldValue ?? '';\n return String(v) === String(sucursalOppId);\n })\n );\n if (match) {\n action = 'UPDATE';\n opportunityId = match.id;\n matchReason = 'Match por ID Oportunidad Sucursal (contacto)';\n } else {\n action = 'CREATE';\n matchReason = 'Sin match (Baserow ni contacto) -> CREATE (contacto tiene ' + opportunities.length + ' opps)';\n }\n}\n\nreturn [{\n json: {\n action,\n opportunityId,\n matchReason,\n sucursalOppId,\n contactId: $('Set Contact ID Resuelto').first().json.contactId,\n totalOppsContacto: opportunities.length\n }\n}];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4560, + 960 + ], + "id": "6ca30f13-8503-427f-95ec-b243d30688ec", + "name": "Decidir Match (Create vs Update)" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-update", + "leftValue": "={{ $json.action }}", + "rightValue": "UPDATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "UPDATE" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-create", + "leftValue": "={{ $json.action }}", + "rightValue": "CREATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "CREATE" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 4800, + 960 + ], + "id": "bff7a5dc-b4af-4033-83ea-c508aa92264a", + "name": "Switch UPDATE vs CREATE" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Decidir Match (Create vs Update)').item.json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5040, + 816 + ], + "id": "83cd0fd7-1ee9-4896-926e-a0b752e216fe", + "name": "Obtener info de Oportunidad - MARCA (v2)", + "notes": "Sólo se ejecuta en rama UPDATE.\nReemplaza el nodo original 'Obtener info de Oportunidad - MARCA' que dependía de búsqueda por nombre." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 1104 + ], + "id": "0fb2fdea-cf40-4dfb-abfa-c422282e5d2a", + "name": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 1104 + ], + "id": "58114758-f0fb-483a-bd5b-d28188880a02", + "name": "Obtener Pipelines - MARCA (CREATE)" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA CREAR OPORTUNIDAD EN MARCA ──────────────────────\n// Mapea custom fields por fieldKey (fallback name) y FIJA el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia futura).\n\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (CREATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (CREATE)').first().json.pipelines || [];\nconst locationId = $('DATOS API').first().json['Location ID'];\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst contactId = decision.contactId;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\n// Map pipelines/stages de la marca por nombre normalizado\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)];\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)];\n\nif (!resolvedPipelineId) {\n throw new Error(`No se encontró pipeline en marca: \"${sucursalOpp.pipelineName}\". Revisa que el nombre coincida.`);\n}\nif (!resolvedStageId) {\n throw new Error(`No se encontró stage en marca: \"${sucursalOpp.pipelineStageName}\". Revisa que el nombre coincida.`);\n}\n\n// Mapear custom fields: preferir fieldKey, fallback a name normalizado\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null || value === '') continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// FIJAR el campo de enlace \"ID Oportunidad Sucursal\" = id de la opp de sucursal\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n locationId: locationId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || 'open',\n contactId: contactId,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source || 'Sincronización Sucursal',\n customFields: customFields\n};\n\nreturn [{\n json: {\n body: JSON.stringify(body),\n debug: { resolvedPipelineId, resolvedStageId, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5520, + 1104 + ], + "id": "2f9a63e7-277d-49de-8838-a00922c993b2", + "name": "Armar Body - CREATE" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5760, + 1104 + ], + "id": "fc4b3398-91bb-4dd9-b406-96c8d75a17be", + "name": "Crear Oportunidad - MARCA" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA ACTUALIZAR OPORTUNIDAD EN MARCA (v2) ────────────\n// Mapea CF por fieldKey (fallback name) y reafirma el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia).\n\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA (v2)').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (UPDATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (UPDATE)').first().json.pipelines || [];\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)] || marcaOpp.pipelineStageId;\n\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null) {\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n value = marcaOriginal?.fieldValue;\n }\n if (value === undefined || value === null) continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// Reafirmar el campo de enlace\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || marcaOpp.status,\n monetaryValue: sucursalOpp.monetaryValue ?? marcaOpp.monetaryValue,\n source: sucursalOpp.source || marcaOpp.source,\n customFields: customFields\n};\n\nreturn [{\n json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body),\n debug: { matchReason: decision.matchReason, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5280, + 816 + ], + "id": "969e46cd-bbae-4372-9808-c939f63e3c02", + "name": "Armar Body - UPDATE (v2)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5520, + 816 + ], + "id": "83929e08-7afd-4670-9c18-be22b8c3656f", + "name": "Actualizar Oportunidad - MARCA (v2)" + }, + { + "parameters": { + "content": "# RAMA UPDATE\n## Oportunidad ya existe en marca → se actualiza", + "height": 280, + "width": 880, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 720 + ], + "typeVersion": 1, + "id": "1c285234-8d90-49ef-8dac-c98cb379299a", + "name": "Sticky Update" + }, + { + "parameters": { + "content": "# RAMA CREATE\n## Oportunidad no existe en marca → se crea desde cero\n### Incluye contacto + custom fields + tag origin-id", + "height": 280, + "width": 1120, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 1024 + ], + "typeVersion": 1, + "id": "7e8f350c-9562-485b-8ce5-1b13b4086856", + "name": "Sticky Create" + }, + { + "parameters": { + "content": "# MATCHING ROBUSTO\n## 1. Buscar/crear contacto en marca\n## 2. Buscar oportunidades DEL CONTACTO\n## 3. Decidir match por cantidad + tag origin-id", + "height": 320, + "width": 1500, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3328, + 864 + ], + "typeVersion": 1, + "id": "8a583e46-41b6-44e0-9313-b70ee085b740", + "name": "Sticky Matching" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 560 + ], + "id": "45f0a6b0-f44d-4e83-b2ab-cdd3fbfe7911", + "name": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 560 + ], + "id": "5ca962a9-9e56-42d1-af05-ce05d39ed77f", + "name": "Obtener Pipelines - MARCA (UPDATE)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ customFields: [ { id: $('Conseguir Custom Fields - Opportunity - SUCURSAL').item.json.customFields.find(f => f.fieldKey === 'opportunity.id_oportunidad_sucursal')?.id, key: 'opportunity.id_oportunidad_sucursal', field_value: $('Datos de Lead').item.json.Oportunidad.opportunity_id } ] }) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1328, + -32 + ], + "id": "a9572dee-ed57-4104-966a-4b22721e75f1", + "name": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Auto-mapeo de seguridad: setea 'ID Oportunidad Sucursal' = id nativo de la propia opp de sucursal (del webhook) antes de procesar. onError=continue para no romper el sync si falla." + }, + { + "parameters": { + "databaseId": 63, + "tableId": 754, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7280, + "value": "={{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 4208, + 1152 + ], + "id": "a64fdffd-ab8f-4ddd-a4d4-4d683a05132d", + "name": "Buscar Mapeo Opp - Baserow", + "alwaysOutputData": true, + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Idempotencia global: busca en la tabla 754 el mapeo id_opp_sucursal->id_opp_marca. Si existe, 'Decidir Match' hara UPDATE de esa opp aunque el contacto resuelto sea otro. alwaysOutputData + onError para no romper el flujo si no hay match o si Baserow falla." + }, + { + "parameters": { + "jsCode": "// Prepara el UPSERT del mapeo Baserow (tabla 754) tras CREATE/UPDATE de la opp en Marca.\n// Solo escribe cuando el mapeo NO existe aun:\n// - action CREATE -> la fila no existe (el lookup Baserow fallo) -> crear.\n// - action UPDATE por contacto -> Baserow no la tenia -> crear (idempotencia futura).\n// - action UPDATE por Baserow -> la fila YA existe -> NO escribir (no duplicar).\n// Asi una re-ejecucion del mismo opp encuentra el mapeo y hace UPDATE, no CREATE duplicado.\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst action = decision.action;\nconst matchReason = decision.matchReason || '';\nconst sucursalOppId = decision.sucursalOppId;\n\nconst matchedByBaserow = matchReason.indexOf('Baserow') !== -1;\nconst needWrite = (action === 'CREATE') || (action === 'UPDATE' && !matchedByBaserow);\nif (!needWrite) return [];\n\n// id de la opp en Marca segun el camino\nlet marcaOppId = decision.opportunityId;\nif (action === 'CREATE') {\n try {\n const co = $('Crear Oportunidad - MARCA').first().json;\n marcaOppId = (co.opportunity && co.opportunity.id) || co.id || marcaOppId;\n } catch (e) {}\n}\nif (!marcaOppId || !sucursalOppId) return [];\n\nlet locationSucursal = '';\ntry { locationSucursal = $('DATOS API - SUCURSAL').first().json['Location ID'] || ''; } catch (e) {}\n\nreturn [{ json: { sucursalOppId, marcaOppId, locationSucursal } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 6048, + 960 + ], + "id": "6542723e-f7b2-41de-b371-9e793d48f560", + "name": "Preparar Upsert Mapeo", + "onError": "continueRegularOutput", + "notes": "Decide si escribir el mapeo Baserow tras CREATE/UPDATE. Solo cuando la fila no existe (CREATE o UPDATE-por-contacto). onError continue para no romper la replicacion." + }, + { + "parameters": { + "operation": "create", + "databaseId": 63, + "tableId": 754, + "fieldsUi": { + "fieldValues": [ + { + "fieldId": 7280, + "fieldValue": "={{ $json.sucursalOppId }}" + }, + { + "fieldId": 7283, + "fieldValue": "={{ $json.marcaOppId }}" + }, + { + "fieldId": 7284, + "fieldValue": "={{ $json.locationSucursal }}" + }, + { + "fieldId": 7285, + "fieldValue": "={{ $now.toISO() }}" + } + ] + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + 6288, + 960 + ], + "id": "c69036dd-3914-4424-a165-6e0f3b110ebd", + "name": "Crear Mapeo - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Inserta el mapeo id_opp_sucursal->id_opp_marca en la tabla 754 en tiempo real. Idempotente con el backfill (verifica por id_opp_sucursal)." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1750, + -32 + ], + "name": "Obtener Contacto - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Trae el contacto de sucursal para derivar Sucursal/TIENDA/Canal de Origen de la opp cuando la opp de origen no los trae. onError=continue para no romper el sync.", + "id": "3636d605-1c44-4f2d-b64c-10a5c346cd24" + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API": { + "main": [ + [ + { + "node": "Buscar Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "API de Sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - SUCURSAL": { + "main": [ + [ + { + "node": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de Sucursal": { + "main": [ + [ + { + "node": "DATOS API - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de MARCA": { + "main": [ + [ + { + "node": "DATOS API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapeo completo oportunidad origen - SUCURSAL": { + "main": [ + [ + { + "node": "API de MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - SUCURSAL": { + "main": [ + [ + { + "node": "Mapeo completo oportunidad origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contacto en MARCA": { + "main": [ + [ + { + "node": "¿Existe contacto en MARCA?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Existe contacto en MARCA?": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Crear Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Contacto en MARCA": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Contact ID Resuelto": { + "main": [ + [ + { + "node": "Buscar Mapeo Opp - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidades del Contacto - MARCA": { + "main": [ + [ + { + "node": "Decidir Match (Create vs Update)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Decidir Match (Create vs Update)": { + "main": [ + [ + { + "node": "Switch UPDATE vs CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch UPDATE vs CREATE": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - UPDATE (v2)": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Armar Body - CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - CREATE": { + "main": [ + [ + { + "node": "Crear Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Armar Body - UPDATE (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapear ID Oportunidad Sucursal - SUCURSAL": { + "main": [ + [ + { + "node": "Buscar Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Mapeo Opp - Baserow": { + "main": [ + [ + { + "node": "Buscar Oportunidades del Contacto - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Upsert Mapeo": { + "main": [ + [ + { + "node": "Crear Mapeo - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Pipelines - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false + }, + "staticData": null, + "meta": { + "templateCredsSetupCompleted": true + }, + "pinData": { + "Webhook": [ + { + "json": { + "headers": { + "host": "webhookn8.consultoriae3.com", + "user-agent": "axios/1.13.2", + "content-length": "1839", + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, compress, deflate, br", + "content-type": "application/json", + "traceparent": "00-00e6586cc6967f6985b8aa87332de4e0-932bdb81faeff7c4-01", + "x-forwarded-for": "34.55.150.6", + "x-forwarded-host": "webhookn8.consultoriae3.com", + "x-forwarded-port": "443", + "x-forwarded-proto": "https", + "x-forwarded-server": "f569f6e85d30", + "x-real-ip": "34.55.150.6" + }, + "params": {}, + "query": {}, + "body": { + "¿Cuándo necesitas el dinero?": "", + "[Número de Teléfono para Atender]": "", + "Información Adicional": "", + "Presupuesto": "", + "CANAL DE ORIGEN": "", + "[Número de WhatsApp para Atender]": "", + "[Dirección de la Empresa]": "", + "Año del Vehículo": "2015", + "¿Qué modalidad prefieres?": "Tradicional (Resguardo)", + "Marca del Vehiculo": "RENAULT", + "TIENDA": "TAMPICO", + "Sucursal": "Tampico, Tamaulipas", + "Descripción": "", + "[Correo_Empresa]": "", + "ID Contacto Monte Providencia": "", + "Fuente de Prospecto": "REDES SOCIALES", + "[Correo_Canalizados]": "", + "Version del Vehiculo": "LOGAN", + "Acepta los terminos para tu cotización": "", + "[Nombre de la Empresa]": "", + "Check Comunicaciones de Marketing": "", + "Archivos Adicionales": "", + "contact_id": "B9oJWsFXy6Ol9NdHL5Jh", + "first_name": "RAUL", + "last_name": "LOGAN", + "full_name": "RAUL LOGAN", + "email": "raulsincorreo@gmail.com", + "phone": "+528332937527", + "tags": "sucursal", + "country": "MX", + "date_created": "2026-05-27T21:16:44.369Z", + "full_address": "", + "contact_type": "lead", + "opportunity_name": "RAUL LOGAN", + "status": "open", + "lead_value": 40000, + "opportunity_source": "REDES SOCIALES", + "source": "REDES SOCIALES", + "pipleline_stage": "Cliente interesado", + "pipeline_id": "ep1d4VpzRezVqWayFbBf", + "id": "NuFArKAQL72I8QC69MDa", + "pipeline_name": "Standar", + "user": { + "firstName": "Tampico - 85969", + "lastName": "Tampico", + "email": "ezstore85969@ezcorp.com" + }, + "owner": "Tampico - 85969 Tampico", + "location": { + "name": "85969 - MP - Tampico", + "address": "Tampico", + "city": "Tampico", + "state": "Tamaulipas", + "country": "MX", + "postalCode": "10000", + "fullAddress": "Tampico, Tampico Tamaulipas 10000", + "id": "WCHyow6KpjLFYriQWdbJ" + }, + "workflow": { + "id": "8ba8fdf1-85f2-4f1f-bf28-56400e1adcd6", + "name": "Sincronizar Oportunidad V2" + }, + "triggerData": {}, + "contact": { + "attributionSource": { + "sessionSource": "CRM UI", + "mediumId": null, + "medium": "manual" + }, + "lastAttributionSource": {} + }, + "attributionSource": {}, + "customData": {} + }, + "webhookUrl": "https://webhookn8.consultoriae3.com/webhook/3eecb45a-e24c-49ac-aef7-99843f485819", + "executionMode": "production" + }, + "pairedItem": { + "item": 0 + } + } + ] + }, + "versionId": "be97beec-1bd0-43c9-a70e-c3a268d47c3e", + "activeVersionId": "be97beec-1bd0-43c9-a70e-c3a268d47c3e", + "versionCounter": 437, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-03-02T01:12:56.387Z", + "createdAt": "2026-03-02T01:12:56.387Z", + "role": "workflow:owner", + "workflowId": "Cfgwp0bOtDW8zuKW", + "projectId": "aycNlMpnzsxvXbfG", + "project": { + "updatedAt": "2025-11-27T04:06:26.313Z", + "createdAt": "2025-11-27T03:54:24.410Z", + "id": "aycNlMpnzsxvXbfG", + "name": "Consultoria E3 ® ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + } + ], + "tags": [ + { + "updatedAt": "2025-11-27T05:48:12.272Z", + "createdAt": "2025-11-27T05:48:12.272Z", + "id": "pHkPpq3Httzx4hnb", + "name": "Bucefalo" + } + ], + "activeVersion": { + "updatedAt": "2026-05-30T22:42:49.706Z", + "createdAt": "2026-05-30T22:42:49.706Z", + "versionId": "be97beec-1bd0-43c9-a70e-c3a268d47c3e", + "workflowId": "Cfgwp0bOtDW8zuKW", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "3eecb45a-e24c-49ac-aef7-99843f485819", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -336, + -16 + ], + "id": "dd424583-8295-4013-96ba-c7e29ea944c6", + "name": "Webhook", + "webhookId": "3eecb45a-e24c-49ac-aef7-99843f485819" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "b02bc821-6121-452d-a042-623fac6e443c", + "name": "Oportunidad.opportunity_name", + "value": "={{ $json.body.opportunity_name }}", + "type": "string" + }, + { + "id": "898817a2-7926-4ddf-b785-cac8e28afc58", + "name": "Oportunidad.pipleline_stage (new)", + "value": "={{ $json.body.pipleline_stage }}", + "type": "string" + }, + { + "id": "opp-id-sucursal-capture", + "name": "Oportunidad.opportunity_id", + "value": "={{ $json.body.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -96, + -16 + ], + "id": "2b3ec130-27ce-4988-82db-46e4f12c3f7d", + "name": "Datos de Lead" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE SUCURSAL", + "height": 352, + "width": 1152 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1072, + -144 + ], + "typeVersion": 1, + "id": "19023e33-a9f1-4c60-b87c-957e751e3dc2", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE MARCA", + "height": 208, + "width": 1188, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2240, + -80 + ], + "typeVersion": 1, + "id": "3a3d4f18-1a08-4dbc-a00b-7ae6bc4d49d1", + "name": "Sticky Note1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2480, + -32 + ], + "id": "a46df506-6a9a-420d-9cdd-6fc51d1a691e", + "name": "DATOS API" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 80, + -16 + ], + "id": "25db982f-7bc7-4318-a7b0-e94326abc437", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3952, + -32 + ], + "id": "2320b102-816e-4a0c-ae9c-e305d25df6df", + "name": "Actualizar Oportunidad - MARCA2", + "notes": "El código mapea los custom fields de una oportunidad de una sucursal hacia los IDs equivalentes de una marca. Toma tres fuentes: los metadatos de campos de la sucursal, los valores reales de esos campos en la oportunidad, y los metadatos de campos de la marca. Para cada campo con valor en la oportunidad, primero busca su `fieldKey` en los metadatos de la sucursal usando el `id`, luego usa ese `fieldKey` para encontrar el campo equivalente en la marca, y construye un array con el `id` y `key` de la marca pero con el valor original de la sucursal. Finalmente arma el body de una petición para crear/actualizar una oportunidad en la marca, reutilizando pipeline, nombre, stage y valor monetario de otros nodos, con los custom fields ya traducidos al contexto de la marca." + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1536, + -32 + ], + "id": "4e89658b-7ffd-4a3b-b91e-9ed3223a1a73", + "name": "Buscar Oportunidad - SUCURSAL", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 528, + -32 + ], + "id": "b721a65d-bcbb-44ab-bb7f-8f200b34fcdd", + "name": "DATOS API - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1136, + -32 + ], + "id": "39ef9dcc-ac20-4ba2-b891-2a2baa6ea4f5", + "name": "Conseguir Custom Fields - Opportunity - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1712, + -32 + ], + "id": "fbd46ae4-792b-451b-b99e-9816dc175d52", + "name": "Obtener info de Oportunidad - SUCURSAL" + }, + { + "parameters": { + "content": "# ACTUALIZAR OPORTUNIDAD - MARCA", + "height": 352, + "width": 928 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3456, + -144 + ], + "typeVersion": 1, + "id": "f3ca1cfe-08e2-485b-b238-9240f5753809", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "", + "height": 208, + "width": 992, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2768, + -64 + ], + "typeVersion": 1, + "id": "45066721-8404-48ed-94c1-c88edf48bca5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "", + "height": 192, + "width": 928, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1104, + -64 + ], + "typeVersion": 1, + "id": "b1646bb4-8b64-4bf4-b380-189cc7902741", + "name": "Sticky Note7" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Webhook').item.json.body.location.name }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 320, + -32 + ], + "id": "12088f29-8bb4-475d-ae0e-2cbdf8033138", + "name": "API de Sucursal", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "=Monte Providencia" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 2288, + -32 + ], + "id": "8c8fa238-6ce4-4adf-8926-b603987a55cf", + "name": "API de MARCA", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "jsCode": "const opportunityData = $('Obtener info de Oportunidad - SUCURSAL').first().json;\nconst opportunity = opportunityData.opportunity;\nconst customFieldsDefs = $('Conseguir Custom Fields - Opportunity - SUCURSAL').first().json.customFields;\nconst pipelines = $input.first().json.pipelines;\n\n// Map custom fields: id -> { fieldKey, name }\nconst fieldMap = {};\nfor (const def of customFieldsDefs) {\n fieldMap[def.id] = { fieldKey: def.fieldKey, name: def.name };\n}\n\n// Map pipelines: id -> name, stages: id -> { name, position, stageWinProbability }\nconst pipelineMap = {};\nconst stageMap = {};\nfor (const pipeline of pipelines) {\n pipelineMap[pipeline.id] = pipeline.name;\n for (const stage of pipeline.stages) {\n stageMap[stage.id] = {\n name: stage.name,\n position: stage.position,\n stageWinProbability: stage.stageWinProbability,\n color: stage.color || null\n };\n }\n}\n\n// Enrich custom fields with fieldKey and name\nconst enrichedCustomFields = opportunity.customFields.map(cf => ({\n id: cf.id,\n name: fieldMap[cf.id]?.name || null,\n fieldKey: fieldMap[cf.id]?.fieldKey || null,\n fieldValue: cf.fieldValue\n}));\n\n\n// ── CONTACT->OPP ENRICH: garantizar Sucursal / TIENDA / Canal de Origen ──\n// Respaldo cuando la opp de sucursal no trae el CF: tomar el valor del CONTACTO\n// (siempre poblado por [1604]/[2004]) y, en ultimo caso, del webhook. El fieldKey\n// canonico (sufijo) es identico en contact y opportunity.\nconst contactResp = $('Obtener Contacto - SUCURSAL').first().json;\nconst contact = (contactResp && contactResp.contact) ? contactResp.contact : (contactResp || {});\nconst webhookBody = ($('Webhook').first().json && $('Webhook').first().json.body) || {};\nconst norm2 = (s) => (s == null ? '' : String(s)).trim();\n\n// Valores del contacto por fieldKey (contacts traen {id, value}).\nconst contactValueByFieldKey = {};\nfor (const cf of (contact.customFields || [])) {\n const fk = fieldMap[cf.id] ? fieldMap[cf.id].fieldKey : null;\n if (fk) contactValueByFieldKey[fk] = cf.value;\n}\n\nconst ENRICH_TARGETS = [\n { oppKey: 'opportunity.sucursal', contactKey: 'contact.sucursal', webhookKey: 'Sucursal' },\n { oppKey: 'opportunity.tienda', contactKey: 'contact.tienda', webhookKey: 'TIENDA' },\n { oppKey: 'opportunity.fuente_de_posible_cliente', contactKey: 'contact.fuente_de_posible_cliente', webhookKey: 'CANAL DE ORIGEN' },\n];\n\nfor (const t of ENRICH_TARGETS) {\n const existing = enrichedCustomFields.find(cf => cf.fieldKey === t.oppKey);\n if (existing && norm2(existing.fieldValue) !== '') continue; // (a) ya viene de la opp\n let value = norm2(contactValueByFieldKey[t.contactKey]); // (b) contacto\n if (value === '') value = norm2(webhookBody[t.webhookKey]); // (c) webhook\n if (value === '') continue;\n if (existing) {\n existing.fieldValue = value;\n } else {\n const def = customFieldsDefs.find(d => d.fieldKey === t.oppKey);\n enrichedCustomFields.push({ id: def ? def.id : null, name: def ? def.name : null, fieldKey: t.oppKey, fieldValue: value });\n }\n}\n// ── fin CONTACT->OPP ENRICH ──\n\n// Resolve pipeline and stage info\nconst stageInfo = stageMap[opportunity.pipelineStageId] || {};\n\n// Build enriched opportunity\nconst enrichedOpportunity = {\n ...opportunity,\n pipelineName: pipelineMap[opportunity.pipelineId] || null,\n pipelineStageName: stageInfo.name || null,\n pipelineStagePosition: stageInfo.position ?? null,\n pipelineStageWinProbability: stageInfo.stageWinProbability ?? null,\n pipelineStageColor: stageInfo.color || null,\n customFields: enrichedCustomFields\n};\n\nreturn [{ json: { opportunity: enrichedOpportunity, traceId: opportunityData.traceId } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + -32 + ], + "id": "d9609dd9-9493-4f3d-b356-99438cedada6", + "name": "Mapeo completo oportunidad origen - SUCURSAL" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('Webhook').item.json.body.location.id }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de Sucursal').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1872, + -32 + ], + "id": "2d799ae3-b6b1-4e4f-8774-714ebe4f009f", + "name": "Obtener Pipelines - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Buscar Oportunidad - MARCA').item.json.opportunities[0].id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3216, + -32 + ], + "id": "1eee43d2-c6a8-414b-bebf-4b31d6fb34d7", + "name": "Obtener info de Oportunidad - MARCA" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('API de MARCA').item.json.Location_ID }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de MARCA').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3424, + -32 + ], + "id": "20a1a288-2cd8-471b-9203-5ba88770edb0", + "name": "Obtener Pipelines - MARCA" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2800, + -32 + ], + "id": "798a7e51-b8ed-4364-947a-7396fab47940", + "name": "Buscar Oportunidad - MARCA", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3008, + -32 + ], + "id": "cc37ea9b-0a9f-44ce-8ce2-ee2444822ecf", + "name": "Conseguir Custom Fields - Opportunity - MARCA" + }, + { + "parameters": { + "jsCode": "// ── DATOS MARCA ──────────────────────────────────────────────────────────────\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA').first().json.customFields;\nconst marcaPipelines = $input.first().json.pipelines;\n\n// ── DATOS SUCURSAL (ya enriquecidos) ─────────────────────────────────────────\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\n\n// ── MAP PIPELINES MARCA: name -> { id, stages } ───────────────────────────────\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[pipeline.name.trim()] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[stage.name.trim()] = stage.id;\n }\n}\n\n// ── RESOLVER PIPELINE Y STAGE POR NAME DESDE SUCURSAL ────────────────────────\nconst resolvedPipelineId = marcaPipelineByName[sucursalOpp.pipelineName?.trim()] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[sucursalOpp.pipelineStageName?.trim()] || marcaOpp.pipelineStageId;\n\n// ── MAP CUSTOM FIELDS SUCURSAL: name -> fieldValue ────────────────────────────\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields) {\n if (cf.name) {\n sucursalValueByName[cf.name.trim()] = cf.fieldValue;\n }\n}\n\n// ── CONSTRUIR CUSTOM FIELDS PARA EL BODY ─────────────────────────────────────\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n const nameKey = def.name.trim();\n const sucursalValue = sucursalValueByName[nameKey];\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n const fieldValue = sucursalValue !== undefined ? sucursalValue : (marcaOriginal?.fieldValue ?? null);\n\n if (fieldValue === null) continue;\n\n customFields.push({\n id: def.id,\n key: def.fieldKey,\n field_value: fieldValue\n });\n}\n\n// ── CONSTRUIR BODY VÁLIDO PARA UPDATE OPPORTUNITY ────────────────────────────\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source,\n customFields: customFields\n};\n\nreturn [{ json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body)\n} }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3632, + -32 + ], + "id": "3b547c2a-a541-4b48-8d97-ea2d5df52010", + "name": "Code in JavaScript" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"pageLimit\": 10,\n \"query\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono || $('Datos de Lead').item.json.Cliente.Email }}\"\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3360, + 960 + ], + "id": "02cd0fd6-6bb8-4faa-b85f-5d42663fba29", + "name": "Buscar Contacto en MARCA", + "notes": "Busca contacto en la location de marca por email.\nSi no encuentra, la siguiente IF deriva a creación." + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "exist-contact-check", + "leftValue": "={{ $json.contacts ? $json.contacts.length : 0 }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3600, + 960 + ], + "id": "aa2644dc-34b4-4159-8afb-89147b12f5ee", + "name": "¿Existe contacto en MARCA?" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/upsert", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"firstName\": \"{{ $('Datos de Lead').item.json.Cliente.Apellido }}\",\n \"name\": \"{{ $('Datos de Lead').item.json.Cliente['Nombre Completo'] }}\",\n \"email\": \"{{ $('Datos de Lead').item.json.Cliente.Email }}\",\n \"phone\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono }}\",\n \"source\": \"Sincronización Sucursal\",\n \"tags\": [\"sincronizado-sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3840, + 1104 + ], + "id": "f6c75352-3b9e-4fc9-b8e4-063c2792d190", + "name": "Crear Contacto en MARCA", + "notes": "Crea contacto en la marca solo si no existía.\nIMPORTANTE: revisa el mapeo de Nombre/Apellido, en el workflow original están invertidos." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "contact-id-resolved", + "name": "contactId", + "value": "={{ $json.contact ? $json.contact.id : $json.contacts[0].id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 4080, + 960 + ], + "id": "2a49d01e-6679-4584-9d9b-5778dfc4431e", + "name": "Set Contact ID Resuelto", + "notes": "Unifica el contactId tanto si vino de la búsqueda (contacts[0].id) como de la creación (contact.id)." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "location_id", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + }, + { + "name": "contact_id", + "value": "={{ $('Set Contact ID Resuelto').item.json.contactId }}" + }, + { + "name": "limit", + "value": "100" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 4320, + 960 + ], + "id": "8e25f660-3ed2-4597-9c2a-7480ac7d150f", + "name": "Buscar Oportunidades del Contacto - MARCA", + "notes": "Busca TODAS las oportunidades del contacto en la marca.\nDespués el Code de decisión elige cuál (o crea nueva)." + }, + { + "parameters": { + "jsCode": "// DECISION (Create vs Update) con idempotencia GLOBAL via mapeo Baserow (tabla 754).\n// (1) Si el mapeo id_opp_sucursal -> id_opp_marca existe -> UPDATE esa opp de Marca,\n// independiente del contacto resuelto (evita duplicados por contacto ambiguo).\n// (2) Si no hay mapeo, match por CF entre las opps del contacto resuelto.\n// (3) Si nada -> CREATE.\nconst result = $input.first().json;\nconst opportunities = result.opportunities || [];\nconst sucursalOppId = $('Datos de Lead').first().json.Oportunidad.opportunity_id;\n\nlet action = 'CREATE';\nlet opportunityId = null;\nlet matchReason = '';\n\n// (1) Mapeo Baserow global\nlet baserowRows = [];\ntry { baserowRows = $('Buscar Mapeo Opp - Baserow').all().map(i => i.json); } catch (e) { baserowRows = []; }\nconst mapped = baserowRows.find(r =>\n String(r.id_opp_sucursal || '') === String(sucursalOppId) && String(r.id_opp_marca || '').trim()\n);\n\nif (mapped) {\n action = 'UPDATE';\n opportunityId = String(mapped.id_opp_marca).trim();\n matchReason = 'Match por mapeo Baserow (global, tabla 754)';\n} else {\n // (2) match por CF entre las opps del contacto resuelto\n const match = opportunities.find(o =>\n (o.customFields || []).some(cf => {\n const v = cf.fieldValueString ?? cf.fieldValue ?? '';\n return String(v) === String(sucursalOppId);\n })\n );\n if (match) {\n action = 'UPDATE';\n opportunityId = match.id;\n matchReason = 'Match por ID Oportunidad Sucursal (contacto)';\n } else {\n action = 'CREATE';\n matchReason = 'Sin match (Baserow ni contacto) -> CREATE (contacto tiene ' + opportunities.length + ' opps)';\n }\n}\n\nreturn [{\n json: {\n action,\n opportunityId,\n matchReason,\n sucursalOppId,\n contactId: $('Set Contact ID Resuelto').first().json.contactId,\n totalOppsContacto: opportunities.length\n }\n}];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4560, + 960 + ], + "id": "6ca30f13-8503-427f-95ec-b243d30688ec", + "name": "Decidir Match (Create vs Update)" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-update", + "leftValue": "={{ $json.action }}", + "rightValue": "UPDATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "UPDATE" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-create", + "leftValue": "={{ $json.action }}", + "rightValue": "CREATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "CREATE" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 4800, + 960 + ], + "id": "bff7a5dc-b4af-4033-83ea-c508aa92264a", + "name": "Switch UPDATE vs CREATE" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Decidir Match (Create vs Update)').item.json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5040, + 816 + ], + "id": "83cd0fd7-1ee9-4896-926e-a0b752e216fe", + "name": "Obtener info de Oportunidad - MARCA (v2)", + "notes": "Sólo se ejecuta en rama UPDATE.\nReemplaza el nodo original 'Obtener info de Oportunidad - MARCA' que dependía de búsqueda por nombre." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 1104 + ], + "id": "0fb2fdea-cf40-4dfb-abfa-c422282e5d2a", + "name": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 1104 + ], + "id": "58114758-f0fb-483a-bd5b-d28188880a02", + "name": "Obtener Pipelines - MARCA (CREATE)" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA CREAR OPORTUNIDAD EN MARCA ──────────────────────\n// Mapea custom fields por fieldKey (fallback name) y FIJA el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia futura).\n\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (CREATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (CREATE)').first().json.pipelines || [];\nconst locationId = $('DATOS API').first().json['Location ID'];\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst contactId = decision.contactId;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\n// Map pipelines/stages de la marca por nombre normalizado\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)];\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)];\n\nif (!resolvedPipelineId) {\n throw new Error(`No se encontró pipeline en marca: \"${sucursalOpp.pipelineName}\". Revisa que el nombre coincida.`);\n}\nif (!resolvedStageId) {\n throw new Error(`No se encontró stage en marca: \"${sucursalOpp.pipelineStageName}\". Revisa que el nombre coincida.`);\n}\n\n// Mapear custom fields: preferir fieldKey, fallback a name normalizado\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null || value === '') continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// FIJAR el campo de enlace \"ID Oportunidad Sucursal\" = id de la opp de sucursal\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n locationId: locationId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || 'open',\n contactId: contactId,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source || 'Sincronización Sucursal',\n customFields: customFields\n};\n\nreturn [{\n json: {\n body: JSON.stringify(body),\n debug: { resolvedPipelineId, resolvedStageId, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5520, + 1104 + ], + "id": "2f9a63e7-277d-49de-8838-a00922c993b2", + "name": "Armar Body - CREATE" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5760, + 1104 + ], + "id": "fc4b3398-91bb-4dd9-b406-96c8d75a17be", + "name": "Crear Oportunidad - MARCA" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA ACTUALIZAR OPORTUNIDAD EN MARCA (v2) ────────────\n// Mapea CF por fieldKey (fallback name) y reafirma el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia).\n\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA (v2)').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (UPDATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (UPDATE)').first().json.pipelines || [];\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)] || marcaOpp.pipelineStageId;\n\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null) {\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n value = marcaOriginal?.fieldValue;\n }\n if (value === undefined || value === null) continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// Reafirmar el campo de enlace\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || marcaOpp.status,\n monetaryValue: sucursalOpp.monetaryValue ?? marcaOpp.monetaryValue,\n source: sucursalOpp.source || marcaOpp.source,\n customFields: customFields\n};\n\nreturn [{\n json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body),\n debug: { matchReason: decision.matchReason, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5280, + 816 + ], + "id": "969e46cd-bbae-4372-9808-c939f63e3c02", + "name": "Armar Body - UPDATE (v2)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5520, + 816 + ], + "id": "83929e08-7afd-4670-9c18-be22b8c3656f", + "name": "Actualizar Oportunidad - MARCA (v2)" + }, + { + "parameters": { + "content": "# RAMA UPDATE\n## Oportunidad ya existe en marca → se actualiza", + "height": 280, + "width": 880, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 720 + ], + "typeVersion": 1, + "id": "1c285234-8d90-49ef-8dac-c98cb379299a", + "name": "Sticky Update" + }, + { + "parameters": { + "content": "# RAMA CREATE\n## Oportunidad no existe en marca → se crea desde cero\n### Incluye contacto + custom fields + tag origin-id", + "height": 280, + "width": 1120, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 1024 + ], + "typeVersion": 1, + "id": "7e8f350c-9562-485b-8ce5-1b13b4086856", + "name": "Sticky Create" + }, + { + "parameters": { + "content": "# MATCHING ROBUSTO\n## 1. Buscar/crear contacto en marca\n## 2. Buscar oportunidades DEL CONTACTO\n## 3. Decidir match por cantidad + tag origin-id", + "height": 320, + "width": 1500, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3328, + 864 + ], + "typeVersion": 1, + "id": "8a583e46-41b6-44e0-9313-b70ee085b740", + "name": "Sticky Matching" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 560 + ], + "id": "45f0a6b0-f44d-4e83-b2ab-cdd3fbfe7911", + "name": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 560 + ], + "id": "5ca962a9-9e56-42d1-af05-ce05d39ed77f", + "name": "Obtener Pipelines - MARCA (UPDATE)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ customFields: [ { id: $('Conseguir Custom Fields - Opportunity - SUCURSAL').item.json.customFields.find(f => f.fieldKey === 'opportunity.id_oportunidad_sucursal')?.id, key: 'opportunity.id_oportunidad_sucursal', field_value: $('Datos de Lead').item.json.Oportunidad.opportunity_id } ] }) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1328, + -32 + ], + "id": "a9572dee-ed57-4104-966a-4b22721e75f1", + "name": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Auto-mapeo de seguridad: setea 'ID Oportunidad Sucursal' = id nativo de la propia opp de sucursal (del webhook) antes de procesar. onError=continue para no romper el sync si falla." + }, + { + "parameters": { + "databaseId": 63, + "tableId": 754, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7280, + "value": "={{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 4208, + 1152 + ], + "id": "a64fdffd-ab8f-4ddd-a4d4-4d683a05132d", + "name": "Buscar Mapeo Opp - Baserow", + "alwaysOutputData": true, + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Idempotencia global: busca en la tabla 754 el mapeo id_opp_sucursal->id_opp_marca. Si existe, 'Decidir Match' hara UPDATE de esa opp aunque el contacto resuelto sea otro. alwaysOutputData + onError para no romper el flujo si no hay match o si Baserow falla." + }, + { + "parameters": { + "jsCode": "// Prepara el UPSERT del mapeo Baserow (tabla 754) tras CREATE/UPDATE de la opp en Marca.\n// Solo escribe cuando el mapeo NO existe aun:\n// - action CREATE -> la fila no existe (el lookup Baserow fallo) -> crear.\n// - action UPDATE por contacto -> Baserow no la tenia -> crear (idempotencia futura).\n// - action UPDATE por Baserow -> la fila YA existe -> NO escribir (no duplicar).\n// Asi una re-ejecucion del mismo opp encuentra el mapeo y hace UPDATE, no CREATE duplicado.\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst action = decision.action;\nconst matchReason = decision.matchReason || '';\nconst sucursalOppId = decision.sucursalOppId;\n\nconst matchedByBaserow = matchReason.indexOf('Baserow') !== -1;\nconst needWrite = (action === 'CREATE') || (action === 'UPDATE' && !matchedByBaserow);\nif (!needWrite) return [];\n\n// id de la opp en Marca segun el camino\nlet marcaOppId = decision.opportunityId;\nif (action === 'CREATE') {\n try {\n const co = $('Crear Oportunidad - MARCA').first().json;\n marcaOppId = (co.opportunity && co.opportunity.id) || co.id || marcaOppId;\n } catch (e) {}\n}\nif (!marcaOppId || !sucursalOppId) return [];\n\nlet locationSucursal = '';\ntry { locationSucursal = $('DATOS API - SUCURSAL').first().json['Location ID'] || ''; } catch (e) {}\n\nreturn [{ json: { sucursalOppId, marcaOppId, locationSucursal } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 6048, + 960 + ], + "id": "6542723e-f7b2-41de-b371-9e793d48f560", + "name": "Preparar Upsert Mapeo", + "onError": "continueRegularOutput", + "notes": "Decide si escribir el mapeo Baserow tras CREATE/UPDATE. Solo cuando la fila no existe (CREATE o UPDATE-por-contacto). onError continue para no romper la replicacion." + }, + { + "parameters": { + "operation": "create", + "databaseId": 63, + "tableId": 754, + "fieldsUi": { + "fieldValues": [ + { + "fieldId": 7280, + "fieldValue": "={{ $json.sucursalOppId }}" + }, + { + "fieldId": 7283, + "fieldValue": "={{ $json.marcaOppId }}" + }, + { + "fieldId": 7284, + "fieldValue": "={{ $json.locationSucursal }}" + }, + { + "fieldId": 7285, + "fieldValue": "={{ $now.toISO() }}" + } + ] + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + 6288, + 960 + ], + "id": "c69036dd-3914-4424-a165-6e0f3b110ebd", + "name": "Crear Mapeo - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Inserta el mapeo id_opp_sucursal->id_opp_marca en la tabla 754 en tiempo real. Idempotente con el backfill (verifica por id_opp_sucursal)." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1750, + -32 + ], + "name": "Obtener Contacto - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Trae el contacto de sucursal para derivar Sucursal/TIENDA/Canal de Origen de la opp cuando la opp de origen no los trae. onError=continue para no romper el sync.", + "id": "3636d605-1c44-4f2d-b64c-10a5c346cd24" + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API": { + "main": [ + [ + { + "node": "Buscar Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "API de Sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - SUCURSAL": { + "main": [ + [ + { + "node": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de Sucursal": { + "main": [ + [ + { + "node": "DATOS API - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de MARCA": { + "main": [ + [ + { + "node": "DATOS API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapeo completo oportunidad origen - SUCURSAL": { + "main": [ + [ + { + "node": "API de MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - SUCURSAL": { + "main": [ + [ + { + "node": "Mapeo completo oportunidad origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contacto en MARCA": { + "main": [ + [ + { + "node": "¿Existe contacto en MARCA?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Existe contacto en MARCA?": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Crear Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Contacto en MARCA": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Contact ID Resuelto": { + "main": [ + [ + { + "node": "Buscar Mapeo Opp - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidades del Contacto - MARCA": { + "main": [ + [ + { + "node": "Decidir Match (Create vs Update)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Decidir Match (Create vs Update)": { + "main": [ + [ + { + "node": "Switch UPDATE vs CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch UPDATE vs CREATE": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - UPDATE (v2)": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Armar Body - CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - CREATE": { + "main": [ + [ + { + "node": "Crear Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Armar Body - UPDATE (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapear ID Oportunidad Sucursal - SUCURSAL": { + "main": [ + [ + { + "node": "Buscar Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Mapeo Opp - Baserow": { + "main": [ + [ + { + "node": "Buscar Oportunidades del Contacto - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Upsert Mapeo": { + "main": [ + [ + { + "node": "Crear Mapeo - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Pipelines - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "Consultoria E3 ®", + "name": null, + "description": null, + "autosaved": false, + "workflowPublishHistory": [ + { + "createdAt": "2026-05-30T22:42:50.131Z", + "id": 677, + "workflowId": "Cfgwp0bOtDW8zuKW", + "versionId": "be97beec-1bd0-43c9-a70e-c3a268d47c3e", + "event": "activated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + ] + } +} \ No newline at end of file diff --git a/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_165946.json b/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_165946.json new file mode 100644 index 0000000..fb3de80 --- /dev/null +++ b/n8n/backup_contact_to_opp_mapping_Cfgwp0bOtDW8zuKW_20260530_165946.json @@ -0,0 +1,4228 @@ +{ + "updatedAt": "2026-05-30T22:42:49.703Z", + "createdAt": "2026-03-02T01:12:56.387Z", + "id": "Cfgwp0bOtDW8zuKW", + "name": "Sincronizar Oportunidad - Nodos Nuevos (Create/Update)", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "3eecb45a-e24c-49ac-aef7-99843f485819", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -336, + -16 + ], + "id": "dd424583-8295-4013-96ba-c7e29ea944c6", + "name": "Webhook", + "webhookId": "3eecb45a-e24c-49ac-aef7-99843f485819" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "b02bc821-6121-452d-a042-623fac6e443c", + "name": "Oportunidad.opportunity_name", + "value": "={{ $json.body.opportunity_name }}", + "type": "string" + }, + { + "id": "898817a2-7926-4ddf-b785-cac8e28afc58", + "name": "Oportunidad.pipleline_stage (new)", + "value": "={{ $json.body.pipleline_stage }}", + "type": "string" + }, + { + "id": "opp-id-sucursal-capture", + "name": "Oportunidad.opportunity_id", + "value": "={{ $json.body.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -96, + -16 + ], + "id": "2b3ec130-27ce-4988-82db-46e4f12c3f7d", + "name": "Datos de Lead" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE SUCURSAL", + "height": 352, + "width": 1152 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1072, + -144 + ], + "typeVersion": 1, + "id": "19023e33-a9f1-4c60-b87c-957e751e3dc2", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE MARCA", + "height": 208, + "width": 1188, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2240, + -80 + ], + "typeVersion": 1, + "id": "3a3d4f18-1a08-4dbc-a00b-7ae6bc4d49d1", + "name": "Sticky Note1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2480, + -32 + ], + "id": "a46df506-6a9a-420d-9cdd-6fc51d1a691e", + "name": "DATOS API" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 80, + -16 + ], + "id": "25db982f-7bc7-4318-a7b0-e94326abc437", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3952, + -32 + ], + "id": "2320b102-816e-4a0c-ae9c-e305d25df6df", + "name": "Actualizar Oportunidad - MARCA2", + "notes": "El código mapea los custom fields de una oportunidad de una sucursal hacia los IDs equivalentes de una marca. Toma tres fuentes: los metadatos de campos de la sucursal, los valores reales de esos campos en la oportunidad, y los metadatos de campos de la marca. Para cada campo con valor en la oportunidad, primero busca su `fieldKey` en los metadatos de la sucursal usando el `id`, luego usa ese `fieldKey` para encontrar el campo equivalente en la marca, y construye un array con el `id` y `key` de la marca pero con el valor original de la sucursal. Finalmente arma el body de una petición para crear/actualizar una oportunidad en la marca, reutilizando pipeline, nombre, stage y valor monetario de otros nodos, con los custom fields ya traducidos al contexto de la marca." + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1536, + -32 + ], + "id": "4e89658b-7ffd-4a3b-b91e-9ed3223a1a73", + "name": "Buscar Oportunidad - SUCURSAL", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 528, + -32 + ], + "id": "b721a65d-bcbb-44ab-bb7f-8f200b34fcdd", + "name": "DATOS API - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1136, + -32 + ], + "id": "39ef9dcc-ac20-4ba2-b891-2a2baa6ea4f5", + "name": "Conseguir Custom Fields - Opportunity - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1712, + -32 + ], + "id": "fbd46ae4-792b-451b-b99e-9816dc175d52", + "name": "Obtener info de Oportunidad - SUCURSAL" + }, + { + "parameters": { + "content": "# ACTUALIZAR OPORTUNIDAD - MARCA", + "height": 352, + "width": 928 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3456, + -144 + ], + "typeVersion": 1, + "id": "f3ca1cfe-08e2-485b-b238-9240f5753809", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "", + "height": 208, + "width": 992, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2768, + -64 + ], + "typeVersion": 1, + "id": "45066721-8404-48ed-94c1-c88edf48bca5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "", + "height": 192, + "width": 928, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1104, + -64 + ], + "typeVersion": 1, + "id": "b1646bb4-8b64-4bf4-b380-189cc7902741", + "name": "Sticky Note7" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Webhook').item.json.body.location.name }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 320, + -32 + ], + "id": "12088f29-8bb4-475d-ae0e-2cbdf8033138", + "name": "API de Sucursal", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "=Monte Providencia" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 2288, + -32 + ], + "id": "8c8fa238-6ce4-4adf-8926-b603987a55cf", + "name": "API de MARCA", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "jsCode": "const opportunityData = $('Obtener info de Oportunidad - SUCURSAL').first().json;\nconst opportunity = opportunityData.opportunity;\nconst customFieldsDefs = $('Conseguir Custom Fields - Opportunity - SUCURSAL').first().json.customFields;\nconst pipelines = $input.first().json.pipelines;\n\n// Map custom fields: id -> { fieldKey, name }\nconst fieldMap = {};\nfor (const def of customFieldsDefs) {\n fieldMap[def.id] = { fieldKey: def.fieldKey, name: def.name };\n}\n\n// Map pipelines: id -> name, stages: id -> { name, position, stageWinProbability }\nconst pipelineMap = {};\nconst stageMap = {};\nfor (const pipeline of pipelines) {\n pipelineMap[pipeline.id] = pipeline.name;\n for (const stage of pipeline.stages) {\n stageMap[stage.id] = {\n name: stage.name,\n position: stage.position,\n stageWinProbability: stage.stageWinProbability,\n color: stage.color || null\n };\n }\n}\n\n// Enrich custom fields with fieldKey and name\nconst enrichedCustomFields = opportunity.customFields.map(cf => ({\n id: cf.id,\n name: fieldMap[cf.id]?.name || null,\n fieldKey: fieldMap[cf.id]?.fieldKey || null,\n fieldValue: cf.fieldValue\n}));\n\n\n// ── CONTACT->OPP ENRICH: garantizar Sucursal / TIENDA / Canal de Origen ──\n// Respaldo cuando la opp de sucursal no trae el CF: tomar el valor del CONTACTO\n// (siempre poblado por [1604]/[2004]) y, en ultimo caso, del webhook. El fieldKey\n// canonico (sufijo) es identico en contact y opportunity.\nconst contactResp = $('Obtener Contacto - SUCURSAL').first().json;\nconst contact = (contactResp && contactResp.contact) ? contactResp.contact : (contactResp || {});\nconst webhookBody = ($('Webhook').first().json && $('Webhook').first().json.body) || {};\nconst norm2 = (s) => (s == null ? '' : String(s)).trim();\n\n// Valores del contacto por fieldKey (contacts traen {id, value}).\nconst contactValueByFieldKey = {};\nfor (const cf of (contact.customFields || [])) {\n const fk = fieldMap[cf.id] ? fieldMap[cf.id].fieldKey : null;\n if (fk) contactValueByFieldKey[fk] = cf.value;\n}\n\nconst ENRICH_TARGETS = [\n { oppKey: 'opportunity.sucursal', contactKey: 'contact.sucursal', webhookKey: 'Sucursal' },\n { oppKey: 'opportunity.tienda', contactKey: 'contact.tienda', webhookKey: 'TIENDA' },\n { oppKey: 'opportunity.fuente_de_posible_cliente', contactKey: 'contact.fuente_de_posible_cliente', webhookKey: 'CANAL DE ORIGEN' },\n];\n\nfor (const t of ENRICH_TARGETS) {\n const existing = enrichedCustomFields.find(cf => cf.fieldKey === t.oppKey);\n if (existing && norm2(existing.fieldValue) !== '') continue; // (a) ya viene de la opp\n let value = norm2(contactValueByFieldKey[t.contactKey]); // (b) contacto\n if (value === '') value = norm2(webhookBody[t.webhookKey]); // (c) webhook\n if (value === '') continue;\n if (existing) {\n existing.fieldValue = value;\n } else {\n const def = customFieldsDefs.find(d => d.fieldKey === t.oppKey);\n enrichedCustomFields.push({ id: def ? def.id : null, name: def ? def.name : null, fieldKey: t.oppKey, fieldValue: value });\n }\n}\n// ── fin CONTACT->OPP ENRICH ──\n\n// Resolve pipeline and stage info\nconst stageInfo = stageMap[opportunity.pipelineStageId] || {};\n\n// Build enriched opportunity\nconst enrichedOpportunity = {\n ...opportunity,\n pipelineName: pipelineMap[opportunity.pipelineId] || null,\n pipelineStageName: stageInfo.name || null,\n pipelineStagePosition: stageInfo.position ?? null,\n pipelineStageWinProbability: stageInfo.stageWinProbability ?? null,\n pipelineStageColor: stageInfo.color || null,\n customFields: enrichedCustomFields\n};\n\nreturn [{ json: { opportunity: enrichedOpportunity, traceId: opportunityData.traceId } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + -32 + ], + "id": "d9609dd9-9493-4f3d-b356-99438cedada6", + "name": "Mapeo completo oportunidad origen - SUCURSAL" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('Webhook').item.json.body.location.id }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de Sucursal').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1872, + -32 + ], + "id": "2d799ae3-b6b1-4e4f-8774-714ebe4f009f", + "name": "Obtener Pipelines - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Buscar Oportunidad - MARCA').item.json.opportunities[0].id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3216, + -32 + ], + "id": "1eee43d2-c6a8-414b-bebf-4b31d6fb34d7", + "name": "Obtener info de Oportunidad - MARCA" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('API de MARCA').item.json.Location_ID }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de MARCA').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3424, + -32 + ], + "id": "20a1a288-2cd8-471b-9203-5ba88770edb0", + "name": "Obtener Pipelines - MARCA" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2800, + -32 + ], + "id": "798a7e51-b8ed-4364-947a-7396fab47940", + "name": "Buscar Oportunidad - MARCA", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3008, + -32 + ], + "id": "cc37ea9b-0a9f-44ce-8ce2-ee2444822ecf", + "name": "Conseguir Custom Fields - Opportunity - MARCA" + }, + { + "parameters": { + "jsCode": "// ── DATOS MARCA ──────────────────────────────────────────────────────────────\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA').first().json.customFields;\nconst marcaPipelines = $input.first().json.pipelines;\n\n// ── DATOS SUCURSAL (ya enriquecidos) ─────────────────────────────────────────\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\n\n// ── MAP PIPELINES MARCA: name -> { id, stages } ───────────────────────────────\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[pipeline.name.trim()] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[stage.name.trim()] = stage.id;\n }\n}\n\n// ── RESOLVER PIPELINE Y STAGE POR NAME DESDE SUCURSAL ────────────────────────\nconst resolvedPipelineId = marcaPipelineByName[sucursalOpp.pipelineName?.trim()] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[sucursalOpp.pipelineStageName?.trim()] || marcaOpp.pipelineStageId;\n\n// ── MAP CUSTOM FIELDS SUCURSAL: name -> fieldValue ────────────────────────────\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields) {\n if (cf.name) {\n sucursalValueByName[cf.name.trim()] = cf.fieldValue;\n }\n}\n\n// ── CONSTRUIR CUSTOM FIELDS PARA EL BODY ─────────────────────────────────────\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n const nameKey = def.name.trim();\n const sucursalValue = sucursalValueByName[nameKey];\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n const fieldValue = sucursalValue !== undefined ? sucursalValue : (marcaOriginal?.fieldValue ?? null);\n\n if (fieldValue === null) continue;\n\n customFields.push({\n id: def.id,\n key: def.fieldKey,\n field_value: fieldValue\n });\n}\n\n// ── CONSTRUIR BODY VÁLIDO PARA UPDATE OPPORTUNITY ────────────────────────────\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source,\n customFields: customFields\n};\n\nreturn [{ json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body)\n} }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3632, + -32 + ], + "id": "3b547c2a-a541-4b48-8d97-ea2d5df52010", + "name": "Code in JavaScript" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"pageLimit\": 10,\n \"query\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono || $('Datos de Lead').item.json.Cliente.Email }}\"\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3360, + 960 + ], + "id": "02cd0fd6-6bb8-4faa-b85f-5d42663fba29", + "name": "Buscar Contacto en MARCA", + "notes": "Busca contacto en la location de marca por email.\nSi no encuentra, la siguiente IF deriva a creación." + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "exist-contact-check", + "leftValue": "={{ $json.contacts ? $json.contacts.length : 0 }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3600, + 960 + ], + "id": "aa2644dc-34b4-4159-8afb-89147b12f5ee", + "name": "¿Existe contacto en MARCA?" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/upsert", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"firstName\": \"{{ $('Datos de Lead').item.json.Cliente.Apellido }}\",\n \"name\": \"{{ $('Datos de Lead').item.json.Cliente['Nombre Completo'] }}\",\n \"email\": \"{{ $('Datos de Lead').item.json.Cliente.Email }}\",\n \"phone\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono }}\",\n \"source\": \"Sincronización Sucursal\",\n \"tags\": [\"sincronizado-sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3840, + 1104 + ], + "id": "f6c75352-3b9e-4fc9-b8e4-063c2792d190", + "name": "Crear Contacto en MARCA", + "notes": "Crea contacto en la marca solo si no existía.\nIMPORTANTE: revisa el mapeo de Nombre/Apellido, en el workflow original están invertidos." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "contact-id-resolved", + "name": "contactId", + "value": "={{ $json.contact ? $json.contact.id : $json.contacts[0].id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 4080, + 960 + ], + "id": "2a49d01e-6679-4584-9d9b-5778dfc4431e", + "name": "Set Contact ID Resuelto", + "notes": "Unifica el contactId tanto si vino de la búsqueda (contacts[0].id) como de la creación (contact.id)." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "location_id", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + }, + { + "name": "contact_id", + "value": "={{ $('Set Contact ID Resuelto').item.json.contactId }}" + }, + { + "name": "limit", + "value": "100" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 4320, + 960 + ], + "id": "8e25f660-3ed2-4597-9c2a-7480ac7d150f", + "name": "Buscar Oportunidades del Contacto - MARCA", + "notes": "Busca TODAS las oportunidades del contacto en la marca.\nDespués el Code de decisión elige cuál (o crea nueva)." + }, + { + "parameters": { + "jsCode": "// DECISION (Create vs Update) con idempotencia GLOBAL via mapeo Baserow (tabla 754).\n// (1) Si el mapeo id_opp_sucursal -> id_opp_marca existe -> UPDATE esa opp de Marca,\n// independiente del contacto resuelto (evita duplicados por contacto ambiguo).\n// (2) Si no hay mapeo, match por CF entre las opps del contacto resuelto.\n// (3) Si nada -> CREATE.\nconst result = $input.first().json;\nconst opportunities = result.opportunities || [];\nconst sucursalOppId = $('Datos de Lead').first().json.Oportunidad.opportunity_id;\n\nlet action = 'CREATE';\nlet opportunityId = null;\nlet matchReason = '';\n\n// (1) Mapeo Baserow global\nlet baserowRows = [];\ntry { baserowRows = $('Buscar Mapeo Opp - Baserow').all().map(i => i.json); } catch (e) { baserowRows = []; }\nconst mapped = baserowRows.find(r =>\n String(r.id_opp_sucursal || '') === String(sucursalOppId) && String(r.id_opp_marca || '').trim()\n);\n\nif (mapped) {\n action = 'UPDATE';\n opportunityId = String(mapped.id_opp_marca).trim();\n matchReason = 'Match por mapeo Baserow (global, tabla 754)';\n} else {\n // (2) match por CF entre las opps del contacto resuelto\n const match = opportunities.find(o =>\n (o.customFields || []).some(cf => {\n const v = cf.fieldValueString ?? cf.fieldValue ?? '';\n return String(v) === String(sucursalOppId);\n })\n );\n if (match) {\n action = 'UPDATE';\n opportunityId = match.id;\n matchReason = 'Match por ID Oportunidad Sucursal (contacto)';\n } else {\n action = 'CREATE';\n matchReason = 'Sin match (Baserow ni contacto) -> CREATE (contacto tiene ' + opportunities.length + ' opps)';\n }\n}\n\nreturn [{\n json: {\n action,\n opportunityId,\n matchReason,\n sucursalOppId,\n contactId: $('Set Contact ID Resuelto').first().json.contactId,\n totalOppsContacto: opportunities.length\n }\n}];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4560, + 960 + ], + "id": "6ca30f13-8503-427f-95ec-b243d30688ec", + "name": "Decidir Match (Create vs Update)" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-update", + "leftValue": "={{ $json.action }}", + "rightValue": "UPDATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "UPDATE" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-create", + "leftValue": "={{ $json.action }}", + "rightValue": "CREATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "CREATE" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 4800, + 960 + ], + "id": "bff7a5dc-b4af-4033-83ea-c508aa92264a", + "name": "Switch UPDATE vs CREATE" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Decidir Match (Create vs Update)').item.json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5040, + 816 + ], + "id": "83cd0fd7-1ee9-4896-926e-a0b752e216fe", + "name": "Obtener info de Oportunidad - MARCA (v2)", + "notes": "Sólo se ejecuta en rama UPDATE.\nReemplaza el nodo original 'Obtener info de Oportunidad - MARCA' que dependía de búsqueda por nombre." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 1104 + ], + "id": "0fb2fdea-cf40-4dfb-abfa-c422282e5d2a", + "name": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 1104 + ], + "id": "58114758-f0fb-483a-bd5b-d28188880a02", + "name": "Obtener Pipelines - MARCA (CREATE)" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA CREAR OPORTUNIDAD EN MARCA ──────────────────────\n// Mapea custom fields por fieldKey (fallback name) y FIJA el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia futura).\n\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (CREATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (CREATE)').first().json.pipelines || [];\nconst locationId = $('DATOS API').first().json['Location ID'];\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst contactId = decision.contactId;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\n// Map pipelines/stages de la marca por nombre normalizado\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)];\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)];\n\nif (!resolvedPipelineId) {\n throw new Error(`No se encontró pipeline en marca: \"${sucursalOpp.pipelineName}\". Revisa que el nombre coincida.`);\n}\nif (!resolvedStageId) {\n throw new Error(`No se encontró stage en marca: \"${sucursalOpp.pipelineStageName}\". Revisa que el nombre coincida.`);\n}\n\n// Mapear custom fields: preferir fieldKey, fallback a name normalizado\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null || value === '') continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// FIJAR el campo de enlace \"ID Oportunidad Sucursal\" = id de la opp de sucursal\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n locationId: locationId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || 'open',\n contactId: contactId,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source || 'Sincronización Sucursal',\n customFields: customFields\n};\n\nreturn [{\n json: {\n body: JSON.stringify(body),\n debug: { resolvedPipelineId, resolvedStageId, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5520, + 1104 + ], + "id": "2f9a63e7-277d-49de-8838-a00922c993b2", + "name": "Armar Body - CREATE" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5760, + 1104 + ], + "id": "fc4b3398-91bb-4dd9-b406-96c8d75a17be", + "name": "Crear Oportunidad - MARCA" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA ACTUALIZAR OPORTUNIDAD EN MARCA (v2) ────────────\n// Mapea CF por fieldKey (fallback name) y reafirma el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia).\n\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA (v2)').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (UPDATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (UPDATE)').first().json.pipelines || [];\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)] || marcaOpp.pipelineStageId;\n\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null) {\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n value = marcaOriginal?.fieldValue;\n }\n if (value === undefined || value === null) continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// Reafirmar el campo de enlace\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || marcaOpp.status,\n monetaryValue: sucursalOpp.monetaryValue ?? marcaOpp.monetaryValue,\n source: sucursalOpp.source || marcaOpp.source,\n customFields: customFields\n};\n\nreturn [{\n json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body),\n debug: { matchReason: decision.matchReason, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5280, + 816 + ], + "id": "969e46cd-bbae-4372-9808-c939f63e3c02", + "name": "Armar Body - UPDATE (v2)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5520, + 816 + ], + "id": "83929e08-7afd-4670-9c18-be22b8c3656f", + "name": "Actualizar Oportunidad - MARCA (v2)" + }, + { + "parameters": { + "content": "# RAMA UPDATE\n## Oportunidad ya existe en marca → se actualiza", + "height": 280, + "width": 880, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 720 + ], + "typeVersion": 1, + "id": "1c285234-8d90-49ef-8dac-c98cb379299a", + "name": "Sticky Update" + }, + { + "parameters": { + "content": "# RAMA CREATE\n## Oportunidad no existe en marca → se crea desde cero\n### Incluye contacto + custom fields + tag origin-id", + "height": 280, + "width": 1120, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 1024 + ], + "typeVersion": 1, + "id": "7e8f350c-9562-485b-8ce5-1b13b4086856", + "name": "Sticky Create" + }, + { + "parameters": { + "content": "# MATCHING ROBUSTO\n## 1. Buscar/crear contacto en marca\n## 2. Buscar oportunidades DEL CONTACTO\n## 3. Decidir match por cantidad + tag origin-id", + "height": 320, + "width": 1500, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3328, + 864 + ], + "typeVersion": 1, + "id": "8a583e46-41b6-44e0-9313-b70ee085b740", + "name": "Sticky Matching" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 560 + ], + "id": "45f0a6b0-f44d-4e83-b2ab-cdd3fbfe7911", + "name": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 560 + ], + "id": "5ca962a9-9e56-42d1-af05-ce05d39ed77f", + "name": "Obtener Pipelines - MARCA (UPDATE)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ customFields: [ { id: $('Conseguir Custom Fields - Opportunity - SUCURSAL').item.json.customFields.find(f => f.fieldKey === 'opportunity.id_oportunidad_sucursal')?.id, key: 'opportunity.id_oportunidad_sucursal', field_value: $('Datos de Lead').item.json.Oportunidad.opportunity_id } ] }) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1328, + -32 + ], + "id": "a9572dee-ed57-4104-966a-4b22721e75f1", + "name": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Auto-mapeo de seguridad: setea 'ID Oportunidad Sucursal' = id nativo de la propia opp de sucursal (del webhook) antes de procesar. onError=continue para no romper el sync si falla." + }, + { + "parameters": { + "databaseId": 63, + "tableId": 754, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7280, + "value": "={{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 4208, + 1152 + ], + "id": "a64fdffd-ab8f-4ddd-a4d4-4d683a05132d", + "name": "Buscar Mapeo Opp - Baserow", + "alwaysOutputData": true, + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Idempotencia global: busca en la tabla 754 el mapeo id_opp_sucursal->id_opp_marca. Si existe, 'Decidir Match' hara UPDATE de esa opp aunque el contacto resuelto sea otro. alwaysOutputData + onError para no romper el flujo si no hay match o si Baserow falla." + }, + { + "parameters": { + "jsCode": "// Prepara el UPSERT del mapeo Baserow (tabla 754) tras CREATE/UPDATE de la opp en Marca.\n// Solo escribe cuando el mapeo NO existe aun:\n// - action CREATE -> la fila no existe (el lookup Baserow fallo) -> crear.\n// - action UPDATE por contacto -> Baserow no la tenia -> crear (idempotencia futura).\n// - action UPDATE por Baserow -> la fila YA existe -> NO escribir (no duplicar).\n// Asi una re-ejecucion del mismo opp encuentra el mapeo y hace UPDATE, no CREATE duplicado.\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst action = decision.action;\nconst matchReason = decision.matchReason || '';\nconst sucursalOppId = decision.sucursalOppId;\n\nconst matchedByBaserow = matchReason.indexOf('Baserow') !== -1;\nconst needWrite = (action === 'CREATE') || (action === 'UPDATE' && !matchedByBaserow);\nif (!needWrite) return [];\n\n// id de la opp en Marca segun el camino\nlet marcaOppId = decision.opportunityId;\nif (action === 'CREATE') {\n try {\n const co = $('Crear Oportunidad - MARCA').first().json;\n marcaOppId = (co.opportunity && co.opportunity.id) || co.id || marcaOppId;\n } catch (e) {}\n}\nif (!marcaOppId || !sucursalOppId) return [];\n\nlet locationSucursal = '';\ntry { locationSucursal = $('DATOS API - SUCURSAL').first().json['Location ID'] || ''; } catch (e) {}\n\nreturn [{ json: { sucursalOppId, marcaOppId, locationSucursal } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 6048, + 960 + ], + "id": "6542723e-f7b2-41de-b371-9e793d48f560", + "name": "Preparar Upsert Mapeo", + "onError": "continueRegularOutput", + "notes": "Decide si escribir el mapeo Baserow tras CREATE/UPDATE. Solo cuando la fila no existe (CREATE o UPDATE-por-contacto). onError continue para no romper la replicacion." + }, + { + "parameters": { + "operation": "create", + "databaseId": 63, + "tableId": 754, + "fieldsUi": { + "fieldValues": [ + { + "fieldId": 7280, + "fieldValue": "={{ $json.sucursalOppId }}" + }, + { + "fieldId": 7283, + "fieldValue": "={{ $json.marcaOppId }}" + }, + { + "fieldId": 7284, + "fieldValue": "={{ $json.locationSucursal }}" + }, + { + "fieldId": 7285, + "fieldValue": "={{ $now.toISO() }}" + } + ] + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + 6288, + 960 + ], + "id": "c69036dd-3914-4424-a165-6e0f3b110ebd", + "name": "Crear Mapeo - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Inserta el mapeo id_opp_sucursal->id_opp_marca en la tabla 754 en tiempo real. Idempotente con el backfill (verifica por id_opp_sucursal)." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1750, + -32 + ], + "name": "Obtener Contacto - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Trae el contacto de sucursal para derivar Sucursal/TIENDA/Canal de Origen de la opp cuando la opp de origen no los trae. onError=continue para no romper el sync.", + "id": "3636d605-1c44-4f2d-b64c-10a5c346cd24" + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API": { + "main": [ + [ + { + "node": "Buscar Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "API de Sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - SUCURSAL": { + "main": [ + [ + { + "node": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de Sucursal": { + "main": [ + [ + { + "node": "DATOS API - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de MARCA": { + "main": [ + [ + { + "node": "DATOS API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapeo completo oportunidad origen - SUCURSAL": { + "main": [ + [ + { + "node": "API de MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - SUCURSAL": { + "main": [ + [ + { + "node": "Mapeo completo oportunidad origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contacto en MARCA": { + "main": [ + [ + { + "node": "¿Existe contacto en MARCA?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Existe contacto en MARCA?": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Crear Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Contacto en MARCA": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Contact ID Resuelto": { + "main": [ + [ + { + "node": "Buscar Mapeo Opp - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidades del Contacto - MARCA": { + "main": [ + [ + { + "node": "Decidir Match (Create vs Update)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Decidir Match (Create vs Update)": { + "main": [ + [ + { + "node": "Switch UPDATE vs CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch UPDATE vs CREATE": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - UPDATE (v2)": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Armar Body - CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - CREATE": { + "main": [ + [ + { + "node": "Crear Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Armar Body - UPDATE (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapear ID Oportunidad Sucursal - SUCURSAL": { + "main": [ + [ + { + "node": "Buscar Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Mapeo Opp - Baserow": { + "main": [ + [ + { + "node": "Buscar Oportunidades del Contacto - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Upsert Mapeo": { + "main": [ + [ + { + "node": "Crear Mapeo - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Pipelines - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false + }, + "staticData": null, + "meta": { + "templateCredsSetupCompleted": true + }, + "pinData": { + "Webhook": [ + { + "json": { + "headers": { + "host": "webhookn8.consultoriae3.com", + "user-agent": "axios/1.13.2", + "content-length": "1839", + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, compress, deflate, br", + "content-type": "application/json", + "traceparent": "00-00e6586cc6967f6985b8aa87332de4e0-932bdb81faeff7c4-01", + "x-forwarded-for": "34.55.150.6", + "x-forwarded-host": "webhookn8.consultoriae3.com", + "x-forwarded-port": "443", + "x-forwarded-proto": "https", + "x-forwarded-server": "f569f6e85d30", + "x-real-ip": "34.55.150.6" + }, + "params": {}, + "query": {}, + "body": { + "¿Cuándo necesitas el dinero?": "", + "[Número de Teléfono para Atender]": "", + "Información Adicional": "", + "Presupuesto": "", + "CANAL DE ORIGEN": "", + "[Número de WhatsApp para Atender]": "", + "[Dirección de la Empresa]": "", + "Año del Vehículo": "2015", + "¿Qué modalidad prefieres?": "Tradicional (Resguardo)", + "Marca del Vehiculo": "RENAULT", + "TIENDA": "TAMPICO", + "Sucursal": "Tampico, Tamaulipas", + "Descripción": "", + "[Correo_Empresa]": "", + "ID Contacto Monte Providencia": "", + "Fuente de Prospecto": "REDES SOCIALES", + "[Correo_Canalizados]": "", + "Version del Vehiculo": "LOGAN", + "Acepta los terminos para tu cotización": "", + "[Nombre de la Empresa]": "", + "Check Comunicaciones de Marketing": "", + "Archivos Adicionales": "", + "contact_id": "B9oJWsFXy6Ol9NdHL5Jh", + "first_name": "RAUL", + "last_name": "LOGAN", + "full_name": "RAUL LOGAN", + "email": "raulsincorreo@gmail.com", + "phone": "+528332937527", + "tags": "sucursal", + "country": "MX", + "date_created": "2026-05-27T21:16:44.369Z", + "full_address": "", + "contact_type": "lead", + "opportunity_name": "RAUL LOGAN", + "status": "open", + "lead_value": 40000, + "opportunity_source": "REDES SOCIALES", + "source": "REDES SOCIALES", + "pipleline_stage": "Cliente interesado", + "pipeline_id": "ep1d4VpzRezVqWayFbBf", + "id": "NuFArKAQL72I8QC69MDa", + "pipeline_name": "Standar", + "user": { + "firstName": "Tampico - 85969", + "lastName": "Tampico", + "email": "ezstore85969@ezcorp.com" + }, + "owner": "Tampico - 85969 Tampico", + "location": { + "name": "85969 - MP - Tampico", + "address": "Tampico", + "city": "Tampico", + "state": "Tamaulipas", + "country": "MX", + "postalCode": "10000", + "fullAddress": "Tampico, Tampico Tamaulipas 10000", + "id": "WCHyow6KpjLFYriQWdbJ" + }, + "workflow": { + "id": "8ba8fdf1-85f2-4f1f-bf28-56400e1adcd6", + "name": "Sincronizar Oportunidad V2" + }, + "triggerData": {}, + "contact": { + "attributionSource": { + "sessionSource": "CRM UI", + "mediumId": null, + "medium": "manual" + }, + "lastAttributionSource": {} + }, + "attributionSource": {}, + "customData": {} + }, + "webhookUrl": "https://webhookn8.consultoriae3.com/webhook/3eecb45a-e24c-49ac-aef7-99843f485819", + "executionMode": "production" + }, + "pairedItem": { + "item": 0 + } + } + ] + }, + "versionId": "be97beec-1bd0-43c9-a70e-c3a268d47c3e", + "activeVersionId": "be97beec-1bd0-43c9-a70e-c3a268d47c3e", + "versionCounter": 437, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-03-02T01:12:56.387Z", + "createdAt": "2026-03-02T01:12:56.387Z", + "role": "workflow:owner", + "workflowId": "Cfgwp0bOtDW8zuKW", + "projectId": "aycNlMpnzsxvXbfG", + "project": { + "updatedAt": "2025-11-27T04:06:26.313Z", + "createdAt": "2025-11-27T03:54:24.410Z", + "id": "aycNlMpnzsxvXbfG", + "name": "Consultoria E3 ® ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + } + ], + "tags": [ + { + "updatedAt": "2025-11-27T05:48:12.272Z", + "createdAt": "2025-11-27T05:48:12.272Z", + "id": "pHkPpq3Httzx4hnb", + "name": "Bucefalo" + } + ], + "activeVersion": { + "updatedAt": "2026-05-30T22:42:49.706Z", + "createdAt": "2026-05-30T22:42:49.706Z", + "versionId": "be97beec-1bd0-43c9-a70e-c3a268d47c3e", + "workflowId": "Cfgwp0bOtDW8zuKW", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "3eecb45a-e24c-49ac-aef7-99843f485819", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -336, + -16 + ], + "id": "dd424583-8295-4013-96ba-c7e29ea944c6", + "name": "Webhook", + "webhookId": "3eecb45a-e24c-49ac-aef7-99843f485819" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "b02bc821-6121-452d-a042-623fac6e443c", + "name": "Oportunidad.opportunity_name", + "value": "={{ $json.body.opportunity_name }}", + "type": "string" + }, + { + "id": "898817a2-7926-4ddf-b785-cac8e28afc58", + "name": "Oportunidad.pipleline_stage (new)", + "value": "={{ $json.body.pipleline_stage }}", + "type": "string" + }, + { + "id": "opp-id-sucursal-capture", + "name": "Oportunidad.opportunity_id", + "value": "={{ $json.body.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -96, + -16 + ], + "id": "2b3ec130-27ce-4988-82db-46e4f12c3f7d", + "name": "Datos de Lead" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE SUCURSAL", + "height": 352, + "width": 1152 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1072, + -144 + ], + "typeVersion": 1, + "id": "19023e33-a9f1-4c60-b87c-957e751e3dc2", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE MARCA", + "height": 208, + "width": 1188, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2240, + -80 + ], + "typeVersion": 1, + "id": "3a3d4f18-1a08-4dbc-a00b-7ae6bc4d49d1", + "name": "Sticky Note1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2480, + -32 + ], + "id": "a46df506-6a9a-420d-9cdd-6fc51d1a691e", + "name": "DATOS API" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 80, + -16 + ], + "id": "25db982f-7bc7-4318-a7b0-e94326abc437", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3952, + -32 + ], + "id": "2320b102-816e-4a0c-ae9c-e305d25df6df", + "name": "Actualizar Oportunidad - MARCA2", + "notes": "El código mapea los custom fields de una oportunidad de una sucursal hacia los IDs equivalentes de una marca. Toma tres fuentes: los metadatos de campos de la sucursal, los valores reales de esos campos en la oportunidad, y los metadatos de campos de la marca. Para cada campo con valor en la oportunidad, primero busca su `fieldKey` en los metadatos de la sucursal usando el `id`, luego usa ese `fieldKey` para encontrar el campo equivalente en la marca, y construye un array con el `id` y `key` de la marca pero con el valor original de la sucursal. Finalmente arma el body de una petición para crear/actualizar una oportunidad en la marca, reutilizando pipeline, nombre, stage y valor monetario de otros nodos, con los custom fields ya traducidos al contexto de la marca." + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1536, + -32 + ], + "id": "4e89658b-7ffd-4a3b-b91e-9ed3223a1a73", + "name": "Buscar Oportunidad - SUCURSAL", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 528, + -32 + ], + "id": "b721a65d-bcbb-44ab-bb7f-8f200b34fcdd", + "name": "DATOS API - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1136, + -32 + ], + "id": "39ef9dcc-ac20-4ba2-b891-2a2baa6ea4f5", + "name": "Conseguir Custom Fields - Opportunity - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1712, + -32 + ], + "id": "fbd46ae4-792b-451b-b99e-9816dc175d52", + "name": "Obtener info de Oportunidad - SUCURSAL" + }, + { + "parameters": { + "content": "# ACTUALIZAR OPORTUNIDAD - MARCA", + "height": 352, + "width": 928 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3456, + -144 + ], + "typeVersion": 1, + "id": "f3ca1cfe-08e2-485b-b238-9240f5753809", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "", + "height": 208, + "width": 992, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2768, + -64 + ], + "typeVersion": 1, + "id": "45066721-8404-48ed-94c1-c88edf48bca5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "", + "height": 192, + "width": 928, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1104, + -64 + ], + "typeVersion": 1, + "id": "b1646bb4-8b64-4bf4-b380-189cc7902741", + "name": "Sticky Note7" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Webhook').item.json.body.location.name }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 320, + -32 + ], + "id": "12088f29-8bb4-475d-ae0e-2cbdf8033138", + "name": "API de Sucursal", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "=Monte Providencia" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 2288, + -32 + ], + "id": "8c8fa238-6ce4-4adf-8926-b603987a55cf", + "name": "API de MARCA", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "jsCode": "const opportunityData = $('Obtener info de Oportunidad - SUCURSAL').first().json;\nconst opportunity = opportunityData.opportunity;\nconst customFieldsDefs = $('Conseguir Custom Fields - Opportunity - SUCURSAL').first().json.customFields;\nconst pipelines = $input.first().json.pipelines;\n\n// Map custom fields: id -> { fieldKey, name }\nconst fieldMap = {};\nfor (const def of customFieldsDefs) {\n fieldMap[def.id] = { fieldKey: def.fieldKey, name: def.name };\n}\n\n// Map pipelines: id -> name, stages: id -> { name, position, stageWinProbability }\nconst pipelineMap = {};\nconst stageMap = {};\nfor (const pipeline of pipelines) {\n pipelineMap[pipeline.id] = pipeline.name;\n for (const stage of pipeline.stages) {\n stageMap[stage.id] = {\n name: stage.name,\n position: stage.position,\n stageWinProbability: stage.stageWinProbability,\n color: stage.color || null\n };\n }\n}\n\n// Enrich custom fields with fieldKey and name\nconst enrichedCustomFields = opportunity.customFields.map(cf => ({\n id: cf.id,\n name: fieldMap[cf.id]?.name || null,\n fieldKey: fieldMap[cf.id]?.fieldKey || null,\n fieldValue: cf.fieldValue\n}));\n\n\n// ── CONTACT->OPP ENRICH: garantizar Sucursal / TIENDA / Canal de Origen ──\n// Respaldo cuando la opp de sucursal no trae el CF: tomar el valor del CONTACTO\n// (siempre poblado por [1604]/[2004]) y, en ultimo caso, del webhook. El fieldKey\n// canonico (sufijo) es identico en contact y opportunity.\nconst contactResp = $('Obtener Contacto - SUCURSAL').first().json;\nconst contact = (contactResp && contactResp.contact) ? contactResp.contact : (contactResp || {});\nconst webhookBody = ($('Webhook').first().json && $('Webhook').first().json.body) || {};\nconst norm2 = (s) => (s == null ? '' : String(s)).trim();\n\n// Valores del contacto por fieldKey (contacts traen {id, value}).\nconst contactValueByFieldKey = {};\nfor (const cf of (contact.customFields || [])) {\n const fk = fieldMap[cf.id] ? fieldMap[cf.id].fieldKey : null;\n if (fk) contactValueByFieldKey[fk] = cf.value;\n}\n\nconst ENRICH_TARGETS = [\n { oppKey: 'opportunity.sucursal', contactKey: 'contact.sucursal', webhookKey: 'Sucursal' },\n { oppKey: 'opportunity.tienda', contactKey: 'contact.tienda', webhookKey: 'TIENDA' },\n { oppKey: 'opportunity.fuente_de_posible_cliente', contactKey: 'contact.fuente_de_posible_cliente', webhookKey: 'CANAL DE ORIGEN' },\n];\n\nfor (const t of ENRICH_TARGETS) {\n const existing = enrichedCustomFields.find(cf => cf.fieldKey === t.oppKey);\n if (existing && norm2(existing.fieldValue) !== '') continue; // (a) ya viene de la opp\n let value = norm2(contactValueByFieldKey[t.contactKey]); // (b) contacto\n if (value === '') value = norm2(webhookBody[t.webhookKey]); // (c) webhook\n if (value === '') continue;\n if (existing) {\n existing.fieldValue = value;\n } else {\n const def = customFieldsDefs.find(d => d.fieldKey === t.oppKey);\n enrichedCustomFields.push({ id: def ? def.id : null, name: def ? def.name : null, fieldKey: t.oppKey, fieldValue: value });\n }\n}\n// ── fin CONTACT->OPP ENRICH ──\n\n// Resolve pipeline and stage info\nconst stageInfo = stageMap[opportunity.pipelineStageId] || {};\n\n// Build enriched opportunity\nconst enrichedOpportunity = {\n ...opportunity,\n pipelineName: pipelineMap[opportunity.pipelineId] || null,\n pipelineStageName: stageInfo.name || null,\n pipelineStagePosition: stageInfo.position ?? null,\n pipelineStageWinProbability: stageInfo.stageWinProbability ?? null,\n pipelineStageColor: stageInfo.color || null,\n customFields: enrichedCustomFields\n};\n\nreturn [{ json: { opportunity: enrichedOpportunity, traceId: opportunityData.traceId } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + -32 + ], + "id": "d9609dd9-9493-4f3d-b356-99438cedada6", + "name": "Mapeo completo oportunidad origen - SUCURSAL" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('Webhook').item.json.body.location.id }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de Sucursal').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1872, + -32 + ], + "id": "2d799ae3-b6b1-4e4f-8774-714ebe4f009f", + "name": "Obtener Pipelines - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Buscar Oportunidad - MARCA').item.json.opportunities[0].id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3216, + -32 + ], + "id": "1eee43d2-c6a8-414b-bebf-4b31d6fb34d7", + "name": "Obtener info de Oportunidad - MARCA" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('API de MARCA').item.json.Location_ID }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de MARCA').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3424, + -32 + ], + "id": "20a1a288-2cd8-471b-9203-5ba88770edb0", + "name": "Obtener Pipelines - MARCA" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2800, + -32 + ], + "id": "798a7e51-b8ed-4364-947a-7396fab47940", + "name": "Buscar Oportunidad - MARCA", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3008, + -32 + ], + "id": "cc37ea9b-0a9f-44ce-8ce2-ee2444822ecf", + "name": "Conseguir Custom Fields - Opportunity - MARCA" + }, + { + "parameters": { + "jsCode": "// ── DATOS MARCA ──────────────────────────────────────────────────────────────\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA').first().json.customFields;\nconst marcaPipelines = $input.first().json.pipelines;\n\n// ── DATOS SUCURSAL (ya enriquecidos) ─────────────────────────────────────────\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\n\n// ── MAP PIPELINES MARCA: name -> { id, stages } ───────────────────────────────\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[pipeline.name.trim()] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[stage.name.trim()] = stage.id;\n }\n}\n\n// ── RESOLVER PIPELINE Y STAGE POR NAME DESDE SUCURSAL ────────────────────────\nconst resolvedPipelineId = marcaPipelineByName[sucursalOpp.pipelineName?.trim()] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[sucursalOpp.pipelineStageName?.trim()] || marcaOpp.pipelineStageId;\n\n// ── MAP CUSTOM FIELDS SUCURSAL: name -> fieldValue ────────────────────────────\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields) {\n if (cf.name) {\n sucursalValueByName[cf.name.trim()] = cf.fieldValue;\n }\n}\n\n// ── CONSTRUIR CUSTOM FIELDS PARA EL BODY ─────────────────────────────────────\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n const nameKey = def.name.trim();\n const sucursalValue = sucursalValueByName[nameKey];\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n const fieldValue = sucursalValue !== undefined ? sucursalValue : (marcaOriginal?.fieldValue ?? null);\n\n if (fieldValue === null) continue;\n\n customFields.push({\n id: def.id,\n key: def.fieldKey,\n field_value: fieldValue\n });\n}\n\n// ── CONSTRUIR BODY VÁLIDO PARA UPDATE OPPORTUNITY ────────────────────────────\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source,\n customFields: customFields\n};\n\nreturn [{ json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body)\n} }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3632, + -32 + ], + "id": "3b547c2a-a541-4b48-8d97-ea2d5df52010", + "name": "Code in JavaScript" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"pageLimit\": 10,\n \"query\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono || $('Datos de Lead').item.json.Cliente.Email }}\"\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3360, + 960 + ], + "id": "02cd0fd6-6bb8-4faa-b85f-5d42663fba29", + "name": "Buscar Contacto en MARCA", + "notes": "Busca contacto en la location de marca por email.\nSi no encuentra, la siguiente IF deriva a creación." + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "exist-contact-check", + "leftValue": "={{ $json.contacts ? $json.contacts.length : 0 }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3600, + 960 + ], + "id": "aa2644dc-34b4-4159-8afb-89147b12f5ee", + "name": "¿Existe contacto en MARCA?" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/upsert", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"firstName\": \"{{ $('Datos de Lead').item.json.Cliente.Apellido }}\",\n \"name\": \"{{ $('Datos de Lead').item.json.Cliente['Nombre Completo'] }}\",\n \"email\": \"{{ $('Datos de Lead').item.json.Cliente.Email }}\",\n \"phone\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono }}\",\n \"source\": \"Sincronización Sucursal\",\n \"tags\": [\"sincronizado-sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3840, + 1104 + ], + "id": "f6c75352-3b9e-4fc9-b8e4-063c2792d190", + "name": "Crear Contacto en MARCA", + "notes": "Crea contacto en la marca solo si no existía.\nIMPORTANTE: revisa el mapeo de Nombre/Apellido, en el workflow original están invertidos." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "contact-id-resolved", + "name": "contactId", + "value": "={{ $json.contact ? $json.contact.id : $json.contacts[0].id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 4080, + 960 + ], + "id": "2a49d01e-6679-4584-9d9b-5778dfc4431e", + "name": "Set Contact ID Resuelto", + "notes": "Unifica el contactId tanto si vino de la búsqueda (contacts[0].id) como de la creación (contact.id)." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "location_id", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + }, + { + "name": "contact_id", + "value": "={{ $('Set Contact ID Resuelto').item.json.contactId }}" + }, + { + "name": "limit", + "value": "100" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 4320, + 960 + ], + "id": "8e25f660-3ed2-4597-9c2a-7480ac7d150f", + "name": "Buscar Oportunidades del Contacto - MARCA", + "notes": "Busca TODAS las oportunidades del contacto en la marca.\nDespués el Code de decisión elige cuál (o crea nueva)." + }, + { + "parameters": { + "jsCode": "// DECISION (Create vs Update) con idempotencia GLOBAL via mapeo Baserow (tabla 754).\n// (1) Si el mapeo id_opp_sucursal -> id_opp_marca existe -> UPDATE esa opp de Marca,\n// independiente del contacto resuelto (evita duplicados por contacto ambiguo).\n// (2) Si no hay mapeo, match por CF entre las opps del contacto resuelto.\n// (3) Si nada -> CREATE.\nconst result = $input.first().json;\nconst opportunities = result.opportunities || [];\nconst sucursalOppId = $('Datos de Lead').first().json.Oportunidad.opportunity_id;\n\nlet action = 'CREATE';\nlet opportunityId = null;\nlet matchReason = '';\n\n// (1) Mapeo Baserow global\nlet baserowRows = [];\ntry { baserowRows = $('Buscar Mapeo Opp - Baserow').all().map(i => i.json); } catch (e) { baserowRows = []; }\nconst mapped = baserowRows.find(r =>\n String(r.id_opp_sucursal || '') === String(sucursalOppId) && String(r.id_opp_marca || '').trim()\n);\n\nif (mapped) {\n action = 'UPDATE';\n opportunityId = String(mapped.id_opp_marca).trim();\n matchReason = 'Match por mapeo Baserow (global, tabla 754)';\n} else {\n // (2) match por CF entre las opps del contacto resuelto\n const match = opportunities.find(o =>\n (o.customFields || []).some(cf => {\n const v = cf.fieldValueString ?? cf.fieldValue ?? '';\n return String(v) === String(sucursalOppId);\n })\n );\n if (match) {\n action = 'UPDATE';\n opportunityId = match.id;\n matchReason = 'Match por ID Oportunidad Sucursal (contacto)';\n } else {\n action = 'CREATE';\n matchReason = 'Sin match (Baserow ni contacto) -> CREATE (contacto tiene ' + opportunities.length + ' opps)';\n }\n}\n\nreturn [{\n json: {\n action,\n opportunityId,\n matchReason,\n sucursalOppId,\n contactId: $('Set Contact ID Resuelto').first().json.contactId,\n totalOppsContacto: opportunities.length\n }\n}];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4560, + 960 + ], + "id": "6ca30f13-8503-427f-95ec-b243d30688ec", + "name": "Decidir Match (Create vs Update)" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-update", + "leftValue": "={{ $json.action }}", + "rightValue": "UPDATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "UPDATE" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-create", + "leftValue": "={{ $json.action }}", + "rightValue": "CREATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "CREATE" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 4800, + 960 + ], + "id": "bff7a5dc-b4af-4033-83ea-c508aa92264a", + "name": "Switch UPDATE vs CREATE" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Decidir Match (Create vs Update)').item.json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5040, + 816 + ], + "id": "83cd0fd7-1ee9-4896-926e-a0b752e216fe", + "name": "Obtener info de Oportunidad - MARCA (v2)", + "notes": "Sólo se ejecuta en rama UPDATE.\nReemplaza el nodo original 'Obtener info de Oportunidad - MARCA' que dependía de búsqueda por nombre." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 1104 + ], + "id": "0fb2fdea-cf40-4dfb-abfa-c422282e5d2a", + "name": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 1104 + ], + "id": "58114758-f0fb-483a-bd5b-d28188880a02", + "name": "Obtener Pipelines - MARCA (CREATE)" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA CREAR OPORTUNIDAD EN MARCA ──────────────────────\n// Mapea custom fields por fieldKey (fallback name) y FIJA el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia futura).\n\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (CREATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (CREATE)').first().json.pipelines || [];\nconst locationId = $('DATOS API').first().json['Location ID'];\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst contactId = decision.contactId;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\n// Map pipelines/stages de la marca por nombre normalizado\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)];\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)];\n\nif (!resolvedPipelineId) {\n throw new Error(`No se encontró pipeline en marca: \"${sucursalOpp.pipelineName}\". Revisa que el nombre coincida.`);\n}\nif (!resolvedStageId) {\n throw new Error(`No se encontró stage en marca: \"${sucursalOpp.pipelineStageName}\". Revisa que el nombre coincida.`);\n}\n\n// Mapear custom fields: preferir fieldKey, fallback a name normalizado\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null || value === '') continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// FIJAR el campo de enlace \"ID Oportunidad Sucursal\" = id de la opp de sucursal\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n locationId: locationId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || 'open',\n contactId: contactId,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source || 'Sincronización Sucursal',\n customFields: customFields\n};\n\nreturn [{\n json: {\n body: JSON.stringify(body),\n debug: { resolvedPipelineId, resolvedStageId, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5520, + 1104 + ], + "id": "2f9a63e7-277d-49de-8838-a00922c993b2", + "name": "Armar Body - CREATE" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5760, + 1104 + ], + "id": "fc4b3398-91bb-4dd9-b406-96c8d75a17be", + "name": "Crear Oportunidad - MARCA" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA ACTUALIZAR OPORTUNIDAD EN MARCA (v2) ────────────\n// Mapea CF por fieldKey (fallback name) y reafirma el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia).\n\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA (v2)').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (UPDATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (UPDATE)').first().json.pipelines || [];\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)] || marcaOpp.pipelineStageId;\n\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null) {\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n value = marcaOriginal?.fieldValue;\n }\n if (value === undefined || value === null) continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// Reafirmar el campo de enlace\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || marcaOpp.status,\n monetaryValue: sucursalOpp.monetaryValue ?? marcaOpp.monetaryValue,\n source: sucursalOpp.source || marcaOpp.source,\n customFields: customFields\n};\n\nreturn [{\n json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body),\n debug: { matchReason: decision.matchReason, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5280, + 816 + ], + "id": "969e46cd-bbae-4372-9808-c939f63e3c02", + "name": "Armar Body - UPDATE (v2)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5520, + 816 + ], + "id": "83929e08-7afd-4670-9c18-be22b8c3656f", + "name": "Actualizar Oportunidad - MARCA (v2)" + }, + { + "parameters": { + "content": "# RAMA UPDATE\n## Oportunidad ya existe en marca → se actualiza", + "height": 280, + "width": 880, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 720 + ], + "typeVersion": 1, + "id": "1c285234-8d90-49ef-8dac-c98cb379299a", + "name": "Sticky Update" + }, + { + "parameters": { + "content": "# RAMA CREATE\n## Oportunidad no existe en marca → se crea desde cero\n### Incluye contacto + custom fields + tag origin-id", + "height": 280, + "width": 1120, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 1024 + ], + "typeVersion": 1, + "id": "7e8f350c-9562-485b-8ce5-1b13b4086856", + "name": "Sticky Create" + }, + { + "parameters": { + "content": "# MATCHING ROBUSTO\n## 1. Buscar/crear contacto en marca\n## 2. Buscar oportunidades DEL CONTACTO\n## 3. Decidir match por cantidad + tag origin-id", + "height": 320, + "width": 1500, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3328, + 864 + ], + "typeVersion": 1, + "id": "8a583e46-41b6-44e0-9313-b70ee085b740", + "name": "Sticky Matching" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 560 + ], + "id": "45f0a6b0-f44d-4e83-b2ab-cdd3fbfe7911", + "name": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 560 + ], + "id": "5ca962a9-9e56-42d1-af05-ce05d39ed77f", + "name": "Obtener Pipelines - MARCA (UPDATE)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ customFields: [ { id: $('Conseguir Custom Fields - Opportunity - SUCURSAL').item.json.customFields.find(f => f.fieldKey === 'opportunity.id_oportunidad_sucursal')?.id, key: 'opportunity.id_oportunidad_sucursal', field_value: $('Datos de Lead').item.json.Oportunidad.opportunity_id } ] }) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1328, + -32 + ], + "id": "a9572dee-ed57-4104-966a-4b22721e75f1", + "name": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Auto-mapeo de seguridad: setea 'ID Oportunidad Sucursal' = id nativo de la propia opp de sucursal (del webhook) antes de procesar. onError=continue para no romper el sync si falla." + }, + { + "parameters": { + "databaseId": 63, + "tableId": 754, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7280, + "value": "={{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 4208, + 1152 + ], + "id": "a64fdffd-ab8f-4ddd-a4d4-4d683a05132d", + "name": "Buscar Mapeo Opp - Baserow", + "alwaysOutputData": true, + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Idempotencia global: busca en la tabla 754 el mapeo id_opp_sucursal->id_opp_marca. Si existe, 'Decidir Match' hara UPDATE de esa opp aunque el contacto resuelto sea otro. alwaysOutputData + onError para no romper el flujo si no hay match o si Baserow falla." + }, + { + "parameters": { + "jsCode": "// Prepara el UPSERT del mapeo Baserow (tabla 754) tras CREATE/UPDATE de la opp en Marca.\n// Solo escribe cuando el mapeo NO existe aun:\n// - action CREATE -> la fila no existe (el lookup Baserow fallo) -> crear.\n// - action UPDATE por contacto -> Baserow no la tenia -> crear (idempotencia futura).\n// - action UPDATE por Baserow -> la fila YA existe -> NO escribir (no duplicar).\n// Asi una re-ejecucion del mismo opp encuentra el mapeo y hace UPDATE, no CREATE duplicado.\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst action = decision.action;\nconst matchReason = decision.matchReason || '';\nconst sucursalOppId = decision.sucursalOppId;\n\nconst matchedByBaserow = matchReason.indexOf('Baserow') !== -1;\nconst needWrite = (action === 'CREATE') || (action === 'UPDATE' && !matchedByBaserow);\nif (!needWrite) return [];\n\n// id de la opp en Marca segun el camino\nlet marcaOppId = decision.opportunityId;\nif (action === 'CREATE') {\n try {\n const co = $('Crear Oportunidad - MARCA').first().json;\n marcaOppId = (co.opportunity && co.opportunity.id) || co.id || marcaOppId;\n } catch (e) {}\n}\nif (!marcaOppId || !sucursalOppId) return [];\n\nlet locationSucursal = '';\ntry { locationSucursal = $('DATOS API - SUCURSAL').first().json['Location ID'] || ''; } catch (e) {}\n\nreturn [{ json: { sucursalOppId, marcaOppId, locationSucursal } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 6048, + 960 + ], + "id": "6542723e-f7b2-41de-b371-9e793d48f560", + "name": "Preparar Upsert Mapeo", + "onError": "continueRegularOutput", + "notes": "Decide si escribir el mapeo Baserow tras CREATE/UPDATE. Solo cuando la fila no existe (CREATE o UPDATE-por-contacto). onError continue para no romper la replicacion." + }, + { + "parameters": { + "operation": "create", + "databaseId": 63, + "tableId": 754, + "fieldsUi": { + "fieldValues": [ + { + "fieldId": 7280, + "fieldValue": "={{ $json.sucursalOppId }}" + }, + { + "fieldId": 7283, + "fieldValue": "={{ $json.marcaOppId }}" + }, + { + "fieldId": 7284, + "fieldValue": "={{ $json.locationSucursal }}" + }, + { + "fieldId": 7285, + "fieldValue": "={{ $now.toISO() }}" + } + ] + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + 6288, + 960 + ], + "id": "c69036dd-3914-4424-a165-6e0f3b110ebd", + "name": "Crear Mapeo - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Inserta el mapeo id_opp_sucursal->id_opp_marca en la tabla 754 en tiempo real. Idempotente con el backfill (verifica por id_opp_sucursal)." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1750, + -32 + ], + "name": "Obtener Contacto - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Trae el contacto de sucursal para derivar Sucursal/TIENDA/Canal de Origen de la opp cuando la opp de origen no los trae. onError=continue para no romper el sync.", + "id": "3636d605-1c44-4f2d-b64c-10a5c346cd24" + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API": { + "main": [ + [ + { + "node": "Buscar Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "API de Sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - SUCURSAL": { + "main": [ + [ + { + "node": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de Sucursal": { + "main": [ + [ + { + "node": "DATOS API - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de MARCA": { + "main": [ + [ + { + "node": "DATOS API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapeo completo oportunidad origen - SUCURSAL": { + "main": [ + [ + { + "node": "API de MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - SUCURSAL": { + "main": [ + [ + { + "node": "Mapeo completo oportunidad origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contacto en MARCA": { + "main": [ + [ + { + "node": "¿Existe contacto en MARCA?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Existe contacto en MARCA?": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Crear Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Contacto en MARCA": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Contact ID Resuelto": { + "main": [ + [ + { + "node": "Buscar Mapeo Opp - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidades del Contacto - MARCA": { + "main": [ + [ + { + "node": "Decidir Match (Create vs Update)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Decidir Match (Create vs Update)": { + "main": [ + [ + { + "node": "Switch UPDATE vs CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch UPDATE vs CREATE": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - UPDATE (v2)": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Armar Body - CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - CREATE": { + "main": [ + [ + { + "node": "Crear Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Armar Body - UPDATE (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapear ID Oportunidad Sucursal - SUCURSAL": { + "main": [ + [ + { + "node": "Buscar Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Mapeo Opp - Baserow": { + "main": [ + [ + { + "node": "Buscar Oportunidades del Contacto - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Upsert Mapeo": { + "main": [ + [ + { + "node": "Crear Mapeo - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Pipelines - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "Consultoria E3 ®", + "name": null, + "description": null, + "autosaved": false, + "workflowPublishHistory": [ + { + "createdAt": "2026-05-30T22:42:50.131Z", + "id": 677, + "workflowId": "Cfgwp0bOtDW8zuKW", + "versionId": "be97beec-1bd0-43c9-a70e-c3a268d47c3e", + "event": "activated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + ] + } +} \ No newline at end of file diff --git a/n8n/backup_fuente_reconcile_ddUEORBEtZLzsQF2_20260530_185416.json b/n8n/backup_fuente_reconcile_ddUEORBEtZLzsQF2_20260530_185416.json new file mode 100644 index 0000000..0560546 --- /dev/null +++ b/n8n/backup_fuente_reconcile_ddUEORBEtZLzsQF2_20260530_185416.json @@ -0,0 +1,1903 @@ +{ + "updatedAt": "2026-05-30T07:15:08.610Z", + "createdAt": "2026-04-20T22:29:00.636Z", + "id": "ddUEORBEtZLzsQF2", + "name": "[2004][Monte Providencia] Actualizar \"contact.sucursal\", \"contact.tienda\" al crear contacto", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "content": "# De Sucursal a Marca", + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -2288, + -320 + ], + "typeVersion": 1, + "id": "d91325e1-1763-486c-a2c9-8cfffddd57b0", + "name": "Sticky Note3" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Datos de Lead').item.json.Sucursal['Cuenta Bucéfalo'] }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + -912, + -320 + ], + "id": "50da84c1-c2d3-4a36-91d3-e3745edccdc6", + "name": "Obtener Info de cuenta origen - SUCURSAL", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -224, + -320 + ], + "id": "a0c27c4c-0f20-4bab-bf6b-bcdd8281a92b", + "name": "Conseguir Custom Cuenta Origen- SUCURSAL" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + }, + { + "id": "64f2add6-506c-4950-8026-c04c9547aeeb", + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -1392, + -304 + ], + "id": "d68558b5-52b8-46b1-a359-fd956c7edc09", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "57199999-2d9b-41c7-8269-5a8183ca8132", + "name": "Cliente.Cuándo necesitas el dinero", + "value": "={{ $json.body[\"¿Cuándo necesitas el dinero?\"] }}", + "type": "string" + }, + { + "id": "8060d06e-b1da-4a65-8a0a-3c21d237d77e", + "name": "Sucursal.Cuenta Bucéfalo", + "value": "={{ $json.body.location.name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -1568, + -304 + ], + "id": "d0e455ac-7ccc-4c55-bda2-1cd22092e2d2", + "name": "Datos de Lead" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -720, + -320 + ], + "id": "f0cff3ec-8464-45d4-9e64-713c36e247c6", + "name": "Datos API Cuenta Origen - SUCURSAL", + "notes": "Esta en modo prueba forzada para Queretaro" + }, + { + "parameters": { + "content": "# CUENTA ORIGEN", + "height": 240, + "width": 1744 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -976, + -384 + ], + "typeVersion": 1, + "id": "16a74d85-1e6c-4fc7-b854-580a2d3827a0", + "name": "Sticky Note" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Webhook').item.json.body.contact_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -400, + -320 + ], + "id": "92f33c6e-ee64-409d-8c60-9fbfe48b3265", + "name": "Obtener Contacto Cuenta Origen - SUCURSAL" + }, + { + "parameters": { + "content": "", + "height": 176, + "width": 608, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -336 + ], + "typeVersion": 1, + "id": "768a4001-5109-493f-b12e-e8ba5d30ec2f", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.sucursal.id }}\",\n \"key\": \"contact.sucursal\",\n \"field_value\": \"{{ $json.SUCURSAL }}\"\n },\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.tienda.id }}\",\n \"key\": \"contact.tienda\",\n \"field_value\": \"{{ $json.TIENDA }}\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 512, + -320 + ], + "id": "794ce5bb-dea0-4720-958a-1f0940c79e6d", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "8d574598-d977-4052-823a-26def39c6a64", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1776, + -304 + ], + "id": "d4312610-e978-424c-a8a0-426026c4d4f6", + "name": "Webhook", + "webhookId": "8d574598-d977-4052-823a-26def39c6a64" + }, + { + "parameters": { + "jsCode": "const customFields = $input.first().json.customFields;\n\nfunction findField(key, names) {\n let f = customFields.find(x => x.fieldKey === key);\n if (!f) {\n const wanted = names.map(n => n.toLowerCase().trim());\n f = customFields.find(x => wanted.includes((x.name || \"\").toLowerCase().trim()));\n }\n return f || null;\n}\n\nconst sucursalField = findField(\"contact.sucursal\", [\"Sucursal\"]);\nconst tiendaField = findField(\"contact.tienda\", [\"TIENDA\", \"Tienda\"]);\nconst canalField = findField(\"contact.fuente_de_posible_cliente\", [\"CANAL DE ORIGEN\", \"Canal de Origen\"]);\n\n// createdBy.source SOLO viene del GET individual del contacto.\nconst contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json;\nconst createdBySource =\n (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) ||\n (contactResp && contactResp.createdBy && contactResp.createdBy.source) ||\n null;\nconst esUsuario = createdBySource === \"WEB_USER\" || createdBySource === \"MOBILE_USER\";\n\nreturn [\n {\n json: {\n sucursal: {\n id: sucursalField?.id ?? null,\n name: sucursalField?.name ?? null,\n fieldKey: sucursalField?.fieldKey ?? null,\n picklistOptions: sucursalField?.picklistOptions ?? [],\n },\n tienda: {\n id: tiendaField?.id ?? null,\n name: tiendaField?.name ?? null,\n fieldKey: tiendaField?.fieldKey ?? null,\n picklistOptions: tiendaField?.picklistOptions ?? [],\n },\n canal: {\n id: canalField?.id ?? null,\n name: canalField?.name ?? null,\n fieldKey: canalField?.fieldKey ?? null,\n picklistOptions: canalField?.picklistOptions ?? [],\n },\n createdBySource: createdBySource,\n esUsuario: esUsuario,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 32, + -320 + ], + "id": "5a1cfe47-862c-4d28-a4ca-57a9f8c54a7c", + "name": "Buscar \"contact.sucursal\" y \"contact.tienda\"" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 750, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7247, + "value": "={{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Name Location'] }}" + }, + { + "field": 7279, + "operator": "not_equal", + "value": "NO DIGITAL" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 256, + -320 + ], + "id": "a912fac3-25d4-492a-9648-8c472098b9ca", + "name": "Buscar Sucursal en Verificador de Sucursales", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2023-02-21" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}\",\n \"query\": \"{{ $json.contact.email }}\",\n \"limit\": 20,\n \"page\": 1\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1152, + -320 + ], + "id": "4b899380-fe4a-40ad-80bb-21ef504b30ac", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL1" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "canal-origen-esusuario", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.esUsuario }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 720, + -120 + ], + "name": "Creado por usuario (Canal de Origen)", + "id": "49879f5e-7ce8-4ced-b1a6-96df44ac2e0a" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 940, + -120 + ], + "name": "PUT Canal de Origen = SUCURSAL", + "id": "d0597a9c-aca0-40bb-98d8-bb584d2a2c3e" + }, + { + "parameters": { + "method": "POST", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1160, + -120 + ], + "name": "Tag+ sucursal", + "id": "86bfddcb-c402-413f-9d32-c55050dc470d" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"formulario\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1380, + -120 + ], + "name": "Tag- formulario", + "onError": "continueRegularOutput", + "id": "cbd3cfb2-28d0-44e1-9567-a269382497ae" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"facebook-ads\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1600, + -120 + ], + "name": "Tag- facebook-ads", + "onError": "continueRegularOutput", + "id": "19bdc2b2-9345-4294-8e59-7f35963f261d" + } + ], + "connections": { + "Obtener Info de cuenta origen - SUCURSAL": { + "main": [ + [ + { + "node": "Datos API Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Cuenta Origen- SUCURSAL": { + "main": [ + [ + { + "node": "Buscar \"contact.sucursal\" y \"contact.tienda\"", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "Obtener Info de cuenta origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos API Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Cuenta Origen- SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Sucursal en Verificador de Sucursales": { + "main": [ + [ + { + "node": "Actualizar Contacto Cuenta Objetivo - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar \"contact.sucursal\" y \"contact.tienda\"": { + "main": [ + [ + { + "node": "Buscar Sucursal en Verificador de Sucursales", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Contacto Cuenta Objetivo - SUCURSAL": { + "main": [ + [ + { + "node": "Creado por usuario (Canal de Origen)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Creado por usuario (Canal de Origen)": { + "main": [ + [ + { + "node": "PUT Canal de Origen = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = SUCURSAL": { + "main": [ + [ + { + "node": "Tag+ sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag+ sucursal": { + "main": [ + [ + { + "node": "Tag- formulario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- formulario": { + "main": [ + [ + { + "node": "Tag- facebook-ads", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false + }, + "staticData": null, + "meta": { + "templateCredsSetupCompleted": true + }, + "pinData": { + "Webhook": [ + { + "json": { + "headers": { + "host": "webhookn8.consultoriae3.com", + "user-agent": "axios/1.13.2", + "content-length": "1572", + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, compress, deflate, br", + "content-type": "application/json", + "traceparent": "00-bfdb4ad6378402bf71f61ff3c72d0d02-ee175e0dcbcec88e-01", + "x-forwarded-for": "35.253.135.103", + "x-forwarded-host": "webhookn8.consultoriae3.com", + "x-forwarded-port": "443", + "x-forwarded-proto": "https", + "x-forwarded-server": "f569f6e85d30", + "x-real-ip": "35.253.135.103" + }, + "params": {}, + "query": {}, + "body": { + "Canal de Origen": "", + "¿Qué modalidad prefieres?": "Sin Dejarlo (GPS)", + "Fuente de Prospecto": "GALLARDETES", + "Marca del Vehiculo": "VW", + "[Número de WhatsApp para Atender]": "", + "TIENDA": "", + "Presupuesto": "", + "Acepta los terminos para tu cotización": "", + "Sucursal": "", + "ID Contacto Monte Providencia": "", + "Información Adicional": "", + "[Número de Teléfono para Atender]": "", + "Version del Vehiculo": "POLO", + "[Correo_Canalizados]": "", + "[Correo_Empresa]": "", + "Check Comunicaciones de Marketing": "", + "Descripción": "", + "[Nombre de la Empresa]": "", + "Año del Vehículo": "2019", + "[Dirección de la Empresa]": "", + "¿Cuándo necesitas el dinero?": "", + "Archivos Adicionales": "", + "contact_id": "ZHj8ek6lQ6M6EeCIU7Uj", + "first_name": "LILIA LISSETH", + "last_name": "ZAVALA HEREDIA", + "full_name": "LILIA LISSETH ZAVALA HEREDIA", + "email": "lilia.zh0209@gmail.com", + "phone": "+524361175941", + "tags": "sucursal", + "country": "MX", + "date_created": "2026-05-25T23:59:53.804Z", + "full_address": "", + "contact_type": "lead", + "location": { + "name": "85964 - MP - Morelia 1", + "address": "Morelia", + "city": "Morelia", + "state": "Michoacán", + "country": "MX", + "postalCode": "58855", + "fullAddress": "Morelia, Morelia Michoacán 58855", + "id": "jE41bVhhnb5T505BFm4F" + }, + "user": { + "firstName": "Morelia 1 - 85964", + "lastName": "Morelia 1", + "email": "ezstore85964@ezcorp.com", + "phone": "+524434589152" + }, + "workflow": { + "id": "7c92fec9-b858-4a83-b2d9-31891c9b9231", + "name": "Sincronizar Contacto Creado V2" + }, + "triggerData": {}, + "contact": { + "attributionSource": { + "mediumId": null, + "medium": "manual", + "sessionSource": "CRM UI" + }, + "lastAttributionSource": {} + }, + "attributionSource": {}, + "customData": {} + }, + "webhookUrl": "https://webhookn8.consultoriae3.com/webhook/8d574598-d977-4052-823a-26def39c6a64", + "executionMode": "production" + }, + "pairedItem": { + "item": 0 + } + } + ] + }, + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "activeVersionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "versionCounter": 234, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-04-20T22:29:00.636Z", + "createdAt": "2026-04-20T22:29:00.636Z", + "role": "workflow:owner", + "workflowId": "ddUEORBEtZLzsQF2", + "projectId": "aycNlMpnzsxvXbfG", + "project": { + "updatedAt": "2025-11-27T04:06:26.313Z", + "createdAt": "2025-11-27T03:54:24.410Z", + "id": "aycNlMpnzsxvXbfG", + "name": "Consultoria E3 ® ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + } + ], + "tags": [ + { + "updatedAt": "2025-11-27T05:48:12.272Z", + "createdAt": "2025-11-27T05:48:12.272Z", + "id": "pHkPpq3Httzx4hnb", + "name": "Bucefalo" + }, + { + "updatedAt": "2025-12-01T22:00:17.627Z", + "createdAt": "2025-12-01T22:00:17.627Z", + "id": "uIWgqVuJXbqA7m39", + "name": "PRUEBAS" + } + ], + "activeVersion": { + "updatedAt": "2026-05-30T07:15:08.612Z", + "createdAt": "2026-05-30T07:15:08.612Z", + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "workflowId": "ddUEORBEtZLzsQF2", + "nodes": [ + { + "parameters": { + "content": "# De Sucursal a Marca", + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -2288, + -320 + ], + "typeVersion": 1, + "id": "d91325e1-1763-486c-a2c9-8cfffddd57b0", + "name": "Sticky Note3" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Datos de Lead').item.json.Sucursal['Cuenta Bucéfalo'] }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + -912, + -320 + ], + "id": "50da84c1-c2d3-4a36-91d3-e3745edccdc6", + "name": "Obtener Info de cuenta origen - SUCURSAL", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -224, + -320 + ], + "id": "a0c27c4c-0f20-4bab-bf6b-bcdd8281a92b", + "name": "Conseguir Custom Cuenta Origen- SUCURSAL" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + }, + { + "id": "64f2add6-506c-4950-8026-c04c9547aeeb", + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -1392, + -304 + ], + "id": "d68558b5-52b8-46b1-a359-fd956c7edc09", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "57199999-2d9b-41c7-8269-5a8183ca8132", + "name": "Cliente.Cuándo necesitas el dinero", + "value": "={{ $json.body[\"¿Cuándo necesitas el dinero?\"] }}", + "type": "string" + }, + { + "id": "8060d06e-b1da-4a65-8a0a-3c21d237d77e", + "name": "Sucursal.Cuenta Bucéfalo", + "value": "={{ $json.body.location.name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -1568, + -304 + ], + "id": "d0e455ac-7ccc-4c55-bda2-1cd22092e2d2", + "name": "Datos de Lead" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -720, + -320 + ], + "id": "f0cff3ec-8464-45d4-9e64-713c36e247c6", + "name": "Datos API Cuenta Origen - SUCURSAL", + "notes": "Esta en modo prueba forzada para Queretaro" + }, + { + "parameters": { + "content": "# CUENTA ORIGEN", + "height": 240, + "width": 1744 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -976, + -384 + ], + "typeVersion": 1, + "id": "16a74d85-1e6c-4fc7-b854-580a2d3827a0", + "name": "Sticky Note" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Webhook').item.json.body.contact_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -400, + -320 + ], + "id": "92f33c6e-ee64-409d-8c60-9fbfe48b3265", + "name": "Obtener Contacto Cuenta Origen - SUCURSAL" + }, + { + "parameters": { + "content": "", + "height": 176, + "width": 608, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -336 + ], + "typeVersion": 1, + "id": "768a4001-5109-493f-b12e-e8ba5d30ec2f", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.sucursal.id }}\",\n \"key\": \"contact.sucursal\",\n \"field_value\": \"{{ $json.SUCURSAL }}\"\n },\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.tienda.id }}\",\n \"key\": \"contact.tienda\",\n \"field_value\": \"{{ $json.TIENDA }}\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 512, + -320 + ], + "id": "794ce5bb-dea0-4720-958a-1f0940c79e6d", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "8d574598-d977-4052-823a-26def39c6a64", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1776, + -304 + ], + "id": "d4312610-e978-424c-a8a0-426026c4d4f6", + "name": "Webhook", + "webhookId": "8d574598-d977-4052-823a-26def39c6a64" + }, + { + "parameters": { + "jsCode": "const customFields = $input.first().json.customFields;\n\nfunction findField(key, names) {\n let f = customFields.find(x => x.fieldKey === key);\n if (!f) {\n const wanted = names.map(n => n.toLowerCase().trim());\n f = customFields.find(x => wanted.includes((x.name || \"\").toLowerCase().trim()));\n }\n return f || null;\n}\n\nconst sucursalField = findField(\"contact.sucursal\", [\"Sucursal\"]);\nconst tiendaField = findField(\"contact.tienda\", [\"TIENDA\", \"Tienda\"]);\nconst canalField = findField(\"contact.fuente_de_posible_cliente\", [\"CANAL DE ORIGEN\", \"Canal de Origen\"]);\n\n// createdBy.source SOLO viene del GET individual del contacto.\nconst contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json;\nconst createdBySource =\n (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) ||\n (contactResp && contactResp.createdBy && contactResp.createdBy.source) ||\n null;\nconst esUsuario = createdBySource === \"WEB_USER\" || createdBySource === \"MOBILE_USER\";\n\nreturn [\n {\n json: {\n sucursal: {\n id: sucursalField?.id ?? null,\n name: sucursalField?.name ?? null,\n fieldKey: sucursalField?.fieldKey ?? null,\n picklistOptions: sucursalField?.picklistOptions ?? [],\n },\n tienda: {\n id: tiendaField?.id ?? null,\n name: tiendaField?.name ?? null,\n fieldKey: tiendaField?.fieldKey ?? null,\n picklistOptions: tiendaField?.picklistOptions ?? [],\n },\n canal: {\n id: canalField?.id ?? null,\n name: canalField?.name ?? null,\n fieldKey: canalField?.fieldKey ?? null,\n picklistOptions: canalField?.picklistOptions ?? [],\n },\n createdBySource: createdBySource,\n esUsuario: esUsuario,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 32, + -320 + ], + "id": "5a1cfe47-862c-4d28-a4ca-57a9f8c54a7c", + "name": "Buscar \"contact.sucursal\" y \"contact.tienda\"" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 750, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7247, + "value": "={{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Name Location'] }}" + }, + { + "field": 7279, + "operator": "not_equal", + "value": "NO DIGITAL" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 256, + -320 + ], + "id": "a912fac3-25d4-492a-9648-8c472098b9ca", + "name": "Buscar Sucursal en Verificador de Sucursales", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2023-02-21" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}\",\n \"query\": \"{{ $json.contact.email }}\",\n \"limit\": 20,\n \"page\": 1\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1152, + -320 + ], + "id": "4b899380-fe4a-40ad-80bb-21ef504b30ac", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL1" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "canal-origen-esusuario", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.esUsuario }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 720, + -120 + ], + "name": "Creado por usuario (Canal de Origen)", + "id": "49879f5e-7ce8-4ced-b1a6-96df44ac2e0a" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 940, + -120 + ], + "name": "PUT Canal de Origen = SUCURSAL", + "id": "d0597a9c-aca0-40bb-98d8-bb584d2a2c3e" + }, + { + "parameters": { + "method": "POST", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1160, + -120 + ], + "name": "Tag+ sucursal", + "id": "86bfddcb-c402-413f-9d32-c55050dc470d" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"formulario\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1380, + -120 + ], + "name": "Tag- formulario", + "onError": "continueRegularOutput", + "id": "cbd3cfb2-28d0-44e1-9567-a269382497ae" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"facebook-ads\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1600, + -120 + ], + "name": "Tag- facebook-ads", + "onError": "continueRegularOutput", + "id": "19bdc2b2-9345-4294-8e59-7f35963f261d" + } + ], + "connections": { + "Obtener Info de cuenta origen - SUCURSAL": { + "main": [ + [ + { + "node": "Datos API Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Cuenta Origen- SUCURSAL": { + "main": [ + [ + { + "node": "Buscar \"contact.sucursal\" y \"contact.tienda\"", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "Obtener Info de cuenta origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos API Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Cuenta Origen- SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Sucursal en Verificador de Sucursales": { + "main": [ + [ + { + "node": "Actualizar Contacto Cuenta Objetivo - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar \"contact.sucursal\" y \"contact.tienda\"": { + "main": [ + [ + { + "node": "Buscar Sucursal en Verificador de Sucursales", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Contacto Cuenta Objetivo - SUCURSAL": { + "main": [ + [ + { + "node": "Creado por usuario (Canal de Origen)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Creado por usuario (Canal de Origen)": { + "main": [ + [ + { + "node": "PUT Canal de Origen = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = SUCURSAL": { + "main": [ + [ + { + "node": "Tag+ sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag+ sucursal": { + "main": [ + [ + { + "node": "Tag- formulario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- formulario": { + "main": [ + [ + { + "node": "Tag- facebook-ads", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "Consultoria E3 ®", + "name": null, + "description": null, + "autosaved": false, + "workflowPublishHistory": [ + { + "createdAt": "2026-05-30T07:15:09.042Z", + "id": 666, + "workflowId": "ddUEORBEtZLzsQF2", + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "event": "deactivated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + }, + { + "createdAt": "2026-05-30T07:15:09.069Z", + "id": 667, + "workflowId": "ddUEORBEtZLzsQF2", + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "event": "activated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + }, + { + "createdAt": "2026-05-30T07:15:08.695Z", + "id": 664, + "workflowId": "ddUEORBEtZLzsQF2", + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "event": "deactivated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + }, + { + "createdAt": "2026-05-30T07:15:08.729Z", + "id": 665, + "workflowId": "ddUEORBEtZLzsQF2", + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "event": "activated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + ] + } +} \ No newline at end of file diff --git a/n8n/backup_fuente_reconcile_ddUEORBEtZLzsQF2_20260530_190108.json b/n8n/backup_fuente_reconcile_ddUEORBEtZLzsQF2_20260530_190108.json new file mode 100644 index 0000000..0560546 --- /dev/null +++ b/n8n/backup_fuente_reconcile_ddUEORBEtZLzsQF2_20260530_190108.json @@ -0,0 +1,1903 @@ +{ + "updatedAt": "2026-05-30T07:15:08.610Z", + "createdAt": "2026-04-20T22:29:00.636Z", + "id": "ddUEORBEtZLzsQF2", + "name": "[2004][Monte Providencia] Actualizar \"contact.sucursal\", \"contact.tienda\" al crear contacto", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "content": "# De Sucursal a Marca", + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -2288, + -320 + ], + "typeVersion": 1, + "id": "d91325e1-1763-486c-a2c9-8cfffddd57b0", + "name": "Sticky Note3" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Datos de Lead').item.json.Sucursal['Cuenta Bucéfalo'] }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + -912, + -320 + ], + "id": "50da84c1-c2d3-4a36-91d3-e3745edccdc6", + "name": "Obtener Info de cuenta origen - SUCURSAL", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -224, + -320 + ], + "id": "a0c27c4c-0f20-4bab-bf6b-bcdd8281a92b", + "name": "Conseguir Custom Cuenta Origen- SUCURSAL" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + }, + { + "id": "64f2add6-506c-4950-8026-c04c9547aeeb", + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -1392, + -304 + ], + "id": "d68558b5-52b8-46b1-a359-fd956c7edc09", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "57199999-2d9b-41c7-8269-5a8183ca8132", + "name": "Cliente.Cuándo necesitas el dinero", + "value": "={{ $json.body[\"¿Cuándo necesitas el dinero?\"] }}", + "type": "string" + }, + { + "id": "8060d06e-b1da-4a65-8a0a-3c21d237d77e", + "name": "Sucursal.Cuenta Bucéfalo", + "value": "={{ $json.body.location.name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -1568, + -304 + ], + "id": "d0e455ac-7ccc-4c55-bda2-1cd22092e2d2", + "name": "Datos de Lead" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -720, + -320 + ], + "id": "f0cff3ec-8464-45d4-9e64-713c36e247c6", + "name": "Datos API Cuenta Origen - SUCURSAL", + "notes": "Esta en modo prueba forzada para Queretaro" + }, + { + "parameters": { + "content": "# CUENTA ORIGEN", + "height": 240, + "width": 1744 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -976, + -384 + ], + "typeVersion": 1, + "id": "16a74d85-1e6c-4fc7-b854-580a2d3827a0", + "name": "Sticky Note" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Webhook').item.json.body.contact_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -400, + -320 + ], + "id": "92f33c6e-ee64-409d-8c60-9fbfe48b3265", + "name": "Obtener Contacto Cuenta Origen - SUCURSAL" + }, + { + "parameters": { + "content": "", + "height": 176, + "width": 608, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -336 + ], + "typeVersion": 1, + "id": "768a4001-5109-493f-b12e-e8ba5d30ec2f", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.sucursal.id }}\",\n \"key\": \"contact.sucursal\",\n \"field_value\": \"{{ $json.SUCURSAL }}\"\n },\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.tienda.id }}\",\n \"key\": \"contact.tienda\",\n \"field_value\": \"{{ $json.TIENDA }}\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 512, + -320 + ], + "id": "794ce5bb-dea0-4720-958a-1f0940c79e6d", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "8d574598-d977-4052-823a-26def39c6a64", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1776, + -304 + ], + "id": "d4312610-e978-424c-a8a0-426026c4d4f6", + "name": "Webhook", + "webhookId": "8d574598-d977-4052-823a-26def39c6a64" + }, + { + "parameters": { + "jsCode": "const customFields = $input.first().json.customFields;\n\nfunction findField(key, names) {\n let f = customFields.find(x => x.fieldKey === key);\n if (!f) {\n const wanted = names.map(n => n.toLowerCase().trim());\n f = customFields.find(x => wanted.includes((x.name || \"\").toLowerCase().trim()));\n }\n return f || null;\n}\n\nconst sucursalField = findField(\"contact.sucursal\", [\"Sucursal\"]);\nconst tiendaField = findField(\"contact.tienda\", [\"TIENDA\", \"Tienda\"]);\nconst canalField = findField(\"contact.fuente_de_posible_cliente\", [\"CANAL DE ORIGEN\", \"Canal de Origen\"]);\n\n// createdBy.source SOLO viene del GET individual del contacto.\nconst contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json;\nconst createdBySource =\n (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) ||\n (contactResp && contactResp.createdBy && contactResp.createdBy.source) ||\n null;\nconst esUsuario = createdBySource === \"WEB_USER\" || createdBySource === \"MOBILE_USER\";\n\nreturn [\n {\n json: {\n sucursal: {\n id: sucursalField?.id ?? null,\n name: sucursalField?.name ?? null,\n fieldKey: sucursalField?.fieldKey ?? null,\n picklistOptions: sucursalField?.picklistOptions ?? [],\n },\n tienda: {\n id: tiendaField?.id ?? null,\n name: tiendaField?.name ?? null,\n fieldKey: tiendaField?.fieldKey ?? null,\n picklistOptions: tiendaField?.picklistOptions ?? [],\n },\n canal: {\n id: canalField?.id ?? null,\n name: canalField?.name ?? null,\n fieldKey: canalField?.fieldKey ?? null,\n picklistOptions: canalField?.picklistOptions ?? [],\n },\n createdBySource: createdBySource,\n esUsuario: esUsuario,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 32, + -320 + ], + "id": "5a1cfe47-862c-4d28-a4ca-57a9f8c54a7c", + "name": "Buscar \"contact.sucursal\" y \"contact.tienda\"" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 750, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7247, + "value": "={{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Name Location'] }}" + }, + { + "field": 7279, + "operator": "not_equal", + "value": "NO DIGITAL" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 256, + -320 + ], + "id": "a912fac3-25d4-492a-9648-8c472098b9ca", + "name": "Buscar Sucursal en Verificador de Sucursales", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2023-02-21" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}\",\n \"query\": \"{{ $json.contact.email }}\",\n \"limit\": 20,\n \"page\": 1\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1152, + -320 + ], + "id": "4b899380-fe4a-40ad-80bb-21ef504b30ac", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL1" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "canal-origen-esusuario", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.esUsuario }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 720, + -120 + ], + "name": "Creado por usuario (Canal de Origen)", + "id": "49879f5e-7ce8-4ced-b1a6-96df44ac2e0a" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 940, + -120 + ], + "name": "PUT Canal de Origen = SUCURSAL", + "id": "d0597a9c-aca0-40bb-98d8-bb584d2a2c3e" + }, + { + "parameters": { + "method": "POST", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1160, + -120 + ], + "name": "Tag+ sucursal", + "id": "86bfddcb-c402-413f-9d32-c55050dc470d" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"formulario\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1380, + -120 + ], + "name": "Tag- formulario", + "onError": "continueRegularOutput", + "id": "cbd3cfb2-28d0-44e1-9567-a269382497ae" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"facebook-ads\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1600, + -120 + ], + "name": "Tag- facebook-ads", + "onError": "continueRegularOutput", + "id": "19bdc2b2-9345-4294-8e59-7f35963f261d" + } + ], + "connections": { + "Obtener Info de cuenta origen - SUCURSAL": { + "main": [ + [ + { + "node": "Datos API Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Cuenta Origen- SUCURSAL": { + "main": [ + [ + { + "node": "Buscar \"contact.sucursal\" y \"contact.tienda\"", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "Obtener Info de cuenta origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos API Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Cuenta Origen- SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Sucursal en Verificador de Sucursales": { + "main": [ + [ + { + "node": "Actualizar Contacto Cuenta Objetivo - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar \"contact.sucursal\" y \"contact.tienda\"": { + "main": [ + [ + { + "node": "Buscar Sucursal en Verificador de Sucursales", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Contacto Cuenta Objetivo - SUCURSAL": { + "main": [ + [ + { + "node": "Creado por usuario (Canal de Origen)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Creado por usuario (Canal de Origen)": { + "main": [ + [ + { + "node": "PUT Canal de Origen = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = SUCURSAL": { + "main": [ + [ + { + "node": "Tag+ sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag+ sucursal": { + "main": [ + [ + { + "node": "Tag- formulario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- formulario": { + "main": [ + [ + { + "node": "Tag- facebook-ads", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false + }, + "staticData": null, + "meta": { + "templateCredsSetupCompleted": true + }, + "pinData": { + "Webhook": [ + { + "json": { + "headers": { + "host": "webhookn8.consultoriae3.com", + "user-agent": "axios/1.13.2", + "content-length": "1572", + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, compress, deflate, br", + "content-type": "application/json", + "traceparent": "00-bfdb4ad6378402bf71f61ff3c72d0d02-ee175e0dcbcec88e-01", + "x-forwarded-for": "35.253.135.103", + "x-forwarded-host": "webhookn8.consultoriae3.com", + "x-forwarded-port": "443", + "x-forwarded-proto": "https", + "x-forwarded-server": "f569f6e85d30", + "x-real-ip": "35.253.135.103" + }, + "params": {}, + "query": {}, + "body": { + "Canal de Origen": "", + "¿Qué modalidad prefieres?": "Sin Dejarlo (GPS)", + "Fuente de Prospecto": "GALLARDETES", + "Marca del Vehiculo": "VW", + "[Número de WhatsApp para Atender]": "", + "TIENDA": "", + "Presupuesto": "", + "Acepta los terminos para tu cotización": "", + "Sucursal": "", + "ID Contacto Monte Providencia": "", + "Información Adicional": "", + "[Número de Teléfono para Atender]": "", + "Version del Vehiculo": "POLO", + "[Correo_Canalizados]": "", + "[Correo_Empresa]": "", + "Check Comunicaciones de Marketing": "", + "Descripción": "", + "[Nombre de la Empresa]": "", + "Año del Vehículo": "2019", + "[Dirección de la Empresa]": "", + "¿Cuándo necesitas el dinero?": "", + "Archivos Adicionales": "", + "contact_id": "ZHj8ek6lQ6M6EeCIU7Uj", + "first_name": "LILIA LISSETH", + "last_name": "ZAVALA HEREDIA", + "full_name": "LILIA LISSETH ZAVALA HEREDIA", + "email": "lilia.zh0209@gmail.com", + "phone": "+524361175941", + "tags": "sucursal", + "country": "MX", + "date_created": "2026-05-25T23:59:53.804Z", + "full_address": "", + "contact_type": "lead", + "location": { + "name": "85964 - MP - Morelia 1", + "address": "Morelia", + "city": "Morelia", + "state": "Michoacán", + "country": "MX", + "postalCode": "58855", + "fullAddress": "Morelia, Morelia Michoacán 58855", + "id": "jE41bVhhnb5T505BFm4F" + }, + "user": { + "firstName": "Morelia 1 - 85964", + "lastName": "Morelia 1", + "email": "ezstore85964@ezcorp.com", + "phone": "+524434589152" + }, + "workflow": { + "id": "7c92fec9-b858-4a83-b2d9-31891c9b9231", + "name": "Sincronizar Contacto Creado V2" + }, + "triggerData": {}, + "contact": { + "attributionSource": { + "mediumId": null, + "medium": "manual", + "sessionSource": "CRM UI" + }, + "lastAttributionSource": {} + }, + "attributionSource": {}, + "customData": {} + }, + "webhookUrl": "https://webhookn8.consultoriae3.com/webhook/8d574598-d977-4052-823a-26def39c6a64", + "executionMode": "production" + }, + "pairedItem": { + "item": 0 + } + } + ] + }, + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "activeVersionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "versionCounter": 234, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-04-20T22:29:00.636Z", + "createdAt": "2026-04-20T22:29:00.636Z", + "role": "workflow:owner", + "workflowId": "ddUEORBEtZLzsQF2", + "projectId": "aycNlMpnzsxvXbfG", + "project": { + "updatedAt": "2025-11-27T04:06:26.313Z", + "createdAt": "2025-11-27T03:54:24.410Z", + "id": "aycNlMpnzsxvXbfG", + "name": "Consultoria E3 ® ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + } + ], + "tags": [ + { + "updatedAt": "2025-11-27T05:48:12.272Z", + "createdAt": "2025-11-27T05:48:12.272Z", + "id": "pHkPpq3Httzx4hnb", + "name": "Bucefalo" + }, + { + "updatedAt": "2025-12-01T22:00:17.627Z", + "createdAt": "2025-12-01T22:00:17.627Z", + "id": "uIWgqVuJXbqA7m39", + "name": "PRUEBAS" + } + ], + "activeVersion": { + "updatedAt": "2026-05-30T07:15:08.612Z", + "createdAt": "2026-05-30T07:15:08.612Z", + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "workflowId": "ddUEORBEtZLzsQF2", + "nodes": [ + { + "parameters": { + "content": "# De Sucursal a Marca", + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -2288, + -320 + ], + "typeVersion": 1, + "id": "d91325e1-1763-486c-a2c9-8cfffddd57b0", + "name": "Sticky Note3" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Datos de Lead').item.json.Sucursal['Cuenta Bucéfalo'] }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + -912, + -320 + ], + "id": "50da84c1-c2d3-4a36-91d3-e3745edccdc6", + "name": "Obtener Info de cuenta origen - SUCURSAL", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -224, + -320 + ], + "id": "a0c27c4c-0f20-4bab-bf6b-bcdd8281a92b", + "name": "Conseguir Custom Cuenta Origen- SUCURSAL" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + }, + { + "id": "64f2add6-506c-4950-8026-c04c9547aeeb", + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -1392, + -304 + ], + "id": "d68558b5-52b8-46b1-a359-fd956c7edc09", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "57199999-2d9b-41c7-8269-5a8183ca8132", + "name": "Cliente.Cuándo necesitas el dinero", + "value": "={{ $json.body[\"¿Cuándo necesitas el dinero?\"] }}", + "type": "string" + }, + { + "id": "8060d06e-b1da-4a65-8a0a-3c21d237d77e", + "name": "Sucursal.Cuenta Bucéfalo", + "value": "={{ $json.body.location.name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -1568, + -304 + ], + "id": "d0e455ac-7ccc-4c55-bda2-1cd22092e2d2", + "name": "Datos de Lead" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -720, + -320 + ], + "id": "f0cff3ec-8464-45d4-9e64-713c36e247c6", + "name": "Datos API Cuenta Origen - SUCURSAL", + "notes": "Esta en modo prueba forzada para Queretaro" + }, + { + "parameters": { + "content": "# CUENTA ORIGEN", + "height": 240, + "width": 1744 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -976, + -384 + ], + "typeVersion": 1, + "id": "16a74d85-1e6c-4fc7-b854-580a2d3827a0", + "name": "Sticky Note" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Webhook').item.json.body.contact_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -400, + -320 + ], + "id": "92f33c6e-ee64-409d-8c60-9fbfe48b3265", + "name": "Obtener Contacto Cuenta Origen - SUCURSAL" + }, + { + "parameters": { + "content": "", + "height": 176, + "width": 608, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -336 + ], + "typeVersion": 1, + "id": "768a4001-5109-493f-b12e-e8ba5d30ec2f", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.sucursal.id }}\",\n \"key\": \"contact.sucursal\",\n \"field_value\": \"{{ $json.SUCURSAL }}\"\n },\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.tienda.id }}\",\n \"key\": \"contact.tienda\",\n \"field_value\": \"{{ $json.TIENDA }}\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 512, + -320 + ], + "id": "794ce5bb-dea0-4720-958a-1f0940c79e6d", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "8d574598-d977-4052-823a-26def39c6a64", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1776, + -304 + ], + "id": "d4312610-e978-424c-a8a0-426026c4d4f6", + "name": "Webhook", + "webhookId": "8d574598-d977-4052-823a-26def39c6a64" + }, + { + "parameters": { + "jsCode": "const customFields = $input.first().json.customFields;\n\nfunction findField(key, names) {\n let f = customFields.find(x => x.fieldKey === key);\n if (!f) {\n const wanted = names.map(n => n.toLowerCase().trim());\n f = customFields.find(x => wanted.includes((x.name || \"\").toLowerCase().trim()));\n }\n return f || null;\n}\n\nconst sucursalField = findField(\"contact.sucursal\", [\"Sucursal\"]);\nconst tiendaField = findField(\"contact.tienda\", [\"TIENDA\", \"Tienda\"]);\nconst canalField = findField(\"contact.fuente_de_posible_cliente\", [\"CANAL DE ORIGEN\", \"Canal de Origen\"]);\n\n// createdBy.source SOLO viene del GET individual del contacto.\nconst contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json;\nconst createdBySource =\n (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) ||\n (contactResp && contactResp.createdBy && contactResp.createdBy.source) ||\n null;\nconst esUsuario = createdBySource === \"WEB_USER\" || createdBySource === \"MOBILE_USER\";\n\nreturn [\n {\n json: {\n sucursal: {\n id: sucursalField?.id ?? null,\n name: sucursalField?.name ?? null,\n fieldKey: sucursalField?.fieldKey ?? null,\n picklistOptions: sucursalField?.picklistOptions ?? [],\n },\n tienda: {\n id: tiendaField?.id ?? null,\n name: tiendaField?.name ?? null,\n fieldKey: tiendaField?.fieldKey ?? null,\n picklistOptions: tiendaField?.picklistOptions ?? [],\n },\n canal: {\n id: canalField?.id ?? null,\n name: canalField?.name ?? null,\n fieldKey: canalField?.fieldKey ?? null,\n picklistOptions: canalField?.picklistOptions ?? [],\n },\n createdBySource: createdBySource,\n esUsuario: esUsuario,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 32, + -320 + ], + "id": "5a1cfe47-862c-4d28-a4ca-57a9f8c54a7c", + "name": "Buscar \"contact.sucursal\" y \"contact.tienda\"" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 750, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7247, + "value": "={{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Name Location'] }}" + }, + { + "field": 7279, + "operator": "not_equal", + "value": "NO DIGITAL" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 256, + -320 + ], + "id": "a912fac3-25d4-492a-9648-8c472098b9ca", + "name": "Buscar Sucursal en Verificador de Sucursales", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2023-02-21" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}\",\n \"query\": \"{{ $json.contact.email }}\",\n \"limit\": 20,\n \"page\": 1\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1152, + -320 + ], + "id": "4b899380-fe4a-40ad-80bb-21ef504b30ac", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL1" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "canal-origen-esusuario", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.esUsuario }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 720, + -120 + ], + "name": "Creado por usuario (Canal de Origen)", + "id": "49879f5e-7ce8-4ced-b1a6-96df44ac2e0a" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 940, + -120 + ], + "name": "PUT Canal de Origen = SUCURSAL", + "id": "d0597a9c-aca0-40bb-98d8-bb584d2a2c3e" + }, + { + "parameters": { + "method": "POST", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1160, + -120 + ], + "name": "Tag+ sucursal", + "id": "86bfddcb-c402-413f-9d32-c55050dc470d" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"formulario\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1380, + -120 + ], + "name": "Tag- formulario", + "onError": "continueRegularOutput", + "id": "cbd3cfb2-28d0-44e1-9567-a269382497ae" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"facebook-ads\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1600, + -120 + ], + "name": "Tag- facebook-ads", + "onError": "continueRegularOutput", + "id": "19bdc2b2-9345-4294-8e59-7f35963f261d" + } + ], + "connections": { + "Obtener Info de cuenta origen - SUCURSAL": { + "main": [ + [ + { + "node": "Datos API Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Cuenta Origen- SUCURSAL": { + "main": [ + [ + { + "node": "Buscar \"contact.sucursal\" y \"contact.tienda\"", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "Obtener Info de cuenta origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos API Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Cuenta Origen- SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Sucursal en Verificador de Sucursales": { + "main": [ + [ + { + "node": "Actualizar Contacto Cuenta Objetivo - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar \"contact.sucursal\" y \"contact.tienda\"": { + "main": [ + [ + { + "node": "Buscar Sucursal en Verificador de Sucursales", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Contacto Cuenta Objetivo - SUCURSAL": { + "main": [ + [ + { + "node": "Creado por usuario (Canal de Origen)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Creado por usuario (Canal de Origen)": { + "main": [ + [ + { + "node": "PUT Canal de Origen = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = SUCURSAL": { + "main": [ + [ + { + "node": "Tag+ sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag+ sucursal": { + "main": [ + [ + { + "node": "Tag- formulario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- formulario": { + "main": [ + [ + { + "node": "Tag- facebook-ads", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "Consultoria E3 ®", + "name": null, + "description": null, + "autosaved": false, + "workflowPublishHistory": [ + { + "createdAt": "2026-05-30T07:15:09.042Z", + "id": 666, + "workflowId": "ddUEORBEtZLzsQF2", + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "event": "deactivated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + }, + { + "createdAt": "2026-05-30T07:15:09.069Z", + "id": 667, + "workflowId": "ddUEORBEtZLzsQF2", + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "event": "activated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + }, + { + "createdAt": "2026-05-30T07:15:08.695Z", + "id": 664, + "workflowId": "ddUEORBEtZLzsQF2", + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "event": "deactivated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + }, + { + "createdAt": "2026-05-30T07:15:08.729Z", + "id": 665, + "workflowId": "ddUEORBEtZLzsQF2", + "versionId": "11c7184b-0dcd-44ba-9359-e9b83c939348", + "event": "activated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + ] + } +} \ No newline at end of file diff --git a/n8n/backup_redes_sociales_ddUEORBEtZLzsQF2_20260530_192327.json b/n8n/backup_redes_sociales_ddUEORBEtZLzsQF2_20260530_192327.json new file mode 100644 index 0000000..8dbf9f7 --- /dev/null +++ b/n8n/backup_redes_sociales_ddUEORBEtZLzsQF2_20260530_192327.json @@ -0,0 +1,2069 @@ +{ + "updatedAt": "2026-05-31T01:01:08.985Z", + "createdAt": "2026-04-20T22:29:00.636Z", + "id": "ddUEORBEtZLzsQF2", + "name": "[2004][Monte Providencia] Actualizar \"contact.sucursal\", \"contact.tienda\" al crear contacto", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "content": "# De Sucursal a Marca", + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -2288, + -320 + ], + "typeVersion": 1, + "id": "d91325e1-1763-486c-a2c9-8cfffddd57b0", + "name": "Sticky Note3" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Datos de Lead').item.json.Sucursal['Cuenta Bucéfalo'] }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + -912, + -320 + ], + "id": "50da84c1-c2d3-4a36-91d3-e3745edccdc6", + "name": "Obtener Info de cuenta origen - SUCURSAL", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -224, + -320 + ], + "id": "a0c27c4c-0f20-4bab-bf6b-bcdd8281a92b", + "name": "Conseguir Custom Cuenta Origen- SUCURSAL" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + }, + { + "id": "64f2add6-506c-4950-8026-c04c9547aeeb", + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -1392, + -304 + ], + "id": "d68558b5-52b8-46b1-a359-fd956c7edc09", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "57199999-2d9b-41c7-8269-5a8183ca8132", + "name": "Cliente.Cuándo necesitas el dinero", + "value": "={{ $json.body[\"¿Cuándo necesitas el dinero?\"] }}", + "type": "string" + }, + { + "id": "8060d06e-b1da-4a65-8a0a-3c21d237d77e", + "name": "Sucursal.Cuenta Bucéfalo", + "value": "={{ $json.body.location.name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -1568, + -304 + ], + "id": "d0e455ac-7ccc-4c55-bda2-1cd22092e2d2", + "name": "Datos de Lead" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -720, + -320 + ], + "id": "f0cff3ec-8464-45d4-9e64-713c36e247c6", + "name": "Datos API Cuenta Origen - SUCURSAL", + "notes": "Esta en modo prueba forzada para Queretaro" + }, + { + "parameters": { + "content": "# CUENTA ORIGEN", + "height": 240, + "width": 1744 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -976, + -384 + ], + "typeVersion": 1, + "id": "16a74d85-1e6c-4fc7-b854-580a2d3827a0", + "name": "Sticky Note" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Webhook').item.json.body.contact_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -400, + -320 + ], + "id": "92f33c6e-ee64-409d-8c60-9fbfe48b3265", + "name": "Obtener Contacto Cuenta Origen - SUCURSAL" + }, + { + "parameters": { + "content": "", + "height": 176, + "width": 608, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -336 + ], + "typeVersion": 1, + "id": "768a4001-5109-493f-b12e-e8ba5d30ec2f", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.sucursal.id }}\",\n \"key\": \"contact.sucursal\",\n \"field_value\": \"{{ $json.SUCURSAL }}\"\n },\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.tienda.id }}\",\n \"key\": \"contact.tienda\",\n \"field_value\": \"{{ $json.TIENDA }}\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 512, + -320 + ], + "id": "794ce5bb-dea0-4720-958a-1f0940c79e6d", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "8d574598-d977-4052-823a-26def39c6a64", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1776, + -304 + ], + "id": "d4312610-e978-424c-a8a0-426026c4d4f6", + "name": "Webhook", + "webhookId": "8d574598-d977-4052-823a-26def39c6a64" + }, + { + "parameters": { + "jsCode": "const customFields = $input.first().json.customFields;\n\nfunction findField(key, names) {\n let f = customFields.find(x => x.fieldKey === key);\n if (!f) {\n const wanted = names.map(n => n.toLowerCase().trim());\n f = customFields.find(x => wanted.includes((x.name || \"\").toLowerCase().trim()));\n }\n return f || null;\n}\n\nconst sucursalField = findField(\"contact.sucursal\", [\"Sucursal\"]);\nconst tiendaField = findField(\"contact.tienda\", [\"TIENDA\", \"Tienda\"]);\nconst canalField = findField(\"contact.fuente_de_posible_cliente\", [\"CANAL DE ORIGEN\", \"Canal de Origen\"]);\nconst fuenteField = findField(\"contact.fuente_de_prospecto\", [\"Fuente de Prospecto\", \"FUENTE DE PROSPECTO\"]);\n\n// createdBy.source SOLO viene del GET individual del contacto.\nconst contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json;\nconst createdBySource =\n (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) ||\n (contactResp && contactResp.createdBy && contactResp.createdBy.source) ||\n null;\nconst esUsuario = createdBySource === \"WEB_USER\" || createdBySource === \"MOBILE_USER\";\n\n// Valor ACTUAL de \"Fuente de Prospecto\" en el contacto (para reconciliar SOLO si = LEAD DIGITAL).\nconst contactCFs =\n (contactResp && contactResp.contact && contactResp.contact.customFields) ||\n (contactResp && contactResp.customFields) ||\n [];\nlet fuenteActual = null;\nif (fuenteField && fuenteField.id) {\n const hit = contactCFs.find(cf => cf.id === fuenteField.id);\n fuenteActual = hit ? (hit.value != null ? hit.value : (hit.fieldValue != null ? hit.fieldValue : null)) : null;\n}\nconst fuenteEsLeadDigital = String(fuenteActual || \"\").trim().toUpperCase() === \"LEAD DIGITAL\";\n\nreturn [\n {\n json: {\n sucursal: {\n id: sucursalField?.id ?? null,\n name: sucursalField?.name ?? null,\n fieldKey: sucursalField?.fieldKey ?? null,\n picklistOptions: sucursalField?.picklistOptions ?? [],\n },\n tienda: {\n id: tiendaField?.id ?? null,\n name: tiendaField?.name ?? null,\n fieldKey: tiendaField?.fieldKey ?? null,\n picklistOptions: tiendaField?.picklistOptions ?? [],\n },\n canal: {\n id: canalField?.id ?? null,\n name: canalField?.name ?? null,\n fieldKey: canalField?.fieldKey ?? null,\n picklistOptions: canalField?.picklistOptions ?? [],\n },\n fuente: {\n id: fuenteField?.id ?? null,\n name: fuenteField?.name ?? null,\n fieldKey: fuenteField?.fieldKey ?? null,\n picklistOptions: fuenteField?.picklistOptions ?? [],\n },\n createdBySource: createdBySource,\n esUsuario: esUsuario,\n fuenteActual: fuenteActual,\n fuenteEsLeadDigital: fuenteEsLeadDigital,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 32, + -320 + ], + "id": "5a1cfe47-862c-4d28-a4ca-57a9f8c54a7c", + "name": "Buscar \"contact.sucursal\" y \"contact.tienda\"" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 750, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7247, + "value": "={{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Name Location'] }}" + }, + { + "field": 7279, + "operator": "not_equal", + "value": "NO DIGITAL" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 256, + -320 + ], + "id": "a912fac3-25d4-492a-9648-8c472098b9ca", + "name": "Buscar Sucursal en Verificador de Sucursales", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2023-02-21" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}\",\n \"query\": \"{{ $json.contact.email }}\",\n \"limit\": 20,\n \"page\": 1\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1152, + -320 + ], + "id": "4b899380-fe4a-40ad-80bb-21ef504b30ac", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL1" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "canal-origen-esusuario", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.esUsuario }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 720, + -120 + ], + "name": "Creado por usuario (Canal de Origen)", + "id": "49879f5e-7ce8-4ced-b1a6-96df44ac2e0a" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 940, + -120 + ], + "name": "PUT Canal de Origen = SUCURSAL", + "id": "d0597a9c-aca0-40bb-98d8-bb584d2a2c3e" + }, + { + "parameters": { + "method": "POST", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1160, + -120 + ], + "name": "Tag+ sucursal", + "id": "86bfddcb-c402-413f-9d32-c55050dc470d" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"formulario\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1380, + -120 + ], + "name": "Tag- formulario", + "onError": "continueRegularOutput", + "id": "cbd3cfb2-28d0-44e1-9567-a269382497ae" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"facebook-ads\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1600, + -120 + ], + "name": "Tag- facebook-ads", + "onError": "continueRegularOutput", + "id": "19bdc2b2-9345-4294-8e59-7f35963f261d" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "fuente-es-lead-digital", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuenteEsLeadDigital }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1820, + -120 + ], + "name": "Fuente = LEAD DIGITAL (reconciliar)", + "id": "e6bc4a7a-9304-4d00-9750-4bb64a76c3d2" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2040, + -120 + ], + "name": "PUT Fuente de Prospecto = SUCURSAL", + "id": "5ef526d6-4947-4725-a3d5-f85cab931142" + } + ], + "connections": { + "Obtener Info de cuenta origen - SUCURSAL": { + "main": [ + [ + { + "node": "Datos API Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Cuenta Origen- SUCURSAL": { + "main": [ + [ + { + "node": "Buscar \"contact.sucursal\" y \"contact.tienda\"", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "Obtener Info de cuenta origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos API Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Cuenta Origen- SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Sucursal en Verificador de Sucursales": { + "main": [ + [ + { + "node": "Actualizar Contacto Cuenta Objetivo - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar \"contact.sucursal\" y \"contact.tienda\"": { + "main": [ + [ + { + "node": "Buscar Sucursal en Verificador de Sucursales", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Contacto Cuenta Objetivo - SUCURSAL": { + "main": [ + [ + { + "node": "Creado por usuario (Canal de Origen)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Creado por usuario (Canal de Origen)": { + "main": [ + [ + { + "node": "PUT Canal de Origen = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = SUCURSAL": { + "main": [ + [ + { + "node": "Tag+ sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag+ sucursal": { + "main": [ + [ + { + "node": "Tag- formulario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- formulario": { + "main": [ + [ + { + "node": "Tag- facebook-ads", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- facebook-ads": { + "main": [ + [ + { + "node": "Fuente = LEAD DIGITAL (reconciliar)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fuente = LEAD DIGITAL (reconciliar)": { + "main": [ + [ + { + "node": "PUT Fuente de Prospecto = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false + }, + "staticData": null, + "meta": { + "templateCredsSetupCompleted": true + }, + "pinData": { + "Webhook": [ + { + "json": { + "headers": { + "host": "webhookn8.consultoriae3.com", + "user-agent": "axios/1.13.2", + "content-length": "1572", + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, compress, deflate, br", + "content-type": "application/json", + "traceparent": "00-bfdb4ad6378402bf71f61ff3c72d0d02-ee175e0dcbcec88e-01", + "x-forwarded-for": "35.253.135.103", + "x-forwarded-host": "webhookn8.consultoriae3.com", + "x-forwarded-port": "443", + "x-forwarded-proto": "https", + "x-forwarded-server": "f569f6e85d30", + "x-real-ip": "35.253.135.103" + }, + "params": {}, + "query": {}, + "body": { + "Canal de Origen": "", + "¿Qué modalidad prefieres?": "Sin Dejarlo (GPS)", + "Fuente de Prospecto": "GALLARDETES", + "Marca del Vehiculo": "VW", + "[Número de WhatsApp para Atender]": "", + "TIENDA": "", + "Presupuesto": "", + "Acepta los terminos para tu cotización": "", + "Sucursal": "", + "ID Contacto Monte Providencia": "", + "Información Adicional": "", + "[Número de Teléfono para Atender]": "", + "Version del Vehiculo": "POLO", + "[Correo_Canalizados]": "", + "[Correo_Empresa]": "", + "Check Comunicaciones de Marketing": "", + "Descripción": "", + "[Nombre de la Empresa]": "", + "Año del Vehículo": "2019", + "[Dirección de la Empresa]": "", + "¿Cuándo necesitas el dinero?": "", + "Archivos Adicionales": "", + "contact_id": "ZHj8ek6lQ6M6EeCIU7Uj", + "first_name": "LILIA LISSETH", + "last_name": "ZAVALA HEREDIA", + "full_name": "LILIA LISSETH ZAVALA HEREDIA", + "email": "lilia.zh0209@gmail.com", + "phone": "+524361175941", + "tags": "sucursal", + "country": "MX", + "date_created": "2026-05-25T23:59:53.804Z", + "full_address": "", + "contact_type": "lead", + "location": { + "name": "85964 - MP - Morelia 1", + "address": "Morelia", + "city": "Morelia", + "state": "Michoacán", + "country": "MX", + "postalCode": "58855", + "fullAddress": "Morelia, Morelia Michoacán 58855", + "id": "jE41bVhhnb5T505BFm4F" + }, + "user": { + "firstName": "Morelia 1 - 85964", + "lastName": "Morelia 1", + "email": "ezstore85964@ezcorp.com", + "phone": "+524434589152" + }, + "workflow": { + "id": "7c92fec9-b858-4a83-b2d9-31891c9b9231", + "name": "Sincronizar Contacto Creado V2" + }, + "triggerData": {}, + "contact": { + "attributionSource": { + "mediumId": null, + "medium": "manual", + "sessionSource": "CRM UI" + }, + "lastAttributionSource": {} + }, + "attributionSource": {}, + "customData": {} + }, + "webhookUrl": "https://webhookn8.consultoriae3.com/webhook/8d574598-d977-4052-823a-26def39c6a64", + "executionMode": "production" + }, + "pairedItem": { + "item": 0 + } + } + ] + }, + "versionId": "e8f6f33f-a308-4fa6-a7a7-2c344311b69e", + "activeVersionId": "e8f6f33f-a308-4fa6-a7a7-2c344311b69e", + "versionCounter": 238, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-04-20T22:29:00.636Z", + "createdAt": "2026-04-20T22:29:00.636Z", + "role": "workflow:owner", + "workflowId": "ddUEORBEtZLzsQF2", + "projectId": "aycNlMpnzsxvXbfG", + "project": { + "updatedAt": "2025-11-27T04:06:26.313Z", + "createdAt": "2025-11-27T03:54:24.410Z", + "id": "aycNlMpnzsxvXbfG", + "name": "Consultoria E3 ® ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + } + ], + "tags": [ + { + "updatedAt": "2025-11-27T05:48:12.272Z", + "createdAt": "2025-11-27T05:48:12.272Z", + "id": "pHkPpq3Httzx4hnb", + "name": "Bucefalo" + }, + { + "updatedAt": "2025-12-01T22:00:17.627Z", + "createdAt": "2025-12-01T22:00:17.627Z", + "id": "uIWgqVuJXbqA7m39", + "name": "PRUEBAS" + } + ], + "activeVersion": { + "updatedAt": "2026-05-31T01:01:08.989Z", + "createdAt": "2026-05-31T01:01:08.989Z", + "versionId": "e8f6f33f-a308-4fa6-a7a7-2c344311b69e", + "workflowId": "ddUEORBEtZLzsQF2", + "nodes": [ + { + "parameters": { + "content": "# De Sucursal a Marca", + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -2288, + -320 + ], + "typeVersion": 1, + "id": "d91325e1-1763-486c-a2c9-8cfffddd57b0", + "name": "Sticky Note3" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Datos de Lead').item.json.Sucursal['Cuenta Bucéfalo'] }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + -912, + -320 + ], + "id": "50da84c1-c2d3-4a36-91d3-e3745edccdc6", + "name": "Obtener Info de cuenta origen - SUCURSAL", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -224, + -320 + ], + "id": "a0c27c4c-0f20-4bab-bf6b-bcdd8281a92b", + "name": "Conseguir Custom Cuenta Origen- SUCURSAL" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + }, + { + "id": "64f2add6-506c-4950-8026-c04c9547aeeb", + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -1392, + -304 + ], + "id": "d68558b5-52b8-46b1-a359-fd956c7edc09", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "57199999-2d9b-41c7-8269-5a8183ca8132", + "name": "Cliente.Cuándo necesitas el dinero", + "value": "={{ $json.body[\"¿Cuándo necesitas el dinero?\"] }}", + "type": "string" + }, + { + "id": "8060d06e-b1da-4a65-8a0a-3c21d237d77e", + "name": "Sucursal.Cuenta Bucéfalo", + "value": "={{ $json.body.location.name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -1568, + -304 + ], + "id": "d0e455ac-7ccc-4c55-bda2-1cd22092e2d2", + "name": "Datos de Lead" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -720, + -320 + ], + "id": "f0cff3ec-8464-45d4-9e64-713c36e247c6", + "name": "Datos API Cuenta Origen - SUCURSAL", + "notes": "Esta en modo prueba forzada para Queretaro" + }, + { + "parameters": { + "content": "# CUENTA ORIGEN", + "height": 240, + "width": 1744 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -976, + -384 + ], + "typeVersion": 1, + "id": "16a74d85-1e6c-4fc7-b854-580a2d3827a0", + "name": "Sticky Note" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Webhook').item.json.body.contact_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -400, + -320 + ], + "id": "92f33c6e-ee64-409d-8c60-9fbfe48b3265", + "name": "Obtener Contacto Cuenta Origen - SUCURSAL" + }, + { + "parameters": { + "content": "", + "height": 176, + "width": 608, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -336 + ], + "typeVersion": 1, + "id": "768a4001-5109-493f-b12e-e8ba5d30ec2f", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.sucursal.id }}\",\n \"key\": \"contact.sucursal\",\n \"field_value\": \"{{ $json.SUCURSAL }}\"\n },\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.tienda.id }}\",\n \"key\": \"contact.tienda\",\n \"field_value\": \"{{ $json.TIENDA }}\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 512, + -320 + ], + "id": "794ce5bb-dea0-4720-958a-1f0940c79e6d", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "8d574598-d977-4052-823a-26def39c6a64", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1776, + -304 + ], + "id": "d4312610-e978-424c-a8a0-426026c4d4f6", + "name": "Webhook", + "webhookId": "8d574598-d977-4052-823a-26def39c6a64" + }, + { + "parameters": { + "jsCode": "const customFields = $input.first().json.customFields;\n\nfunction findField(key, names) {\n let f = customFields.find(x => x.fieldKey === key);\n if (!f) {\n const wanted = names.map(n => n.toLowerCase().trim());\n f = customFields.find(x => wanted.includes((x.name || \"\").toLowerCase().trim()));\n }\n return f || null;\n}\n\nconst sucursalField = findField(\"contact.sucursal\", [\"Sucursal\"]);\nconst tiendaField = findField(\"contact.tienda\", [\"TIENDA\", \"Tienda\"]);\nconst canalField = findField(\"contact.fuente_de_posible_cliente\", [\"CANAL DE ORIGEN\", \"Canal de Origen\"]);\nconst fuenteField = findField(\"contact.fuente_de_prospecto\", [\"Fuente de Prospecto\", \"FUENTE DE PROSPECTO\"]);\n\n// createdBy.source SOLO viene del GET individual del contacto.\nconst contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json;\nconst createdBySource =\n (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) ||\n (contactResp && contactResp.createdBy && contactResp.createdBy.source) ||\n null;\nconst esUsuario = createdBySource === \"WEB_USER\" || createdBySource === \"MOBILE_USER\";\n\n// Valor ACTUAL de \"Fuente de Prospecto\" en el contacto (para reconciliar SOLO si = LEAD DIGITAL).\nconst contactCFs =\n (contactResp && contactResp.contact && contactResp.contact.customFields) ||\n (contactResp && contactResp.customFields) ||\n [];\nlet fuenteActual = null;\nif (fuenteField && fuenteField.id) {\n const hit = contactCFs.find(cf => cf.id === fuenteField.id);\n fuenteActual = hit ? (hit.value != null ? hit.value : (hit.fieldValue != null ? hit.fieldValue : null)) : null;\n}\nconst fuenteEsLeadDigital = String(fuenteActual || \"\").trim().toUpperCase() === \"LEAD DIGITAL\";\n\nreturn [\n {\n json: {\n sucursal: {\n id: sucursalField?.id ?? null,\n name: sucursalField?.name ?? null,\n fieldKey: sucursalField?.fieldKey ?? null,\n picklistOptions: sucursalField?.picklistOptions ?? [],\n },\n tienda: {\n id: tiendaField?.id ?? null,\n name: tiendaField?.name ?? null,\n fieldKey: tiendaField?.fieldKey ?? null,\n picklistOptions: tiendaField?.picklistOptions ?? [],\n },\n canal: {\n id: canalField?.id ?? null,\n name: canalField?.name ?? null,\n fieldKey: canalField?.fieldKey ?? null,\n picklistOptions: canalField?.picklistOptions ?? [],\n },\n fuente: {\n id: fuenteField?.id ?? null,\n name: fuenteField?.name ?? null,\n fieldKey: fuenteField?.fieldKey ?? null,\n picklistOptions: fuenteField?.picklistOptions ?? [],\n },\n createdBySource: createdBySource,\n esUsuario: esUsuario,\n fuenteActual: fuenteActual,\n fuenteEsLeadDigital: fuenteEsLeadDigital,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 32, + -320 + ], + "id": "5a1cfe47-862c-4d28-a4ca-57a9f8c54a7c", + "name": "Buscar \"contact.sucursal\" y \"contact.tienda\"" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 750, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7247, + "value": "={{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Name Location'] }}" + }, + { + "field": 7279, + "operator": "not_equal", + "value": "NO DIGITAL" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 256, + -320 + ], + "id": "a912fac3-25d4-492a-9648-8c472098b9ca", + "name": "Buscar Sucursal en Verificador de Sucursales", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2023-02-21" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}\",\n \"query\": \"{{ $json.contact.email }}\",\n \"limit\": 20,\n \"page\": 1\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1152, + -320 + ], + "id": "4b899380-fe4a-40ad-80bb-21ef504b30ac", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL1" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "canal-origen-esusuario", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.esUsuario }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 720, + -120 + ], + "name": "Creado por usuario (Canal de Origen)", + "id": "49879f5e-7ce8-4ced-b1a6-96df44ac2e0a" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 940, + -120 + ], + "name": "PUT Canal de Origen = SUCURSAL", + "id": "d0597a9c-aca0-40bb-98d8-bb584d2a2c3e" + }, + { + "parameters": { + "method": "POST", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1160, + -120 + ], + "name": "Tag+ sucursal", + "id": "86bfddcb-c402-413f-9d32-c55050dc470d" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"formulario\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1380, + -120 + ], + "name": "Tag- formulario", + "onError": "continueRegularOutput", + "id": "cbd3cfb2-28d0-44e1-9567-a269382497ae" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"facebook-ads\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1600, + -120 + ], + "name": "Tag- facebook-ads", + "onError": "continueRegularOutput", + "id": "19bdc2b2-9345-4294-8e59-7f35963f261d" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "fuente-es-lead-digital", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuenteEsLeadDigital }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1820, + -120 + ], + "name": "Fuente = LEAD DIGITAL (reconciliar)", + "id": "e6bc4a7a-9304-4d00-9750-4bb64a76c3d2" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2040, + -120 + ], + "name": "PUT Fuente de Prospecto = SUCURSAL", + "id": "5ef526d6-4947-4725-a3d5-f85cab931142" + } + ], + "connections": { + "Obtener Info de cuenta origen - SUCURSAL": { + "main": [ + [ + { + "node": "Datos API Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Cuenta Origen- SUCURSAL": { + "main": [ + [ + { + "node": "Buscar \"contact.sucursal\" y \"contact.tienda\"", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "Obtener Info de cuenta origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos API Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Cuenta Origen- SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Sucursal en Verificador de Sucursales": { + "main": [ + [ + { + "node": "Actualizar Contacto Cuenta Objetivo - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar \"contact.sucursal\" y \"contact.tienda\"": { + "main": [ + [ + { + "node": "Buscar Sucursal en Verificador de Sucursales", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Contacto Cuenta Objetivo - SUCURSAL": { + "main": [ + [ + { + "node": "Creado por usuario (Canal de Origen)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Creado por usuario (Canal de Origen)": { + "main": [ + [ + { + "node": "PUT Canal de Origen = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = SUCURSAL": { + "main": [ + [ + { + "node": "Tag+ sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag+ sucursal": { + "main": [ + [ + { + "node": "Tag- formulario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- formulario": { + "main": [ + [ + { + "node": "Tag- facebook-ads", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- facebook-ads": { + "main": [ + [ + { + "node": "Fuente = LEAD DIGITAL (reconciliar)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fuente = LEAD DIGITAL (reconciliar)": { + "main": [ + [ + { + "node": "PUT Fuente de Prospecto = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "Consultoria E3 ®", + "name": null, + "description": null, + "autosaved": false, + "workflowPublishHistory": [ + { + "createdAt": "2026-05-31T01:01:09.401Z", + "id": 681, + "workflowId": "ddUEORBEtZLzsQF2", + "versionId": "e8f6f33f-a308-4fa6-a7a7-2c344311b69e", + "event": "activated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + ] + } +} \ No newline at end of file diff --git a/n8n/backup_redes_sociales_ddUEORBEtZLzsQF2_20260530_192343.json b/n8n/backup_redes_sociales_ddUEORBEtZLzsQF2_20260530_192343.json new file mode 100644 index 0000000..8dbf9f7 --- /dev/null +++ b/n8n/backup_redes_sociales_ddUEORBEtZLzsQF2_20260530_192343.json @@ -0,0 +1,2069 @@ +{ + "updatedAt": "2026-05-31T01:01:08.985Z", + "createdAt": "2026-04-20T22:29:00.636Z", + "id": "ddUEORBEtZLzsQF2", + "name": "[2004][Monte Providencia] Actualizar \"contact.sucursal\", \"contact.tienda\" al crear contacto", + "description": null, + "active": true, + "isArchived": false, + "nodes": [ + { + "parameters": { + "content": "# De Sucursal a Marca", + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -2288, + -320 + ], + "typeVersion": 1, + "id": "d91325e1-1763-486c-a2c9-8cfffddd57b0", + "name": "Sticky Note3" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Datos de Lead').item.json.Sucursal['Cuenta Bucéfalo'] }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + -912, + -320 + ], + "id": "50da84c1-c2d3-4a36-91d3-e3745edccdc6", + "name": "Obtener Info de cuenta origen - SUCURSAL", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -224, + -320 + ], + "id": "a0c27c4c-0f20-4bab-bf6b-bcdd8281a92b", + "name": "Conseguir Custom Cuenta Origen- SUCURSAL" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + }, + { + "id": "64f2add6-506c-4950-8026-c04c9547aeeb", + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -1392, + -304 + ], + "id": "d68558b5-52b8-46b1-a359-fd956c7edc09", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "57199999-2d9b-41c7-8269-5a8183ca8132", + "name": "Cliente.Cuándo necesitas el dinero", + "value": "={{ $json.body[\"¿Cuándo necesitas el dinero?\"] }}", + "type": "string" + }, + { + "id": "8060d06e-b1da-4a65-8a0a-3c21d237d77e", + "name": "Sucursal.Cuenta Bucéfalo", + "value": "={{ $json.body.location.name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -1568, + -304 + ], + "id": "d0e455ac-7ccc-4c55-bda2-1cd22092e2d2", + "name": "Datos de Lead" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -720, + -320 + ], + "id": "f0cff3ec-8464-45d4-9e64-713c36e247c6", + "name": "Datos API Cuenta Origen - SUCURSAL", + "notes": "Esta en modo prueba forzada para Queretaro" + }, + { + "parameters": { + "content": "# CUENTA ORIGEN", + "height": 240, + "width": 1744 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -976, + -384 + ], + "typeVersion": 1, + "id": "16a74d85-1e6c-4fc7-b854-580a2d3827a0", + "name": "Sticky Note" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Webhook').item.json.body.contact_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -400, + -320 + ], + "id": "92f33c6e-ee64-409d-8c60-9fbfe48b3265", + "name": "Obtener Contacto Cuenta Origen - SUCURSAL" + }, + { + "parameters": { + "content": "", + "height": 176, + "width": 608, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -336 + ], + "typeVersion": 1, + "id": "768a4001-5109-493f-b12e-e8ba5d30ec2f", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.sucursal.id }}\",\n \"key\": \"contact.sucursal\",\n \"field_value\": \"{{ $json.SUCURSAL }}\"\n },\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.tienda.id }}\",\n \"key\": \"contact.tienda\",\n \"field_value\": \"{{ $json.TIENDA }}\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 512, + -320 + ], + "id": "794ce5bb-dea0-4720-958a-1f0940c79e6d", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "8d574598-d977-4052-823a-26def39c6a64", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1776, + -304 + ], + "id": "d4312610-e978-424c-a8a0-426026c4d4f6", + "name": "Webhook", + "webhookId": "8d574598-d977-4052-823a-26def39c6a64" + }, + { + "parameters": { + "jsCode": "const customFields = $input.first().json.customFields;\n\nfunction findField(key, names) {\n let f = customFields.find(x => x.fieldKey === key);\n if (!f) {\n const wanted = names.map(n => n.toLowerCase().trim());\n f = customFields.find(x => wanted.includes((x.name || \"\").toLowerCase().trim()));\n }\n return f || null;\n}\n\nconst sucursalField = findField(\"contact.sucursal\", [\"Sucursal\"]);\nconst tiendaField = findField(\"contact.tienda\", [\"TIENDA\", \"Tienda\"]);\nconst canalField = findField(\"contact.fuente_de_posible_cliente\", [\"CANAL DE ORIGEN\", \"Canal de Origen\"]);\nconst fuenteField = findField(\"contact.fuente_de_prospecto\", [\"Fuente de Prospecto\", \"FUENTE DE PROSPECTO\"]);\n\n// createdBy.source SOLO viene del GET individual del contacto.\nconst contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json;\nconst createdBySource =\n (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) ||\n (contactResp && contactResp.createdBy && contactResp.createdBy.source) ||\n null;\nconst esUsuario = createdBySource === \"WEB_USER\" || createdBySource === \"MOBILE_USER\";\n\n// Valor ACTUAL de \"Fuente de Prospecto\" en el contacto (para reconciliar SOLO si = LEAD DIGITAL).\nconst contactCFs =\n (contactResp && contactResp.contact && contactResp.contact.customFields) ||\n (contactResp && contactResp.customFields) ||\n [];\nlet fuenteActual = null;\nif (fuenteField && fuenteField.id) {\n const hit = contactCFs.find(cf => cf.id === fuenteField.id);\n fuenteActual = hit ? (hit.value != null ? hit.value : (hit.fieldValue != null ? hit.fieldValue : null)) : null;\n}\nconst fuenteEsLeadDigital = String(fuenteActual || \"\").trim().toUpperCase() === \"LEAD DIGITAL\";\n\nreturn [\n {\n json: {\n sucursal: {\n id: sucursalField?.id ?? null,\n name: sucursalField?.name ?? null,\n fieldKey: sucursalField?.fieldKey ?? null,\n picklistOptions: sucursalField?.picklistOptions ?? [],\n },\n tienda: {\n id: tiendaField?.id ?? null,\n name: tiendaField?.name ?? null,\n fieldKey: tiendaField?.fieldKey ?? null,\n picklistOptions: tiendaField?.picklistOptions ?? [],\n },\n canal: {\n id: canalField?.id ?? null,\n name: canalField?.name ?? null,\n fieldKey: canalField?.fieldKey ?? null,\n picklistOptions: canalField?.picklistOptions ?? [],\n },\n fuente: {\n id: fuenteField?.id ?? null,\n name: fuenteField?.name ?? null,\n fieldKey: fuenteField?.fieldKey ?? null,\n picklistOptions: fuenteField?.picklistOptions ?? [],\n },\n createdBySource: createdBySource,\n esUsuario: esUsuario,\n fuenteActual: fuenteActual,\n fuenteEsLeadDigital: fuenteEsLeadDigital,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 32, + -320 + ], + "id": "5a1cfe47-862c-4d28-a4ca-57a9f8c54a7c", + "name": "Buscar \"contact.sucursal\" y \"contact.tienda\"" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 750, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7247, + "value": "={{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Name Location'] }}" + }, + { + "field": 7279, + "operator": "not_equal", + "value": "NO DIGITAL" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 256, + -320 + ], + "id": "a912fac3-25d4-492a-9648-8c472098b9ca", + "name": "Buscar Sucursal en Verificador de Sucursales", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2023-02-21" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}\",\n \"query\": \"{{ $json.contact.email }}\",\n \"limit\": 20,\n \"page\": 1\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1152, + -320 + ], + "id": "4b899380-fe4a-40ad-80bb-21ef504b30ac", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL1" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "canal-origen-esusuario", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.esUsuario }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 720, + -120 + ], + "name": "Creado por usuario (Canal de Origen)", + "id": "49879f5e-7ce8-4ced-b1a6-96df44ac2e0a" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 940, + -120 + ], + "name": "PUT Canal de Origen = SUCURSAL", + "id": "d0597a9c-aca0-40bb-98d8-bb584d2a2c3e" + }, + { + "parameters": { + "method": "POST", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1160, + -120 + ], + "name": "Tag+ sucursal", + "id": "86bfddcb-c402-413f-9d32-c55050dc470d" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"formulario\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1380, + -120 + ], + "name": "Tag- formulario", + "onError": "continueRegularOutput", + "id": "cbd3cfb2-28d0-44e1-9567-a269382497ae" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"facebook-ads\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1600, + -120 + ], + "name": "Tag- facebook-ads", + "onError": "continueRegularOutput", + "id": "19bdc2b2-9345-4294-8e59-7f35963f261d" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "fuente-es-lead-digital", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuenteEsLeadDigital }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1820, + -120 + ], + "name": "Fuente = LEAD DIGITAL (reconciliar)", + "id": "e6bc4a7a-9304-4d00-9750-4bb64a76c3d2" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2040, + -120 + ], + "name": "PUT Fuente de Prospecto = SUCURSAL", + "id": "5ef526d6-4947-4725-a3d5-f85cab931142" + } + ], + "connections": { + "Obtener Info de cuenta origen - SUCURSAL": { + "main": [ + [ + { + "node": "Datos API Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Cuenta Origen- SUCURSAL": { + "main": [ + [ + { + "node": "Buscar \"contact.sucursal\" y \"contact.tienda\"", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "Obtener Info de cuenta origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos API Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Cuenta Origen- SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Sucursal en Verificador de Sucursales": { + "main": [ + [ + { + "node": "Actualizar Contacto Cuenta Objetivo - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar \"contact.sucursal\" y \"contact.tienda\"": { + "main": [ + [ + { + "node": "Buscar Sucursal en Verificador de Sucursales", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Contacto Cuenta Objetivo - SUCURSAL": { + "main": [ + [ + { + "node": "Creado por usuario (Canal de Origen)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Creado por usuario (Canal de Origen)": { + "main": [ + [ + { + "node": "PUT Canal de Origen = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = SUCURSAL": { + "main": [ + [ + { + "node": "Tag+ sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag+ sucursal": { + "main": [ + [ + { + "node": "Tag- formulario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- formulario": { + "main": [ + [ + { + "node": "Tag- facebook-ads", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- facebook-ads": { + "main": [ + [ + { + "node": "Fuente = LEAD DIGITAL (reconciliar)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fuente = LEAD DIGITAL (reconciliar)": { + "main": [ + [ + { + "node": "PUT Fuente de Prospecto = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1", + "binaryMode": "separate", + "availableInMCP": false + }, + "staticData": null, + "meta": { + "templateCredsSetupCompleted": true + }, + "pinData": { + "Webhook": [ + { + "json": { + "headers": { + "host": "webhookn8.consultoriae3.com", + "user-agent": "axios/1.13.2", + "content-length": "1572", + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, compress, deflate, br", + "content-type": "application/json", + "traceparent": "00-bfdb4ad6378402bf71f61ff3c72d0d02-ee175e0dcbcec88e-01", + "x-forwarded-for": "35.253.135.103", + "x-forwarded-host": "webhookn8.consultoriae3.com", + "x-forwarded-port": "443", + "x-forwarded-proto": "https", + "x-forwarded-server": "f569f6e85d30", + "x-real-ip": "35.253.135.103" + }, + "params": {}, + "query": {}, + "body": { + "Canal de Origen": "", + "¿Qué modalidad prefieres?": "Sin Dejarlo (GPS)", + "Fuente de Prospecto": "GALLARDETES", + "Marca del Vehiculo": "VW", + "[Número de WhatsApp para Atender]": "", + "TIENDA": "", + "Presupuesto": "", + "Acepta los terminos para tu cotización": "", + "Sucursal": "", + "ID Contacto Monte Providencia": "", + "Información Adicional": "", + "[Número de Teléfono para Atender]": "", + "Version del Vehiculo": "POLO", + "[Correo_Canalizados]": "", + "[Correo_Empresa]": "", + "Check Comunicaciones de Marketing": "", + "Descripción": "", + "[Nombre de la Empresa]": "", + "Año del Vehículo": "2019", + "[Dirección de la Empresa]": "", + "¿Cuándo necesitas el dinero?": "", + "Archivos Adicionales": "", + "contact_id": "ZHj8ek6lQ6M6EeCIU7Uj", + "first_name": "LILIA LISSETH", + "last_name": "ZAVALA HEREDIA", + "full_name": "LILIA LISSETH ZAVALA HEREDIA", + "email": "lilia.zh0209@gmail.com", + "phone": "+524361175941", + "tags": "sucursal", + "country": "MX", + "date_created": "2026-05-25T23:59:53.804Z", + "full_address": "", + "contact_type": "lead", + "location": { + "name": "85964 - MP - Morelia 1", + "address": "Morelia", + "city": "Morelia", + "state": "Michoacán", + "country": "MX", + "postalCode": "58855", + "fullAddress": "Morelia, Morelia Michoacán 58855", + "id": "jE41bVhhnb5T505BFm4F" + }, + "user": { + "firstName": "Morelia 1 - 85964", + "lastName": "Morelia 1", + "email": "ezstore85964@ezcorp.com", + "phone": "+524434589152" + }, + "workflow": { + "id": "7c92fec9-b858-4a83-b2d9-31891c9b9231", + "name": "Sincronizar Contacto Creado V2" + }, + "triggerData": {}, + "contact": { + "attributionSource": { + "mediumId": null, + "medium": "manual", + "sessionSource": "CRM UI" + }, + "lastAttributionSource": {} + }, + "attributionSource": {}, + "customData": {} + }, + "webhookUrl": "https://webhookn8.consultoriae3.com/webhook/8d574598-d977-4052-823a-26def39c6a64", + "executionMode": "production" + }, + "pairedItem": { + "item": 0 + } + } + ] + }, + "versionId": "e8f6f33f-a308-4fa6-a7a7-2c344311b69e", + "activeVersionId": "e8f6f33f-a308-4fa6-a7a7-2c344311b69e", + "versionCounter": 238, + "triggerCount": 1, + "shared": [ + { + "updatedAt": "2026-04-20T22:29:00.636Z", + "createdAt": "2026-04-20T22:29:00.636Z", + "role": "workflow:owner", + "workflowId": "ddUEORBEtZLzsQF2", + "projectId": "aycNlMpnzsxvXbfG", + "project": { + "updatedAt": "2025-11-27T04:06:26.313Z", + "createdAt": "2025-11-27T03:54:24.410Z", + "id": "aycNlMpnzsxvXbfG", + "name": "Consultoria E3 ® ", + "type": "personal", + "icon": null, + "description": null, + "creatorId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + } + ], + "tags": [ + { + "updatedAt": "2025-11-27T05:48:12.272Z", + "createdAt": "2025-11-27T05:48:12.272Z", + "id": "pHkPpq3Httzx4hnb", + "name": "Bucefalo" + }, + { + "updatedAt": "2025-12-01T22:00:17.627Z", + "createdAt": "2025-12-01T22:00:17.627Z", + "id": "uIWgqVuJXbqA7m39", + "name": "PRUEBAS" + } + ], + "activeVersion": { + "updatedAt": "2026-05-31T01:01:08.989Z", + "createdAt": "2026-05-31T01:01:08.989Z", + "versionId": "e8f6f33f-a308-4fa6-a7a7-2c344311b69e", + "workflowId": "ddUEORBEtZLzsQF2", + "nodes": [ + { + "parameters": { + "content": "# De Sucursal a Marca", + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -2288, + -320 + ], + "typeVersion": 1, + "id": "d91325e1-1763-486c-a2c9-8cfffddd57b0", + "name": "Sticky Note3" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Datos de Lead').item.json.Sucursal['Cuenta Bucéfalo'] }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + -912, + -320 + ], + "id": "50da84c1-c2d3-4a36-91d3-e3745edccdc6", + "name": "Obtener Info de cuenta origen - SUCURSAL", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -224, + -320 + ], + "id": "a0c27c4c-0f20-4bab-bf6b-bcdd8281a92b", + "name": "Conseguir Custom Cuenta Origen- SUCURSAL" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + }, + { + "id": "64f2add6-506c-4950-8026-c04c9547aeeb", + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -1392, + -304 + ], + "id": "d68558b5-52b8-46b1-a359-fd956c7edc09", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "57199999-2d9b-41c7-8269-5a8183ca8132", + "name": "Cliente.Cuándo necesitas el dinero", + "value": "={{ $json.body[\"¿Cuándo necesitas el dinero?\"] }}", + "type": "string" + }, + { + "id": "8060d06e-b1da-4a65-8a0a-3c21d237d77e", + "name": "Sucursal.Cuenta Bucéfalo", + "value": "={{ $json.body.location.name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -1568, + -304 + ], + "id": "d0e455ac-7ccc-4c55-bda2-1cd22092e2d2", + "name": "Datos de Lead" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -720, + -320 + ], + "id": "f0cff3ec-8464-45d4-9e64-713c36e247c6", + "name": "Datos API Cuenta Origen - SUCURSAL", + "notes": "Esta en modo prueba forzada para Queretaro" + }, + { + "parameters": { + "content": "# CUENTA ORIGEN", + "height": 240, + "width": 1744 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -976, + -384 + ], + "typeVersion": 1, + "id": "16a74d85-1e6c-4fc7-b854-580a2d3827a0", + "name": "Sticky Note" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Webhook').item.json.body.contact_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -400, + -320 + ], + "id": "92f33c6e-ee64-409d-8c60-9fbfe48b3265", + "name": "Obtener Contacto Cuenta Origen - SUCURSAL" + }, + { + "parameters": { + "content": "", + "height": 176, + "width": 608, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -336 + ], + "typeVersion": 1, + "id": "768a4001-5109-493f-b12e-e8ba5d30ec2f", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.sucursal.id }}\",\n \"key\": \"contact.sucursal\",\n \"field_value\": \"{{ $json.SUCURSAL }}\"\n },\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.tienda.id }}\",\n \"key\": \"contact.tienda\",\n \"field_value\": \"{{ $json.TIENDA }}\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 512, + -320 + ], + "id": "794ce5bb-dea0-4720-958a-1f0940c79e6d", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "8d574598-d977-4052-823a-26def39c6a64", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1776, + -304 + ], + "id": "d4312610-e978-424c-a8a0-426026c4d4f6", + "name": "Webhook", + "webhookId": "8d574598-d977-4052-823a-26def39c6a64" + }, + { + "parameters": { + "jsCode": "const customFields = $input.first().json.customFields;\n\nfunction findField(key, names) {\n let f = customFields.find(x => x.fieldKey === key);\n if (!f) {\n const wanted = names.map(n => n.toLowerCase().trim());\n f = customFields.find(x => wanted.includes((x.name || \"\").toLowerCase().trim()));\n }\n return f || null;\n}\n\nconst sucursalField = findField(\"contact.sucursal\", [\"Sucursal\"]);\nconst tiendaField = findField(\"contact.tienda\", [\"TIENDA\", \"Tienda\"]);\nconst canalField = findField(\"contact.fuente_de_posible_cliente\", [\"CANAL DE ORIGEN\", \"Canal de Origen\"]);\nconst fuenteField = findField(\"contact.fuente_de_prospecto\", [\"Fuente de Prospecto\", \"FUENTE DE PROSPECTO\"]);\n\n// createdBy.source SOLO viene del GET individual del contacto.\nconst contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json;\nconst createdBySource =\n (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) ||\n (contactResp && contactResp.createdBy && contactResp.createdBy.source) ||\n null;\nconst esUsuario = createdBySource === \"WEB_USER\" || createdBySource === \"MOBILE_USER\";\n\n// Valor ACTUAL de \"Fuente de Prospecto\" en el contacto (para reconciliar SOLO si = LEAD DIGITAL).\nconst contactCFs =\n (contactResp && contactResp.contact && contactResp.contact.customFields) ||\n (contactResp && contactResp.customFields) ||\n [];\nlet fuenteActual = null;\nif (fuenteField && fuenteField.id) {\n const hit = contactCFs.find(cf => cf.id === fuenteField.id);\n fuenteActual = hit ? (hit.value != null ? hit.value : (hit.fieldValue != null ? hit.fieldValue : null)) : null;\n}\nconst fuenteEsLeadDigital = String(fuenteActual || \"\").trim().toUpperCase() === \"LEAD DIGITAL\";\n\nreturn [\n {\n json: {\n sucursal: {\n id: sucursalField?.id ?? null,\n name: sucursalField?.name ?? null,\n fieldKey: sucursalField?.fieldKey ?? null,\n picklistOptions: sucursalField?.picklistOptions ?? [],\n },\n tienda: {\n id: tiendaField?.id ?? null,\n name: tiendaField?.name ?? null,\n fieldKey: tiendaField?.fieldKey ?? null,\n picklistOptions: tiendaField?.picklistOptions ?? [],\n },\n canal: {\n id: canalField?.id ?? null,\n name: canalField?.name ?? null,\n fieldKey: canalField?.fieldKey ?? null,\n picklistOptions: canalField?.picklistOptions ?? [],\n },\n fuente: {\n id: fuenteField?.id ?? null,\n name: fuenteField?.name ?? null,\n fieldKey: fuenteField?.fieldKey ?? null,\n picklistOptions: fuenteField?.picklistOptions ?? [],\n },\n createdBySource: createdBySource,\n esUsuario: esUsuario,\n fuenteActual: fuenteActual,\n fuenteEsLeadDigital: fuenteEsLeadDigital,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 32, + -320 + ], + "id": "5a1cfe47-862c-4d28-a4ca-57a9f8c54a7c", + "name": "Buscar \"contact.sucursal\" y \"contact.tienda\"" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 750, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7247, + "value": "={{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Name Location'] }}" + }, + { + "field": 7279, + "operator": "not_equal", + "value": "NO DIGITAL" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 256, + -320 + ], + "id": "a912fac3-25d4-492a-9648-8c472098b9ca", + "name": "Buscar Sucursal en Verificador de Sucursales", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2023-02-21" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}\",\n \"query\": \"{{ $json.contact.email }}\",\n \"limit\": 20,\n \"page\": 1\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1152, + -320 + ], + "id": "4b899380-fe4a-40ad-80bb-21ef504b30ac", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL1" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "canal-origen-esusuario", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.esUsuario }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 720, + -120 + ], + "name": "Creado por usuario (Canal de Origen)", + "id": "49879f5e-7ce8-4ced-b1a6-96df44ac2e0a" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 940, + -120 + ], + "name": "PUT Canal de Origen = SUCURSAL", + "id": "d0597a9c-aca0-40bb-98d8-bb584d2a2c3e" + }, + { + "parameters": { + "method": "POST", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1160, + -120 + ], + "name": "Tag+ sucursal", + "id": "86bfddcb-c402-413f-9d32-c55050dc470d" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"formulario\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1380, + -120 + ], + "name": "Tag- formulario", + "onError": "continueRegularOutput", + "id": "cbd3cfb2-28d0-44e1-9567-a269382497ae" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"facebook-ads\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1600, + -120 + ], + "name": "Tag- facebook-ads", + "onError": "continueRegularOutput", + "id": "19bdc2b2-9345-4294-8e59-7f35963f261d" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "fuente-es-lead-digital", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuenteEsLeadDigital }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1820, + -120 + ], + "name": "Fuente = LEAD DIGITAL (reconciliar)", + "id": "e6bc4a7a-9304-4d00-9750-4bb64a76c3d2" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2040, + -120 + ], + "name": "PUT Fuente de Prospecto = SUCURSAL", + "id": "5ef526d6-4947-4725-a3d5-f85cab931142" + } + ], + "connections": { + "Obtener Info de cuenta origen - SUCURSAL": { + "main": [ + [ + { + "node": "Datos API Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Cuenta Origen- SUCURSAL": { + "main": [ + [ + { + "node": "Buscar \"contact.sucursal\" y \"contact.tienda\"", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "Obtener Info de cuenta origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos API Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Cuenta Origen- SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Sucursal en Verificador de Sucursales": { + "main": [ + [ + { + "node": "Actualizar Contacto Cuenta Objetivo - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar \"contact.sucursal\" y \"contact.tienda\"": { + "main": [ + [ + { + "node": "Buscar Sucursal en Verificador de Sucursales", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Contacto Cuenta Objetivo - SUCURSAL": { + "main": [ + [ + { + "node": "Creado por usuario (Canal de Origen)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Creado por usuario (Canal de Origen)": { + "main": [ + [ + { + "node": "PUT Canal de Origen = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = SUCURSAL": { + "main": [ + [ + { + "node": "Tag+ sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag+ sucursal": { + "main": [ + [ + { + "node": "Tag- formulario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- formulario": { + "main": [ + [ + { + "node": "Tag- facebook-ads", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- facebook-ads": { + "main": [ + [ + { + "node": "Fuente = LEAD DIGITAL (reconciliar)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fuente = LEAD DIGITAL (reconciliar)": { + "main": [ + [ + { + "node": "PUT Fuente de Prospecto = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "authors": "Consultoria E3 ®", + "name": null, + "description": null, + "autosaved": false, + "workflowPublishHistory": [ + { + "createdAt": "2026-05-31T01:01:09.401Z", + "id": 681, + "workflowId": "ddUEORBEtZLzsQF2", + "versionId": "e8f6f33f-a308-4fa6-a7a7-2c344311b69e", + "event": "activated", + "userId": "74a7f127-b963-4eb0-aff4-1190428e8409" + } + ] + } +} \ No newline at end of file diff --git a/n8n/dryrun_Cfgwp0bOtDW8zuKW_20260530_163623.json b/n8n/dryrun_Cfgwp0bOtDW8zuKW_20260530_163623.json new file mode 100644 index 0000000..5575b07 --- /dev/null +++ b/n8n/dryrun_Cfgwp0bOtDW8zuKW_20260530_163623.json @@ -0,0 +1,2033 @@ +{ + "name": "Sincronizar Oportunidad - Nodos Nuevos (Create/Update)", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "3eecb45a-e24c-49ac-aef7-99843f485819", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -336, + -16 + ], + "id": "dd424583-8295-4013-96ba-c7e29ea944c6", + "name": "Webhook", + "webhookId": "3eecb45a-e24c-49ac-aef7-99843f485819" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "b02bc821-6121-452d-a042-623fac6e443c", + "name": "Oportunidad.opportunity_name", + "value": "={{ $json.body.opportunity_name }}", + "type": "string" + }, + { + "id": "898817a2-7926-4ddf-b785-cac8e28afc58", + "name": "Oportunidad.pipleline_stage (new)", + "value": "={{ $json.body.pipleline_stage }}", + "type": "string" + }, + { + "id": "opp-id-sucursal-capture", + "name": "Oportunidad.opportunity_id", + "value": "={{ $json.body.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -96, + -16 + ], + "id": "2b3ec130-27ce-4988-82db-46e4f12c3f7d", + "name": "Datos de Lead" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE SUCURSAL", + "height": 352, + "width": 1152 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1072, + -144 + ], + "typeVersion": 1, + "id": "19023e33-a9f1-4c60-b87c-957e751e3dc2", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE MARCA", + "height": 208, + "width": 1188, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2240, + -80 + ], + "typeVersion": 1, + "id": "3a3d4f18-1a08-4dbc-a00b-7ae6bc4d49d1", + "name": "Sticky Note1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2480, + -32 + ], + "id": "a46df506-6a9a-420d-9cdd-6fc51d1a691e", + "name": "DATOS API" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 80, + -16 + ], + "id": "25db982f-7bc7-4318-a7b0-e94326abc437", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3952, + -32 + ], + "id": "2320b102-816e-4a0c-ae9c-e305d25df6df", + "name": "Actualizar Oportunidad - MARCA2", + "notes": "El código mapea los custom fields de una oportunidad de una sucursal hacia los IDs equivalentes de una marca. Toma tres fuentes: los metadatos de campos de la sucursal, los valores reales de esos campos en la oportunidad, y los metadatos de campos de la marca. Para cada campo con valor en la oportunidad, primero busca su `fieldKey` en los metadatos de la sucursal usando el `id`, luego usa ese `fieldKey` para encontrar el campo equivalente en la marca, y construye un array con el `id` y `key` de la marca pero con el valor original de la sucursal. Finalmente arma el body de una petición para crear/actualizar una oportunidad en la marca, reutilizando pipeline, nombre, stage y valor monetario de otros nodos, con los custom fields ya traducidos al contexto de la marca." + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1536, + -32 + ], + "id": "4e89658b-7ffd-4a3b-b91e-9ed3223a1a73", + "name": "Buscar Oportunidad - SUCURSAL", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 528, + -32 + ], + "id": "b721a65d-bcbb-44ab-bb7f-8f200b34fcdd", + "name": "DATOS API - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1136, + -32 + ], + "id": "39ef9dcc-ac20-4ba2-b891-2a2baa6ea4f5", + "name": "Conseguir Custom Fields - Opportunity - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1712, + -32 + ], + "id": "fbd46ae4-792b-451b-b99e-9816dc175d52", + "name": "Obtener info de Oportunidad - SUCURSAL" + }, + { + "parameters": { + "content": "# ACTUALIZAR OPORTUNIDAD - MARCA", + "height": 352, + "width": 928 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3456, + -144 + ], + "typeVersion": 1, + "id": "f3ca1cfe-08e2-485b-b238-9240f5753809", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "", + "height": 208, + "width": 992, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2768, + -64 + ], + "typeVersion": 1, + "id": "45066721-8404-48ed-94c1-c88edf48bca5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "", + "height": 192, + "width": 928, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1104, + -64 + ], + "typeVersion": 1, + "id": "b1646bb4-8b64-4bf4-b380-189cc7902741", + "name": "Sticky Note7" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Webhook').item.json.body.location.name }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 320, + -32 + ], + "id": "12088f29-8bb4-475d-ae0e-2cbdf8033138", + "name": "API de Sucursal", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "=Monte Providencia" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 2288, + -32 + ], + "id": "8c8fa238-6ce4-4adf-8926-b603987a55cf", + "name": "API de MARCA", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "jsCode": "const opportunityData = $('Obtener info de Oportunidad - SUCURSAL').first().json;\nconst opportunity = opportunityData.opportunity;\nconst customFieldsDefs = $('Conseguir Custom Fields - Opportunity - SUCURSAL').first().json.customFields;\nconst pipelines = $input.first().json.pipelines;\n\n// Map custom fields: id -> { fieldKey, name }\nconst fieldMap = {};\nfor (const def of customFieldsDefs) {\n fieldMap[def.id] = { fieldKey: def.fieldKey, name: def.name };\n}\n\n// Map pipelines: id -> name, stages: id -> { name, position, stageWinProbability }\nconst pipelineMap = {};\nconst stageMap = {};\nfor (const pipeline of pipelines) {\n pipelineMap[pipeline.id] = pipeline.name;\n for (const stage of pipeline.stages) {\n stageMap[stage.id] = {\n name: stage.name,\n position: stage.position,\n stageWinProbability: stage.stageWinProbability,\n color: stage.color || null\n };\n }\n}\n\n// Enrich custom fields with fieldKey and name\nconst enrichedCustomFields = opportunity.customFields.map(cf => ({\n id: cf.id,\n name: fieldMap[cf.id]?.name || null,\n fieldKey: fieldMap[cf.id]?.fieldKey || null,\n fieldValue: cf.fieldValue\n}));\n\n\n// ── CONTACT->OPP ENRICH: garantizar Sucursal / TIENDA / Canal de Origen ──\n// Respaldo cuando la opp de sucursal no trae el CF: tomar el valor del CONTACTO\n// (siempre poblado por [1604]/[2004]) y, en ultimo caso, del webhook. El fieldKey\n// canonico (sufijo) es identico en contact y opportunity.\nconst contactResp = $('Obtener Contacto - SUCURSAL').first().json;\nconst contact = (contactResp && contactResp.contact) ? contactResp.contact : (contactResp || {});\nconst webhookBody = ($('Webhook').first().json && $('Webhook').first().json.body) || {};\nconst norm2 = (s) => (s == null ? '' : String(s)).trim();\n\n// Valores del contacto por fieldKey (contacts traen {id, value}).\nconst contactValueByFieldKey = {};\nfor (const cf of (contact.customFields || [])) {\n const fk = fieldMap[cf.id] ? fieldMap[cf.id].fieldKey : null;\n if (fk) contactValueByFieldKey[fk] = cf.value;\n}\n\nconst ENRICH_TARGETS = [\n { oppKey: 'opportunity.sucursal', contactKey: 'contact.sucursal', webhookKey: 'Sucursal' },\n { oppKey: 'opportunity.tienda', contactKey: 'contact.tienda', webhookKey: 'TIENDA' },\n { oppKey: 'opportunity.fuente_de_posible_cliente', contactKey: 'contact.fuente_de_posible_cliente', webhookKey: 'CANAL DE ORIGEN' },\n];\n\nfor (const t of ENRICH_TARGETS) {\n const existing = enrichedCustomFields.find(cf => cf.fieldKey === t.oppKey);\n if (existing && norm2(existing.fieldValue) !== '') continue; // (a) ya viene de la opp\n let value = norm2(contactValueByFieldKey[t.contactKey]); // (b) contacto\n if (value === '') value = norm2(webhookBody[t.webhookKey]); // (c) webhook\n if (value === '') continue;\n if (existing) {\n existing.fieldValue = value;\n } else {\n const def = customFieldsDefs.find(d => d.fieldKey === t.oppKey);\n enrichedCustomFields.push({ id: def ? def.id : null, name: def ? def.name : null, fieldKey: t.oppKey, fieldValue: value });\n }\n}\n// ── fin CONTACT->OPP ENRICH ──\n\n// Resolve pipeline and stage info\nconst stageInfo = stageMap[opportunity.pipelineStageId] || {};\n\n// Build enriched opportunity\nconst enrichedOpportunity = {\n ...opportunity,\n pipelineName: pipelineMap[opportunity.pipelineId] || null,\n pipelineStageName: stageInfo.name || null,\n pipelineStagePosition: stageInfo.position ?? null,\n pipelineStageWinProbability: stageInfo.stageWinProbability ?? null,\n pipelineStageColor: stageInfo.color || null,\n customFields: enrichedCustomFields\n};\n\nreturn [{ json: { opportunity: enrichedOpportunity, traceId: opportunityData.traceId } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + -32 + ], + "id": "d9609dd9-9493-4f3d-b356-99438cedada6", + "name": "Mapeo completo oportunidad origen - SUCURSAL" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('Webhook').item.json.body.location.id }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de Sucursal').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1872, + -32 + ], + "id": "2d799ae3-b6b1-4e4f-8774-714ebe4f009f", + "name": "Obtener Pipelines - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Buscar Oportunidad - MARCA').item.json.opportunities[0].id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3216, + -32 + ], + "id": "1eee43d2-c6a8-414b-bebf-4b31d6fb34d7", + "name": "Obtener info de Oportunidad - MARCA" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('API de MARCA').item.json.Location_ID }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de MARCA').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3424, + -32 + ], + "id": "20a1a288-2cd8-471b-9203-5ba88770edb0", + "name": "Obtener Pipelines - MARCA" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2800, + -32 + ], + "id": "798a7e51-b8ed-4364-947a-7396fab47940", + "name": "Buscar Oportunidad - MARCA", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3008, + -32 + ], + "id": "cc37ea9b-0a9f-44ce-8ce2-ee2444822ecf", + "name": "Conseguir Custom Fields - Opportunity - MARCA" + }, + { + "parameters": { + "jsCode": "// ── DATOS MARCA ──────────────────────────────────────────────────────────────\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA').first().json.customFields;\nconst marcaPipelines = $input.first().json.pipelines;\n\n// ── DATOS SUCURSAL (ya enriquecidos) ─────────────────────────────────────────\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\n\n// ── MAP PIPELINES MARCA: name -> { id, stages } ───────────────────────────────\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[pipeline.name.trim()] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[stage.name.trim()] = stage.id;\n }\n}\n\n// ── RESOLVER PIPELINE Y STAGE POR NAME DESDE SUCURSAL ────────────────────────\nconst resolvedPipelineId = marcaPipelineByName[sucursalOpp.pipelineName?.trim()] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[sucursalOpp.pipelineStageName?.trim()] || marcaOpp.pipelineStageId;\n\n// ── MAP CUSTOM FIELDS SUCURSAL: name -> fieldValue ────────────────────────────\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields) {\n if (cf.name) {\n sucursalValueByName[cf.name.trim()] = cf.fieldValue;\n }\n}\n\n// ── CONSTRUIR CUSTOM FIELDS PARA EL BODY ─────────────────────────────────────\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n const nameKey = def.name.trim();\n const sucursalValue = sucursalValueByName[nameKey];\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n const fieldValue = sucursalValue !== undefined ? sucursalValue : (marcaOriginal?.fieldValue ?? null);\n\n if (fieldValue === null) continue;\n\n customFields.push({\n id: def.id,\n key: def.fieldKey,\n field_value: fieldValue\n });\n}\n\n// ── CONSTRUIR BODY VÁLIDO PARA UPDATE OPPORTUNITY ────────────────────────────\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source,\n customFields: customFields\n};\n\nreturn [{ json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body)\n} }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3632, + -32 + ], + "id": "3b547c2a-a541-4b48-8d97-ea2d5df52010", + "name": "Code in JavaScript" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"pageLimit\": 10,\n \"query\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono || $('Datos de Lead').item.json.Cliente.Email }}\"\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3360, + 960 + ], + "id": "02cd0fd6-6bb8-4faa-b85f-5d42663fba29", + "name": "Buscar Contacto en MARCA", + "notes": "Busca contacto en la location de marca por email.\nSi no encuentra, la siguiente IF deriva a creación." + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "exist-contact-check", + "leftValue": "={{ $json.contacts ? $json.contacts.length : 0 }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3600, + 960 + ], + "id": "aa2644dc-34b4-4159-8afb-89147b12f5ee", + "name": "¿Existe contacto en MARCA?" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/upsert", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"firstName\": \"{{ $('Datos de Lead').item.json.Cliente.Apellido }}\",\n \"name\": \"{{ $('Datos de Lead').item.json.Cliente['Nombre Completo'] }}\",\n \"email\": \"{{ $('Datos de Lead').item.json.Cliente.Email }}\",\n \"phone\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono }}\",\n \"source\": \"Sincronización Sucursal\",\n \"tags\": [\"sincronizado-sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3840, + 1104 + ], + "id": "f6c75352-3b9e-4fc9-b8e4-063c2792d190", + "name": "Crear Contacto en MARCA", + "notes": "Crea contacto en la marca solo si no existía.\nIMPORTANTE: revisa el mapeo de Nombre/Apellido, en el workflow original están invertidos." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "contact-id-resolved", + "name": "contactId", + "value": "={{ $json.contact ? $json.contact.id : $json.contacts[0].id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 4080, + 960 + ], + "id": "2a49d01e-6679-4584-9d9b-5778dfc4431e", + "name": "Set Contact ID Resuelto", + "notes": "Unifica el contactId tanto si vino de la búsqueda (contacts[0].id) como de la creación (contact.id)." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "location_id", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + }, + { + "name": "contact_id", + "value": "={{ $('Set Contact ID Resuelto').item.json.contactId }}" + }, + { + "name": "limit", + "value": "100" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 4320, + 960 + ], + "id": "8e25f660-3ed2-4597-9c2a-7480ac7d150f", + "name": "Buscar Oportunidades del Contacto - MARCA", + "notes": "Busca TODAS las oportunidades del contacto en la marca.\nDespués el Code de decisión elige cuál (o crea nueva)." + }, + { + "parameters": { + "jsCode": "// DECISION (Create vs Update) con idempotencia GLOBAL via mapeo Baserow (tabla 754).\n// (1) Si el mapeo id_opp_sucursal -> id_opp_marca existe -> UPDATE esa opp de Marca,\n// independiente del contacto resuelto (evita duplicados por contacto ambiguo).\n// (2) Si no hay mapeo, match por CF entre las opps del contacto resuelto.\n// (3) Si nada -> CREATE.\nconst result = $input.first().json;\nconst opportunities = result.opportunities || [];\nconst sucursalOppId = $('Datos de Lead').first().json.Oportunidad.opportunity_id;\n\nlet action = 'CREATE';\nlet opportunityId = null;\nlet matchReason = '';\n\n// (1) Mapeo Baserow global\nlet baserowRows = [];\ntry { baserowRows = $('Buscar Mapeo Opp - Baserow').all().map(i => i.json); } catch (e) { baserowRows = []; }\nconst mapped = baserowRows.find(r =>\n String(r.id_opp_sucursal || '') === String(sucursalOppId) && String(r.id_opp_marca || '').trim()\n);\n\nif (mapped) {\n action = 'UPDATE';\n opportunityId = String(mapped.id_opp_marca).trim();\n matchReason = 'Match por mapeo Baserow (global, tabla 754)';\n} else {\n // (2) match por CF entre las opps del contacto resuelto\n const match = opportunities.find(o =>\n (o.customFields || []).some(cf => {\n const v = cf.fieldValueString ?? cf.fieldValue ?? '';\n return String(v) === String(sucursalOppId);\n })\n );\n if (match) {\n action = 'UPDATE';\n opportunityId = match.id;\n matchReason = 'Match por ID Oportunidad Sucursal (contacto)';\n } else {\n action = 'CREATE';\n matchReason = 'Sin match (Baserow ni contacto) -> CREATE (contacto tiene ' + opportunities.length + ' opps)';\n }\n}\n\nreturn [{\n json: {\n action,\n opportunityId,\n matchReason,\n sucursalOppId,\n contactId: $('Set Contact ID Resuelto').first().json.contactId,\n totalOppsContacto: opportunities.length\n }\n}];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4560, + 960 + ], + "id": "6ca30f13-8503-427f-95ec-b243d30688ec", + "name": "Decidir Match (Create vs Update)" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-update", + "leftValue": "={{ $json.action }}", + "rightValue": "UPDATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "UPDATE" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-create", + "leftValue": "={{ $json.action }}", + "rightValue": "CREATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "CREATE" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 4800, + 960 + ], + "id": "bff7a5dc-b4af-4033-83ea-c508aa92264a", + "name": "Switch UPDATE vs CREATE" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Decidir Match (Create vs Update)').item.json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5040, + 816 + ], + "id": "83cd0fd7-1ee9-4896-926e-a0b752e216fe", + "name": "Obtener info de Oportunidad - MARCA (v2)", + "notes": "Sólo se ejecuta en rama UPDATE.\nReemplaza el nodo original 'Obtener info de Oportunidad - MARCA' que dependía de búsqueda por nombre." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 1104 + ], + "id": "0fb2fdea-cf40-4dfb-abfa-c422282e5d2a", + "name": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 1104 + ], + "id": "58114758-f0fb-483a-bd5b-d28188880a02", + "name": "Obtener Pipelines - MARCA (CREATE)" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA CREAR OPORTUNIDAD EN MARCA ──────────────────────\n// Mapea custom fields por fieldKey (fallback name) y FIJA el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia futura).\n\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (CREATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (CREATE)').first().json.pipelines || [];\nconst locationId = $('DATOS API').first().json['Location ID'];\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst contactId = decision.contactId;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\n// Map pipelines/stages de la marca por nombre normalizado\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)];\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)];\n\nif (!resolvedPipelineId) {\n throw new Error(`No se encontró pipeline en marca: \"${sucursalOpp.pipelineName}\". Revisa que el nombre coincida.`);\n}\nif (!resolvedStageId) {\n throw new Error(`No se encontró stage en marca: \"${sucursalOpp.pipelineStageName}\". Revisa que el nombre coincida.`);\n}\n\n// Mapear custom fields: preferir fieldKey, fallback a name normalizado\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null || value === '') continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// FIJAR el campo de enlace \"ID Oportunidad Sucursal\" = id de la opp de sucursal\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n locationId: locationId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || 'open',\n contactId: contactId,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source || 'Sincronización Sucursal',\n customFields: customFields\n};\n\nreturn [{\n json: {\n body: JSON.stringify(body),\n debug: { resolvedPipelineId, resolvedStageId, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5520, + 1104 + ], + "id": "2f9a63e7-277d-49de-8838-a00922c993b2", + "name": "Armar Body - CREATE" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5760, + 1104 + ], + "id": "fc4b3398-91bb-4dd9-b406-96c8d75a17be", + "name": "Crear Oportunidad - MARCA" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA ACTUALIZAR OPORTUNIDAD EN MARCA (v2) ────────────\n// Mapea CF por fieldKey (fallback name) y reafirma el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia).\n\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA (v2)').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (UPDATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (UPDATE)').first().json.pipelines || [];\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)] || marcaOpp.pipelineStageId;\n\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null) {\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n value = marcaOriginal?.fieldValue;\n }\n if (value === undefined || value === null) continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// Reafirmar el campo de enlace\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || marcaOpp.status,\n monetaryValue: sucursalOpp.monetaryValue ?? marcaOpp.monetaryValue,\n source: sucursalOpp.source || marcaOpp.source,\n customFields: customFields\n};\n\nreturn [{\n json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body),\n debug: { matchReason: decision.matchReason, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5280, + 816 + ], + "id": "969e46cd-bbae-4372-9808-c939f63e3c02", + "name": "Armar Body - UPDATE (v2)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5520, + 816 + ], + "id": "83929e08-7afd-4670-9c18-be22b8c3656f", + "name": "Actualizar Oportunidad - MARCA (v2)" + }, + { + "parameters": { + "content": "# RAMA UPDATE\n## Oportunidad ya existe en marca → se actualiza", + "height": 280, + "width": 880, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 720 + ], + "typeVersion": 1, + "id": "1c285234-8d90-49ef-8dac-c98cb379299a", + "name": "Sticky Update" + }, + { + "parameters": { + "content": "# RAMA CREATE\n## Oportunidad no existe en marca → se crea desde cero\n### Incluye contacto + custom fields + tag origin-id", + "height": 280, + "width": 1120, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 1024 + ], + "typeVersion": 1, + "id": "7e8f350c-9562-485b-8ce5-1b13b4086856", + "name": "Sticky Create" + }, + { + "parameters": { + "content": "# MATCHING ROBUSTO\n## 1. Buscar/crear contacto en marca\n## 2. Buscar oportunidades DEL CONTACTO\n## 3. Decidir match por cantidad + tag origin-id", + "height": 320, + "width": 1500, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3328, + 864 + ], + "typeVersion": 1, + "id": "8a583e46-41b6-44e0-9313-b70ee085b740", + "name": "Sticky Matching" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 560 + ], + "id": "45f0a6b0-f44d-4e83-b2ab-cdd3fbfe7911", + "name": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 560 + ], + "id": "5ca962a9-9e56-42d1-af05-ce05d39ed77f", + "name": "Obtener Pipelines - MARCA (UPDATE)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ customFields: [ { id: $('Conseguir Custom Fields - Opportunity - SUCURSAL').item.json.customFields.find(f => f.fieldKey === 'opportunity.id_oportunidad_sucursal')?.id, key: 'opportunity.id_oportunidad_sucursal', field_value: $('Datos de Lead').item.json.Oportunidad.opportunity_id } ] }) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1328, + -32 + ], + "id": "a9572dee-ed57-4104-966a-4b22721e75f1", + "name": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Auto-mapeo de seguridad: setea 'ID Oportunidad Sucursal' = id nativo de la propia opp de sucursal (del webhook) antes de procesar. onError=continue para no romper el sync si falla." + }, + { + "parameters": { + "databaseId": 63, + "tableId": 754, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7280, + "value": "={{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 4208, + 1152 + ], + "id": "a64fdffd-ab8f-4ddd-a4d4-4d683a05132d", + "name": "Buscar Mapeo Opp - Baserow", + "alwaysOutputData": true, + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Idempotencia global: busca en la tabla 754 el mapeo id_opp_sucursal->id_opp_marca. Si existe, 'Decidir Match' hara UPDATE de esa opp aunque el contacto resuelto sea otro. alwaysOutputData + onError para no romper el flujo si no hay match o si Baserow falla." + }, + { + "parameters": { + "jsCode": "// Prepara el UPSERT del mapeo Baserow (tabla 754) tras CREATE/UPDATE de la opp en Marca.\n// Solo escribe cuando el mapeo NO existe aun:\n// - action CREATE -> la fila no existe (el lookup Baserow fallo) -> crear.\n// - action UPDATE por contacto -> Baserow no la tenia -> crear (idempotencia futura).\n// - action UPDATE por Baserow -> la fila YA existe -> NO escribir (no duplicar).\n// Asi una re-ejecucion del mismo opp encuentra el mapeo y hace UPDATE, no CREATE duplicado.\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst action = decision.action;\nconst matchReason = decision.matchReason || '';\nconst sucursalOppId = decision.sucursalOppId;\n\nconst matchedByBaserow = matchReason.indexOf('Baserow') !== -1;\nconst needWrite = (action === 'CREATE') || (action === 'UPDATE' && !matchedByBaserow);\nif (!needWrite) return [];\n\n// id de la opp en Marca segun el camino\nlet marcaOppId = decision.opportunityId;\nif (action === 'CREATE') {\n try {\n const co = $('Crear Oportunidad - MARCA').first().json;\n marcaOppId = (co.opportunity && co.opportunity.id) || co.id || marcaOppId;\n } catch (e) {}\n}\nif (!marcaOppId || !sucursalOppId) return [];\n\nlet locationSucursal = '';\ntry { locationSucursal = $('DATOS API - SUCURSAL').first().json['Location ID'] || ''; } catch (e) {}\n\nreturn [{ json: { sucursalOppId, marcaOppId, locationSucursal } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 6048, + 960 + ], + "id": "6542723e-f7b2-41de-b371-9e793d48f560", + "name": "Preparar Upsert Mapeo", + "onError": "continueRegularOutput", + "notes": "Decide si escribir el mapeo Baserow tras CREATE/UPDATE. Solo cuando la fila no existe (CREATE o UPDATE-por-contacto). onError continue para no romper la replicacion." + }, + { + "parameters": { + "operation": "create", + "databaseId": 63, + "tableId": 754, + "fieldsUi": { + "fieldValues": [ + { + "fieldId": 7280, + "fieldValue": "={{ $json.sucursalOppId }}" + }, + { + "fieldId": 7283, + "fieldValue": "={{ $json.marcaOppId }}" + }, + { + "fieldId": 7284, + "fieldValue": "={{ $json.locationSucursal }}" + }, + { + "fieldId": 7285, + "fieldValue": "={{ $now.toISO() }}" + } + ] + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + 6288, + 960 + ], + "id": "c69036dd-3914-4424-a165-6e0f3b110ebd", + "name": "Crear Mapeo - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Inserta el mapeo id_opp_sucursal->id_opp_marca en la tabla 754 en tiempo real. Idempotente con el backfill (verifica por id_opp_sucursal)." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1750, + -32 + ], + "name": "Obtener Contacto - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Trae el contacto de sucursal para derivar Sucursal/TIENDA/Canal de Origen de la opp cuando la opp de origen no los trae. onError=continue para no romper el sync.", + "id": "ef9aad0d-3139-4348-9237-9219ed11a2df" + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API": { + "main": [ + [ + { + "node": "Buscar Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "API de Sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - SUCURSAL": { + "main": [ + [ + { + "node": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de Sucursal": { + "main": [ + [ + { + "node": "DATOS API - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de MARCA": { + "main": [ + [ + { + "node": "DATOS API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapeo completo oportunidad origen - SUCURSAL": { + "main": [ + [ + { + "node": "API de MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - SUCURSAL": { + "main": [ + [ + { + "node": "Mapeo completo oportunidad origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contacto en MARCA": { + "main": [ + [ + { + "node": "¿Existe contacto en MARCA?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Existe contacto en MARCA?": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Crear Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Contacto en MARCA": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Contact ID Resuelto": { + "main": [ + [ + { + "node": "Buscar Mapeo Opp - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidades del Contacto - MARCA": { + "main": [ + [ + { + "node": "Decidir Match (Create vs Update)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Decidir Match (Create vs Update)": { + "main": [ + [ + { + "node": "Switch UPDATE vs CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch UPDATE vs CREATE": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - UPDATE (v2)": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Armar Body - CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - CREATE": { + "main": [ + [ + { + "node": "Crear Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Armar Body - UPDATE (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapear ID Oportunidad Sucursal - SUCURSAL": { + "main": [ + [ + { + "node": "Buscar Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Mapeo Opp - Baserow": { + "main": [ + [ + { + "node": "Buscar Oportunidades del Contacto - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Upsert Mapeo": { + "main": [ + [ + { + "node": "Crear Mapeo - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Pipelines - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1" + } +} \ No newline at end of file diff --git a/n8n/dryrun_Cfgwp0bOtDW8zuKW_20260530_165900.json b/n8n/dryrun_Cfgwp0bOtDW8zuKW_20260530_165900.json new file mode 100644 index 0000000..cec2a7c --- /dev/null +++ b/n8n/dryrun_Cfgwp0bOtDW8zuKW_20260530_165900.json @@ -0,0 +1,2041 @@ +{ + "name": "Sincronizar Oportunidad - Nodos Nuevos (Create/Update)", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "3eecb45a-e24c-49ac-aef7-99843f485819", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -336, + -16 + ], + "id": "dd424583-8295-4013-96ba-c7e29ea944c6", + "name": "Webhook", + "webhookId": "3eecb45a-e24c-49ac-aef7-99843f485819" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "b02bc821-6121-452d-a042-623fac6e443c", + "name": "Oportunidad.opportunity_name", + "value": "={{ $json.body.opportunity_name }}", + "type": "string" + }, + { + "id": "898817a2-7926-4ddf-b785-cac8e28afc58", + "name": "Oportunidad.pipleline_stage (new)", + "value": "={{ $json.body.pipleline_stage }}", + "type": "string" + }, + { + "id": "opp-id-sucursal-capture", + "name": "Oportunidad.opportunity_id", + "value": "={{ $json.body.id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -96, + -16 + ], + "id": "2b3ec130-27ce-4988-82db-46e4f12c3f7d", + "name": "Datos de Lead" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE SUCURSAL", + "height": 352, + "width": 1152 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1072, + -144 + ], + "typeVersion": 1, + "id": "19023e33-a9f1-4c60-b87c-957e751e3dc2", + "name": "Sticky Note" + }, + { + "parameters": { + "content": "# OBTENER DATOS DE MARCA", + "height": 208, + "width": 1188, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2240, + -80 + ], + "typeVersion": 1, + "id": "3a3d4f18-1a08-4dbc-a00b-7ae6bc4d49d1", + "name": "Sticky Note1" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 2480, + -32 + ], + "id": "a46df506-6a9a-420d-9cdd-6fc51d1a691e", + "name": "DATOS API" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 80, + -16 + ], + "id": "25db982f-7bc7-4318-a7b0-e94326abc437", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3952, + -32 + ], + "id": "2320b102-816e-4a0c-ae9c-e305d25df6df", + "name": "Actualizar Oportunidad - MARCA2", + "notes": "El código mapea los custom fields de una oportunidad de una sucursal hacia los IDs equivalentes de una marca. Toma tres fuentes: los metadatos de campos de la sucursal, los valores reales de esos campos en la oportunidad, y los metadatos de campos de la marca. Para cada campo con valor en la oportunidad, primero busca su `fieldKey` en los metadatos de la sucursal usando el `id`, luego usa ese `fieldKey` para encontrar el campo equivalente en la marca, y construye un array con el `id` y `key` de la marca pero con el valor original de la sucursal. Finalmente arma el body de una petición para crear/actualizar una oportunidad en la marca, reutilizando pipeline, nombre, stage y valor monetario de otros nodos, con los custom fields ya traducidos al contexto de la marca." + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1536, + -32 + ], + "id": "4e89658b-7ffd-4a3b-b91e-9ed3223a1a73", + "name": "Buscar Oportunidad - SUCURSAL", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 528, + -32 + ], + "id": "b721a65d-bcbb-44ab-bb7f-8f200b34fcdd", + "name": "DATOS API - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + }, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "all" + } + ] + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1136, + -32 + ], + "id": "39ef9dcc-ac20-4ba2-b891-2a2baa6ea4f5", + "name": "Conseguir Custom Fields - Opportunity - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1712, + -32 + ], + "id": "fbd46ae4-792b-451b-b99e-9816dc175d52", + "name": "Obtener info de Oportunidad - SUCURSAL" + }, + { + "parameters": { + "content": "# ACTUALIZAR OPORTUNIDAD - MARCA", + "height": 352, + "width": 928 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3456, + -144 + ], + "typeVersion": 1, + "id": "f3ca1cfe-08e2-485b-b238-9240f5753809", + "name": "Sticky Note2" + }, + { + "parameters": { + "content": "", + "height": 208, + "width": 992, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 2768, + -64 + ], + "typeVersion": 1, + "id": "45066721-8404-48ed-94c1-c88edf48bca5", + "name": "Sticky Note5" + }, + { + "parameters": { + "content": "", + "height": 192, + "width": 928, + "color": 3 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 1104, + -64 + ], + "typeVersion": 1, + "id": "b1646bb4-8b64-4bf4-b380-189cc7902741", + "name": "Sticky Note7" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Webhook').item.json.body.location.name }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 320, + -32 + ], + "id": "12088f29-8bb4-475d-ae0e-2cbdf8033138", + "name": "API de Sucursal", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "=Monte Providencia" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 2288, + -32 + ], + "id": "8c8fa238-6ce4-4adf-8926-b603987a55cf", + "name": "API de MARCA", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "jsCode": "const opportunityData = $('Obtener info de Oportunidad - SUCURSAL').first().json;\nconst opportunity = opportunityData.opportunity;\nconst customFieldsDefs = $('Conseguir Custom Fields - Opportunity - SUCURSAL').first().json.customFields;\nconst pipelines = $input.first().json.pipelines;\n\n// Map custom fields: id -> { fieldKey, name }\nconst fieldMap = {};\nfor (const def of customFieldsDefs) {\n fieldMap[def.id] = { fieldKey: def.fieldKey, name: def.name };\n}\n\n// Map pipelines: id -> name, stages: id -> { name, position, stageWinProbability }\nconst pipelineMap = {};\nconst stageMap = {};\nfor (const pipeline of pipelines) {\n pipelineMap[pipeline.id] = pipeline.name;\n for (const stage of pipeline.stages) {\n stageMap[stage.id] = {\n name: stage.name,\n position: stage.position,\n stageWinProbability: stage.stageWinProbability,\n color: stage.color || null\n };\n }\n}\n\n// Enrich custom fields with fieldKey and name\nconst enrichedCustomFields = opportunity.customFields.map(cf => ({\n id: cf.id,\n name: fieldMap[cf.id]?.name || null,\n fieldKey: fieldMap[cf.id]?.fieldKey || null,\n fieldValue: cf.fieldValue\n}));\n\n\n// ── CONTACT->OPP ENRICH: garantizar Sucursal / TIENDA / Canal de Origen ──\n// Respaldo cuando la opp de sucursal no trae el CF: tomar el valor del CONTACTO\n// (siempre poblado por [1604]/[2004]) y, en ultimo caso, del webhook. El fieldKey\n// canonico (sufijo) es identico en contact y opportunity.\nconst contactResp = $('Obtener Contacto - SUCURSAL').first().json;\nconst contact = (contactResp && contactResp.contact) ? contactResp.contact : (contactResp || {});\nconst webhookBody = ($('Webhook').first().json && $('Webhook').first().json.body) || {};\nconst norm2 = (s) => (s == null ? '' : String(s)).trim();\n\n// Valores del contacto por fieldKey (contacts traen {id, value}).\nconst contactValueByFieldKey = {};\nfor (const cf of (contact.customFields || [])) {\n const fk = fieldMap[cf.id] ? fieldMap[cf.id].fieldKey : null;\n if (fk) contactValueByFieldKey[fk] = cf.value;\n}\n\nconst ENRICH_TARGETS = [\n { oppKey: 'opportunity.sucursal', contactKey: 'contact.sucursal', webhookKey: 'Sucursal' },\n { oppKey: 'opportunity.tienda', contactKey: 'contact.tienda', webhookKey: 'TIENDA' },\n { oppKey: 'opportunity.fuente_de_posible_cliente', contactKey: 'contact.fuente_de_posible_cliente', webhookKey: 'CANAL DE ORIGEN' },\n];\n\nfor (const t of ENRICH_TARGETS) {\n const existing = enrichedCustomFields.find(cf => cf.fieldKey === t.oppKey);\n if (existing && norm2(existing.fieldValue) !== '') continue; // (a) ya viene de la opp\n let value = norm2(contactValueByFieldKey[t.contactKey]); // (b) contacto\n if (value === '') value = norm2(webhookBody[t.webhookKey]); // (c) webhook\n if (value === '') continue;\n if (existing) {\n existing.fieldValue = value;\n } else {\n const def = customFieldsDefs.find(d => d.fieldKey === t.oppKey);\n enrichedCustomFields.push({ id: def ? def.id : null, name: def ? def.name : null, fieldKey: t.oppKey, fieldValue: value });\n }\n}\n// ── fin CONTACT->OPP ENRICH ──\n\n// Resolve pipeline and stage info\nconst stageInfo = stageMap[opportunity.pipelineStageId] || {};\n\n// Build enriched opportunity\nconst enrichedOpportunity = {\n ...opportunity,\n pipelineName: pipelineMap[opportunity.pipelineId] || null,\n pipelineStageName: stageInfo.name || null,\n pipelineStagePosition: stageInfo.position ?? null,\n pipelineStageWinProbability: stageInfo.stageWinProbability ?? null,\n pipelineStageColor: stageInfo.color || null,\n customFields: enrichedCustomFields\n};\n\nreturn [{ json: { opportunity: enrichedOpportunity, traceId: opportunityData.traceId } }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 2080, + -32 + ], + "id": "d9609dd9-9493-4f3d-b356-99438cedada6", + "name": "Mapeo completo oportunidad origen - SUCURSAL" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('Webhook').item.json.body.location.id }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de Sucursal').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 1872, + -32 + ], + "id": "2d799ae3-b6b1-4e4f-8774-714ebe4f009f", + "name": "Obtener Pipelines - SUCURSAL" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Buscar Oportunidad - MARCA').item.json.opportunities[0].id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3216, + -32 + ], + "id": "1eee43d2-c6a8-414b-bebf-4b31d6fb34d7", + "name": "Obtener info de Oportunidad - MARCA" + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('API de MARCA').item.json.Location_ID }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('API de MARCA').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + {} + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 3424, + -32 + ], + "id": "20a1a288-2cd8-471b-9203-5ba88770edb0", + "name": "Obtener Pipelines - MARCA" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $json['Location ID'] }}\",\n \"query\": \"{{ $('Datos de Lead').item.json.Oportunidad.opportunity_name }}\",\n \"limit\": 1,\n \"page\": 0\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2800, + -32 + ], + "id": "798a7e51-b8ed-4364-947a-7396fab47940", + "name": "Buscar Oportunidad - MARCA", + "notes": "NECESITA MEJORAS\n\nActualmente solo hace busqueda con base al NOMBRE DE OPORTUNIDAD.\n\nSe puede optimizar la busqueda futura añadiendo más parametros como valor de oportunidad o datos que sean 100% seguros e inmutables." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3008, + -32 + ], + "id": "cc37ea9b-0a9f-44ce-8ce2-ee2444822ecf", + "name": "Conseguir Custom Fields - Opportunity - MARCA" + }, + { + "parameters": { + "jsCode": "// ── DATOS MARCA ──────────────────────────────────────────────────────────────\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA').first().json.customFields;\nconst marcaPipelines = $input.first().json.pipelines;\n\n// ── DATOS SUCURSAL (ya enriquecidos) ─────────────────────────────────────────\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\n\n// ── MAP PIPELINES MARCA: name -> { id, stages } ───────────────────────────────\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[pipeline.name.trim()] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[stage.name.trim()] = stage.id;\n }\n}\n\n// ── RESOLVER PIPELINE Y STAGE POR NAME DESDE SUCURSAL ────────────────────────\nconst resolvedPipelineId = marcaPipelineByName[sucursalOpp.pipelineName?.trim()] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[sucursalOpp.pipelineStageName?.trim()] || marcaOpp.pipelineStageId;\n\n// ── MAP CUSTOM FIELDS SUCURSAL: name -> fieldValue ────────────────────────────\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields) {\n if (cf.name) {\n sucursalValueByName[cf.name.trim()] = cf.fieldValue;\n }\n}\n\n// ── CONSTRUIR CUSTOM FIELDS PARA EL BODY ─────────────────────────────────────\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n const nameKey = def.name.trim();\n const sucursalValue = sucursalValueByName[nameKey];\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n const fieldValue = sucursalValue !== undefined ? sucursalValue : (marcaOriginal?.fieldValue ?? null);\n\n if (fieldValue === null) continue;\n\n customFields.push({\n id: def.id,\n key: def.fieldKey,\n field_value: fieldValue\n });\n}\n\n// ── CONSTRUIR BODY VÁLIDO PARA UPDATE OPPORTUNITY ────────────────────────────\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source,\n customFields: customFields\n};\n\nreturn [{ json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body)\n} }];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 3632, + -32 + ], + "id": "3b547c2a-a541-4b48-8d97-ea2d5df52010", + "name": "Code in JavaScript" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"pageLimit\": 10,\n \"query\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono || $('Datos de Lead').item.json.Cliente.Email }}\"\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3360, + 960 + ], + "id": "02cd0fd6-6bb8-4faa-b85f-5d42663fba29", + "name": "Buscar Contacto en MARCA", + "notes": "Busca contacto en la location de marca por email.\nSi no encuentra, la siguiente IF deriva a creación." + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "exist-contact-check", + "leftValue": "={{ $json.contacts ? $json.contacts.length : 0 }}", + "rightValue": 0, + "operator": { + "type": "number", + "operation": "gt" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 3600, + 960 + ], + "id": "aa2644dc-34b4-4159-8afb-89147b12f5ee", + "name": "¿Existe contacto en MARCA?" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/contacts/upsert", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('DATOS API').item.json['Location ID'] }}\",\n \"firstName\": \"{{ $('Datos de Lead').item.json.Cliente.Apellido }}\",\n \"name\": \"{{ $('Datos de Lead').item.json.Cliente['Nombre Completo'] }}\",\n \"email\": \"{{ $('Datos de Lead').item.json.Cliente.Email }}\",\n \"phone\": \"{{ $('Datos de Lead').item.json.Cliente.Telefono }}\",\n \"source\": \"Sincronización Sucursal\",\n \"tags\": [\"sincronizado-sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 3840, + 1104 + ], + "id": "f6c75352-3b9e-4fc9-b8e4-063c2792d190", + "name": "Crear Contacto en MARCA", + "notes": "Crea contacto en la marca solo si no existía.\nIMPORTANTE: revisa el mapeo de Nombre/Apellido, en el workflow original están invertidos." + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "contact-id-resolved", + "name": "contactId", + "value": "={{ $json.contact ? $json.contact.id : $json.contacts[0].id }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + 4080, + 960 + ], + "id": "2a49d01e-6679-4584-9d9b-5778dfc4431e", + "name": "Set Contact ID Resuelto", + "notes": "Unifica el contactId tanto si vino de la búsqueda (contacts[0].id) como de la creación (contact.id)." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "location_id", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + }, + { + "name": "contact_id", + "value": "={{ $('Set Contact ID Resuelto').item.json.contactId }}" + }, + { + "name": "limit", + "value": "100" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 4320, + 960 + ], + "id": "8e25f660-3ed2-4597-9c2a-7480ac7d150f", + "name": "Buscar Oportunidades del Contacto - MARCA", + "notes": "Busca TODAS las oportunidades del contacto en la marca.\nDespués el Code de decisión elige cuál (o crea nueva)." + }, + { + "parameters": { + "jsCode": "// DECISION (Create vs Update) con idempotencia GLOBAL via mapeo Baserow (tabla 754).\n// (1) Si el mapeo id_opp_sucursal -> id_opp_marca existe -> UPDATE esa opp de Marca,\n// independiente del contacto resuelto (evita duplicados por contacto ambiguo).\n// (2) Si no hay mapeo, match por CF entre las opps del contacto resuelto.\n// (3) Si nada -> CREATE.\nconst result = $input.first().json;\nconst opportunities = result.opportunities || [];\nconst sucursalOppId = $('Datos de Lead').first().json.Oportunidad.opportunity_id;\n\nlet action = 'CREATE';\nlet opportunityId = null;\nlet matchReason = '';\n\n// (1) Mapeo Baserow global\nlet baserowRows = [];\ntry { baserowRows = $('Buscar Mapeo Opp - Baserow').all().map(i => i.json); } catch (e) { baserowRows = []; }\nconst mapped = baserowRows.find(r =>\n String(r.id_opp_sucursal || '') === String(sucursalOppId) && String(r.id_opp_marca || '').trim()\n);\n\nif (mapped) {\n action = 'UPDATE';\n opportunityId = String(mapped.id_opp_marca).trim();\n matchReason = 'Match por mapeo Baserow (global, tabla 754)';\n} else {\n // (2) match por CF entre las opps del contacto resuelto\n const match = opportunities.find(o =>\n (o.customFields || []).some(cf => {\n const v = cf.fieldValueString ?? cf.fieldValue ?? '';\n return String(v) === String(sucursalOppId);\n })\n );\n if (match) {\n action = 'UPDATE';\n opportunityId = match.id;\n matchReason = 'Match por ID Oportunidad Sucursal (contacto)';\n } else {\n action = 'CREATE';\n matchReason = 'Sin match (Baserow ni contacto) -> CREATE (contacto tiene ' + opportunities.length + ' opps)';\n }\n}\n\nreturn [{\n json: {\n action,\n opportunityId,\n matchReason,\n sucursalOppId,\n contactId: $('Set Contact ID Resuelto').first().json.contactId,\n totalOppsContacto: opportunities.length\n }\n}];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 4560, + 960 + ], + "id": "6ca30f13-8503-427f-95ec-b243d30688ec", + "name": "Decidir Match (Create vs Update)" + }, + { + "parameters": { + "rules": { + "values": [ + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-update", + "leftValue": "={{ $json.action }}", + "rightValue": "UPDATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "UPDATE" + }, + { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "switch-create", + "leftValue": "={{ $json.action }}", + "rightValue": "CREATE", + "operator": { + "type": "string", + "operation": "equals" + } + } + ], + "combinator": "and" + }, + "renameOutput": true, + "outputKey": "CREATE" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.switch", + "typeVersion": 3.2, + "position": [ + 4800, + 960 + ], + "id": "bff7a5dc-b4af-4033-83ea-c508aa92264a", + "name": "Switch UPDATE vs CREATE" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Decidir Match (Create vs Update)').item.json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5040, + 816 + ], + "id": "83cd0fd7-1ee9-4896-926e-a0b752e216fe", + "name": "Obtener info de Oportunidad - MARCA (v2)", + "notes": "Sólo se ejecuta en rama UPDATE.\nReemplaza el nodo original 'Obtener info de Oportunidad - MARCA' que dependía de búsqueda por nombre." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 1104 + ], + "id": "0fb2fdea-cf40-4dfb-abfa-c422282e5d2a", + "name": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 1104 + ], + "id": "58114758-f0fb-483a-bd5b-d28188880a02", + "name": "Obtener Pipelines - MARCA (CREATE)" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA CREAR OPORTUNIDAD EN MARCA ──────────────────────\n// Mapea custom fields por fieldKey (fallback name) y FIJA el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia futura).\n\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (CREATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (CREATE)').first().json.pipelines || [];\nconst locationId = $('DATOS API').first().json['Location ID'];\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst contactId = decision.contactId;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\n// Map pipelines/stages de la marca por nombre normalizado\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)];\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)];\n\nif (!resolvedPipelineId) {\n throw new Error(`No se encontró pipeline en marca: \"${sucursalOpp.pipelineName}\". Revisa que el nombre coincida.`);\n}\nif (!resolvedStageId) {\n throw new Error(`No se encontró stage en marca: \"${sucursalOpp.pipelineStageName}\". Revisa que el nombre coincida.`);\n}\n\n// Mapear custom fields: preferir fieldKey, fallback a name normalizado\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null || value === '') continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// FIJAR el campo de enlace \"ID Oportunidad Sucursal\" = id de la opp de sucursal\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n locationId: locationId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || 'open',\n contactId: contactId,\n monetaryValue: sucursalOpp.monetaryValue,\n source: sucursalOpp.source || 'Sincronización Sucursal',\n customFields: customFields\n};\n\nreturn [{\n json: {\n body: JSON.stringify(body),\n debug: { resolvedPipelineId, resolvedStageId, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5520, + 1104 + ], + "id": "2f9a63e7-277d-49de-8838-a00922c993b2", + "name": "Armar Body - CREATE" + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5760, + 1104 + ], + "id": "fc4b3398-91bb-4dd9-b406-96c8d75a17be", + "name": "Crear Oportunidad - MARCA" + }, + { + "parameters": { + "jsCode": "// ─── ARMAR BODY PARA ACTUALIZAR OPORTUNIDAD EN MARCA (v2) ────────────\n// Mapea CF por fieldKey (fallback name) y reafirma el campo de enlace\n// \"ID Oportunidad Sucursal\" = id de la opp de sucursal (idempotencia).\n\nconst marcaOppData = $('Obtener info de Oportunidad - MARCA (v2)').first().json;\nconst marcaOpp = marcaOppData.opportunity;\nconst marcaCustomFieldsDefs = $('Conseguir Custom Fields - Opportunity - MARCA (UPDATE)').first().json.customFields || [];\nconst marcaPipelines = $('Obtener Pipelines - MARCA (UPDATE)').first().json.pipelines || [];\nconst sucursalOpp = $('Mapeo completo oportunidad origen - SUCURSAL').first().json.opportunity;\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst sucursalOppId = decision.sucursalOppId;\n\nconst norm = (s) => (s || '').toString().trim().toLowerCase();\n\nconst marcaPipelineByName = {};\nconst marcaStageByName = {};\nfor (const pipeline of marcaPipelines) {\n marcaPipelineByName[norm(pipeline.name)] = pipeline.id;\n for (const stage of pipeline.stages) {\n marcaStageByName[norm(stage.name)] = stage.id;\n }\n}\n\nconst resolvedPipelineId = marcaPipelineByName[norm(sucursalOpp.pipelineName)] || marcaOpp.pipelineId;\nconst resolvedStageId = marcaStageByName[norm(sucursalOpp.pipelineStageName)] || marcaOpp.pipelineStageId;\n\nconst sucursalValueByFieldKey = {};\nconst sucursalValueByName = {};\nfor (const cf of sucursalOpp.customFields || []) {\n if (cf.fieldKey) sucursalValueByFieldKey[cf.fieldKey] = cf.fieldValue;\n if (cf.name) sucursalValueByName[norm(cf.name)] = cf.fieldValue;\n}\n\nconst customFields = [];\nfor (const def of marcaCustomFieldsDefs) {\n let value = sucursalValueByFieldKey[def.fieldKey];\n if (value === undefined && def.name) value = sucursalValueByName[norm(def.name)];\n if (value === undefined || value === null) {\n const marcaOriginal = marcaOpp.customFields?.find(cf => cf.id === def.id);\n value = marcaOriginal?.fieldValue;\n }\n if (value === undefined || value === null) continue;\n customFields.push({ id: def.id, key: def.fieldKey, field_value: value });\n}\n\n// Reafirmar el campo de enlace\nconst linkDef = marcaCustomFieldsDefs.find(d =>\n d.fieldKey === 'opportunity.id_oportunidad_sucursal' || norm(d.name) === 'id oportunidad sucursal'\n);\nlet linkFieldFound = false;\nif (linkDef) {\n linkFieldFound = true;\n const idx = customFields.findIndex(c => c.id === linkDef.id);\n const entry = { id: linkDef.id, key: linkDef.fieldKey, field_value: sucursalOppId };\n if (idx >= 0) customFields[idx] = entry; else customFields.push(entry);\n}\n\nconst body = {\n pipelineId: resolvedPipelineId,\n name: sucursalOpp.name,\n pipelineStageId: resolvedStageId,\n status: sucursalOpp.status || marcaOpp.status,\n monetaryValue: sucursalOpp.monetaryValue ?? marcaOpp.monetaryValue,\n source: sucursalOpp.source || marcaOpp.source,\n customFields: customFields\n};\n\nreturn [{\n json: {\n opportunityId: marcaOpp.id,\n body: JSON.stringify(body),\n debug: { matchReason: decision.matchReason, customFieldsCount: customFields.length, sucursalOppId, linkFieldFound }\n }\n}];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 5280, + 816 + ], + "id": "969e46cd-bbae-4372-9808-c939f63e3c02", + "name": "Armar Body - UPDATE (v2)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $json.opportunityId }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5520, + 816 + ], + "id": "83929e08-7afd-4670-9c18-be22b8c3656f", + "name": "Actualizar Oportunidad - MARCA (v2)" + }, + { + "parameters": { + "content": "# RAMA UPDATE\n## Oportunidad ya existe en marca → se actualiza", + "height": 280, + "width": 880, + "color": 4 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 720 + ], + "typeVersion": 1, + "id": "1c285234-8d90-49ef-8dac-c98cb379299a", + "name": "Sticky Update" + }, + { + "parameters": { + "content": "# RAMA CREATE\n## Oportunidad no existe en marca → se crea desde cero\n### Incluye contacto + custom fields + tag origin-id", + "height": 280, + "width": 1120, + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 5008, + 1024 + ], + "typeVersion": 1, + "id": "7e8f350c-9562-485b-8ce5-1b13b4086856", + "name": "Sticky Create" + }, + { + "parameters": { + "content": "# MATCHING ROBUSTO\n## 1. Buscar/crear contacto en marca\n## 2. Buscar oportunidades DEL CONTACTO\n## 3. Decidir match por cantidad + tag origin-id", + "height": 320, + "width": 1500, + "color": 6 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + 3328, + 864 + ], + "typeVersion": 1, + "id": "8a583e46-41b6-44e0-9313-b70ee085b740", + "name": "Sticky Matching" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('DATOS API').item.json['Location ID'] }}/customFields", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "model", + "value": "opportunity" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": { + "maxRedirects": 90 + } + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 5040, + 560 + ], + "id": "45f0a6b0-f44d-4e83-b2ab-cdd3fbfe7911", + "name": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "notes": "Necesitamos los custom fields de la marca también en la rama CREATE para mapear los valores que vienen de sucursal." + }, + { + "parameters": { + "url": "https://services.leadconnectorhq.com/opportunities/pipelines", + "sendQuery": true, + "queryParameters": { + "parameters": [ + { + "name": "locationId", + "value": "={{ $('DATOS API').item.json['Location ID'] }}" + } + ] + }, + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API').item.json['Token/API'] }}" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + 5280, + 560 + ], + "id": "5ca962a9-9e56-42d1-af05-ce05d39ed77f", + "name": "Obtener Pipelines - MARCA (UPDATE)" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/opportunities/{{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify({ customFields: [ { id: $('Conseguir Custom Fields - Opportunity - SUCURSAL').item.json.customFields.find(f => f.fieldKey === 'opportunity.id_oportunidad_sucursal')?.id, key: 'opportunity.id_oportunidad_sucursal', field_value: $('Datos de Lead').item.json.Oportunidad.opportunity_id } ] }) }}", + "options": {} + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1328, + -32 + ], + "id": "a9572dee-ed57-4104-966a-4b22721e75f1", + "name": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Auto-mapeo de seguridad: setea 'ID Oportunidad Sucursal' = id nativo de la propia opp de sucursal (del webhook) antes de procesar. onError=continue para no romper el sync si falla." + }, + { + "parameters": { + "databaseId": 63, + "tableId": 754, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7280, + "value": "={{ $('Datos de Lead').item.json.Oportunidad.opportunity_id }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 4208, + 1152 + ], + "id": "a64fdffd-ab8f-4ddd-a4d4-4d683a05132d", + "name": "Buscar Mapeo Opp - Baserow", + "alwaysOutputData": true, + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Idempotencia global: busca en la tabla 754 el mapeo id_opp_sucursal->id_opp_marca. Si existe, 'Decidir Match' hara UPDATE de esa opp aunque el contacto resuelto sea otro. alwaysOutputData + onError para no romper el flujo si no hay match o si Baserow falla." + }, + { + "parameters": { + "jsCode": "// Prepara el UPSERT del mapeo Baserow (tabla 754) tras CREATE/UPDATE de la opp en Marca.\n// Solo escribe cuando el mapeo NO existe aun:\n// - action CREATE -> la fila no existe (el lookup Baserow fallo) -> crear.\n// - action UPDATE por contacto -> Baserow no la tenia -> crear (idempotencia futura).\n// - action UPDATE por Baserow -> la fila YA existe -> NO escribir (no duplicar).\n// Asi una re-ejecucion del mismo opp encuentra el mapeo y hace UPDATE, no CREATE duplicado.\nconst decision = $('Decidir Match (Create vs Update)').first().json;\nconst action = decision.action;\nconst matchReason = decision.matchReason || '';\nconst sucursalOppId = decision.sucursalOppId;\n\nconst matchedByBaserow = matchReason.indexOf('Baserow') !== -1;\nconst needWrite = (action === 'CREATE') || (action === 'UPDATE' && !matchedByBaserow);\nif (!needWrite) return [];\n\n// id de la opp en Marca segun el camino\nlet marcaOppId = decision.opportunityId;\nif (action === 'CREATE') {\n try {\n const co = $('Crear Oportunidad - MARCA').first().json;\n marcaOppId = (co.opportunity && co.opportunity.id) || co.id || marcaOppId;\n } catch (e) {}\n}\nif (!marcaOppId || !sucursalOppId) return [];\n\nlet locationSucursal = '';\ntry { locationSucursal = $('DATOS API - SUCURSAL').first().json['Location ID'] || ''; } catch (e) {}\n\nreturn [{ json: { sucursalOppId, marcaOppId, locationSucursal } }];\n" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 6048, + 960 + ], + "id": "6542723e-f7b2-41de-b371-9e793d48f560", + "name": "Preparar Upsert Mapeo", + "onError": "continueRegularOutput", + "notes": "Decide si escribir el mapeo Baserow tras CREATE/UPDATE. Solo cuando la fila no existe (CREATE o UPDATE-por-contacto). onError continue para no romper la replicacion." + }, + { + "parameters": { + "operation": "create", + "databaseId": 63, + "tableId": 754, + "fieldsUi": { + "fieldValues": [ + { + "fieldId": 7280, + "fieldValue": "={{ $json.sucursalOppId }}" + }, + { + "fieldId": 7283, + "fieldValue": "={{ $json.marcaOppId }}" + }, + { + "fieldId": 7284, + "fieldValue": "={{ $json.locationSucursal }}" + }, + { + "fieldId": 7285, + "fieldValue": "={{ $now.toISO() }}" + } + ] + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + 6288, + 960 + ], + "id": "c69036dd-3914-4424-a165-6e0f3b110ebd", + "name": "Crear Mapeo - Baserow", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + }, + "onError": "continueRegularOutput", + "notes": "Inserta el mapeo id_opp_sucursal->id_opp_marca en la tabla 754 en tiempo real. Idempotente con el backfill (verifica por id_opp_sucursal)." + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('DATOS API - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1750, + -32 + ], + "name": "Obtener Contacto - SUCURSAL", + "onError": "continueRegularOutput", + "notes": "Trae el contacto de sucursal para derivar Sucursal/TIENDA/Canal de Origen de la opp cuando la opp de origen no los trae. onError=continue para no romper el sync.", + "id": "3636d605-1c44-4f2d-b64c-10a5c346cd24" + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API": { + "main": [ + [ + { + "node": "Buscar Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "API de Sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "DATOS API - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - SUCURSAL": { + "main": [ + [ + { + "node": "Mapear ID Oportunidad Sucursal - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de Sucursal": { + "main": [ + [ + { + "node": "DATOS API - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "API de MARCA": { + "main": [ + [ + { + "node": "DATOS API", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapeo completo oportunidad origen - SUCURSAL": { + "main": [ + [ + { + "node": "API de MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - SUCURSAL": { + "main": [ + [ + { + "node": "Mapeo completo oportunidad origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA": { + "main": [ + [ + { + "node": "Code in JavaScript", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Code in JavaScript": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA2", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Contacto en MARCA": { + "main": [ + [ + { + "node": "¿Existe contacto en MARCA?", + "type": "main", + "index": 0 + } + ] + ] + }, + "¿Existe contacto en MARCA?": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Crear Contacto en MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Contacto en MARCA": { + "main": [ + [ + { + "node": "Set Contact ID Resuelto", + "type": "main", + "index": 0 + } + ] + ] + }, + "Set Contact ID Resuelto": { + "main": [ + [ + { + "node": "Buscar Mapeo Opp - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Oportunidades del Contacto - MARCA": { + "main": [ + [ + { + "node": "Decidir Match (Create vs Update)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Decidir Match (Create vs Update)": { + "main": [ + [ + { + "node": "Switch UPDATE vs CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Switch UPDATE vs CREATE": { + "main": [ + [ + { + "node": "Obtener info de Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener info de Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - UPDATE (v2)": { + "main": [ + [ + { + "node": "Actualizar Oportunidad - MARCA (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (CREATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (CREATE)": { + "main": [ + [ + { + "node": "Armar Body - CREATE", + "type": "main", + "index": 0 + } + ] + ] + }, + "Armar Body - CREATE": { + "main": [ + [ + { + "node": "Crear Oportunidad - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Fields - Opportunity - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Obtener Pipelines - MARCA (UPDATE)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Pipelines - MARCA (UPDATE)": { + "main": [ + [ + { + "node": "Armar Body - UPDATE (v2)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Mapear ID Oportunidad Sucursal - SUCURSAL": { + "main": [ + [ + { + "node": "Buscar Oportunidad - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Mapeo Opp - Baserow": { + "main": [ + [ + { + "node": "Buscar Oportunidades del Contacto - MARCA", + "type": "main", + "index": 0 + } + ] + ] + }, + "Crear Oportunidad - MARCA": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Oportunidad - MARCA (v2)": { + "main": [ + [ + { + "node": "Preparar Upsert Mapeo", + "type": "main", + "index": 0 + } + ] + ] + }, + "Preparar Upsert Mapeo": { + "main": [ + [ + { + "node": "Crear Mapeo - Baserow", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Pipelines - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1" + } +} \ No newline at end of file diff --git a/n8n/dryrun_ddUEORBEtZLzsQF2_20260530_185416.json b/n8n/dryrun_ddUEORBEtZLzsQF2_20260530_185416.json new file mode 100644 index 0000000..1ca8350 --- /dev/null +++ b/n8n/dryrun_ddUEORBEtZLzsQF2_20260530_185416.json @@ -0,0 +1,955 @@ +{ + "name": "[2004][Monte Providencia] Actualizar \"contact.sucursal\", \"contact.tienda\" al crear contacto", + "nodes": [ + { + "parameters": { + "content": "# De Sucursal a Marca", + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -2288, + -320 + ], + "typeVersion": 1, + "id": "d91325e1-1763-486c-a2c9-8cfffddd57b0", + "name": "Sticky Note3" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Datos de Lead').item.json.Sucursal['Cuenta Bucéfalo'] }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + -912, + -320 + ], + "id": "50da84c1-c2d3-4a36-91d3-e3745edccdc6", + "name": "Obtener Info de cuenta origen - SUCURSAL", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -224, + -320 + ], + "id": "a0c27c4c-0f20-4bab-bf6b-bcdd8281a92b", + "name": "Conseguir Custom Cuenta Origen- SUCURSAL" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + }, + { + "id": "64f2add6-506c-4950-8026-c04c9547aeeb", + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -1392, + -304 + ], + "id": "d68558b5-52b8-46b1-a359-fd956c7edc09", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "57199999-2d9b-41c7-8269-5a8183ca8132", + "name": "Cliente.Cuándo necesitas el dinero", + "value": "={{ $json.body[\"¿Cuándo necesitas el dinero?\"] }}", + "type": "string" + }, + { + "id": "8060d06e-b1da-4a65-8a0a-3c21d237d77e", + "name": "Sucursal.Cuenta Bucéfalo", + "value": "={{ $json.body.location.name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -1568, + -304 + ], + "id": "d0e455ac-7ccc-4c55-bda2-1cd22092e2d2", + "name": "Datos de Lead" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -720, + -320 + ], + "id": "f0cff3ec-8464-45d4-9e64-713c36e247c6", + "name": "Datos API Cuenta Origen - SUCURSAL", + "notes": "Esta en modo prueba forzada para Queretaro" + }, + { + "parameters": { + "content": "# CUENTA ORIGEN", + "height": 240, + "width": 1744 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -976, + -384 + ], + "typeVersion": 1, + "id": "16a74d85-1e6c-4fc7-b854-580a2d3827a0", + "name": "Sticky Note" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Webhook').item.json.body.contact_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -400, + -320 + ], + "id": "92f33c6e-ee64-409d-8c60-9fbfe48b3265", + "name": "Obtener Contacto Cuenta Origen - SUCURSAL" + }, + { + "parameters": { + "content": "", + "height": 176, + "width": 608, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -336 + ], + "typeVersion": 1, + "id": "768a4001-5109-493f-b12e-e8ba5d30ec2f", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.sucursal.id }}\",\n \"key\": \"contact.sucursal\",\n \"field_value\": \"{{ $json.SUCURSAL }}\"\n },\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.tienda.id }}\",\n \"key\": \"contact.tienda\",\n \"field_value\": \"{{ $json.TIENDA }}\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 512, + -320 + ], + "id": "794ce5bb-dea0-4720-958a-1f0940c79e6d", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "8d574598-d977-4052-823a-26def39c6a64", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1776, + -304 + ], + "id": "d4312610-e978-424c-a8a0-426026c4d4f6", + "name": "Webhook", + "webhookId": "8d574598-d977-4052-823a-26def39c6a64" + }, + { + "parameters": { + "jsCode": "const customFields = $input.first().json.customFields;\n\nfunction findField(key, names) {\n let f = customFields.find(x => x.fieldKey === key);\n if (!f) {\n const wanted = names.map(n => n.toLowerCase().trim());\n f = customFields.find(x => wanted.includes((x.name || \"\").toLowerCase().trim()));\n }\n return f || null;\n}\n\nconst sucursalField = findField(\"contact.sucursal\", [\"Sucursal\"]);\nconst tiendaField = findField(\"contact.tienda\", [\"TIENDA\", \"Tienda\"]);\nconst canalField = findField(\"contact.fuente_de_posible_cliente\", [\"CANAL DE ORIGEN\", \"Canal de Origen\"]);\nconst fuenteField = findField(\"contact.fuente_de_prospecto\", [\"Fuente de Prospecto\", \"FUENTE DE PROSPECTO\"]);\n\n// createdBy.source SOLO viene del GET individual del contacto.\nconst contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json;\nconst createdBySource =\n (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) ||\n (contactResp && contactResp.createdBy && contactResp.createdBy.source) ||\n null;\nconst esUsuario = createdBySource === \"WEB_USER\" || createdBySource === \"MOBILE_USER\";\n\n// Valor ACTUAL de \"Fuente de Prospecto\" en el contacto (para reconciliar SOLO si = LEAD DIGITAL).\nconst contactCFs =\n (contactResp && contactResp.contact && contactResp.contact.customFields) ||\n (contactResp && contactResp.customFields) ||\n [];\nlet fuenteActual = null;\nif (fuenteField && fuenteField.id) {\n const hit = contactCFs.find(cf => cf.id === fuenteField.id);\n fuenteActual = hit ? (hit.value != null ? hit.value : (hit.fieldValue != null ? hit.fieldValue : null)) : null;\n}\nconst fuenteEsLeadDigital = String(fuenteActual || \"\").trim().toUpperCase() === \"LEAD DIGITAL\";\n\nreturn [\n {\n json: {\n sucursal: {\n id: sucursalField?.id ?? null,\n name: sucursalField?.name ?? null,\n fieldKey: sucursalField?.fieldKey ?? null,\n picklistOptions: sucursalField?.picklistOptions ?? [],\n },\n tienda: {\n id: tiendaField?.id ?? null,\n name: tiendaField?.name ?? null,\n fieldKey: tiendaField?.fieldKey ?? null,\n picklistOptions: tiendaField?.picklistOptions ?? [],\n },\n canal: {\n id: canalField?.id ?? null,\n name: canalField?.name ?? null,\n fieldKey: canalField?.fieldKey ?? null,\n picklistOptions: canalField?.picklistOptions ?? [],\n },\n fuente: {\n id: fuenteField?.id ?? null,\n name: fuenteField?.name ?? null,\n fieldKey: fuenteField?.fieldKey ?? null,\n picklistOptions: fuenteField?.picklistOptions ?? [],\n },\n createdBySource: createdBySource,\n esUsuario: esUsuario,\n fuenteActual: fuenteActual,\n fuenteEsLeadDigital: fuenteEsLeadDigital,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 32, + -320 + ], + "id": "5a1cfe47-862c-4d28-a4ca-57a9f8c54a7c", + "name": "Buscar \"contact.sucursal\" y \"contact.tienda\"" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 750, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7247, + "value": "={{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Name Location'] }}" + }, + { + "field": 7279, + "operator": "not_equal", + "value": "NO DIGITAL" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 256, + -320 + ], + "id": "a912fac3-25d4-492a-9648-8c472098b9ca", + "name": "Buscar Sucursal en Verificador de Sucursales", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2023-02-21" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}\",\n \"query\": \"{{ $json.contact.email }}\",\n \"limit\": 20,\n \"page\": 1\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1152, + -320 + ], + "id": "4b899380-fe4a-40ad-80bb-21ef504b30ac", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL1" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "canal-origen-esusuario", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.esUsuario }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 720, + -120 + ], + "name": "Creado por usuario (Canal de Origen)", + "id": "49879f5e-7ce8-4ced-b1a6-96df44ac2e0a" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 940, + -120 + ], + "name": "PUT Canal de Origen = SUCURSAL", + "id": "d0597a9c-aca0-40bb-98d8-bb584d2a2c3e" + }, + { + "parameters": { + "method": "POST", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1160, + -120 + ], + "name": "Tag+ sucursal", + "id": "86bfddcb-c402-413f-9d32-c55050dc470d" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"formulario\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1380, + -120 + ], + "name": "Tag- formulario", + "onError": "continueRegularOutput", + "id": "cbd3cfb2-28d0-44e1-9567-a269382497ae" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"facebook-ads\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1600, + -120 + ], + "name": "Tag- facebook-ads", + "onError": "continueRegularOutput", + "id": "19bdc2b2-9345-4294-8e59-7f35963f261d" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "fuente-es-lead-digital", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuenteEsLeadDigital }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1820, + -120 + ], + "name": "Fuente = LEAD DIGITAL (reconciliar)", + "id": "d4ba3c4b-c220-43ab-a9a7-e4f4da407647" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2040, + -120 + ], + "name": "PUT Fuente de Prospecto = SUCURSAL", + "id": "1eafae90-261d-40d9-aada-01678027d915" + } + ], + "connections": { + "Obtener Info de cuenta origen - SUCURSAL": { + "main": [ + [ + { + "node": "Datos API Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Cuenta Origen- SUCURSAL": { + "main": [ + [ + { + "node": "Buscar \"contact.sucursal\" y \"contact.tienda\"", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "Obtener Info de cuenta origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos API Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Cuenta Origen- SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Sucursal en Verificador de Sucursales": { + "main": [ + [ + { + "node": "Actualizar Contacto Cuenta Objetivo - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar \"contact.sucursal\" y \"contact.tienda\"": { + "main": [ + [ + { + "node": "Buscar Sucursal en Verificador de Sucursales", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Contacto Cuenta Objetivo - SUCURSAL": { + "main": [ + [ + { + "node": "Creado por usuario (Canal de Origen)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Creado por usuario (Canal de Origen)": { + "main": [ + [ + { + "node": "PUT Canal de Origen = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = SUCURSAL": { + "main": [ + [ + { + "node": "Tag+ sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag+ sucursal": { + "main": [ + [ + { + "node": "Tag- formulario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- formulario": { + "main": [ + [ + { + "node": "Tag- facebook-ads", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- facebook-ads": { + "main": [ + [ + { + "node": "Fuente = LEAD DIGITAL (reconciliar)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fuente = LEAD DIGITAL (reconciliar)": { + "main": [ + [ + { + "node": "PUT Fuente de Prospecto = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1" + } +} \ No newline at end of file diff --git a/n8n/dryrun_ddUEORBEtZLzsQF2_20260530_192327.json b/n8n/dryrun_ddUEORBEtZLzsQF2_20260530_192327.json new file mode 100644 index 0000000..a8a7ef5 --- /dev/null +++ b/n8n/dryrun_ddUEORBEtZLzsQF2_20260530_192327.json @@ -0,0 +1,1096 @@ +{ + "name": "[2004][Monte Providencia] Actualizar \"contact.sucursal\", \"contact.tienda\" al crear contacto", + "nodes": [ + { + "parameters": { + "content": "# De Sucursal a Marca", + "color": 5 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -2288, + -320 + ], + "typeVersion": 1, + "id": "d91325e1-1763-486c-a2c9-8cfffddd57b0", + "name": "Sticky Note3" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 749, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7235, + "value": "={{ $('Datos de Lead').item.json.Sucursal['Cuenta Bucéfalo'] }}" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1, + "position": [ + -912, + -320 + ], + "id": "50da84c1-c2d3-4a36-91d3-e3745edccdc6", + "name": "Obtener Info de cuenta origen - SUCURSAL", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/locations/{{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Location ID'] }}/customFields", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + -224, + -320 + ], + "id": "a0c27c4c-0f20-4bab-bf6b-bcdd8281a92b", + "name": "Conseguir Custom Cuenta Origen- SUCURSAL" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "strict", + "version": 3 + }, + "conditions": [ + { + "id": "44d54b9e-d192-4b54-bf0c-156b79afc6e2", + "leftValue": "={{ $json.Cliente.Email }}", + "rightValue": "@ezcorp.com", + "operator": { + "type": "string", + "operation": "notContains" + } + }, + { + "id": "64f2add6-506c-4950-8026-c04c9547aeeb", + "leftValue": "", + "rightValue": "", + "operator": { + "type": "string", + "operation": "equals", + "name": "filter.operator.equals" + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + -1392, + -304 + ], + "id": "d68558b5-52b8-46b1-a359-fd956c7edc09", + "name": "Omitir @ezcorp.com" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "8a998fd4-2de6-4895-ab3d-e052e823d1b8", + "name": "Cliente.Fuente Posible Cliente", + "value": "={{ $json.body['Fuente de Posible cliente'] }}", + "type": "string" + }, + { + "id": "938c6fec-ae16-4e7a-ba2a-f450794fa40d", + "name": "Cliente.Fecha de creación", + "value": "={{ $json.body.date_created }}", + "type": "string" + }, + { + "id": "b56a1939-2608-47c8-85ad-b60b557d2a27", + "name": "Cliente.Sucursal.Sucursal", + "value": "={{ $json.body.Sucursal }}", + "type": "string" + }, + { + "id": "0d07b9c9-4450-497b-ab81-3baa441787fb", + "name": "Vehiculo.Versión.Versión", + "value": "={{ $json.body['Version del Vehiculo'] }}", + "type": "string" + }, + { + "id": "75e3f337-00d1-429d-8d5b-85ec18e6c5e4", + "name": "Vehiculo.Marca.Marca", + "value": "={{ $json.body['Marca del Vehiculo'] }}", + "type": "string" + }, + { + "id": "cb09c536-fe84-4598-aaae-aa79f2eda61b", + "name": "Vehiculo.Marca.fieldKey", + "value": "=contact.marca_del_vehiculo", + "type": "string" + }, + { + "id": "17d36409-4c54-48dd-8100-f7f667fd2415", + "name": "Vehiculo.Año.Año", + "value": "={{ $json.body['Año del Vehículo'] }}", + "type": "string" + }, + { + "id": "a1886afc-b0af-4950-9752-f8bfff594896", + "name": "Vehiculo.Modalidad.Modalidad", + "value": "={{ $json.body['¿Qué modalidad prefieres?'] }}", + "type": "string" + }, + { + "id": "33b2c28a-1ad3-4c74-917f-3cd718a3a709", + "name": "Cliente.Nombre", + "value": "={{ $json.body['Información Adicional'] }}", + "type": "string" + }, + { + "id": "b36131ac-2d88-41f8-8f0f-7640cdb02b57", + "name": "Cliente.Apellido", + "value": "={{ $json.body.first_name }}", + "type": "string" + }, + { + "id": "ae252c8f-f0a1-41d9-a21d-04ca949f01c8", + "name": "Cliente.Nombre Completo", + "value": "={{ $json.body.full_name }}", + "type": "string" + }, + { + "id": "342a9377-0ded-4f23-b93b-1f76e57c0cbd", + "name": "Cliente.Email", + "value": "={{ $json.body.email }}", + "type": "string" + }, + { + "id": "cf1b7058-96c2-4c73-9250-719a88b68673", + "name": "Cliente.Telefono", + "value": "={{ $json.body.phone }}", + "type": "string" + }, + { + "id": "0b916193-15e8-4a91-9ff2-0a6f262b3c38", + "name": "Cliente.Contact ID", + "value": "={{ $json.body.contact_id }}", + "type": "string" + }, + { + "id": "67eeaa9b-f703-4521-82af-9d2797131edc", + "name": "Cliente.Sucursal.fieldKey", + "value": "contact.sucursal", + "type": "string" + }, + { + "id": "b05ea7bb-bbaa-467b-8247-eabb162ff029", + "name": "Vehiculo.Versión.fieldKey", + "value": "contact.version_del_vehiculo", + "type": "string" + }, + { + "id": "9891b919-ef4c-46bd-8414-6d916565d896", + "name": "Vehiculo.Año.fieldKey", + "value": "contact.ano_del_vehiculo", + "type": "string" + }, + { + "id": "0b6bf582-49c1-41de-9c2e-6ae85c4e41e8", + "name": "Vehiculo.Modalidad.fieldKey", + "value": "contact.que_modalidad_prefieres", + "type": "string" + }, + { + "id": "57199999-2d9b-41c7-8269-5a8183ca8132", + "name": "Cliente.Cuándo necesitas el dinero", + "value": "={{ $json.body[\"¿Cuándo necesitas el dinero?\"] }}", + "type": "string" + }, + { + "id": "8060d06e-b1da-4a65-8a0a-3c21d237d77e", + "name": "Sucursal.Cuenta Bucéfalo", + "value": "={{ $json.body.location.name }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -1568, + -304 + ], + "id": "d0e455ac-7ccc-4c55-bda2-1cd22092e2d2", + "name": "Datos de Lead" + }, + { + "parameters": { + "assignments": { + "assignments": [ + { + "id": "55ff7d12-17b9-4bec-a324-e633020b131d", + "name": "Name Location", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Nombre }}", + "type": "string" + }, + { + "id": "d877c8cd-db32-4c16-96dd-4eeb2dc48efe", + "name": "Location ID", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}", + "type": "string" + }, + { + "id": "7698f395-5db8-415b-919e-3ad61c6566f8", + "name": "Token/API", + "value": "={{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}", + "type": "string" + } + ] + }, + "options": {} + }, + "type": "n8n-nodes-base.set", + "typeVersion": 3.4, + "position": [ + -720, + -320 + ], + "id": "f0cff3ec-8464-45d4-9e64-713c36e247c6", + "name": "Datos API Cuenta Origen - SUCURSAL", + "notes": "Esta en modo prueba forzada para Queretaro" + }, + { + "parameters": { + "content": "# CUENTA ORIGEN", + "height": 240, + "width": 1744 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -976, + -384 + ], + "typeVersion": 1, + "id": "16a74d85-1e6c-4fc7-b854-580a2d3827a0", + "name": "Sticky Note" + }, + { + "parameters": { + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Webhook').item.json.body.contact_id }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['Token/API'] }}" + } + ] + }, + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.4, + "position": [ + -400, + -320 + ], + "id": "92f33c6e-ee64-409d-8c60-9fbfe48b3265", + "name": "Obtener Contacto Cuenta Origen - SUCURSAL" + }, + { + "parameters": { + "content": "", + "height": 176, + "width": 608, + "color": 7 + }, + "type": "n8n-nodes-base.stickyNote", + "position": [ + -448, + -336 + ], + "typeVersion": 1, + "id": "768a4001-5109-493f-b12e-e8ba5d30ec2f", + "name": "Sticky Note1" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.sucursal.id }}\",\n \"key\": \"contact.sucursal\",\n \"field_value\": \"{{ $json.SUCURSAL }}\"\n },\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.tienda.id }}\",\n \"key\": \"contact.tienda\",\n \"field_value\": \"{{ $json.TIENDA }}\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 512, + -320 + ], + "id": "794ce5bb-dea0-4720-958a-1f0940c79e6d", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL" + }, + { + "parameters": { + "httpMethod": "POST", + "path": "8d574598-d977-4052-823a-26def39c6a64", + "options": {} + }, + "type": "n8n-nodes-base.webhook", + "typeVersion": 2.1, + "position": [ + -1776, + -304 + ], + "id": "d4312610-e978-424c-a8a0-426026c4d4f6", + "name": "Webhook", + "webhookId": "8d574598-d977-4052-823a-26def39c6a64" + }, + { + "parameters": { + "jsCode": "const customFields = $input.first().json.customFields;\n\nfunction findField(key, names) {\n let f = customFields.find(x => x.fieldKey === key);\n if (!f) {\n const wanted = names.map(n => n.toLowerCase().trim());\n f = customFields.find(x => wanted.includes((x.name || \"\").toLowerCase().trim()));\n }\n return f || null;\n}\n\nconst sucursalField = findField(\"contact.sucursal\", [\"Sucursal\"]);\nconst tiendaField = findField(\"contact.tienda\", [\"TIENDA\", \"Tienda\"]);\nconst canalField = findField(\"contact.fuente_de_posible_cliente\", [\"CANAL DE ORIGEN\", \"Canal de Origen\"]);\nconst fuenteField = findField(\"contact.fuente_de_prospecto\", [\"Fuente de Prospecto\", \"FUENTE DE PROSPECTO\"]);\n\n// createdBy.source SOLO viene del GET individual del contacto.\nconst contactResp = $('Obtener Contacto Cuenta Origen - SUCURSAL').item.json;\nconst createdBySource =\n (contactResp && contactResp.contact && contactResp.contact.createdBy && contactResp.contact.createdBy.source) ||\n (contactResp && contactResp.createdBy && contactResp.createdBy.source) ||\n null;\nconst esUsuario = createdBySource === \"WEB_USER\" || createdBySource === \"MOBILE_USER\";\n\n// Valor ACTUAL de \"Fuente de Prospecto\" en el contacto.\nconst contactCFs =\n (contactResp && contactResp.contact && contactResp.contact.customFields) ||\n (contactResp && contactResp.customFields) ||\n [];\nlet fuenteActual = null;\nif (fuenteField && fuenteField.id) {\n const hit = contactCFs.find(cf => cf.id === fuenteField.id);\n fuenteActual = hit ? (hit.value != null ? hit.value : (hit.fieldValue != null ? hit.fieldValue : null)) : null;\n}\nconst fuenteNorm = String(fuenteActual || \"\").trim().toUpperCase();\nconst fuenteEsLeadDigital = fuenteNorm === \"LEAD DIGITAL\";\nconst fuenteEsRedesSociales = fuenteNorm === \"REDES SOCIALES\";\n\nreturn [\n {\n json: {\n sucursal: {\n id: sucursalField?.id ?? null,\n name: sucursalField?.name ?? null,\n fieldKey: sucursalField?.fieldKey ?? null,\n picklistOptions: sucursalField?.picklistOptions ?? [],\n },\n tienda: {\n id: tiendaField?.id ?? null,\n name: tiendaField?.name ?? null,\n fieldKey: tiendaField?.fieldKey ?? null,\n picklistOptions: tiendaField?.picklistOptions ?? [],\n },\n canal: {\n id: canalField?.id ?? null,\n name: canalField?.name ?? null,\n fieldKey: canalField?.fieldKey ?? null,\n picklistOptions: canalField?.picklistOptions ?? [],\n },\n fuente: {\n id: fuenteField?.id ?? null,\n name: fuenteField?.name ?? null,\n fieldKey: fuenteField?.fieldKey ?? null,\n picklistOptions: fuenteField?.picklistOptions ?? [],\n },\n createdBySource: createdBySource,\n esUsuario: esUsuario,\n fuenteActual: fuenteActual,\n fuenteEsLeadDigital: fuenteEsLeadDigital,\n fuenteEsRedesSociales: fuenteEsRedesSociales,\n },\n },\n];" + }, + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [ + 32, + -320 + ], + "id": "5a1cfe47-862c-4d28-a4ca-57a9f8c54a7c", + "name": "Buscar \"contact.sucursal\" y \"contact.tienda\"" + }, + { + "parameters": { + "databaseId": 63, + "tableId": 750, + "limit": 1, + "additionalOptions": { + "filters": { + "fields": [ + { + "field": 7247, + "value": "={{ $('Datos API Cuenta Origen - SUCURSAL').item.json['Name Location'] }}" + }, + { + "field": 7279, + "operator": "not_equal", + "value": "NO DIGITAL" + } + ] + } + } + }, + "type": "n8n-nodes-base.baserow", + "typeVersion": 1.1, + "position": [ + 256, + -320 + ], + "id": "a912fac3-25d4-492a-9648-8c472098b9ca", + "name": "Buscar Sucursal en Verificador de Sucursales", + "credentials": { + "baserowApi": { + "id": "LZztQ3WMpzXjSTIH", + "name": "Baserow account" + } + } + }, + { + "parameters": { + "method": "POST", + "url": "https://services.leadconnectorhq.com/opportunities/search", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2023-02-21" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.API_token }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"locationId\": \"{{ $('Obtener Info de cuenta origen - SUCURSAL').item.json.Location_ID }}\",\n \"query\": \"{{ $json.contact.email }}\",\n \"limit\": 20,\n \"page\": 1\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1152, + -320 + ], + "id": "4b899380-fe4a-40ad-80bb-21ef504b30ac", + "name": "Actualizar Contacto Cuenta Objetivo - SUCURSAL1" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "canal-origen-esusuario", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.esUsuario }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 720, + -120 + ], + "name": "Creado por usuario (Canal de Origen)", + "id": "49879f5e-7ce8-4ced-b1a6-96df44ac2e0a" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 940, + -120 + ], + "name": "PUT Canal de Origen = SUCURSAL", + "id": "d0597a9c-aca0-40bb-98d8-bb584d2a2c3e" + }, + { + "parameters": { + "method": "POST", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"sucursal\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1160, + -120 + ], + "name": "Tag+ sucursal", + "id": "86bfddcb-c402-413f-9d32-c55050dc470d" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"formulario\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1380, + -120 + ], + "name": "Tag- formulario", + "onError": "continueRegularOutput", + "id": "cbd3cfb2-28d0-44e1-9567-a269382497ae" + }, + { + "parameters": { + "method": "DELETE", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}/tags", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"tags\": [\"facebook-ads\"]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1600, + -120 + ], + "name": "Tag- facebook-ads", + "onError": "continueRegularOutput", + "id": "19bdc2b2-9345-4294-8e59-7f35963f261d" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "fuente-es-lead-digital", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuenteEsLeadDigital }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 1820, + -120 + ], + "name": "Fuente = LEAD DIGITAL (reconciliar)", + "id": "e6bc4a7a-9304-4d00-9750-4bb64a76c3d2" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.fieldKey }}\",\n \"field_value\": \"SUCURSAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 2040, + -120 + ], + "name": "PUT Fuente de Prospecto = SUCURSAL", + "id": "5ef526d6-4947-4725-a3d5-f85cab931142" + }, + { + "parameters": { + "conditions": { + "options": { + "caseSensitive": true, + "leftValue": "", + "typeValidation": "loose", + "version": 3 + }, + "conditions": [ + { + "id": "fuente-es-redes-sociales", + "leftValue": "={{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuenteEsRedesSociales }}", + "rightValue": "", + "operator": { + "type": "boolean", + "operation": "true", + "singleValue": true + } + } + ], + "combinator": "and" + }, + "options": {} + }, + "type": "n8n-nodes-base.if", + "typeVersion": 2.3, + "position": [ + 820, + -320 + ], + "name": "Fuente es REDES SOCIALES", + "id": "215a1570-a538-4b2a-97e7-afc0c4dfae93" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.canal.fieldKey }}\",\n \"field_value\": \"FACEBOOK\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1040, + -320 + ], + "name": "PUT Canal de Origen = FACEBOOK", + "id": "0d27dc59-52b0-49cf-871b-605b24b04f3f" + }, + { + "parameters": { + "method": "PUT", + "url": "=https://services.leadconnectorhq.com/contacts/{{ $('Datos de Lead').item.json.Cliente['Contact ID'] }}", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Accept", + "value": "application/json" + }, + { + "name": "Version", + "value": "2021-07-28" + }, + { + "name": "Authorization", + "value": "=Bearer {{ $('Buscar Sucursal en Verificador de Sucursales').item.json['SC TOKEN BUCEFALO'] }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={\n \"customFields\": [\n {\n \"id\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.id }}\",\n \"key\": \"{{ $('Buscar \"contact.sucursal\" y \"contact.tienda\"').item.json.fuente.fieldKey }}\",\n \"field_value\": \"LEAD DIGITAL\"\n }\n ]\n}", + "options": { + "redirect": { + "redirect": {} + } + } + }, + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4.2, + "position": [ + 1260, + -320 + ], + "name": "PUT Fuente de Prospecto = LEAD DIGITAL", + "id": "834217ba-07ed-47cb-b771-1cd5e99bf11e" + } + ], + "connections": { + "Obtener Info de cuenta origen - SUCURSAL": { + "main": [ + [ + { + "node": "Datos API Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Conseguir Custom Cuenta Origen- SUCURSAL": { + "main": [ + [ + { + "node": "Buscar \"contact.sucursal\" y \"contact.tienda\"", + "type": "main", + "index": 0 + } + ] + ] + }, + "Omitir @ezcorp.com": { + "main": [ + [ + { + "node": "Obtener Info de cuenta origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos de Lead": { + "main": [ + [ + { + "node": "Omitir @ezcorp.com", + "type": "main", + "index": 0 + } + ] + ] + }, + "Datos API Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Obtener Contacto Cuenta Origen - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Obtener Contacto Cuenta Origen - SUCURSAL": { + "main": [ + [ + { + "node": "Conseguir Custom Cuenta Origen- SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Webhook": { + "main": [ + [ + { + "node": "Datos de Lead", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar Sucursal en Verificador de Sucursales": { + "main": [ + [ + { + "node": "Actualizar Contacto Cuenta Objetivo - SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Buscar \"contact.sucursal\" y \"contact.tienda\"": { + "main": [ + [ + { + "node": "Buscar Sucursal en Verificador de Sucursales", + "type": "main", + "index": 0 + } + ] + ] + }, + "Actualizar Contacto Cuenta Objetivo - SUCURSAL": { + "main": [ + [ + { + "node": "Creado por usuario (Canal de Origen)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Creado por usuario (Canal de Origen)": { + "main": [ + [ + { + "node": "Fuente es REDES SOCIALES", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = SUCURSAL": { + "main": [ + [ + { + "node": "Tag+ sucursal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag+ sucursal": { + "main": [ + [ + { + "node": "Tag- formulario", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- formulario": { + "main": [ + [ + { + "node": "Tag- facebook-ads", + "type": "main", + "index": 0 + } + ] + ] + }, + "Tag- facebook-ads": { + "main": [ + [ + { + "node": "Fuente = LEAD DIGITAL (reconciliar)", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fuente = LEAD DIGITAL (reconciliar)": { + "main": [ + [ + { + "node": "PUT Fuente de Prospecto = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "Fuente es REDES SOCIALES": { + "main": [ + [ + { + "node": "PUT Canal de Origen = FACEBOOK", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "PUT Canal de Origen = SUCURSAL", + "type": "main", + "index": 0 + } + ] + ] + }, + "PUT Canal de Origen = FACEBOOK": { + "main": [ + [ + { + "node": "PUT Fuente de Prospecto = LEAD DIGITAL", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1" + } +} \ No newline at end of file diff --git a/scripts/_investigate_erika.py b/scripts/_investigate_erika.py new file mode 100644 index 0000000..337eff6 --- /dev/null +++ b/scripts/_investigate_erika.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Investigacion read-only: opp de Marca hNPaauyuqM1Epycwj0wj (ERIKA RUBI CONCHA). +Imprime CF resueltos, contactId, link a sucursal y busca el contacto en sucursales.""" +import os, sys +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if ROOT_DIR not in sys.path: + sys.path.insert(0, ROOT_DIR) +import sync_engine +from scripts import common + +gc = sync_engine.ghl_client +BRAND = common.BRAND_LOCATION_ID +OPP_ID = "hNPaauyuqM1Epycwj0wj" + +accounts = {a["location_id"]: a for a in common.load_accounts()} +brand_token = accounts[BRAND]["token"] + + +def keymap(token, loc, obj): + fields = gc.get_object_schema_fields(token, loc, obj) + return {f["id"]: f.get("fieldKey") for f in fields if f.get("id")} + + +def cfval(cf): + for k in ("fieldValue", "fieldValueString", "value"): + if cf.get(k) is not None: + return cf[k] + return None + + +opp = (gc.get_opportunity(brand_token, OPP_ID) or {}).get("opportunity") or {} +print("=== OPP MARCA ===") +print("name:", opp.get("name")) +print("status:", opp.get("status"), "value:", opp.get("monetaryValue")) +print("contactId:", opp.get("contactId") or (opp.get("contact") or {}).get("id")) +id2key = keymap(brand_token, BRAND, "opportunity") +print("--- customFields (opp Marca) ---") +link = None +for cf in opp.get("customFields") or []: + k = id2key.get(cf.get("id")) + v = cfval(cf) + print(f" {k} = {v!r}") + if k == "opportunity.id_oportunidad_sucursal": + link = v +print("LINK id_oportunidad_sucursal:", link) + +# contacto de Marca +cid = opp.get("contactId") or (opp.get("contact") or {}).get("id") +if cid: + c = (gc._request("GET", f"/contacts/{cid}", brand_token) or {}).get("contact") or {} + print("\n=== CONTACTO MARCA ===") + print("name:", c.get("contactName") or f"{c.get('firstName')} {c.get('lastName')}") + print("phone:", c.get("phone"), "email:", c.get("email")) + ck = keymap(brand_token, BRAND, "contact") + for cf in c.get("customFields") or []: + k = ck.get(cf.get("id")) + if k in ("contact.id_contacto_sucursal", "contact.sucursal", "contact.tienda", + "contact.fuente_de_posible_cliente", "contact.fuente_de_prospecto"): + print(f" {k} = {cf.get('value')!r}") + +# buscar la opp link en cada sucursal para identificar la sucursal de origen +if link: + print("\n=== BUSCANDO opp", link, "EN SUCURSALES ===") + for loc, acc in accounts.items(): + if loc == BRAND or acc.get("type") == "brand": + continue + try: + bo = (gc.get_opportunity(acc["token"], link) or {}).get("opportunity") or {} + except Exception: + bo = {} + if bo: + print(f" ENCONTRADA en {acc['nombre']} ({loc})") + bk = keymap(acc["token"], loc, "opportunity") + for cf in bo.get("customFields") or []: + k = bk.get(cf.get("id")) + if k in ("opportunity.sucursal", "opportunity.tienda", + "opportunity.fuente_de_posible_cliente", "opportunity.fuente_de_prospecto"): + print(f" {k} = {cfval(cf)!r}") + break + else: + print(" No encontrada por GET directo en ninguna sucursal.") diff --git a/scripts/audit_origen_fuente_incoherencia.py b/scripts/audit_origen_fuente_incoherencia.py new file mode 100644 index 0000000..6d3b552 --- /dev/null +++ b/scripts/audit_origen_fuente_incoherencia.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Read-only: clasifica la incoherencia Canal de Origen / Fuente de Prospecto. + +Detecta en SUCURSALES los dos patrones incoherentes y, para cada contacto, +lee ``createdBy.source`` EN VIVO (solo viene en el GET individual) para decidir +la verdad del lead: + + Patron A: Canal=SUCURSAL & Fuente=LEAD DIGITAL (viola AGENTS Cap.3) + - createdBy in {WEB_USER, MOBILE_USER} -> manual sucursal -> arreglar FUENTE (->SUCURSAL) + - otro (INTEGRATION/form/etc.) -> lead digital -> arreglar CANAL (->FORMULARIO/FACEBOOK) + Patron B: Fuente=REDES SOCIALES -> lead digital -> Canal=FACEBOOK + Fuente=LEAD DIGITAL + +No escribe nada. Imprime la particion y el plan por contacto. +""" + +import argparse +import json +import os +import sqlite3 +import sys +from collections import Counter + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if ROOT_DIR not in sys.path: + sys.path.insert(0, ROOT_DIR) + +import paths # noqa: E402 +from tag_canal_origen_workflow import ( # noqa: E402 + MAIN_LOCATION_ID, + contact_display_name, + ghl_request, + load_locations, +) +from canal_origen_resolver import classify_source # noqa: E402 + +USER_SOURCES = {"WEB_USER", "MOBILE_USER"} + + +def cfval(custom_fields, fid): + for f in custom_fields or []: + if f.get("id") == fid: + for k in ("value", "fieldValue", "fieldValueString"): + if f.get(k) is not None: + return f[k] + return None + + +def field_maps(conn, location_id): + m = {} + for r in conn.execute( + "select field_id, field_name from object_schemas where location_id=? and object_key='contact'", + (location_id,), + ): + m[r[1].strip().lower()] = r[0] + return m + + +def incoherent_from_cache(conn, location_id): + """Devuelve [(contact_id, patron, canal, fuente)] desde la cache.""" + fm = field_maps(conn, location_id) + canal_id = fm.get("canal de origen") + fuente_id = fm.get("fuente de prospecto") + out = [] + for r in conn.execute( + "select id, custom_fields_json from contacts where location_id=?", + (location_id,), + ): + cf = json.loads(r[1] or "[]") + canal = cfval(cf, canal_id) + fuente = cfval(cf, fuente_id) + if canal == "SUCURSAL" and fuente == "LEAD DIGITAL": + out.append((r[0], "A", canal, fuente)) + elif fuente == "REDES SOCIALES": + out.append((r[0], "B", canal, fuente)) + return out + + +def get_contact_full(contact_id, token): + data = ghl_request("GET", f"/contacts/{contact_id}", token) + inner = data.get("contact") + return inner if isinstance(inner, dict) else data + + +def main(): + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8") + parser = argparse.ArgumentParser(description="Audit read-only de incoherencia origen/fuente") + parser.add_argument("--all", action="store_true", help="Todas las sucursales productivas") + parser.add_argument("--location", help="Una location especifica") + args = parser.parse_args() + + accounts = load_locations(include_main=False) + accounts = [a for a in accounts if a["location_id"] != MAIN_LOCATION_ID] + if args.location: + accounts = [a for a in accounts if a["location_id"] == args.location] + elif not args.all: + raise SystemExit("Usa --all o --location ") + + conn = sqlite3.connect(paths.DB_PATH) + + grand = Counter() + for acc in accounts: + loc = acc["location_id"] + token = acc["token"] + targets = incoherent_from_cache(conn, loc) + if not targets: + continue + print(f"\n{'='*70}\n{acc['nombre']} ({loc}) - {len(targets)} incoherentes\n{'='*70}") + for cid, patron, canal, fuente in targets: + full = get_contact_full(cid, token) + created = (full.get("createdBy") or {}).get("source") + src = full.get("source") + name = contact_display_name(full) + if patron == "B": + plan = "Canal->FACEBOOK + Fuente->LEAD DIGITAL" + bucket = "B_redes->digital" + else: + if created in USER_SOURCES: + plan = "Fuente->SUCURSAL (manual sucursal)" + bucket = "A_manual->fuente" + else: + # createdBy no-usuario => digital; canal segun source + src_tag = classify_source(src) + canal_target = "FACEBOOK" if src_tag == "facebook-ads" else "FORMULARIO" + plan = f"Canal->{canal_target} (digital)" + bucket = f"A_digital->canal({canal_target})" + grand[bucket] += 1 + print(f" {name:35.35} | createdBy={created or '-':12} source={src or '-':12} | {plan}") + + print(f"\n{'='*70}\nRESUMEN GLOBAL (plan)\n{'='*70}") + for k, v in grand.most_common(): + print(f" {v:4} {k}") + + +if __name__ == "__main__": + main() diff --git a/scripts/backfill_brand_opp_cf_from_source.py b/scripts/backfill_brand_opp_cf_from_source.py new file mode 100644 index 0000000..f49155f --- /dev/null +++ b/scripts/backfill_brand_opp_cf_from_source.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Backfill de custom fields descriptivos en una opp de MARCA que quedó "solo +enlace" (solo `opportunity.id_oportunidad_sucursal`), tomando los valores de la +opp de sucursal enlazada y, como respaldo, del CONTACTO de sucursal. + +Caso de origen: la opp de Marca `8HITkGkOn3gN23Tl8LBr` (Miguel Temixco) quedó sin +Sucursal / TIENDA / Canal de Origen / Vehículo. El nodo de réplica las copia por +`fieldKey`, pero esta opp se creó fuera del flujo normal. Mismo `fieldKey` +canónico (sufijo) en contact y opportunity. + +Prioridad por campo: (a) valor en la opp de sucursal → (b) valor en el contacto +de sucursal. Solo rellena campos que estén VACÍOS en la opp de Marca (no +sobreescribe). dry-run por defecto; snapshot + script_audit para rollback. + +Uso: + python scripts/backfill_brand_opp_cf_from_source.py \\ + --brand-opp-id 8HITkGkOn3gN23Tl8LBr --branch-location-id yjqKxoO02rsdwdJZSPmD + # añade --apply para escribir en Bucéfalo +""" + +import argparse +import datetime +import json +import os +import sys + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if ROOT_DIR not in sys.path: + sys.path.insert(0, ROOT_DIR) + +import script_audit # noqa: E402 +import sync_engine # noqa: E402 +from scripts import common # noqa: E402 +from paths import MIGRATIONS_DIR # noqa: E402 + +gc = sync_engine.ghl_client +BRAND_LOCATION_ID = common.BRAND_LOCATION_ID +LINK_KEY = "opportunity.id_oportunidad_sucursal" + +# (opp fieldKey canónico, contact fieldKey de respaldo o None). +TARGETS = [ + ("opportunity.sucursal", "contact.sucursal"), + ("opportunity.tienda", "contact.tienda"), + ("opportunity.fuente_de_posible_cliente", "contact.fuente_de_posible_cliente"), # CANAL DE ORIGEN + ("opportunity.fuente_de_prospecto", "contact.fuente_de_prospecto"), + ("opportunity.vehiculo", None), # el contacto guarda marca/versión/año por separado +] + + +def clean(v): + return "" if v is None else str(v).strip() + + +def opp_cf_value(cf): + for k in ("fieldValue", "fieldValueString", "value"): + if cf.get(k) is not None: + return cf[k] + return None + + +def schema_key_by_id(token, location_id, object_key): + """id -> fieldKey usando el schema dinámico de la location.""" + fields = gc.get_object_schema_fields(token, location_id, object_key) + return {f["id"]: f.get("fieldKey") for f in fields if f.get("id")}, \ + {f.get("fieldKey"): f for f in fields if f.get("fieldKey")} + + +def main(): + ap = argparse.ArgumentParser(description="Backfill CF descriptivos en opp de Marca desde la opp/contacto de sucursal.") + ap.add_argument("--brand-opp-id", required=True) + ap.add_argument("--branch-location-id", required=True) + ap.add_argument("--brand-location-id", default=BRAND_LOCATION_ID) + ap.add_argument("--apply", action="store_true") + ap.add_argument("--run-id", default=None) + args = ap.parse_args() + + accounts = {a["location_id"]: a for a in common.load_accounts()} + brand = accounts.get(args.brand_location_id) + branch = accounts.get(args.branch_location_id) + if not brand or not branch: + raise SystemExit("No se encontró el token de la location de Marca o sucursal en el CSV.") + brand_token, branch_token = brand["token"], branch["token"] + + # 1. Opp de Marca (estado actual). + brand_opp = (gc.get_opportunity(brand_token, args.brand_opp_id) or {}).get("opportunity") or {} + if not brand_opp: + raise SystemExit(f"No se pudo leer la opp de Marca {args.brand_opp_id}.") + brand_id2key, brand_key2def = schema_key_by_id(brand_token, args.brand_location_id, "opportunity") + brand_val_by_key = {} + for cf in brand_opp.get("customFields") or []: + k = brand_id2key.get(cf.get("id")) + if k: + brand_val_by_key[k] = opp_cf_value(cf) + + # 2. Resolver la opp de sucursal enlazada (del CF link de Marca). + branch_opp_id = clean(brand_val_by_key.get(LINK_KEY)) + if not branch_opp_id: + raise SystemExit(f"La opp de Marca no tiene {LINK_KEY}; no se puede resolver el origen.") + branch_opp = (gc.get_opportunity(branch_token, branch_opp_id) or {}).get("opportunity") or {} + if not branch_opp: + raise SystemExit(f"No se pudo leer la opp de sucursal {branch_opp_id}.") + branch_id2key, _ = schema_key_by_id(branch_token, args.branch_location_id, "opportunity") + branch_opp_val_by_key = {} + for cf in branch_opp.get("customFields") or []: + k = branch_id2key.get(cf.get("id")) + if k: + branch_opp_val_by_key[k] = opp_cf_value(cf) + + # 3. Contacto de sucursal (respaldo). + branch_contact_val_by_key = {} + cid = branch_opp.get("contactId") or branch_opp.get("contact", {}).get("id") + if cid: + contact = (gc._request("GET", f"/contacts/{cid}", branch_token) or {}).get("contact") or {} + c_id2key, _ = schema_key_by_id(branch_token, args.branch_location_id, "contact") + for cf in contact.get("customFields") or []: + k = c_id2key.get(cf.get("id")) + if k: + branch_contact_val_by_key[k] = cf.get("value") + + # 4. Calcular el backfill (solo campos vacíos en Marca). + run_id = args.run_id or f"backfill-opp-cf-{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}" + plan = [] + for opp_key, contact_key in TARGETS: + if clean(brand_val_by_key.get(opp_key)) != "": + continue # ya tiene valor, no sobreescribir + value = clean(branch_opp_val_by_key.get(opp_key)) + source = "opp_sucursal" + if value == "" and contact_key: + value = clean(branch_contact_val_by_key.get(contact_key)) + source = "contacto_sucursal" + if value == "": + continue + bdef = brand_key2def.get(opp_key) + if not bdef: + print(f" WARN: Marca no tiene el campo {opp_key}; se omite.") + continue + plan.append({"opp_key": opp_key, "field_id": bdef["id"], "name": bdef.get("name"), + "value": value, "source": source}) + + print(f"Opp Marca: {args.brand_opp_id} <- opp sucursal: {branch_opp_id} (contacto {cid})") + if not plan: + print("Nada que rellenar (todos los campos objetivo ya tienen valor o no hay fuente).") + return + print(f"Campos a rellenar ({len(plan)}):") + for p in plan: + print(f" {p['name']:20} [{p['opp_key']}] = {p['value']!r} (fuente: {p['source']})") + + if not args.apply: + print("\nDRY-RUN. Vuelve a correr con --apply para escribir en Bucéfalo.") + return + + # 5. Snapshot + audit + PUT. + os.makedirs(MIGRATIONS_DIR, exist_ok=True) + snap_path = os.path.join(MIGRATIONS_DIR, f"backfill_opp_cf_{args.brand_opp_id}_{run_id}.json") + with open(snap_path, "w", encoding="utf-8") as fh: + json.dump({"run_id": run_id, "brand_opp_id": args.brand_opp_id, "before": brand_opp, "plan": plan}, + fh, ensure_ascii=False, indent=2) + print(f" snapshot -> {snap_path}") + + script_audit.create_run(run_id, "backfill_brand_opp_cf_from_source", arguments=" ".join(sys.argv[1:]), + locations=[args.brand_location_id]) + change_ids = [] + for p in plan: + cidc = script_audit.record_change(run_id, args.brand_location_id, "opportunity", args.brand_opp_id, + p["field_id"], p["name"], None, p["value"]) + change_ids.append(cidc) + + payload = {"customFields": [{"id": p["field_id"], "key": p["opp_key"], "field_value": p["value"]} for p in plan]} + gc.update_opportunity(brand_token, args.brand_opp_id, payload) + for cidc in change_ids: + script_audit.mark_change(cidc, "applied") + script_audit.update_run_status(run_id, "success") + print(f" PUT aplicado. run_id={run_id} (reversible desde el dashboard).") + + +if __name__ == "__main__": + main() diff --git a/scripts/fix_origen_fuente_incoherencia.py b/scripts/fix_origen_fuente_incoherencia.py new file mode 100644 index 0000000..209450a --- /dev/null +++ b/scripts/fix_origen_fuente_incoherencia.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Corrige la incoherencia Canal de Origen / Fuente de Prospecto en contactos +y propaga a TODAS sus oportunidades asociadas. + +Detecta por el estado ACTUAL de los campos (idempotente) dos patrones: + + Patron A Canal de Origen = SUCURSAL & Fuente de Prospecto = LEAD DIGITAL + (viola AGENTS Cap.3: si Canal=SUCURSAL, Fuente no puede ser LEAD DIGITAL) + -> Contacto: Fuente de Prospecto = SUCURSAL (Canal se conserva) + -> Opps: Canal de Origen de la Oportunidad = Sucursal, Fuente = SUCURSAL + + Patron B Fuente de Prospecto = REDES SOCIALES + (REDES SOCIALES es un canal digital mal clasificado como sucursal) + -> Contacto: Canal de Origen = FACEBOOK, Fuente de Prospecto = LEAD DIGITAL + -> Opps: Canal de Origen de la Oportunidad = Facebook, + Tipo de Lead = Lead digital, Fuente de Prospecto = LEAD DIGITAL + +Decision validada con el usuario el 2026-05-30 (createdBy.source en vivo confirmo +que los 28 del Patron A son captura manual de sucursal -> WEB_USER/MOBILE_USER). + +Dry-run por defecto. Con --apply --run-id registra cada cambio en script_audit +(reversible desde el dashboard). Resuelve los IDs de campo dinamicamente por nombre +(FIELD_ALIASES), nunca hardcodea. Sirve igual para sucursales y para Marca: como +la deteccion es por estado, correrlo en Marca arregla el panel directamente sin +depender de la replicacion. +""" + +import argparse +import os +import sys +from collections import Counter + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if ROOT_DIR not in sys.path: + sys.path.insert(0, ROOT_DIR) + +import script_audit # noqa: E402 +import sync_engine # noqa: E402 +import common # noqa: E402 + +from tag_canal_origen_workflow import ( # noqa: E402 + contact_display_name, + get_all_contacts, + get_all_opportunities_for_contact, + get_custom_field_value, + get_opportunity, + get_schemas, + load_locations, + resolve_opp_field_ids, + safe_update_contact_field, + safe_update_opportunity_field, +) + +SCRIPT_NAME = "fix_origen_fuente_incoherencia.py" + +# Plan de correccion por patron (alineado a TAG_TO_OPP_UPDATES del workflow canonico). +PLAN = { + "A": { + "contact": {"Fuente de Prospecto": "SUCURSAL"}, + "opp": { + "Canal de Origen de la Oportunidad": "Sucursal", + "Fuente de Prospecto": "SUCURSAL", + }, + }, + "B": { + "contact": {"Canal de Origen": "FACEBOOK", "Fuente de Prospecto": "LEAD DIGITAL"}, + "opp": { + "Canal de Origen de la Oportunidad": "Facebook", + "Tipo de Lead": "Lead digital", + "Fuente de Prospecto": "LEAD DIGITAL", + }, + }, +} + +# Mapeo display-name de opp -> alias key de common.FIELD_ALIASES (tolerante a variantes). +OPP_FIELD_ALIAS_KEY = { + "Canal de Origen de la Oportunidad": "canal_origen", + "Tipo de Lead": "tipo_lead", + "Fuente de Prospecto": "fuente_prospecto", +} + + +def resolve_contact_field_id(contact_schema, display_name): + """contact_schema es {name: id}. Resuelve por FIELD_ALIASES, tolerante a mayusculas.""" + alias_key = { + "Canal de Origen": "canal_origen", + "Fuente de Prospecto": "fuente_prospecto", + }.get(display_name, display_name) + candidates = common.FIELD_ALIASES.get(alias_key, [display_name]) + norm_to_id = {common.normalize_name(n): i for n, i in (contact_schema or {}).items()} + for cand in candidates: + fid = norm_to_id.get(common.normalize_name(cand)) + if fid: + return fid + return None + + +def resolve_opp_field_ids_alias(opp_fields_list, display_name): + alias_key = OPP_FIELD_ALIAS_KEY.get(display_name, display_name) + candidates = common.FIELD_ALIASES.get(alias_key, [display_name]) + target_norm = {common.normalize_name(n) for n in candidates} + return [ + f["id"] + for f in (opp_fields_list or []) + if f.get("name") and f.get("id") and common.normalize_name(f["name"]) in target_norm + ] + + +def classify_patron(canal, fuente): + if canal == "SUCURSAL" and fuente == "LEAD DIGITAL": + return "A" + if fuente == "REDES SOCIALES": + return "B" + return None + + +def process_location(account, *, dry_run, run_id): + location_id = account["location_id"] + token = account["token"] + name = account["nombre"] + local = Counter() + + contact_schema = get_schemas(location_id, token, "contact")["contact"] + canal_id = resolve_contact_field_id(contact_schema, "Canal de Origen") + fuente_id = resolve_contact_field_id(contact_schema, "Fuente de Prospecto") + if not canal_id or not fuente_id: + print(f" SKIP {name}: faltan campos de contacto (Canal={bool(canal_id)}, Fuente={bool(fuente_id)})") + return local + + opp_fields_list = sync_engine.ghl_client.get_object_schema_fields(token, location_id, "opportunity") + + contacts = get_all_contacts(location_id, token) + targets = [] + for c in contacts: + canal = get_custom_field_value(c, canal_id) + fuente = get_custom_field_value(c, fuente_id) + patron = classify_patron(canal, fuente) + if patron: + targets.append((c, patron)) + + if not targets: + return local + + print(f"\n{'='*70}\n{name} ({location_id}) - {len(targets)} contactos a corregir\n{'='*70}") + + for contact, patron in targets: + if not script_audit.wait_if_paused_or_stopped(run_id): + print("\n Detencion segura solicitada. Saliendo antes del siguiente contacto.") + break + cid = contact["id"] + display = contact_display_name(contact) + local[f"patron_{patron}"] += 1 + + # --- Contacto --- + for field_name, value in PLAN[patron]["contact"].items(): + fid = canal_id if field_name == "Canal de Origen" else fuente_id + if safe_update_contact_field(run_id, location_id, contact, fid, field_name, value, token, dry_run): + local["contact_fields"] += 1 + print(f" [contacto {patron}] {display} | {field_name} -> {value}") + + # --- Oportunidades asociadas (todas) --- + for opp_summary in get_all_opportunities_for_contact(location_id, cid, token): + opp_id = opp_summary.get("id") + if not opp_id: + continue + opp = get_opportunity(location_id, opp_id, token) or opp_summary + opp_touched = False + for field_name, value in PLAN[patron]["opp"].items(): + for fid in resolve_opp_field_ids_alias(opp_fields_list, field_name) or resolve_opp_field_ids(opp_fields_list, field_name): + if safe_update_opportunity_field(run_id, location_id, opp_id, opp, fid, field_name, value, token, dry_run): + opp_touched = True + if opp_touched: + local["opps"] += 1 + print(f" [opp {patron}] {opp_id} | {opp.get('name') or display}") + + print(f" -> {name}: A={local['patron_A']} B={local['patron_B']} | " + f"campos contacto={local['contact_fields']}, opps={local['opps']}") + return local + + +def select_locations(args): + accounts = load_locations(include_main=True) + if args.location: + m = [a for a in accounts if a["location_id"] == args.location] + if not m: + raise SystemExit(f"Location {args.location} no esta en el CSV") + return m + if args.all: + return accounts + raise SystemExit("Especifica --location o --all. Sin --apply corre en dry-run.") + + +def main(): + if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8") + parser = argparse.ArgumentParser(description="Corrige incoherencia Canal/Fuente + propaga a opps") + parser.add_argument("--location", help="Location ID especifico (sucursal o Marca)") + parser.add_argument("--all", action="store_true", help="Todas las cuentas del CSV (sucursales + Marca)") + parser.add_argument("--apply", action="store_true", help="Aplica en el CRM. Sin este flag corre en dry-run.") + parser.add_argument("--run-id", help="ID de auditoria (lo da el dashboard; en CLI se crea si se aplica)") + args = parser.parse_args() + + dry_run = not args.apply + run_id = args.run_id + accounts = select_locations(args) + + print("=" * 70) + print("FIX INCOHERENCIA ORIGEN / FUENTE (+ propagacion a oportunidades)") + print("=" * 70) + print(f"Modo: {'DRY-RUN (sin cambios)' if dry_run else 'APPLY (escribe en el CRM)'}") + print(f"Cuentas objetivo: {len(accounts)}") + + if not dry_run and run_id: + script_audit.create_run(run_id, SCRIPT_NAME, arguments=" ".join(sys.argv[1:]), + locations=[a["location_id"] for a in accounts]) + + grand = Counter() + errors = 0 + for account in accounts: + try: + for k, v in process_location(account, dry_run=dry_run, run_id=run_id).items(): + grand[k] += v + except Exception as exc: # noqa: BLE001 + errors += 1 + print(f"\nERROR en {account['nombre']}: {exc}") + + print("\n" + "=" * 70) + print(f"RESUMEN: Patron A={grand['patron_A']}, Patron B={grand['patron_B']}, " + f"campos de contacto={grand['contact_fields']}, opps tocadas={grand['opps']}, errores={errors}") + if dry_run: + print("Dry-run terminado. Revisa el plan y vuelve a correr con --apply --run-id .") + + if not dry_run and run_id: + script_audit.update_run_status(run_id, "failed" if errors else "success", + f"{errors} errores" if errors else None) + if errors: + raise SystemExit(1) + + +if __name__ == "__main__": + main()