Before each run, fetch all non-removed campaigns from Google Ads and upsert them into the Airtable campaigns table: new campaigns are created (Activa=False, pending manual PPL/Cap/CPA setup) and existing ones get their name updated if it changed. https://claude.ai/code/session_01WEcdWPxGWZKk8FGwBRLGR2
115 lines
3.9 KiB
Python
115 lines
3.9 KiB
Python
from airtable_client import AirtableClient
|
|
from google_ads_client import GoogleAdsClient
|
|
from analyzer import analyze
|
|
from agent import decide
|
|
from optimizer import apply_decision
|
|
import config
|
|
from datetime import datetime
|
|
|
|
|
|
ICONOS = {
|
|
"PAUSAR": "⛔",
|
|
"SPRINT": "🚀",
|
|
"ACELERAR": "📈",
|
|
"FRENAR": "📉",
|
|
"EN_RITMO": "✅",
|
|
}
|
|
|
|
|
|
def run():
|
|
print(f"\n{'='*55}")
|
|
print(f" LEADS OPTIMIZER — {datetime.now().strftime('%d/%m/%Y %H:%M')}")
|
|
print(f" Modo: {'DRY RUN (sin cambios)' if config.DRY_RUN else '⚡ PRODUCCIÓN'}")
|
|
print(f"{'='*55}\n")
|
|
|
|
at = AirtableClient()
|
|
gads = GoogleAdsClient()
|
|
|
|
# Sincronizar catálogo de campañas desde Google Ads → Airtable
|
|
print("→ Sincronizando campañas desde Google Ads...")
|
|
google_campaigns = gads.get_all_campaigns()
|
|
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}")
|
|
if sync_result["updated"]:
|
|
print(f" 🔄 Campañas con nombre actualizado ({len(sync_result['updated'])}):")
|
|
for name in sync_result["updated"]:
|
|
print(f" ~ {name}")
|
|
if not sync_result["created"] and not sync_result["updated"]:
|
|
print(" ✓ Sin cambios en el catálogo.")
|
|
print()
|
|
|
|
campaigns = at.get_active_campaigns()
|
|
print(f"→ {len(campaigns)} campañas activas encontradas\n")
|
|
|
|
resumen = []
|
|
|
|
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
|
|
leads = at.get_leads_this_month(cid)
|
|
|
|
# 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")
|
|
continue
|
|
|
|
# 3. Análisis
|
|
analysis = analyze(campaign, leads, metrics)
|
|
icono = ICONOS.get(analysis["urgencia"], "❓")
|
|
|
|
print(f" Leads mes: {leads}/{campaign['capping_mensual']} "
|
|
f"({analysis['ratio_leads']*100:.0f}% cap) | "
|
|
f"Ratio mes: {analysis['ratio_mes']*100:.0f}%")
|
|
print(f" CPA actual: {analysis['cpa_actual']}€ | "
|
|
f"CPA máximo: {analysis['cpa_maximo']}€ | "
|
|
f"Margen: {analysis['margen']*100:.0f}%")
|
|
print(f" Urgencia: {icono} {analysis['urgencia']} | "
|
|
f"Rentable: {'✅' if analysis['rentable'] else '❌'}")
|
|
|
|
if analysis["alerta_tracking"]:
|
|
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']} "
|
|
f"(confianza: {decision['confianza']*100:.0f}%)")
|
|
print(f" Justificación: {decision['justificacion']}")
|
|
|
|
if decision.get("alerta"):
|
|
print(f" 🚨 {decision['alerta']}")
|
|
|
|
# 5. Aplicar
|
|
apply_decision(campaign, decision, metrics, gads)
|
|
|
|
resumen.append({
|
|
"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}%",
|
|
})
|
|
|
|
print()
|
|
|
|
# Resumen final
|
|
print(f"{'='*55}")
|
|
print("RESUMEN FINAL")
|
|
print(f"{'='*55}")
|
|
for r in resumen:
|
|
print(f" {r['curso'][:35]:<35} | {r['urgencia']:<12} | {r['accion']}")
|
|
print()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|