# Hyundai Empresas — Campaña Flotas

> **⚠️ ARQUITECTURA NUEVA (julio 2026):** el proyecto migró de CMS estático a **aplicación web PHP + MySQL multi-usuario** en `webapp/`. Ver `webapp/DEPLOY.md` para desplegarla. Todo lo demás de este README describe la **versión estática anterior** (hyundai-cms-lite.html), que queda como referencia histórica y muere cuando la webapp esté en producción.

## Webapp (versión actual) — resumen

- **Stack**: PHP 8.1+ vanilla + PDO/MySQL + PHPMailer (SMTP). Sin framework, sin Composer. Deployable en subdirectorio o raíz.
- **Roles**: Administrador general (invita directores, ve todo) → Director de Zona (invita vendedores, gestiona su zona: editar/desactivar/borrar/traspasar campañas) → Vendedor (crea campañas, ve las suyas).
- **Flujo**: invitación por email con token → registro (elige password) → completar perfil → dashboard.
- **Campaña**: cliente + logo + título test + hasta 3 preguntas personalizadas (informativas, no puntúan). Landing auto-generada en `/c/{slug}` con vídeo Vimeo + test (matriz de puntuación 5×4×5) + formulario RGPD.
- **Tracking**: invitados vs registrados, campañas creadas, email descargado, visitas a landing (las previews de los paneles no cuentan), leads con respuestas completas.
- **Descargas**: mail en HTML y en EML (con `X-Unsent: 1` → Outlook lo abre como borrador; desde ahí Guardar como → .oft).
- **Emails del sistema**: invitaciones, recuperación de password, notificación de lead (reply-to el lead). Vía SMTP del dominio.
- Estructura y despliegue: **`webapp/DEPLOY.md`**. Esquema BD: **`webapp/sql/schema.sql`**.

---

# Versión estática anterior (referencia)

CMS estático + plantillas para personalizar y enviar una campaña de mail Hyundai con microsite interactivo (vídeo + test de flota).

Todo funciona **client-side**: no hay servidor, no hay backend, no hay build (más allá de un script Python opcional para regenerar el CMS desde plantillas). El comercial descarga dos HTML del CMS, aloja el microsite en cualquier hosting estático, envía el mail.

---

## Archivos

```
hyundai/
├── README.md                    # Este archivo (documentación técnica)
├── hyundai-cms-lite.html        # CMS. Artefacto entregable a comerciales.
├── GUIA-COMERCIALES.html        # Guía paso a paso para comerciales no técnicos.
├── hyundai-mail.html            # Ejemplo de mail generado (para referencia).
├── hyundai-microsite.html       # Ejemplo de microsite generado (para referencia).
└── contact-api/                 # Función serverless que recibe el formulario del microsite.
    ├── SETUP.md                 #   → guía de despliegue (Resend + Netlify, ~15 min)
    ├── netlify.toml
    ├── package.json
    ├── public/index.html
    └── netlify/functions/contact.mjs
```

El CMS es **autocontenido**: dentro lleva embebidas las plantillas del mail y del microsite (como `<script type="text/x-template">`), la lógica de personalización y descarga. No necesita internet para funcionar (excepto para cargar Google Fonts en la UI del propio CMS).

Los dos HTML de ejemplo (`hyundai-mail.html`, `hyundai-microsite.html`) sirven como **referencia** — son una descarga generada con datos placeholder. Los archivos reales que produce el comercial se descargan bajo demanda y se guardan en la carpeta del cliente correspondiente, no aquí.

La guía `GUIA-COMERCIALES.html` es la versión amateur del README, en HTML formateado para distribuir al equipo de comerciales — cubre setup Netlify, rutina por cliente, troubleshooting y FAQ.

---

## Arquitectura del CMS

### Single-file HTML

`hyundai-cms-lite.html` contiene:
- UI del CMS (5 secciones + botones de descarga)
- Plantilla del mail (embebida)
- Plantilla del microsite (embebida)
- Lógica de personalización y descarga

Cuando el comercial pulsa **Descargar mail** o **Descargar microsite**, el CMS:
1. Lee la plantilla correspondiente desde el `<script type="text/x-template">`
2. Sustituye los tokens `{{...}}` con los valores del formulario
3. Genera un `Blob` y dispara la descarga via anchor virtual

### Sistema de tokens

Las plantillas usan tokens `{{NOMBRE}}` que se sustituyen con `String.replace()` — no regex complicadas. Los tokens actuales:

**Mail** (`mail-template`):

