# Corrección ENT-003-001 · Formulario «Nueva promoción»

Se aplica sobre NimiApps **0.3.0** (ENT-003). No es una versión nueva: la
versión sigue siendo 0.3.0, no hay funciones nuevas, no hay migraciones y no
se tocan `.env`, la base de datos, `storage/`, `vendor/` ni los `.htaccess`.

## 1. Incidencia

En Administración → Comercial → Promociones → Nueva promoción:

1. Al elegir «Porcentaje» no aparecía el campo del porcentaje.
2. Al elegir «Importe fijo» tampoco aparecía el campo del importe.
3. «Crear promoción» recargaba la página sin crear nada.
4. No se mostraba ningún mensaje.

## 2. Causa exacta

En `resources/views/admin/commerce/promotions/form.blade.php`, el estado del
formulario para Alpine (el JavaScript de la página) se escribía así:

```blade
<x-form ... x-data="{ type: @js($type), duration: @js($duration) }">
```

Blade **no compila directivas como `@js` dentro de la etiqueta de un
componente** (`<x-form>`). El navegador recibía el texto literal
`x-data="{ type: @js($type), duration: @js($duration) }"`. Alpine no podía
interpretarlo («Alpine Expression Error: type is not defined»), así que:

- El selector «Tipo de descuento» quedaba desconectado y los campos
  «Porcentaje» e «Importe a descontar», que dependen de él, no se mostraban
  nunca (síntomas 1 y 2).
- Al enviar, faltaba el valor del descuento y el servidor rechazaba el
  formulario (síntoma 3).
- El mensaje de error del servidor sí se generaba, pero se pintaba dentro de
  esos campos ocultos (síntoma 4).

El servidor validaba correctamente. Las pruebas automáticas de ENT-003 no lo
detectaron porque enviaban los datos directamente, sin pasar por el HTML de
la página.

## 3. Archivos afectados

| Archivo | Cambio |
|---------|--------|
| `resources/views/admin/commerce/promotions/form.blade.php` | **Corrección.** El estado de Alpine se prepara en PHP (`Js::from`) e incluye `busy`; valores iniciales válidos («Porcentaje», «Solo el primer pago»); el campo que corresponde se ve ya al cargar la página, también sin JavaScript; los campos visibles son obligatorios en el navegador y los ocultos se desactivan; resumen de errores arriba del formulario. |
| `app/Http/Controllers/Admin/Commerce/PromotionAdminController.php` | Solo textos: mensajes de validación claros en español (antes: «El campo porcentaje es obligatorio cuando discount type es percent»). La lógica no cambia. |
| `tests/Feature/Commerce/PromotionFormTest.php` | Nuevo: 5 pruebas que leen el formulario del HTML y lo envían como el navegador. |
| `tests/Feature/Commerce/RenderedViewsTest.php` | Nuevo: recorre todas las páginas comerciales y falla si alguna contiene código Blade sin compilar (protege contra este tipo de fallo en cualquier formulario). |
| `CHECKSUMS.sha256` | Huellas actualizadas. |
| `CORRECCION-ENT-003-001.md` | Este documento. |

## 4. Revisión de otros formularios de ENT-003

- Búsqueda en todas las vistas de directivas dentro de etiquetas `<x-...>`:
  solo estaba este caso.
- Rastreo automático del HTML de **todas** las páginas comerciales
  (`/admin/comercial/*`, catálogo, contratar, contrataciones, pedidos,
  licencias, aplicaciones, demo): ningún código sin compilar.
- Rastreo en Chromium de 25 páginas y formularios (microapps, categorías,
  productos, planes y precios, promociones, pedidos, suscripciones, pagos,
  licencias, eventos, auditoría y área del cliente): **sin errores de
  JavaScript ni de Alpine**.
- Observación sin efecto visible, no modificada: el formulario «Añadir
  precio» del plan define su propio `x-data` sin `busy`. El botón ya lo
  tiene en cuenta (`typeof busy`), por eso funciona; no se cambia para no
  tocar nada ajeno a la incidencia.

## 5. Verificación realizada

