Align field names with actual Airtable schema

- Campaigns: Status='Activa' filter (was Activa=TRUE), CPAMax (was
  CPAMaximo), add MargenObjetivo and CursoID Text per campaign
- Leads: filter by attr_cursoid + creado (was GoogleCampaignID + Fecha)
- analyzer: expose margen_objetivo and margen_ok per campaign
- config: remove global MARGEN_MINIMO, now per-campaign via Airtable
- sync: new campaigns created with Status='Pausada'

https://claude.ai/code/session_01WEcdWPxGWZKk8FGwBRLGR2
This commit is contained in:
Claude 2026-04-17 11:48:52 +00:00
parent d68f9f5659
commit 905ad3bcd2
No known key found for this signature in database
4 changed files with 15 additions and 11 deletions

View File

@ -11,17 +11,19 @@ class AirtableClient:
def get_active_campaigns(self) -> list[dict]: def get_active_campaigns(self) -> list[dict]:
"""Lee todas las campañas activas desde 'Google Ads Campaigns'.""" """Lee todas las campañas activas desde 'Google Ads Campaigns'."""
records = self.campaigns.all(formula="{Activa}=TRUE()") records = self.campaigns.all(formula="{Status}='Activa'")
result = [] result = []
for r in records: for r in records:
f = r["fields"] f = r["fields"]
result.append({ result.append({
"airtable_id": r["id"], "airtable_id": r["id"],
"curso": f.get("Campaign Name", "Sin nombre"), "curso": f.get("Campaign Name", "Sin nombre"),
"cursoid_text": str(f.get("CursoID Text", "")).strip(),
"google_campaign_id": str(f.get("CampaignID", "")).strip(), "google_campaign_id": str(f.get("CampaignID", "")).strip(),
"ppl": float(f.get("PPL", 0)), "ppl": float(f.get("PPL", 0)),
"capping_mensual": int(f.get("CapTotalMes", 0)), "capping_mensual": int(f.get("CapTotalMes", 0)),
"cpa_maximo": float(f.get("CPAMaximo", 0)), "cpa_maximo": float(f.get("CPAMax", 0)),
"margen_objetivo": float(f.get("MargenObjetivo", 0)),
}) })
return [c for c in result if c["google_campaign_id"]] # descartar sin ID return [c for c in result if c["google_campaign_id"]] # descartar sin ID
@ -50,7 +52,7 @@ class AirtableClient:
self.campaigns.create({ self.campaigns.create({
"Campaign Name": gc["name"], "Campaign Name": gc["name"],
"CampaignID": int(gid), "CampaignID": int(gid),
"Activa": False, "Status": "Pausada",
}) })
created.append(gc["name"]) created.append(gc["name"])
else: else:
@ -63,18 +65,18 @@ class AirtableClient:
return {"created": created, "updated": updated} return {"created": created, "updated": updated}
def get_leads_this_month(self, google_campaign_id: str) -> int: def get_leads_this_month(self, cursoid_text: str) -> int:
""" """
Cuenta todos los leads del mes actual para una campaña. Cuenta todos los leads validados del mes actual para un curso.
Todos los leads en Airtable están validados, sin filtro de estado. Filtra por attr_cursoid (que coincide con CursoID Text de la campaña).
""" """
now = datetime.now() now = datetime.now()
mes_inicio = f"{now.year}-{now.month:02d}-01" mes_inicio = f"{now.year}-{now.month:02d}-01"
formula = ( formula = (
f"AND(" f"AND("
f"{{GoogleCampaignID}}='{google_campaign_id}'," f"{{attr_cursoid}}='{cursoid_text}',"
f"{{Fecha}}>='{mes_inicio}'" f"{{creado}}>='{mes_inicio}'"
f")" f")"
) )
records = self.leads.all(formula=formula) records = self.leads.all(formula=formula)

View File

@ -11,6 +11,7 @@ def analyze(campaign_config: dict, leads_entregados: int, ads_metrics: dict) ->
capping = campaign_config["capping_mensual"] capping = campaign_config["capping_mensual"]
ppl = campaign_config["ppl"] ppl = campaign_config["ppl"]
cpa_max = campaign_config["cpa_maximo"] cpa_max = campaign_config["cpa_maximo"]
margen_objetivo = campaign_config.get("margen_objetivo", 0)
gasto = ads_metrics.get("cost", 0) gasto = ads_metrics.get("cost", 0)
conversiones_google = ads_metrics.get("conversions", 0) conversiones_google = ads_metrics.get("conversions", 0)
@ -42,6 +43,8 @@ def analyze(campaign_config: dict, leads_entregados: int, ads_metrics: dict) ->
"campaign_id": campaign_config["google_campaign_id"], "campaign_id": campaign_config["google_campaign_id"],
"ppl": ppl, "ppl": ppl,
"cpa_maximo": cpa_max, "cpa_maximo": cpa_max,
"margen_objetivo": margen_objetivo,
"margen_ok": margen >= margen_objetivo if margen_objetivo > 0 else True,
"capping": capping, "capping": capping,
"leads_entregados": leads_entregados, "leads_entregados": leads_entregados,
"leads_restantes": leads_restantes, "leads_restantes": leads_restantes,

View File

@ -20,5 +20,4 @@ GOOGLE_ADS_LOGIN_CUSTOMER_ID = os.environ["GOOGLE_ADS_LOGIN_CUSTOMER_ID"]
ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"] ANTHROPIC_API_KEY = os.environ["ANTHROPIC_API_KEY"]
# Operación # Operación
DRY_RUN = True # True = solo sugiere, no aplica cambios en Google Ads DRY_RUN = True # True = solo sugiere, no aplica cambios en Google Ads
MARGEN_MINIMO = 0.40 # 40% mínimo de margen sobre PPL

2
run.py
View File

@ -53,7 +53,7 @@ def run():
print(f" Campaign ID: {cid} | PPL: {campaign['ppl']}€ | Cap: {campaign['capping_mensual']} leads") print(f" Campaign ID: {cid} | PPL: {campaign['ppl']}€ | Cap: {campaign['capping_mensual']} leads")
# 1. Leads reales desde Airtable # 1. Leads reales desde Airtable
leads = at.get_leads_this_month(cid) leads = at.get_leads_this_month(campaign["cursoid_text"])
# 2. Métricas de Google Ads # 2. Métricas de Google Ads
metrics = gads.get_campaign_metrics(cid) metrics = gads.get_campaign_metrics(cid)