Merge pull request #1 from jmgomezroi/claude/help-project-setup-t4hyw

Claude/help project setup t4hyw
This commit is contained in:
Jose Manuel 2026-04-17 14:02:45 +02:00 committed by GitHub
commit ef7d494aeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 99 additions and 10 deletions

View File

@ -11,32 +11,77 @@ class AirtableClient:
def get_active_campaigns(self) -> list[dict]:
"""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 = []
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("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
def get_leads_this_month(self, google_campaign_id: str) -> int:
def sync_campaigns_from_google_ads(self, google_campaigns: list[dict]) -> dict:
"""
Cuenta todos los leads del mes actual para una campaña.
Todos los leads en Airtable están validados, sin filtro de estado.
Sincroniza campañas de Google Ads Airtable.
- Crea las campañas nuevas con el status real de Google Ads.
- Actualiza nombre y/o status si han cambiado.
Devuelve un resumen con listas de campañas creadas y actualizadas.
"""
STATUS_MAP = {"ENABLED": "Activa", "PAUSED": "Pausada"}
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_status = STATUS_MAP.get(gc["status"], "Pausada")
at_record = at_by_gid.get(gid)
if at_record is None:
self.campaigns.create({
"Campaign Name": gc["name"],
"CampaignID": int(gid),
"Status": at_status,
})
created.append(gc["name"])
else:
changes = {}
if at_record["fields"].get("Campaign Name") != gc["name"]:
changes["Campaign Name"] = gc["name"]
if at_record["fields"].get("Status") != at_status:
changes["Status"] = at_status
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"{{GoogleCampaignID}}='{google_campaign_id}',"
f"{{Fecha}}>='{mes_inicio}'"
f"{{attr_cursoid}}='{cursoid_text}',"
f"{{creado}}>='{mes_inicio}'"
f")"
)
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"]
ppl = campaign_config["ppl"]
cpa_max = campaign_config["cpa_maximo"]
margen_objetivo = campaign_config.get("margen_objetivo", 0)
gasto = ads_metrics.get("cost", 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"],
"ppl": ppl,
"cpa_maximo": cpa_max,
"margen_objetivo": margen_objetivo,
"margen_ok": margen >= margen_objetivo if margen_objetivo > 0 else True,
"capping": capping,
"leads_entregados": leads_entregados,
"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"]
# Operación
DRY_RUN = True # True = solo sugiere, no aplica cambios en Google Ads
MARGEN_MINIMO = 0.40 # 40% mínimo de margen sobre PPL
DRY_RUN = True # True = solo sugiere, no aplica cambios en Google Ads

View File

@ -15,6 +15,32 @@ class GoogleAdsClient:
})
self.customer_id = config.GOOGLE_ADS_LOGIN_CUSTOMER_ID
def get_all_campaigns(self) -> list[dict]:
"""Obtiene todas las campañas no eliminadas de la cuenta."""
ga_service = self.client.get_service("GoogleAdsService")
query = """
SELECT
campaign.id,
campaign.name,
campaign.status
FROM campaign
WHERE campaign.status != 'REMOVED'
ORDER BY campaign.name
"""
try:
response = ga_service.search(customer_id=self.customer_id, query=query)
return [
{
"id": str(row.campaign.id),
"name": row.campaign.name,
"status": row.campaign.status.name,
}
for row in response
]
except GoogleAdsException as e:
print(f" ❌ Error obteniendo campañas de Google Ads: {e}")
return []
def get_campaign_metrics(self, campaign_id: str) -> dict:
"""Métricas del mes en curso para una campaña concreta."""
ga_service = self.client.get_service("GoogleAdsService")

18
run.py
View File

@ -25,6 +25,22 @@ def run():
at = AirtableClient()
gads = GoogleAdsClient()
# Sincronizar catálogo de campañas desde Google Ads → Airtable
print("→ Sincronizando campañas desde Google Ads...")
google_campaigns = gads.get_all_campaigns()
sync_result = at.sync_campaigns_from_google_ads(google_campaigns)
if sync_result["created"]:
print(f" ✅ Campañas nuevas importadas ({len(sync_result['created'])}):")
for name in sync_result["created"]:
print(f" + {name}")
if sync_result["updated"]:
print(f" 🔄 Campañas con nombre actualizado ({len(sync_result['updated'])}):")
for name in sync_result["updated"]:
print(f" ~ {name}")
if not sync_result["created"] and not sync_result["updated"]:
print(" ✓ Sin cambios en el catálogo.")
print()
campaigns = at.get_active_campaigns()
print(f"{len(campaigns)} campañas activas encontradas\n")
@ -37,7 +53,7 @@ def run():
print(f" Campaign ID: {cid} | PPL: {campaign['ppl']}€ | Cap: {campaign['capping_mensual']} leads")
# 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
metrics = gads.get_campaign_metrics(cid)