| Token | Origen | Escape |
|---|---|---|
| `{{CLIENT_LOGO_HTML}}` | `<img>` construido a partir del logo subido (base64) | HTML |
| `{{CLIENT_NAME}}` | Sección 1 → Nombre de la empresa | `escAttr` |
| `{{MICROSITE_URL}}` | Sección 5 → URL del microsite | `escAttr` |
| `{{ASESOR_BLOCK}}` | Bloque completo `<tr>` construido en `asesorBlockHtml()` — vacío si no hay nombre | HTML |

**Microsite** (`microsite-template`):

| Token | Origen | Escape |
|---|---|---|
| `{{CLIENT_LOGO_HTML}}` | `<img>` del logo (base64) para la cabecera | HTML |
| `{{TEST_TITLE}}` | Sección 3 → Título del test | `escAttr` |
| `{{CONTACT_EMAIL}}` | Sección 2 → Email del asesor. Va dentro de un string literal JS. | escape `\` y `'` |
| `{{QUESTIONS_JSON}}` | Array de preguntas en JSON | `JSON.stringify` |

### El escape de `</script>`

Las plantillas van embebidas dentro de `<script type="text/x-template">`. El microsite tiene su propio `<script>...</script>` interno. Para que el navegador no cierre el wrapper al ver el `</script>` interno:

1. Al **construir** el CMS con `build-cms-lite.py`: cada `</script>` dentro de la plantilla se sustituye por `<\/script>`.
2. Al **leer** la plantilla en el CMS (`readTemplate()`): se deshace la sustitución (`<\/script>` → `</script>`) antes de generar el output final.

**Cuidado**: si escribes literalmente la cadena `</script>` en cualquier comentario o string del script principal del CMS, el navegador cerrará prematuramente el tag y todo el JS posterior queda como texto. Usa `<' + '/script>` o similar si necesitas mencionarlo.

**Bug histórico documentado**: el CMS original (`hyundai-cms.html`, ya no aquí) tenía dos bugs de este tipo — un `\n` literal dentro de un regex literal (JS no permite regex multilínea) y un comentario con `</script>`. Ambos rompían el script entero silenciosamente.

---

## Contenido del mail

Estructura fija (no editable por comerciales):

1. Header — Hyundai logo (sin subtítulo "EMPRESAS")
2. Titular — `¿CÓMO SE MUEVE TU NEGOCIO?`
3. Cuerpo — párrafo con `<strong>{{CLIENT_NAME}}</strong>`
4. Cierre — `Qué, ¿hablamos de flotas?` (negrita azul)
5. Bloque PLAY (fondo azul oscuro) — `href="{{MICROSITE_URL}}"`, texto "VER VÍDEO"
6. **Bloque Información del asesor** (condicional) — ver siguiente sección
7. Banda `HYUNDAI FBC / LA MOVILIDAD DE TU NEGOCIO, SOBRE RUEDAS`
8. Footer legal

### Bloque Información del asesor

Aparece **solo si el comercial rellena el campo Nombre y apellido** en Sección 2 del CMS. La función `asesorBlockHtml()`:

```js
function asesorBlockHtml() {
  const nombre = (document.getElementById('asesorNombre').value || '').trim();
  if (!nombre) return '';               // Nombre vacío → bloque entero omitido
  // ... construye <tr>...</tr> con los campos rellenos, saltando los vacíos
}
```

Las líneas individuales (concesionario, dirección, teléfono) también son condicionales — cada una solo aparece si tiene contenido.

---

## Contenido del microsite

Estructura:

1. Header — Hyundai logo + logo cliente
2. Hero — `HYUNDAI EMPRESAS / La movilidad de tu negocio, sobre ruedas`
3. Bloque de vídeo — thumbnail (poster de Vimeo) + botón PLAY → al click, iframe Vimeo con autoplay
4. Test de flota — intro → 5 preguntas → resultado
5. Footer

### Vídeo

**Actualmente**: Vimeo embed hardcoded en el `<script>` del microsite:

```js
const VIMEO_EMBED = 'https://player.vimeo.com/video/1196606039?h=d845dff738&autoplay=1';
```

El `h=` es el hash de privacidad (vídeo unlisted). El poster se carga desde el CDN de Vimeo:

```
https://i.vimeocdn.com/video/2162765141-...-d_1280x720?region=us
```

**Para cambiar el vídeo**: hay que editar `hyundai-cms-lite.html` directamente (buscar `player.vimeo.com/video/`), obtener el nuevo thumbnail vía oEmbed:

```sh
curl 'https://vimeo.com/api/oembed.json?url=https%3A%2F%2Fvimeo.com%2F<ID>%2F<HASH>' | python3 -m json.tool
```

Extraer `thumbnail_url`, cambiar `_295x166` por `_1280x720` para mejor calidad, y sustituir la URL del poster en el CMS.

### Matriz del test

