- 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
84 lines
3.1 KiB
Python
84 lines
3.1 KiB
Python
from pyairtable import Api
|
|
from datetime import datetime
|
|
import config
|
|
|
|
|
|
class AirtableClient:
|
|
def __init__(self):
|
|
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)
|
|
|
|
def get_active_campaigns(self) -> list[dict]:
|
|
"""Lee todas las campañas activas desde 'Google Ads Campaigns'."""
|
|
records = self.campaigns.all(formula="{Status}='Activa'")
|
|
result = []
|
|
for r in records:
|
|
f = r["fields"]
|
|
result.append({
|
|
"airtable_id": r["id"],
|
|
"curso": f.get("Campaign Name", "Sin nombre"),
|
|
"cursoid_text": str(f.get("CursoID Text", "")).strip(),
|
|
"google_campaign_id": str(f.get("CampaignID", "")).strip(),
|
|
"ppl": float(f.get("PPL", 0)),
|
|
"capping_mensual": int(f.get("CapTotalMes", 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
|
|
|
|
def sync_campaigns_from_google_ads(self, google_campaigns: list[dict]) -> dict:
|
|
"""
|
|
Sincroniza campañas de Google Ads → Airtable.
|
|
- Crea las campañas nuevas (Activa=False, requiere configuración manual de PPL/Cap/CPA).
|
|
- Actualiza el nombre si ha cambiado.
|
|
Devuelve un resumen con listas de campañas creadas y actualizadas.
|
|
"""
|
|
all_records = self.campaigns.all()
|
|
at_by_gid = {
|
|
str(r["fields"].get("CampaignID", "")).strip(): r
|
|
for r in all_records
|
|
if r["fields"].get("CampaignID")
|
|
}
|
|
|
|
created = []
|
|
updated = []
|
|
|
|
for gc in google_campaigns:
|
|
gid = gc["id"]
|
|
at_record = at_by_gid.get(gid)
|
|
|
|
if at_record is None:
|
|
self.campaigns.create({
|
|
"Campaign Name": gc["name"],
|
|
"CampaignID": int(gid),
|
|
"Status": "Pausada",
|
|
})
|
|
created.append(gc["name"])
|
|
else:
|
|
changes = {}
|
|
if at_record["fields"].get("Campaign Name") != gc["name"]:
|
|
changes["Campaign Name"] = gc["name"]
|
|
if changes:
|
|
self.campaigns.update(at_record["id"], changes)
|
|
updated.append(gc["name"])
|
|
|
|
return {"created": created, "updated": updated}
|
|
|
|
def get_leads_this_month(self, cursoid_text: str) -> int:
|
|
"""
|
|
Cuenta todos los leads validados del mes actual para un curso.
|
|
Filtra por attr_cursoid (que coincide con CursoID Text de la campaña).
|
|
"""
|
|
now = datetime.now()
|
|
mes_inicio = f"{now.year}-{now.month:02d}-01"
|
|
|
|
formula = (
|
|
f"AND("
|
|
f"{{attr_cursoid}}='{cursoid_text}',"
|
|
f"{{creado}}>='{mes_inicio}'"
|
|
f")"
|
|
)
|
|
records = self.leads.all(formula=formula)
|
|
return len(records)
|