Auto-calculate PPL and CPAMax from CENTROCURSO on sync

- Add build_ppl_lookup(): loads Cursos + CENTROCURSO ABIERTO in 2
  bulk calls, returns {cursoid_text: sum_ppl} for all courses
- sync_campaigns_from_google_ads() now accepts ppl_lookup and
  updates PPL = sum(CENTROCURSO.PPL where Estado ROI=ABIERTO)
  and CPAMax = PPL * 70% for each campaign
- run.py builds the lookup before sync and passes it in

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jose Manuel 2026-04-19 12:35:49 +02:00
parent 952fabe4ca
commit ec3802eccf
2 changed files with 51 additions and 2 deletions

View File

@ -8,6 +8,40 @@ class AirtableClient:
self.api = Api(config.AIRTABLE_TOKEN)
self.leads = self.api.table(config.AIRTABLE_BASE_ID, config.LEADS_TABLE)
self.campaigns = self.api.table(config.AIRTABLE_BASE_ID, config.CAMPAIGNS_TABLE)
self.cursos = self.api.table(config.AIRTABLE_BASE_ID, "Cursos")
self.centrocurso = self.api.table(config.AIRTABLE_BASE_ID, "CENTROCURSO")
def build_ppl_lookup(self) -> dict:
"""
Construye un dict {cursoid_text: ppl_sum} calculando el PPL de cada curso
sumando los PPL de los registros CENTROCURSO con Estado ROI = 'ABIERTO'.
Cadena: CursoID Text Cursos.CursoID Cursos.CentroCurso CENTROCURSO.PPL
Solo se usan en lotes (2 llamadas bulk), sin llamadas por campaña.
"""
# 1. Cargar CENTROCURSO ABIERTO: {record_id: ppl}
cc_records = self.centrocurso.all(
formula="{Estado ROI}='ABIERTO'",
fields=["PPL"],
)
cc_ppl = {r["id"]: float(r["fields"].get("PPL") or 0) for r in cc_records}
# 2. Cargar Cursos: {cursoid_number: [cc_record_ids]}
cursos_records = self.cursos.all(fields=["CursoID", "CentroCurso"])
curso_to_cc = {}
for r in cursos_records:
cid = r["fields"].get("CursoID")
cc_ids = r["fields"].get("CentroCurso", [])
if cid is not None:
curso_to_cc[str(int(cid))] = cc_ids
# 3. Para cada CursoID Text, sumar PPL de CENTROCURSO ABIERTO
ppl_lookup = {}
for cursoid_text, cc_ids in curso_to_cc.items():
total_ppl = sum(cc_ppl[cc_id] for cc_id in cc_ids if cc_id in cc_ppl)
ppl_lookup[cursoid_text] = round(total_ppl, 2)
return ppl_lookup
def get_active_campaigns(self) -> list[dict]:
"""Lee todas las campañas activas desde 'Google Ads Campaigns'."""
@ -27,7 +61,7 @@ class AirtableClient:
})
return [c for c in result if c["google_campaign_id"]] # descartar sin ID
def sync_campaigns_from_google_ads(self, google_campaigns: list[dict], monthly_metrics: dict = None) -> dict:
def sync_campaigns_from_google_ads(self, google_campaigns: list[dict], monthly_metrics: dict = None, ppl_lookup: dict = None) -> dict:
"""
Sincroniza campañas de Google Ads Airtable.
- Crea las campañas nuevas con el status real de Google Ads.
@ -57,11 +91,19 @@ class AirtableClient:
conv = round(metrics.get("conversions", 0), 2)
cost = round(metrics.get("cost", 0), 2)
# PPL calculado desde CENTROCURSO (CursoID Text viene de Airtable)
cursoid_text = str((at_record or {}).get("fields", {}).get("CursoID Text", "")).strip() if at_record else ""
ppl = round((ppl_lookup or {}).get(cursoid_text, 0), 2)
cpa_max = round(ppl * 0.70, 2)
if at_record is None:
fields = {"Campaign Name": gc["name"], "CampaignID": gid, "Status": at_status}
if monthly_metrics is not None:
fields["ConvMes"] = conv
fields["CosteMes"] = cost
if ppl_lookup is not None:
fields["PPL"] = ppl
fields["CPAMax"] = cpa_max
to_create.append(fields)
created.append({"name": gc["name"], "id": gid, "status": at_status})
else:
@ -84,6 +126,11 @@ class AirtableClient:
changes["ConvMes"] = conv
if at_record["fields"].get("CosteMes") != cost:
changes["CosteMes"] = cost
if ppl_lookup is not None:
if at_record["fields"].get("PPL") != ppl:
changes["PPL"] = ppl
if at_record["fields"].get("CPAMax") != cpa_max:
changes["CPAMax"] = cpa_max
if changes:
to_update.append((at_record["id"], changes, gc["name"]))
if change_detail:

4
run.py
View File

@ -54,7 +54,9 @@ def run():
print("→ Sincronizando campañas desde Google Ads...")
google_campaigns = gads.get_all_campaigns()
monthly_metrics = gads.get_monthly_metrics_all()
sync_result = at.sync_campaigns_from_google_ads(google_campaigns, monthly_metrics)
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"]: