204 lines
6.7 KiB
Markdown
204 lines
6.7 KiB
Markdown
# 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
|
||
```
|