import sys import io import os sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", line_buffering=True) 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 class Tee: """Escribe simultáneamente en consola y en archivo de log.""" def __init__(self, filepath): os.makedirs(os.path.dirname(filepath), exist_ok=True) self._file = open(filepath, "w", encoding="utf-8") self._stdout = sys.stdout def write(self, data): self._stdout.write(data) self._file.write(data) def flush(self): self._stdout.flush() if not self._file.closed: self._file.flush() def close(self): self._file.close() 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() monthly_metrics = gads.get_monthly_metrics_all() print(" Calculando PPL desde CENTROCURSO...") ppl_lookup = at.build_ppl_lookup() sync_result = at.sync_campaigns_from_google_ads(google_campaigns, monthly_metrics, ppl_lookup) if sync_result["created"]: print(f" ✅ Campañas nuevas importadas ({len(sync_result['created'])}):") for c in sync_result["created"]: print(f" + [{c['id']}] {c['name']} → {c['status']}") if sync_result["updated"]: 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() 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 + 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") 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("consejo"): print(f" 💡 Consejo: {decision['consejo']}") 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}%", "consejo": decision.get("consejo", ""), }) 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']:<25} | {r['leads']} leads | {r['margen']} margen") if r["consejo"]: print(f" {'':35} {'💡':>14} {r['consejo']}") print() if __name__ == "__main__": timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") log_path = os.path.join("logs", f"{timestamp}.log") tee = Tee(log_path) sys.stdout = tee try: run() finally: tee.close() print(f"\nLog guardado en: {log_path}", file=tee._stdout)