Files
MP-Manager/scripts/audit_orphaned_pipelines_readonly.py
T
2026-05-30 14:31:19 -06:00

209 lines
8.0 KiB
Python

# -*- 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 <id> 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 <id> 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()