# -*- coding: utf-8 -*- import os import sys import argparse from datetime import datetime 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 from ghl_client import GHLClient MAIN_LOCATION_ID = "GbKkBpCmKu2QmloKFHy3" def load_locations(include_main=False): accounts = sync_engine.parse_accounts_csv() if not include_main: accounts = [acc for acc in accounts if acc.get("location_id") != MAIN_LOCATION_ID] return accounts def select_locations(args): if args.location: accounts = sync_engine.parse_accounts_csv() matches = [acc for acc in accounts if acc["location_id"] == args.location] if not matches: print(f"Error: La sucursal con ID {args.location} no fue encontrada en el CSV.") sys.exit(1) return matches accounts = load_locations(include_main=args.include_main) if args.all: return accounts print("Error: Debes especificar --location o --all para procesar.") sys.exit(1) def audit_location(account, client): location_id = account["location_id"] name = account["nombre"] token = account["token"] print("\n" + "=" * 80) print(f"AUDITANDO: {name} ({location_id})") print("=" * 80) # 1. Consultar Pipelines de GHL print("[1/2] Consultando pipelines activos desde la API de GHL...") pipelines = [] pipelines_status = "Exitoso" try: pipelines = client.get_pipelines(token, location_id) if not pipelines: print(" -> La API de GHL retorno 0 pipelines (o la consulta fallo con 404/No autorizado).") pipelines_status = "0 pipelines / 404" else: print(f" -> Se encontraron {len(pipelines)} pipelines activos en GHL:") for p in pipelines: print(f" * ID: {p.get('id')} | Nombre: {p.get('name')}") except Exception as exc: print(f" -> Error al consultar pipelines: {exc}") pipelines_status = f"Error: {exc}" # 2. Consultar Oportunidades de GHL print("[2/2] Consultando oportunidades live desde la API de GHL...") opportunities = [] try: opportunities = client.get_all_opportunities(token, location_id) print(f" -> Se recuperaron {len(opportunities)} oportunidades en total desde GHL.") except Exception as exc: print(f" -> Error al consultar oportunidades: {exc}") return { "name": name, "location_id": location_id, "pipelines_status": pipelines_status, "error": str(exc) } # Analisis status_counts = {} opp_pipeline_ids = set() opp_pipeline_stages = {} opps_by_pipeline = {} for opp in opportunities: status = opp.get("status", "unknown").lower() status_counts[status] = status_counts.get(status, 0) + 1 p_id = opp.get("pipelineId") or opp.get("pipeline_id") s_id = opp.get("pipelineStageId") or opp.get("pipeline_stage_id") if p_id: opp_pipeline_ids.add(p_id) opps_by_pipeline.setdefault(p_id, []).append(opp) if s_id: opp_pipeline_stages.setdefault(p_id, set()).add(s_id) # Buscar huerfanas active_pipeline_ids = {p.get("id") for p in pipelines if p.get("id")} orphaned_pipelines = [] print("\n--- Analisis de Pipelines en Oportunidades ---") for p_id in opp_pipeline_ids: opp_count = len(opps_by_pipeline[p_id]) is_orphaned = p_id not in active_pipeline_ids if active_pipeline_ids else True if is_orphaned: orphaned_pipelines.append(p_id) status_label = "HUERFANO (No existe o inactivo)" else: status_label = "ACTIVO" p_name = next((p.get("name") for p in pipelines if p.get("id") == p_id), "Sintetico/Desconocido") print(f" * Pipeline '{p_name}' (ID: {p_id}) | Estado: {status_label} | Opps: {opp_count}") # Mostrar las abiertas open_opps = [opp for opp in opportunities if opp.get("status", "").lower() == "open"] if open_opps: print(f"\n -> Oportunidades en estado Abierto ('open') encontradas: {len(open_opps)}") for idx, opp in enumerate(open_opps[:10], 1): # Mostrar maximo 10 para no saturar contact = opp.get("contact", {}) c_name = contact.get("name") or "Sin Nombre" print(f" {idx}. {opp.get('name')} (Contacto: {c_name}) | Pipeline ID: {opp.get('pipelineId')}") if len(open_opps) > 10: print(f" ... y {len(open_opps) - 10} mas abiertas.") return { "name": name, "location_id": location_id, "pipelines_status": pipelines_status, "total_opps": len(opportunities), "status_counts": status_counts, "pipelines_count": len(pipelines), "orphaned_pipelines": orphaned_pipelines, "error": None } def main(): if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8") parser = argparse.ArgumentParser(description="Auditar Pipelines y Oportunidades (Solo Lectura) de manera Global o Individual") parser.add_argument("--location", help="ID de la sucursal especifica a auditar") parser.add_argument("--all", action="store_true", help="Audita todas las sucursales") parser.add_argument("--include-main", action="store_true", help="Incluye la cuenta de marca principal al usar --all") args = parser.parse_args() # Validar argumentos if not args.location and not args.all: print("Error: Debes especificar --location o --all para poder ejecutar el reporte.") print("Ejemplo individual: python scripts/audit_orphaned_pipelines_readonly.py --location nF1uEaYB3mCK5em9bPn2") print("Ejemplo global: python scripts/audit_orphaned_pipelines_readonly.py --all") sys.exit(1) accounts = select_locations(args) print(f"=== INICIANDO AUDITORIA GLOBAL DE PIPELINES Y OPORTUNIDADES ===") print(f"Fecha/Hora: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"Cuentas seleccionadas para auditar: {len(accounts)}") print("-" * 80) client = GHLClient() results = [] for idx, acc in enumerate(accounts, 1): print(f"\n[{idx}/{len(accounts)}] Preparando {acc['nombre']}...") try: res = audit_location(acc, client) results.append(res) except Exception as exc: print(f" -> ERROR al procesar {acc['nombre']}: {exc}") results.append({ "name": acc["nombre"], "location_id": acc["location_id"], "pipelines_status": "Fallo", "error": str(exc) }) # Imprimir Reporte Consolidado print("\n" + "=" * 100) print("RESUMEN CONSOLIDADO DE LA AUDITORIA GLOBAL") print("=" * 100) print(f"{'SUCURSAL':<35} | {'PIPELINES GHL':<18} | {'OPPS TOTAL':<10} | {'OPPS OPEN':<10} | {'HUERFANOS':<10}") print("-" * 100) total_opps_global = 0 total_open_global = 0 for r in results: if r.get("error"): print(f"{r['name'][:35]:<35} | {'ERROR':<18} | {'N/A':<10} | {'N/A':<10} | {'N/A':<10} (Error: {r['error'][:30]})") else: total_opps = r.get("total_opps", 0) open_opps = r.get("status_counts", {}).get("open", 0) huerfanos = len(r.get("orphaned_pipelines", [])) total_opps_global += total_opps total_open_global += open_opps pipes_info = f"OK ({r.get('pipelines_count')})" if r.get("pipelines_count", 0) > 0 else "404/Vacio" huerfanos_str = f"SI ({huerfanos})" if huerfanos > 0 else "NO" print(f"{r['name'][:35]:<35} | {pipes_info:<18} | {total_opps:<10} | {open_opps:<10} | {huerfanos_str:<10}") print("-" * 100) print(f"{'TOTAL GLOBAL':<35} | {'-':<18} | {total_opps_global:<10} | {total_open_global:<10} |") print("=" * 100) print("\nAuditoria finalizada con éxito.") if __name__ == "__main__": main()