diff --git a/airtable_client.py b/airtable_client.py index debd9e8..0f7fe19 100644 --- a/airtable_client.py +++ b/airtable_client.py @@ -360,12 +360,18 @@ class AirtableClient: def batch_update_gacampaignmes_final_leads(self, gcm_records: list[dict]) -> None: """ - Copia ConvLeadsLakeMes → ConvLeadsLakeMesFinal en GACampaignMes. - Se llama una vez por ejecución diaria. - gcm_records: lista de dicts con 'airtable_id' y 'conv_leads_lake_mes' + Actualiza ConvLeadsLakeMesFinal y ConvLeadsLakeMesGrupo en GACampaignMes. + gcm_records: lista de dicts con: + - 'airtable_id' + - 'conv_leads_lake_mes' → valor para ConvLeadsLakeMesFinal + - 'conv_leads_lake_mes_grupo' → valor para ConvLeadsLakeMesGrupo + (igual al anterior salvo en PMX con Search companion: usa leads del Search) """ batch = [ - {"id": r["airtable_id"], "fields": {"ConvLeadsLakeMesFinal": r["conv_leads_lake_mes"]}} + {"id": r["airtable_id"], "fields": { + "ConvLeadsLakeMesFinal": r["conv_leads_lake_mes"], + "ConvLeadsLakeMesGrupo": r["conv_leads_lake_mes_grupo"], + }} for r in gcm_records ] for i in range(0, len(batch), 10): diff --git a/run.py b/run.py index f3fd062..315f359 100644 --- a/run.py +++ b/run.py @@ -1,6 +1,7 @@ import sys import io import os +import re sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", line_buffering=True) from airtable_client import AirtableClient @@ -173,6 +174,23 @@ def run(): # Ordenar: 0=críticas → 1=no críticas → 2=mantener collected.sort(key=_priority) + # Detectar cursos con campaña Search Y PMX simultáneas (posible reatribución PMX) + 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 + + course_types: dict[str, set] = {} + for item in collected: + name = item["campaign"]["curso"] + num = _course_num(name) + if num: + course_types.setdefault(num, set()) + if "pmx" in name.lower(): + course_types[num].add("pmx") + elif "search" in name.lower(): + course_types[num].add("search") + courses_with_both = {num for num, types in course_types.items() if "pmx" in types and "search" in types} + # === SEGUNDA PASADA: imprimir en orden + aplicar decisiones === resumen = [] advice_updates = [] # (gcm_record_id, consejo, criticidad) para batch update final @@ -235,6 +253,11 @@ def run(): else: log_text = f"OK: {leads} leads Airtable | {conv_google} conv Google Ads" + # Nota de reatribución PMX para campañas Search con companion PMX del mismo curso + course_num = _course_num(campaign["curso"]) + if "search" in campaign["curso"].lower() and course_num in courses_with_both: + log_text += " | ⚠️ PMX ATTRIBUTION: campaña Search con companion PMX activo — parte de las conversiones de Google pueden estar reatribuidas a la campaña PMX" + advice_updates.append(( campaign["airtable_id"], decision.get("consejo", ""), @@ -262,12 +285,27 @@ def run(): at.batch_update_gacampaignmes_advice(advice_updates) print(" ✓ Consejos y criticidad guardados.") - # Copiar ConvLeadsLakeMes → ConvLeadsLakeMesFinal (snapshot diario) - final_leads_data = [ - {"airtable_id": item["campaign"]["airtable_id"], - "conv_leads_lake_mes": item["leads"]} + # Snapshot diario: ConvLeadsLakeMesFinal + ConvLeadsLakeMesGrupo + # Para PMX con Search companion del mismo curso, Grupo = leads del Search + course_search_leads = { + _course_num(item["campaign"]["curso"]): item["leads"] for item in collected - ] + if "search" in item["campaign"]["curso"].lower() + and _course_num(item["campaign"]["curso"]) + } + final_leads_data = [] + for item in collected: + name = item["campaign"]["curso"] + num = _course_num(name) + if num and "pmx" in name.lower() and num in courses_with_both: + grupo = course_search_leads.get(num, item["leads"]) + else: + grupo = item["leads"] + final_leads_data.append({ + "airtable_id": item["campaign"]["airtable_id"], + "conv_leads_lake_mes": item["leads"], + "conv_leads_lake_mes_grupo": grupo, + }) if final_leads_data: print(f"→ Actualizando ConvLeadsLakeMesFinal ({len(final_leads_data)} registros)...") at.batch_update_gacampaignmes_final_leads(final_leads_data)