descripción

This commit is contained in:
2026-05-30 20:16:12 -06:00
parent a35d26fac0
commit fb20cf8bd5
23 changed files with 32388 additions and 2 deletions
+233
View File
@@ -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()
+255
View File
@@ -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()
+273
View File
@@ -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()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -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"
}
}
File diff suppressed because it is too large Load Diff