From e4362c6ad9f8f607c543fa6a34d875fe4137940f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20G=C3=B3mez?= Date: Fri, 17 Apr 2026 16:16:43 +0200 Subject: [PATCH] Add sync detail and per-campaign advice - Sync shows campaign ID, name and status for new campaigns, and field-level changes (before/after) for updated ones - Agent now returns a 'consejo' field with a specific actionable recommendation per campaign - Summary table includes leads, margin and consejo per campaign Co-Authored-By: Claude Sonnet 4.6 --- agent.py | 4 +++- airtable_client.py | 19 ++++++++++++++----- run.py | 19 +++++++++++++------ 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/agent.py b/agent.py index 13cc6e4..3cc95c9 100644 --- a/agent.py +++ b/agent.py @@ -29,12 +29,14 @@ Devuelve ÚNICAMENTE un JSON válido con esta estructura exacta, sin texto adici "accion": "PAUSAR | REDUCIR_PRESUPUESTO | AUMENTAR_PRESUPUESTO | MANTENER", "parametro": 1.0, "nuevo_budget_diario": 0.0, - "justificacion": "explicación breve en español", + "justificacion": "explicación breve del porqué de la decisión", + "consejo": "acción concreta y específica que debería tomar el gestor (keywords, pujas, anuncios, configuración, etc.)", "alerta": "texto si hay algo crítico, null si no hay", "confianza": 0.0 } El campo nuevo_budget_diario = budget_diario_actual × parametro (calcula tú el valor final). +El campo consejo debe ser accionable y específico: qué revisar, qué cambiar, qué hacer a continuación. """ diff --git a/airtable_client.py b/airtable_client.py index 1e9c18a..dbdac3a 100644 --- a/airtable_client.py +++ b/airtable_client.py @@ -43,10 +43,10 @@ class AirtableClient: if r["fields"].get("CampaignID") } - created = [] - updated = [] + created = [] # lista de dicts {name, id, status} + updated = [] # lista de dicts {name, id, changes} to_create = [] - to_update = [] # lista de (record_id, changes, name) + to_update = [] # lista de (record_id, changes, name, gid, at_status) for gc in google_campaigns: gid = gc["id"] @@ -55,16 +55,25 @@ class AirtableClient: if at_record is None: to_create.append({"Campaign Name": gc["name"], "CampaignID": gid, "Status": at_status}) - created.append(gc["name"]) + created.append({"name": gc["name"], "id": gid, "status": at_status}) else: changes = {} + change_detail = {} if at_record["fields"].get("Campaign Name") != gc["name"]: changes["Campaign Name"] = gc["name"] + change_detail["nombre"] = { + "antes": at_record["fields"].get("Campaign Name"), + "ahora": gc["name"], + } if at_record["fields"].get("Status") != at_status: changes["Status"] = at_status + change_detail["status"] = { + "antes": at_record["fields"].get("Status"), + "ahora": at_status, + } if changes: to_update.append((at_record["id"], changes, gc["name"])) - updated.append(gc["name"]) + updated.append({"name": gc["name"], "id": gid, "changes": change_detail}) # Crear en lotes de 10 for i in range(0, len(to_create), 10): diff --git a/run.py b/run.py index ab5d42b..278dfb1 100644 --- a/run.py +++ b/run.py @@ -56,12 +56,13 @@ def run(): sync_result = at.sync_campaigns_from_google_ads(google_campaigns) if sync_result["created"]: print(f" ✅ Campañas nuevas importadas ({len(sync_result['created'])}):") - for name in sync_result["created"]: - print(f" + {name}") + for c in sync_result["created"]: + print(f" + [{c['id']}] {c['name']} → {c['status']}") if sync_result["updated"]: - print(f" 🔄 Campañas con nombre actualizado ({len(sync_result['updated'])}):") - for name in sync_result["updated"]: - print(f" ~ {name}") + print(f" 🔄 Campañas actualizadas ({len(sync_result['updated'])}):") + for c in sync_result["updated"]: + for field, val in c["changes"].items(): + print(f" ~ [{c['id']}] {c['name']} | {field}: '{val['antes']}' → '{val['ahora']}'") if not sync_result["created"] and not sync_result["updated"]: print(" ✓ Sin cambios en el catálogo.") print() @@ -109,6 +110,9 @@ def run(): f"(confianza: {decision['confianza']*100:.0f}%)") print(f" Justificación: {decision['justificacion']}") + if decision.get("consejo"): + print(f" 💡 Consejo: {decision['consejo']}") + if decision.get("alerta"): print(f" 🚨 {decision['alerta']}") @@ -122,6 +126,7 @@ def run(): "leads": f"{leads}/{campaign['capping_mensual']}", "cpa": analysis["cpa_actual"], "margen": f"{analysis['margen']*100:.0f}%", + "consejo": decision.get("consejo", ""), }) print() @@ -131,7 +136,9 @@ def run(): print("RESUMEN FINAL") print(f"{'='*55}") for r in resumen: - print(f" {r['curso'][:35]:<35} | {r['urgencia']:<12} | {r['accion']}") + print(f" {r['curso'][:35]:<35} | {r['urgencia']:<12} | {r['accion']:<25} | {r['leads']} leads | {r['margen']} margen") + if r["consejo"]: + print(f" {'':35} {'💡':>14} {r['consejo']}") print()