**Navegador (Chromium) con la vista original**: se reproducen los cuatro
síntomas (campos ocultos con ambos tipos, la página se recarga sin crear
nada y sin mensaje visible; consola con «Alpine Expression Error»).

**Navegador con la corrección** (capturas en `pruebas-correccion/`):

| Comprobación | Resultado |
|--------------|-----------|
| «Porcentaje» muestra su campo; «Importe fijo» muestra el suyo; «Durante varios meses» muestra «Número de meses» | ✓ |
| Sin nombre ni valor, el navegador no envía y marca los campos | ✓ |
| Porcentaje 150: vuelve con resumen «No se ha guardado la promoción» y el mensaje junto al campo; conserva lo escrito | ✓ |
| Importe 0: vuelve con «Importe fijo» elegido y su mensaje visible | ✓ |
| Fecha de fin anterior a la de inicio: «La fecha de fin debe ser posterior a la fecha de inicio.» | ✓ |
| Crear 20 % automática, con fechas y solo para «Básico»: se guarda y al reabrirla conserva tipo, valor, fechas, plan y «automática» | ✓ |
| Crear 5 € durante 3 meses y su cupón CINCO | ✓ |
| Cliente: la ficha anuncia el 20 % solo en «Básico» y al contratar paga 7,92 € en lugar de 9,90 € | ✓ |
| Cliente: el cupón CINCO se aplica al contratar «Pro» (−5,00 €) | ✓ |

**Pruebas automáticas** (`PromotionFormTest`, `RenderedViewsTest`): fallan
con la vista original y pasan con la corregida. Cubren además: promoción
solo con fecha de inicio o solo de fin, envío sin JavaScript, y que la
automática **no** se aplica antes de su inicio, después de su fin, en planes
no elegidos ni si se desmarca «Aplicar automáticamente».

Suite completa: **239 pruebas** (233 + 6 nuevas) en SQLite y en
**MariaDB 10.6.28**, sin fallos.

Ensayo del ZIP en una copia de la 0.3.0 (usuario sin root, MariaDB):
simulación, detección de cambio local, aplicación, repetición (no hace
nada), integridad y vuelta atrás.

**Pendiente (Parte B, en staging):** repetir en https://staging.nimiapps.com
las comprobaciones de la tabla anterior.

## 6. Aplicar en cPanel

Tiempo: 5 minutos; la web está en mantenimiento menos de 1 minuto.

```bash
PHP=/opt/cpanel/ea-php83/root/usr/bin/php        # o PHP=php si "php -v" ya da 8.3
APP=/home/nimiapss/public_html/staging.nimiapps.com
cd /home/nimiapss
sha256sum -c nimiapps-v0.3.0-correccion-ENT-003-001.zip.sha256    # Debe decir OK
unzip -q nimiapps-v0.3.0-correccion-ENT-003-001.zip
PAQ=/home/nimiapss/nimiapps-v0.3.0-correccion-ENT-003-001

PHP=$PHP bash $PAQ/corregir.sh $APP --simular     # «Todo listo para aplicar la corrección»
cd $APP && $PHP artisan down
PHP=$PHP bash $PAQ/corregir.sh $APP               # «Corrección copiada»
$PHP artisan view:clear
$PHP artisan optimize
$PHP artisan microapps:check                       # Sin [ERROR]
$PHP artisan up
```

No hace falta copia de la base de datos (no cambia). El script guarda los
archivos sustituidos en `~/nimiapps-respaldos/correccion-ent003-001-FECHA/`.

Si `--simular` dice «Modificado localmente», ese archivo del servidor no es
el de la 0.3.0 entregada: no sigas y consúltalo.

Después, en el navegador, recarga la página de nueva promoción con
Ctrl+F5 y repite las comprobaciones del apartado 5.

## 7. Deshacer

```bash
cd $APP && $PHP artisan down
bash ~/nimiapps-respaldos/correccion-ent003-001-FECHA/restaurar-archivos.sh   # escribir RESTAURAR
$PHP artisan view:clear
$PHP artisan optimize
$PHP artisan up
```

Las promociones creadas mientras tanto se conservan (están en la base de
datos y la vuelta atrás no la toca).
