209 lines
8.0 KiB
Python
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() |