# -*- coding: utf-8 -*- """Fix del bug de token en la cascada del workflow Marca->Sucursal V2. El nodo `Buscar Contacto Objetivo - SUCURSAL(mail)` (workflow 4UMRwxJdHFfOGHBp) usa una expression IMPLICITA para el header Authorization: =Bearer {{ $json['Token/API'] }} Esto resuelve al token de MARCA (heredado del set node 'Datos API Cuenta Origen'), no al de sucursal, por lo que el endpoint POST /contacts/search responde 401. El nodo siguiente (`Obtener Contacto Cuenta objetivo - SUCURSAL`) recibe {error: ...} en lugar de {contacts: [...]} y termina rebotando con 403 porque $json.contacts[0].id es undefined. Fix: cambiar la expression a la explicita usada por los otros 9 HTTP nodes: =Bearer {{ $('Datos API Cuenta objetivo - SUCURSAL').item.json['Token/API'] }} Cambio neto: 1 expression. Total nodos sin cambios (42). Modo dry-run por defecto; --apply para PUT real + reactivar. """ import argparse import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from scripts.n8n_workflow_lib import load_credentials, N8NClient WID = "4UMRwxJdHFfOGHBp" TARGET_NODE = "Buscar Contacto Objetivo - SUCURSAL(mail)" BUGGY_VALUE = "=Bearer {{ $json['Token/API'] }}" FIXED_VALUE = "=Bearer {{ $('Datos API Cuenta objetivo - SUCURSAL').item.json['Token/API'] }}" def parse_args(): ap = argparse.ArgumentParser() ap.add_argument("--apply", action="store_true", help="Aplicar PUT real (default: dry-run a n8n/dryrun_*.json)") return ap.parse_args() def main(): args = parse_args() api_key, base_url = load_credentials() client = N8NClient(api_key, base_url) print(f"[1/6] GET workflow {WID}") wf = client.get_workflow(WID) prev_version = wf.get("versionId") print(f" versionId previo: {prev_version}") print(f" active : {wf.get('active')}") print(f" total nodos : {len(wf.get('nodes') or [])}") # Localizar nodo y validar expression actual (defensa contra cambios concurrentes) target = next((n for n in wf["nodes"] if n.get("name") == TARGET_NODE), None) if not target: print(f"\nERROR: nodo {TARGET_NODE!r} no existe. Abortando.") sys.exit(2) headers = ((target.get("parameters") or {}).get("headerParameters") or {}).get("parameters") or [] auth_header = next((h for h in headers if h.get("name") == "Authorization"), None) if not auth_header: print(f"\nERROR: nodo {TARGET_NODE!r} no tiene header Authorization. Abortando.") sys.exit(2) current = auth_header.get("value", "") print(f"\n[2/6] Verificar expression actual") print(f" actual : {current}") print(f" esperada : {BUGGY_VALUE}") if current == FIXED_VALUE: print(f"\n [SKIP] La expression ya esta arreglada. Nada que hacer.") return if current != BUGGY_VALUE: print(f"\nERROR: expression actual difiere de la esperada y tampoco esta arreglada.") print(f" Abortando para no sobrescribir cambios manuales.") sys.exit(2) print(f"\n[3/6] Backup fresco") _, bpath = client.backup_workflow(WID, label="pre_fix_cascada_token_mail") print(f" backup -> {bpath}") print(f"\n[4/6] Aplicar cambio en memoria") auth_header["value"] = FIXED_VALUE print(f" nuevo: {FIXED_VALUE}") print(f"\n[5/6] {'APPLY' if args.apply else 'DRY-RUN'} put_workflow") result = client.put_workflow(WID, wf, dry_run=not args.apply) if not args.apply: print(f" dry-run dump -> {result}") print("\nLISTO. Revisa el JSON. Si todo bien, corre con --apply.") return print(f"\n[6/6] Verificar y reactivar") new_wf = client.verify_post(WID, prev_version_id=prev_version) # Re-verificar la expression target_post = next((n for n in new_wf["nodes"] if n.get("name") == TARGET_NODE), None) headers_post = ((target_post.get("parameters") or {}).get("headerParameters") or {}).get("parameters") or [] auth_post = next((h for h in headers_post if h.get("name") == "Authorization"), None) print(f" versionId nuevo : {new_wf.get('versionId')}") print(f" total nodos : {len(new_wf.get('nodes') or [])} (esperado: 42)") print(f" expression post : {auth_post.get('value') if auth_post else '?'}") print(f" coincide con fix : {auth_post.get('value') == FIXED_VALUE if auth_post else False}") if not new_wf.get("active"): print(" workflow inactivo tras PUT; reactivando...") client.activate(WID) new_wf = client.get_workflow(WID) print(f" active final : {new_wf.get('active')}") print("\nOK.") if __name__ == "__main__": main()