Lógica en `showResult()` del microsite. Matriz 5×4×5 (5 preguntas × 4 opciones × 5 flotas). Cada respuesta da puntos a cada flota, gana la de mayor score. En empate, gana la de menor índice.

Orden de flotas: `[BEV, FCEV, HEV, PHEV, 48V]`.

Extractos representativos:

```js
// Q2 — Distancia diaria
[ 3,  0,  0,  2,  0],   // A · <50 km → BEV manda
[-2,  3,  1,  0,  2]    // D · >300 km → FCEV/48V (BEV penalizada)

// Q4 — Infraestructura
[ 3,  0,  0,  3,  0],   // A · Cargadores eléctricos → BEV/PHEV
[-1,  4,  0,  0,  1],   // B · Hidrógeno o gas → FCEV se dispara
```

**Para ajustar cuándo gana cada flota**: editar la matriz `SCORE` en `showResult()`. Los pesos negativos penalizan (útil para "esta flota queda descartada si eliges esto"). Los pesos +3/+4 son "killer" (casi fuerzan un resultado).

Escenarios canónicos verificados (respuestas → flota):

| Escenario | Q1·Q2·Q3·Q4·Q5 | Resultado |
|---|---|---|
| BEV | A·A·A·A·A | Flota eléctrica |
| FCEV | A·D·A·B·D | Con pila de hidrógeno |
| HEV | D·C·B·C·B | Flota híbrida eléctrica |
| PHEV | C·B·B·A·A | Flota híbrida enchufable |
| 48V | A·D·D·C·D | Flota híbrida |

### Formulario "Solicitar asesoramiento"

