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:
parent
952fabe4ca
commit
ec3802eccf
@ -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
4
run.py
@ -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"]:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user