#!/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()