From d58448f698ae9f448e10373bbca7db4e2fdb0054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20G=C3=B3mez?= Date: Mon, 27 Apr 2026 15:19:30 +0200 Subject: [PATCH] Add technical documentation (docu.md) Co-Authored-By: Claude Sonnet 4.6 --- docu.md | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 docu.md diff --git a/docu.md b/docu.md new file mode 100644 index 0000000..16f9f48 --- /dev/null +++ b/docu.md @@ -0,0 +1,203 @@ +# Leads Optimizer — Documentación técnica + +## Qué hace + +Agente automatizado que cada día: +1. Sincroniza el catálogo de campañas de Google Ads → Airtable +2. Lee las métricas del mes en curso (coste, conversiones, leads) +3. Analiza el rendimiento de cada campaña +4. Toma una decisión con IA (Claude) sobre qué hacer con el presupuesto +5. Aplica la decisión en Google Ads (o la simula en modo DRY_RUN) +6. Guarda el resultado en Airtable + +--- + +## Modelo de negocio + +``` +Ingreso = leads_entregados × PPL +Margen = (Ingreso - Gasto Google Ads) / Ingreso +CPA max = PPL × 0.70 → margen mínimo aceptable del 30% +``` + +- **PPL** (Precio Por Lead): fijo por curso, ponderado por el % de invalidación del centro. +- **Capping mensual**: máximo de leads a entregar por campaña ese mes. Si = 0, sin límite. + +--- + +## Flujo de ejecución (`run.py`) + +``` +1. Sincronizar campañas Google Ads → Google Ads Campaigns (Airtable) +2. Sincronizar GACampaignMes (estado mensual: coste, conversiones, PPL, cap) +3. Leer campañas activas del mes desde GACampaignMes +4. Detección anticipada: + - courses_with_both: cursos con Search Y PMX simultáneos + - courses_with_leadform: cursos con campaña _leadform activa +5. PRIMERA PASADA: para cada campaña + - Contar leads Airtable (get_leads_this_month_gads) + - Obtener métricas Google Ads (get_campaign_metrics) + - Calcular leads_grupo (para PMX con companion: usa conversiones Google) + - Analizar (analyzer.py) → urgencia, CPA, margen, ritmo + - Decidir (agent.py / Claude) → acción, presupuesto, consejo +6. Actualizar Status en ambas tablas Airtable +7. SEGUNDA PASADA (ordenada por criticidad): + - Imprimir resultado por consola + - Aplicar decisión en Google Ads + - Construir log_text con notas de atribución PMX/leadform +8. Guardar en GACampaignMes: Consejo, Criticidad, Log +9. Guardar ConvLeadsLakeMesFinal + ConvLeadsLakeMesGrupo +``` + +--- + +## Módulos + +### `analyzer.py` +Calcula métricas derivadas y asigna la **urgencia**. + +Entradas: `campaign_config`, `leads_entregados`, `ads_metrics` + +| Métrica | Cálculo | +|---------|---------| +| `ratio_leads` | leads / capping | +| `ratio_mes` | día actual / días del mes | +| `ritmo` | ratio_leads − ratio_mes (>0 adelantado, <0 atrasado) | +| `cpa_actual` | gasto / leads | +| `margen` | (leads × PPL − gasto) / (leads × PPL) | + +**Urgencia:** + +| Valor | Condición | +|-------|-----------| +| `PAUSAR` | ratio_leads ≥ 1.0 (capping lleno) | +| `SPRINT` | Atrasado >15% **y** quedan ≤5 días para fin de mes | +| `ACELERAR` | ritmo < −0.15 | +| `FRENAR` | ritmo > +0.15 | +| `EN_RITMO` | diferencia ≤ 15% | + +--- + +### `agent.py` +Llama a Claude (claude-sonnet-4-20250514) con el resultado del análisis y devuelve una decisión en JSON. + +**Reglas de decisión:** + +| Urgencia | Rentable | Acción | Parámetro | +|----------|----------|--------|-----------| +| PAUSAR | — | `PAUSAR` | — | +| SPRINT | — | `AUMENTAR_PRESUPUESTO` | ×1.3–1.5 | +| ACELERAR | ✅ | `AUMENTAR_PRESUPUESTO` | ×1.1–1.25 | +| ACELERAR | ❌ | `MANTENER` | — | +| FRENAR | — | `REDUCIR_PRESUPUESTO` | ×0.75–0.9 | +| EN_RITMO | ✅ | `MANTENER` | — | +| EN_RITMO | ❌ | `REDUCIR_PRESUPUESTO` | ×0.85 | + +Devuelve: `accion`, `parametro`, `nuevo_budget_diario`, `justificacion`, `consejo`, `alerta`, `confianza`. + +--- + +### `optimizer.py` +Aplica la decisión del agente sobre Google Ads. Si `DRY_RUN=True`, solo imprime lo que haría. + +| Acción | Efecto | +|--------|--------| +| `PAUSAR` | Pausa la campaña vía API | +| `AUMENTAR_PRESUPUESTO` / `REDUCIR_PRESUPUESTO` | Ajusta el presupuesto diario | +| `MANTENER` | No hace nada | + +--- + +### `airtable_client.py` +Gestiona toda la comunicación con Airtable. + +**Tablas usadas:** + +| Tabla | Uso | +|-------|-----| +| `Google Ads Campaigns` | Catálogo de campañas (nombre, CampaignID, PPL, Status) | +| `GACampaignMes` | Estado mensual por campaña (coste, conversiones, leads, consejo, criticidad, log) | +| `Leads Lake` | Leads recibidos (filtrado por campaña y mes) | +| `Cursos` | Relación CursoID ↔ CentroCurso | +| `CentroCurso` | PPL y % invalidación por centro | +| `CursoMes` | Capping mensual por curso | + +**Métodos principales:** + +- `build_campaign_lookups()` — calcula PPL ponderado y capping mensual para todos los cursos +- `sync_campaigns_from_google_ads()` — crea/actualiza el catálogo de campañas +- `sync_gacampaignmes()` — crea/actualiza registros mensuales de campañas con actividad +- `get_active_gacampaignmes()` — devuelve las campañas del mes en curso +- `get_leads_this_month_gads()` — cuenta leads Airtable para una campaña Google Ads +- `batch_update_gacampaignmes_advice()` — guarda Consejo, Criticidad y Log +- `batch_update_gacampaignmes_final_leads()` — guarda ConvLeadsLakeMesFinal y ConvLeadsLakeMesGrupo + +--- + +### `google_ads_client.py` +Wrapper sobre la API de Google Ads. + +- `get_all_campaigns()` — lista todas las campañas no eliminadas +- `get_monthly_metrics_all()` — conversiones y coste del mes para todas las campañas (1 query) +- `get_campaign_metrics()` — métricas detalladas de una campaña concreta +- `set_campaign_budget()` — cambia el presupuesto diario +- `pause_campaign()` — pausa una campaña + +--- + +## Criticidad + +Combina urgencia + acción para priorizar el output y el campo `Criticidad` en Airtable: + +| Criticidad | Cuándo | +|------------|--------| +| **Crítico** | Urgencia es `PAUSAR` o `SPRINT` | +| **Peligro** | Acción cambia el presupuesto pero urgencia no es crítica | +| **Mantener** | Acción es `MANTENER` | + +--- + +## Casos especiales de atribución + +### PMX con companion Search (`courses_with_both`) +Cuando un curso tiene activos simultáneamente `fco_search_NNN_*` y `fco_pmx_NNN_*`, Google puede reatribuir conversiones del Search al PMX. + +- La campaña **PMX** usa como `leads_grupo` las **conversiones de Google Ads** (no los leads de Airtable). +- Se añade una nota `⚠️ PMX ATTRIBUTION` en el campo `Log` de la campaña Search. + +### Campaña Leadform (`_leadform`) +Las campañas con sufijo `_leadform` capturan leads directamente dentro de Google (sin que el usuario visite la web). Esos leads **nunca llegan a Airtable**. + +- Se añade `ℹ️ LEADFORM` en el `Log` de la propia campaña. +- Las campañas hermanas del mismo curso reciben `⚠️ LEADFORM COMPANION` en su `Log`. + +--- + +## Configuración (`.env`) + +```env +AIRTABLE_TOKEN=... +AIRTABLE_BASE_ID=... +GOOGLE_ADS_DEVELOPER_TOKEN=... +GOOGLE_ADS_CLIENT_ID=... +GOOGLE_ADS_CLIENT_SECRET=... +GOOGLE_ADS_REFRESH_TOKEN=... +GOOGLE_ADS_LOGIN_CUSTOMER_ID=... +ANTHROPIC_API_KEY=... +``` + +`DRY_RUN = True` en `config.py` — cambiar a `False` para aplicar cambios reales en Google Ads. + +--- + +## Logs + +Cada ejecución genera un archivo en `logs/YYYYMMDD_HHMMSS.log` con la salida completa. + +--- + +## Ejecutar + +```bash +python run.py +```