#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Chequeo agendado de réplicas huérfanas con link muerto en Marca. Corre `reconcile_brand_deadlink_opps.py` en DRY-RUN (con --resync-first, no escribe nada en el CRM) y, si detecta dead-links accionables (DELETE/RELINK), deja una alerta en generated/runtime/deadlink_check_alert.json para que el owner los aplique desde el dashboard tras confirmar (protocolo dry-run → confirmación). Ataca la causa raíz que el workflow n8n NO puede cubrir en tiempo real: cuando una opp de sucursal se BORRA, GHL no dispara webhook → la réplica de Marca queda huérfana con link muerto. Este chequeo la detecta de forma determinista. Uso: python scripts/scheduled_deadlink_check.py """ 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) SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__)) if SCRIPTS_DIR not in sys.path: sys.path.insert(0, SCRIPTS_DIR) from paths import LOGS_DIR, RUNTIME_DIR # noqa: E402 import reconcile_brand_deadlink_opps as recon # noqa: E402 ALERT_PATH = os.path.join(RUNTIME_DIR, "deadlink_check_alert.json") def main(): os.makedirs(LOGS_DIR, exist_ok=True) os.makedirs(RUNTIME_DIR, exist_ok=True) ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") log_path = os.path.join(LOGS_DIR, f"deadlink_check_{ts}.log") lines = [] result = recon.run(apply=False, resync_first=True, log=lambda *a: lines.append(" ".join(str(x) for x in a))) with open(log_path, "w", encoding="utf-8") as fh: fh.write("\n".join(lines)) s = result["summary"] actionable = s["delete"] + s["relink"] print(f"[deadlink-check] {ts} dry-run: candidatos={s['candidates']} " f"delete={s['delete']} relink={s['relink']} skip={s['skip']}") print(f"[deadlink-check] log: {log_path}") if actionable > 0: alert = { "timestamp": ts, "status": "pendientes", "delete": s["delete"], "relink": s["relink"], "skip": s["skip"], "log": log_path, "snapshot": result.get("snapshot"), "aplicar_con": "python scripts/reconcile_brand_deadlink_opps.py --resync-first --apply --run-id ", "nota": "Réplicas huérfanas con link muerto detectadas. Revisa el plan y aplica desde el dashboard tras confirmar (protocolo dry-run).", } with open(ALERT_PATH, "w", encoding="utf-8") as fh: json.dump(alert, fh, ensure_ascii=False, indent=2) print(f"[deadlink-check] ALERTA: {actionable} accionables -> {ALERT_PATH}") else: if os.path.exists(ALERT_PATH): os.remove(ALERT_PATH) print("[deadlink-check] sin dead-links accionables. Todo al día.") if __name__ == "__main__": main()