""" Reconstruye el día 30/06/2026 en MetricasDiarias para las GACampaignMes de junio a las que les falta esa clave. Motivo: en el cambio de mes (1 julio), run.py leía MetricasDiarias del registro de julio (recién creado, casi vacío) en vez del de junio, así que al redirigir la escritura hacia el registro de junio correcto se sobrescribía con un dict casi vacío (perdiendo el día 30 real, que en algunos casos quedó mal grabado en el registro de julio). Aquí se recalcula el día 30 desde Google Ads (coste, conversiones) y Leads Lake, y se añade SOLO a los registros de junio que no tienen ya esa clave — no se toca nada más de su histórico. Uso: python backfill_metricas_30junio.py # dry run — solo muestra python backfill_metricas_30junio.py --apply # escribe en Airtable """ import json import re import sys from airtable_client import AirtableClient from google_ads_client import GoogleAdsClient DATE_STR = "2026-06-30" DAY_KEY = "30" MES, ANIO = 6, 2026 def _course_num(name: str) -> str | None: m = re.search(r'fco_(?:search|pmx)_(\d+)', name, re.IGNORECASE) return m.group(1) if m else None def run(apply: bool): at = AirtableClient() gads = GoogleAdsClient() formula = f"AND({{Mes}}='{MES}',{{Año}}='{ANIO}')" records = at.gacampaignmes.all( formula=formula, fields=["CampaignID", "PPL", "MetricasDiarias", "Campaign Name (from CampaignID)"], ) campaigns_records = at.campaigns.all(fields=["CampaignID"]) at_id_to_gid = {r["id"]: str(r["fields"].get("CampaignID", "")).strip() for r in campaigns_records} campaigns = [] for r in records: f = r["fields"] at_cids = f.get("CampaignID", []) gid = at_id_to_gid.get(at_cids[0], "") if at_cids else "" if not gid: continue name_list = f.get("Campaign Name (from CampaignID)", []) campaigns.append({ "airtable_id": r["id"], "google_campaign_id": gid, "curso": name_list[0] if name_list else "Sin nombre", "ppl": float(f.get("PPL") or 0), "metricas_diarias": f.get("MetricasDiarias") or "{}", }) print(f"→ {len(campaigns)} campañas de {MES}/{ANIO} en GACampaignMes") # Mapping cursoid → PMX campaign_id, igual que run.py, para atribuir leadforms cursoid_to_campaign: dict[str, str] = {} for c in campaigns: num = _course_num(c["curso"]) if num and "pmx" in c["curso"].lower() and "_leadform" not in c["curso"].lower(): cursoid_to_campaign[num] = c["google_campaign_id"] # Detectar registros a los que YA les falta el día 30 targets = [] for c in campaigns: try: md = json.loads(c["metricas_diarias"]) except (json.JSONDecodeError, TypeError): md = {} if DAY_KEY not in md: c["md"] = md targets.append(c) print(f"→ {len(targets)} campañas sin el día {DAY_KEY} en su MetricasDiarias\n") if not targets: print("Nada que hacer.") return ads_metrics = gads.get_metrics_for_date(DATE_STR) leads_lake_counts = at.get_leads_by_campaign_on_date(DATE_STR, cursoid_to_campaign) metricas_updates = [] print(f"{'Campaña':45} {'Coste':>10} {'Conv':>6} {'Ingreso':>10} {'Margen':>10} {'LeadsLake':>10}") for c in targets: cid = c["google_campaign_id"] today_m = ads_metrics.get(cid, {}) coste_hoy = round(today_m.get("cost", 0), 2) conv_hoy = today_m.get("conversions", 0) ingreso_hoy = round(conv_hoy * c["ppl"], 2) margen_hoy = round(ingreso_hoy - coste_hoy, 2) leads_lake_hoy = leads_lake_counts.get(cid, 0) print(f"{c['curso'][:45]:45} {coste_hoy:>9.2f}€ {conv_hoy:>6.0f} " f"{ingreso_hoy:>9.2f}€ {margen_hoy:>9.2f}€ {leads_lake_hoy:>10}") md = dict(c["md"]) md[DAY_KEY] = { "coste": coste_hoy, "ingreso": ingreso_hoy, "margen": margen_hoy, "leads": int(conv_hoy), "leads_lake": leads_lake_hoy, } metricas_updates.append({ "airtable_id": c["airtable_id"], "metricas_json": json.dumps(md, ensure_ascii=False), }) print() if apply: at.batch_update_metricas_diarias(metricas_updates) print(f"✅ {len(metricas_updates)} registros actualizados en Airtable.") else: print(f"DRY RUN — {len(metricas_updates)} registros se actualizarían. " f"Ejecuta con --apply para escribir en Airtable.") if __name__ == "__main__": run(apply="--apply" in sys.argv)