Al final del test aparece un formulario inline con 4 campos: **nombre**, **cargo**, **empresa**, **email**. Al enviar, hace un `fetch()` POST a nuestra Netlify Function (`contact-api/`), que valida los datos y reenvía por email al asesor vía [Resend](https://resend.com). Si el submit tiene éxito, el formulario se oculta y se muestra un estado "¡Gracias!".

**Flujo:**

```
Microsite (form)  ──POST──►  Netlify Function `/contact`  ──API──►  Resend  ──email──►  Asesor
                             (contact-api/)
```

**Configuración en el CMS:**

Al principio del script del CMS hay una constante que hay que editar tras el deploy:

```js
const CONTACT_API_URL = 'REEMPLAZAR_TRAS_DEPLOY';
// Cambiar a algo tipo: 'https://hyundai-contact-api.netlify.app/contact'
```

Esta URL se inyecta como `form.action` en todos los microsites que se generen.

**Payload al endpoint** (multipart/form-data):
- Campos visibles: `nombre`, `cargo`, `empresa`, `email`
- `_recipient`: email del asesor (inyectado por el CMS)
- `_honey`: honeypot antispam
- `respuestas_test`: texto formateado de las respuestas
- `flota_recomendada`: nombre de la flota resultado del test

**Respuesta esperada**: `{ ok: true, success: 'true' }`. Cualquier otra cosa muestra un error rojo bajo el botón.

**Ver `contact-api/SETUP.md` para el despliegue paso a paso** (Resend + Netlify + variables de entorno).

**Fallback si `CONTACT_EMAIL` está vacío**: el formulario se oculta entero. El lead ve solo el resultado del test sin CTA.

**Coste y volumen**:
- Netlify: 125.000 invocaciones/mes gratis
- Resend: 3.000 emails/mes gratis
- Sin límites diarios per-recipient (a diferencia de FormSubmit que teníamos antes)

---

## Regenerar el CMS desde plantillas (opcional)

Si vas a hacer cambios grandes en las plantillas del mail o el microsite, es más cómodo editarlas por separado y luego reconstruir el CMS. El flujo:

1. Extraer las plantillas actuales del CMS (una sola vez, si no las tienes):
   ```py
   import re
   cms = open('hyundai-cms-lite.html').read()
   mail = re.search(r'<script type="text/x-template" id="mail-template">\s*(.*?)\s*</script>', cms, re.DOTALL).group(1).replace('<\\/script>', '</script>')
   micro = re.search(r'<script type="text/x-template" id="microsite-template">\s*(.*?)\s*</script>', cms, re.DOTALL).group(1).replace('<\\/script>', '</script>')
   open('/tmp/mail-template.html', 'w').write(mail)
   open('/tmp/microsite-template.html', 'w').write(micro)
   ```

2. Editar `/tmp/mail-template.html` y `/tmp/microsite-template.html` a mano.

3. Rebuild:
   ```py
   def safe(t): return t.replace('</script>', '<\\/script>')
   mail = safe(open('/tmp/mail-template.html').read())
   micro = safe(open('/tmp/microsite-template.html').read())
   shell = open('/tmp/cms-lite-shell.html').read()   # Shell = CMS sin plantillas, con placeholders <!--MAIL_TEMPLATE_HERE--> y <!--MICROSITE_TEMPLATE_HERE-->
   shell = shell.replace('<!--MAIL_TEMPLATE_HERE-->', mail)
   shell = shell.replace('<!--MICROSITE_TEMPLATE_HERE-->', micro)
   open('hyundai-cms-lite.html', 'w').write(shell)
   ```

Para cambios pequeños (un texto, un color), editar directamente el CMS también funciona — pero busca el token o el fragmento HTML dos veces (aparece dentro del template embebido, no dentro del shell del CMS).

---

## Deploy

### CMS

Un solo archivo HTML estático. Opciones:

- **Local** (por comercial): cada comercial tiene el archivo en Dropbox/Drive/escritorio y lo abre con doble click. Cero infraestructura.
- **Hosting estático** (para todo el equipo): Netlify, Cloudflare Pages, GitHub Pages, S3+CloudFront. Cero configuración porque no hay backend. Añadir Netlify Identity o Cloudflare Access si se quiere gate.

### Microsite (por cliente)

El comercial descarga `hyundai-microsite.html` desde el CMS y lo sube donde vaya a alojarlo. Es autocontenido: no requiere assets externos (logo cliente va base64, vídeo va vía Vimeo, poster desde CDN de Vimeo).

Opciones típicas:

- **Netlify Drop** (per cliente, manual): drag-and-drop del HTML, URL única al momento
- **Netlify site + CLI** (organizado): un solo site con carpetas por cliente
- **Hosting propio de Hyundai/Mostaza**: subdirectorio por cliente en subdominio dedicado

La URL resultante se pega en Sección 5 del CMS y se regenera el mail — así el botón PLAY apunta al sitio real.

### Mail

No se aloja, se envía. Opciones:

- **Manual**: abrir el HTML en Chrome → Cmd+A → Cmd+C → pegar en Apple Mail / Outlook → enviar
- **Masivo**: pegar el código HTML en el editor HTML de Mailchimp / Brevo / Mailjet / HubSpot

---

## Limitaciones y notas

- **Sin tracking**: nada de open-rate, click-tracking, analytics. Si se necesita, hay que añadir tags de terceros (Sendgrid tracking, GA en el microsite, etc.).
- **La `CONTACT_API_URL` del CMS debe apuntar al Netlify site ya desplegado**. Si sigue diciendo `REEMPLAZAR_TRAS_DEPLOY`, los formularios generados no envían y muestran error.
- **Sin domain verification en Resend**: los correos salen desde `onboarding@resend.dev`, no desde `@hyundai.es`. Aumenta el riesgo de que caigan en spam. Verificar dominio en Resend elimina esto — instrucciones en `contact-api/SETUP.md`.
- **Vídeo depende de Vimeo**: si la red del cliente bloquea Vimeo (raro pero sucede en algunas corporativas), el vídeo no carga. El resto del test sigue funcionando.
- **CMS no persiste nada**: cada sesión empieza vacía. Los comerciales no ven un histórico de clientes personalizados.
- **Guía de comerciales desactualizada si cambia la UI del CMS**: si añades/quitas/reordenas secciones del CMS, revisa `GUIA-COMERCIALES.html` para mantener sincronizada la numeración y los pasos.

---

## Historial de cambios importantes

- **Vídeo**: pasó de iframe placeholder → `<video>` HTML5 local con `Flotas.mp4` + poster generado por `qlmanage` → embed de Vimeo con poster desde CDN de Vimeo (versión actual, sin assets locales).
- **Mail v2**: nueva copy — antes decía "Queremos emprender un nuevo camino contigo", ahora `¿CÓMO SE MUEVE TU NEGOCIO?` + banda `HYUNDAI FBC` al final.
- **Bloque asesor**: añadido entre PLAY y banda FBC. Aparece condicional según nombre relleno.
- **Test**: matriz de puntuación 5×4×5 (antes: cadena `if/else` sobre 3 preguntas, resultados sesgados).
- **CMS**: fix crítico del bug de `<\/script>` no revertido al descargar — antes el mail y microsite descargados quedaban rotos porque el `<script>` interno no cerraba nunca.
- **Contacto**: pasó de `mailto:` → formulario inline con FormSubmit.co (fácil pero con tope 50/día por destinatario) → **backend propio** en Netlify Functions + Resend (sin límites diarios, dominio remitente configurable, logs propios). Se hizo el salto cuando quedó claro que un burst de 300 formularios el viernes puede pasar por bajada acumulada de mails enviados durante la semana.
