From 7344cc0c522184762855e50685d10484309d03c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20G=C3=B3mez?= Date: Mon, 20 Apr 2026 18:27:51 +0200 Subject: [PATCH] Order campaign output by priority and add direct Google Ads links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Campaigns now shown in three sections: 1. ACCIONES CRÍTICAS (urgencia PAUSAR/SPRINT) 2. ACCIONES NO CRÍTICAS (other budget changes) 3. SIN CAMBIOS (MANTENER) - First pass collects all data, then sorts before printing - Each campaign shows direct Google Ads link with account ID - Summary table also grouped by section with links Co-Authored-By: Claude Sonnet 4.6 --- run.py | 124 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 102 insertions(+), 22 deletions(-) diff --git a/run.py b/run.py index 789b0fc..9be30a0 100644 --- a/run.py +++ b/run.py @@ -40,6 +40,42 @@ ICONOS = { "EN_RITMO": "✅", } +ACCION_ICONOS = { + "PAUSAR": "⛔", + "AUMENTAR_PRESUPUESTO": "📈", + "REDUCIR_PRESUPUESTO": "📉", + "MANTENER": "✅", +} + +# Google Ads customer ID (sin guiones) para construir enlaces directos +_CUSTOMER_ID = config.GOOGLE_ADS_LOGIN_CUSTOMER_ID.replace("-", "") + + +def _ads_link(campaign_id: str) -> str: + return f"https://ads.google.com/aw/campaigns?campaignId={campaign_id}&__c={_CUSTOMER_ID}" + + +def _priority(item: dict) -> int: + """ + 0 — Crítica: urgencia PAUSAR o SPRINT + 1 — No crítica: accion cambia presupuesto pero urgencia no es crítica + 2 — Mantener: accion MANTENER + """ + urgencia = item["analysis"]["urgencia"] + accion = item["decision"]["accion"] + if urgencia in ("PAUSAR", "SPRINT"): + return 0 + if accion != "MANTENER": + return 1 + return 2 + + +SECCION_LABELS = { + 0: "ACCIONES CRÍTICAS (PAUSAR / AUMENTAR)", + 1: "ACCIONES NO CRÍTICAS", + 2: "SIN CAMBIOS (MANTENER)", +} + def run(): print(f"\n{'='*55}") @@ -71,30 +107,70 @@ def run(): print() campaigns = at.get_active_campaigns() - print(f"→ {len(campaigns)} campañas activas encontradas\n") + print(f"→ {len(campaigns)} campañas activas encontradas") + print("→ Analizando...\n") - resumen = [] + # === PRIMERA PASADA: recopilar datos de todas las campañas === + collected = [] + skipped = [] for campaign in campaigns: cid = campaign["google_campaign_id"] - print(f"{'─'*55}") - print(f"📚 {campaign['curso']}") - print(f" Campaign ID: {cid} | PPL: {campaign['ppl']}€ | Cap: {campaign['capping_mensual']} leads") - # 1. Leads reales desde Airtable + vincular en campo Leads Lake leads, lead_ids = at.get_leads_this_month(campaign["cursoid_text"]) at.update_campaign_leads_lake(campaign["airtable_id"], lead_ids) - # 2. Métricas de Google Ads metrics = gads.get_campaign_metrics(cid) if not metrics: - print(f" ⚠️ Sin métricas en Google Ads, omitiendo.\n") + skipped.append(f"[{cid}] {campaign['curso']}") continue - # 3. Análisis analysis = analyze(campaign, leads, metrics) - icono = ICONOS.get(analysis["urgencia"], "❓") + decision = decide(analysis) + collected.append({ + "campaign": campaign, + "leads": leads, + "metrics": metrics, + "analysis": analysis, + "decision": decision, + }) + + if skipped: + print(f" ⚠️ {len(skipped)} campañas sin métricas omitidas:") + for s in skipped: + print(f" · {s}") + print() + + # Ordenar: 0=críticas → 1=no críticas → 2=mantener + collected.sort(key=_priority) + + # === SEGUNDA PASADA: imprimir en orden + aplicar decisiones === + resumen = [] + last_priority = -1 + + for item in collected: + campaign = item["campaign"] + leads = item["leads"] + metrics = item["metrics"] + analysis = item["analysis"] + decision = item["decision"] + cid = campaign["google_campaign_id"] + + p = _priority(item) + if p != last_priority: + print(f"\n{'━'*55}") + print(f" {SECCION_LABELS[p]}") + print(f"{'━'*55}") + last_priority = p + + icono = ICONOS.get(analysis["urgencia"], "❓") + accion_icono = ACCION_ICONOS.get(decision["accion"], "") + + print(f"{'─'*55}") + print(f"📚 {campaign['curso']}") + print(f" ID: {cid} | PPL: {campaign['ppl']}€ | Cap: {campaign['capping_mensual']} leads") + print(f" 🔗 {_ads_link(cid)}") print(f" Leads mes: {leads}/{campaign['capping_mensual']} " f"({analysis['ratio_leads']*100:.0f}% cap) | " f"Ratio mes: {analysis['ratio_mes']*100:.0f}%") @@ -108,9 +184,7 @@ def run(): print(f" 🚨 ALERTA TRACKING: {analysis['discrepancia_tracking']} leads de diferencia " f"entre Airtable ({leads}) y Google Ads ({int(analysis['conversiones_google'])})") - # 4. Decisión del agente - decision = decide(analysis) - print(f" Decisión: {decision['accion']} " + print(f" Decisión: {accion_icono} {decision['accion']} " f"(confianza: {decision['confianza']*100:.0f}%)") print(f" Justificación: {decision['justificacion']}") @@ -120,29 +194,35 @@ def run(): if decision.get("alerta"): print(f" 🚨 {decision['alerta']}") - # 5. Aplicar apply_decision(campaign, decision, metrics, gads) resumen.append({ - "curso": campaign["curso"], + "curso": campaign["curso"], "urgencia": analysis["urgencia"], - "accion": decision["accion"], - "leads": f"{leads}/{campaign['capping_mensual']}", - "cpa": analysis["cpa_actual"], - "margen": f"{analysis['margen']*100:.0f}%", - "consejo": decision.get("consejo", ""), + "accion": decision["accion"], + "leads": f"{leads}/{campaign['capping_mensual']}", + "cpa": analysis["cpa_actual"], + "margen": f"{analysis['margen']*100:.0f}%", + "consejo": decision.get("consejo", ""), + "link": _ads_link(cid), + "prioridad": p, }) print() - # Resumen final + # Resumen final ordenado por prioridad (ya está ordenado) print(f"{'='*55}") print("RESUMEN FINAL") print(f"{'='*55}") + last_p = -1 for r in resumen: + if r["prioridad"] != last_p: + print(f"\n --- {SECCION_LABELS[r['prioridad']]} ---") + last_p = r["prioridad"] print(f" {r['curso'][:35]:<35} | {r['urgencia']:<12} | {r['accion']:<25} | {r['leads']} leads | {r['margen']} margen") + print(f" {'':35} {r['link']}") if r["consejo"]: - print(f" {'':35} {'💡':>14} {r['consejo']}") + print(f" {'':35} 💡 {r['consejo']}") print()