import os import sys import sqlite3 import json # Ruta a la base de datos (un nivel arriba del script) 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 # noqa: E402 from paths import DB_PATH _schema_cache = {} _tokens_map = None def get_location_token(location_id): global _tokens_map if _tokens_map is None: _tokens_map = sync_engine.get_tokens_map() return _tokens_map.get(location_id) def get_schema_field_id(location_id, object_key, field_name): cache_key = (location_id, object_key) if cache_key not in _schema_cache: token = get_location_token(location_id) if not token: _schema_cache[cache_key] = {} else: _schema_cache[cache_key] = sync_engine.ghl_client.get_object_schema(token, location_id, object_key) return _schema_cache[cache_key].get(field_name) def extract_custom_field_value(cf_json, field_id): if not field_id or not cf_json: return None try: cfs = json.loads(cf_json) if isinstance(cfs, list): for cf in cfs: if cf.get("id") == field_id or cf.get("fieldId") == field_id: return cf.get("value") except Exception: pass return None def analyze_discrepancies(): if not os.path.exists(DB_PATH): print(f"Error: La base de datos local no existe en {DB_PATH}.") print("Por favor, ejecuta la sincronización global primero desde el dashboard.") sys.exit(1) conn = sqlite3.connect(DB_PATH) conn.row_factory = sqlite3.Row try: # 1. Obtener todas las cuentas y mapear sus IDs y nombres accounts_rows = conn.execute("SELECT location_id, nombre FROM accounts").fetchall() branches = {} for r in accounts_rows: name = r['nombre'] # Normalizar nombre de sucursal para comparación (ej. de "85932 - MP - La Viga" a "la viga") normalized_name = name.lower() for token in ["mp", "-", "859", "0001", "qro", "demo", "plaza"]: normalized_name = normalized_name.replace(token, "") normalized_name = " ".join(normalized_name.split()) branches[r['location_id']] = { "full_name": name, "clean_name": normalized_name } # 2. Consultar todas las oportunidades y sus contactos asociados sql = """ SELECT o.id as opp_id, o.name as opp_name, o.location_id as opp_loc_id, o.status as opp_status, c.id as contact_id, c.first_name, c.last_name, c.custom_fields_json, c.location_id as contact_loc_id FROM opportunities o JOIN contacts c ON o.contact_id = c.id AND o.location_id = c.location_id """ opps = conn.execute(sql).fetchall() print("=== ANÁLISIS DE DISCREPANCIAS DE SUCURSAL ===") print(f"Total de oportunidades con contactos analizadas: {len(opps)}") print("Buscando inconsistencias entre la sucursal del contacto (campo custom) y la ubicación real de la oportunidad...\n") print("-" * 110) discrepancies_count = 0 missing_schema_locations = set() for row in opps: opp_loc_id = row['opp_loc_id'] contact_cf_json = row['custom_fields_json'] contact_loc_id = row['contact_loc_id'] # Obtener nombre de la sucursal donde vive la oportunidad opp_branch_info = branches.get(opp_loc_id, {"full_name": "Desconocida", "clean_name": ""}) opp_branch_clean = opp_branch_info["clean_name"] # Leer solo el campo personalizado Sucursal resuelto dinamicamente por schema. contact_sucursal_val = None sucursal_field_id = get_schema_field_id(contact_loc_id, "contact", "Sucursal") if not sucursal_field_id: missing_schema_locations.add(contact_loc_id) else: val = str(extract_custom_field_value(contact_cf_json, sucursal_field_id) or "").strip() val_lower = val.lower() for loc_id, b_info in branches.items(): b_clean = b_info["clean_name"] if b_clean and b_clean in val_lower: contact_sucursal_val = b_info["full_name"] break # Si el contacto tiene un valor de sucursal definido, pero no se parece al de la oportunidad if contact_sucursal_val: # Normalizar la sucursal del contacto para comparar c_suc_clean = contact_sucursal_val.lower() for token in ["mp", "-", "859", "0001", "qro", "demo", "plaza"]: c_suc_clean = c_suc_clean.replace(token, "") c_suc_clean = " ".join(c_suc_clean.split()) # Si los nombres limpios difieren, hay discrepancia if c_suc_clean != opp_branch_clean: discrepancies_count += 1 contact_name = f"{row['first_name'] or ''} {row['last_name'] or ''}".strip() print(f"{discrepancies_count:02d}. Oportunidad: {row['opp_name']}") print(f" Cliente: {contact_name} (ID: {row['contact_id']})") print(f" Ubicación Opp: {opp_branch_info['full_name']} ({opp_loc_id})") print(f" Campo Contact: {contact_sucursal_val}") print(f" Estado Opp: {row['opp_status'].upper()}") print("-" * 110) print(f"\nAnálisis finalizado.") if missing_schema_locations: print(f"Locations sin campo Sucursal resuelto por schema: {len(missing_schema_locations)}") if discrepancies_count == 0: print("¡Felicidades! No se detectaron discrepancias de sucursal en la base de datos.") else: print(f"Se encontraron {discrepancies_count} discrepancias de sucursal.") print("Se recomienda correr el script 'fix_sucursal_discrepancies.py' para corregir automáticamente.") finally: conn.close() if __name__ == "__main__": analyze_discrepancies()