# LeadHunter ↔ Provisionador — Feedback del contrato v1

> Respuesta concreta al doc *"LeadHunter ⇄ Provisionador bewpro — Contrato de output v1"*
> compartido por el equipo del hunter el 2026-05-14.
>
> Basado en **datos reales del primer batch de producción** (13 leads law-firm-digital
> de la campaña 2, provisionados ese mismo día).

---

## TL;DR

El contrato v1 está bien diseñado y **funcionó al primer intento end-to-end**: 6 de 13 tenants con title HTML específico del lead, 180 records seedeados por tenant. Solo 1 issue real (column naming `content_file` vs `json_file`) que ya resolvimos del lado del provisionador con un alias.

**5 cambios MUST** mejoran el provisioning hoy, **4 NICE** son optimizaciones, **6 preguntas abiertas** las dejamos como "no necesario por ahora".

---

## Validación empírica (lo que ya probamos)

Primer batch: `/Users/cokecolombres/Downloads/leadhunter/static/campaigns/2/`

| Componente | Resultado |
|---|---|
| Manifest CSV (13 filas, 20 columnas) | ✅ parseado OK con alias `content_file` → `json_file` |
| JSONs (13 archivos, ~37KB c/u) | ✅ todos válidos, dedup automática de slugs |
| Subdomain (30 chars cap) | ⚠️ truncado agresivo (ver MUST #2) |
| Email vacío (6/13) | ✅ resuelto con `--override-email` del lado provisionador |
| `_meta.lead_snapshot` | ✅ útil para trazabilidad |
| 180 records seedeados por tenant | ✅ JSON suficientemente rico |
| Title HTML de cada tenant | ✅ matchea data del lead específico |

---

## MUST — Pedidos al hunter (high-impact)

### 1. Alias `content_file` ↔ `json_file`

**Hoy**: hunter manda `content_file`, formato base del provisionador esperaba `json_file`.
**Provisionador ya resolvió**: acepta ambos como alias.
**Pedido**: alinear naming a futuro (cualquiera de los dos sirve, solo definir uno canónico).

### 2. Cap de subdomain: 30 → 48 chars

**Hoy**: el hunter trunca a 30, dejando subdominios truncos como `abogado-derecho-penal-ramiro-v` (debería ser `-villalba`).
**Provisionador soporta**: 48 chars `[a-z0-9-]` (validado en `BulkProvisioningService::validate()`).
**Pedido**: subir el cap del hunter de 30 a 48.

Ejemplos del batch real con cap actual vs nuevo cap:

| Truncado a 30 | Completo (≤48 chars) |
|---|---|
| `abogado-derecho-penal-ramiro-v` | `abogado-derecho-penal-ramiro-villalba` |
| `estudio-juridico-perez-hernand` | `estudio-juridico-perez-hernandez` |
| `abogados-de-accidentes-estudio` | `abogados-de-accidentes-estudio-baezza` |

### 3. Agregar columna `priority` (= `qualified_for_demo`)

**Por qué**: ya está calculado, pero embedded en `qualified_for_demo`. Tenerlo como semantic column permite filtros como `--min-priority=70` desde CLI sin parsear.

### 4. Agregar columna `tier` (`caliente`/`tibio`/`frío`)

**Por qué**: hoy está sólo en `_meta.lead_snapshot.tier` (adentro del JSON). En CSV permite batches segmentados sin abrir cada JSON. Caso de uso: "primero corro los calientes con `--filter=tier:caliente`, después los tibios".

### 5. **Generar `services[]` específicos del lead** (el más alto-impacto)

**Por qué**: hoy los 13 demos muestran los mismos 4 services genéricos del seed canónico del core. Si el hunter genera 3-5 services específicos usando `keywords` + `place_types`, los demos pasan de "template" a "hecho a medida". **Es el cambio que más mueve la conversión** de demos a ventas.

**Ejemplo concreto** — lead matchea `place_types: ["lawyer"]` + `keywords: ["penal", "accidentes"]`:

```json
"services": [
  {
    "slug": "derecho-penal",
    "name": "Defensa Penal",
    "description": "Representación legal en causas penales. Asesoramiento desde el primer momento de la imputación."
  },
  {
    "slug": "accidentes-transito",
    "name": "Accidentes de Tránsito",
    "description": "Reclamo integral por daños materiales, físicos y morales tras un siniestro vial."
  },
  ...
]
```

---

## NICE — Optimizaciones opcionales

### 6. `email_source` enum: `scraped` | `manual` | `missing`

Para que el provisionador decida si confiar (`scraped`) o aplicar override automático (`missing`).

### 7. `gallery[]` con URLs CDN de fotos de Google Places

Hoy el hunter cuenta fotos para `demo_readiness` pero no las inyecta. Si las pre-carga a Cloudinary y mete URLs estables en `gallery[]`, todos los demos tendrían fotos reales del lugar (no placeholder).

### 8. `theme_color` aplicado a `brand_defaults`

Hoy se scrapea pero queda en `_meta`. Pisar `site.brand_defaults.primary_color` con el scraped haría que el demo arranque con la paleta del lead. Cambio chico, alto impacto visual.

### 9. Filtro pre-export: `min_qualified_for_demo` configurable

Cap mínimo para no exportar leads con score bajo. Hoy filtramos del lado provisionador, pero meter basura al pipeline cuesta cPanel + DB + ~25min/tenant.

---

## NO NECESARIO — Respuestas a "preguntas abiertas" del contrato v1

| Pregunta del hunter | Respuesta provisionador |
|---|---|
| ¿`_meta` molesta o sirve? | **Sirve.** Dejarlo. Ignorado por el provisionador, útil para trazabilidad. |
| ¿Re-generamos JSON desde `CoreTemplateBuilder`? | **No.** Consumimos el del hunter tal cual. Solo auto-dedup de slugs internos. |
| ¿Validar subdomain pre-export? | **No.** Validación de colisión en runtime contra DB de producción del provisionador. Pre-validar sale stale rápido (~minutos). |
| ¿Callback `provisioned_url` → hunter? | **No por ahora.** Si lo querés, sync vía Airtable: agregamos field `Lead_Hunter_Place_ID` en Projects, lo poblamos desde `_meta.lead_snapshot.place_id`, hunter consulta Airtable. |
| ¿`team[]` desde reviewers de Google? | **No.** Ético-borderline (los reviewers no son staff). |
| ¿Pre-validar email (MX record)? | **No.** Regex actual + blacklist está OK. |
| ¿Subir/expone endpoint para el paquete? | **No.** Local-folder está OK. El provisionador hace SCP desde mi máquina a VPS1. |

---

## Garantía: lo que YA FUNCIONÓ PERFECTO

No tocar. El hunter clavó:

- 5 columnas obligatorias del manifest ✓
- JSON shape canónico (compatible 1:1 con `CoreTemplateBuilder::build()`) ✓
- Sección `_meta.lead_snapshot` con `branding_scraped`, `reasons`, `tier` ✓
- Auto-dedup cross-ciudad por `place_id` (PK compuesta SQLite) ✓
- Filtros editoriales (`exclude_chains`, `min_reviews`, etc.) ✓
- Cap de 6 queries por ciudad ✓
- Orden descendente por `qualified_for_demo` en CSV ✓

---

## Acuerdo final propuesto (v2 del contrato)

Si el hunter implementa los 5 MUST + cualquier subset de los NICE, bumpeamos a **v2** del contrato. Mientras tanto, v1 + alias `content_file ↔ json_file` del provisionador es suficiente para operar.

---

## Roadmap de integración (post-feedback)

| Próximo paso | Quién | Cuándo |
|---|---|---|
| Hunter sube cap subdomain a 48 | Hunter | Cuanto antes (5min) |
| Hunter agrega `priority` + `tier` al CSV | Hunter | Próxima campaña |
| Hunter genera `services[]` específicos | Hunter | Cuando agreguen `services_template[]` por industria al `hunter-catalog.json` |
| Provisionador suma `Lead_Hunter_Place_ID` a Airtable | Provisionador | Cuando habilitemos sync hunter↔airtable |
| Probar batch v2 con 10 leads de otro core (real-estate?) | Ambos | Sprint próximo |

---

## Referencias

- Contrato v1 original: compartido por hunter el 2026-05-14 (no en este repo)
- Validación primer batch: `docs/bewpro2.0/sprint-q2/hestiacp-import-migration.md`
- Comando bulk del provisionador: `docs/bewpro2.0/sprint-q2/bulk-provisioning.md`
- Hunter path local: `/Users/cokecolombres/Downloads/leadhunter/`
- Campaign de ejemplo: `static/campaigns/2/` (13 leads law-firm-digital)
