# Pruebas · NimiApps 0.3.0 (ENT-003)

- **Parte A**: pruebas que he ejecutado de verdad, con sus resultados.
- **Parte B**: pruebas pendientes en el servidor cPanel de staging y con
  Stripe real en modo de pruebas.

> **Lo más importante que NO se ha probado:** ningún cobro ha pasado por
> Stripe real. Desde el entorno de pruebas no hay acceso a `api.stripe.com`
> ni había claves. Todo lo relativo a Stripe se ha probado con la librería
> oficial `stripe/stripe-php` hablando con un **simulador** de la API de
> Stripe escrito para estas pruebas (ver A.3). La Parte B indica qué hay que
> comprobar con claves de prueba reales.

---

# Parte A · Pruebas realizadas

Fecha: 21/10/2026.

## A.1 Entorno

| Elemento      | Entorno de pruebas                                             | Servidor de staging     |
|---------------|----------------------------------------------------------------|-------------------------|
| Sistema       | Ubuntu 24.04 (contenedor Linux)                                | CentOS 7 con cPanel/WHM |
| PHP           | 8.3.6 CLI                                                      | 8.3.x (ea-php83)        |
| Laravel       | 13.38.0 · Livewire 4.4.7 · stripe/stripe-php 17.6.0            | igual                   |
| Base de datos | SQLite en memoria y **MariaDB 10.6.28** (Docker)               | MariaDB 10.6.28         |
| Servidor web  | `php artisan serve`                                            | Apache + PHP-FPM        |
| Navegador     | Chromium (Playwright), escritorio y móvil 390 px               | —                       |
| Stripe        | **Simulador local** de la API (no Stripe real)                 | Stripe en modo prueba   |

Nunca se ha usado la base de datos de staging. Las bases de MariaDB usadas
son locales (`nimiapps_test`, `nimiapps`, `nimiapps_sim`).

## A.2 Pruebas automáticas (PHPUnit)

```bash
php artisan test                                          # SQLite en memoria
DB_CONNECTION=mariadb DB_HOST=127.0.0.1 DB_PORT=3306 \
DB_DATABASE=nimiapps_test DB_USERNAME=... DB_PASSWORD=... \
php artisan test                                          # MariaDB 10.6.28
```

Resultado final, con el código del paquete:

| Base de datos   | Pruebas | Aserciones | Fallos | Tiempo |
|-----------------|--------:|-----------:|-------:|-------:|
| SQLite          | 233     | 1.582      | 0      | ~19 s  |
| MariaDB 10.6.28 | 233     | 1.582      | 0      | ~45 s  |

Son 142 pruebas de ENT-001/ENT-002 (siguen pasando) y 91 nuevas de ENT-003:

| Archivo (`tests/.../Commerce/`) | Pruebas | Qué cubre |
|---------------------------------|--------:|-----------|
| `PersonalPurchaseTest`          | 14 | Plan gratuito sin Stripe; plan de pago **sin credenciales**: no se vende ni se finge; suscripción mensual que da acceso **solo cuando Stripe confirma**; la página de regreso consulta la API (no se fía del navegador); sesión pagada por otro importe no da acceso; prueba gratis (sin cobro inicial, una sola vez); cupón calculado en el servidor y enviado a Stripe; no se compra dos veces lo mismo; compra única tras plan gratuito; pago asíncrono (espera, fallo y éxito); Checkout abandonado caduca; doble clic reutiliza la misma sesión. |
| `OrganizationPurchaseTest`      | 8  | El propietario compra para la organización; administradores y miembros no pueden comprar; no se compra para una organización ajena; planes solo para organizaciones; visibilidad de la facturación por rol; plazas: asignación, límite, miembro expulsado pierde acceso y libera plaza; modo «solo asignados». |
| `SubscriptionLifecycleTest`     | 12 | Renovación; pago fallido con gracia y corte; **acceso cerrado si los webhooks de renovación no llegan**; cancelar a final de periodo y reactivar; ampliación inmediata con cobro; ampliación cuyo cobro falla no se aplica; reducción y cambio de periodicidad; paso a gratuito al final del periodo; rechazo de precios de otro producto o inactivos; cancelación inmediata y reembolsos parcial y total por el administrador (con idempotencia); reembolso total de pago único retira la licencia; un cliente no gestiona suscripciones ajenas. |
| `StripeWebhookTest`             | 12 | Sin configuración: 503; firma ausente o inválida: 400; sin CSRF ni sesión; evento live ignorado en modo test; evento repetido procesado una vez; eventos desordenados convergen al estado de Stripe; **contenido falsificado no se cree** (se relee de la API); fallo → 500 y reprocesado; evento en curso en otro proceso no se duplica; segunda suscripción pagada del mismo producto se cancela y se marca; segundo pago único marcado como duplicado; formulario enviado dos veces crea un solo pedido. |
| `AdminCatalogTest`              | 6  | Solo administradores de plataforma; **el administrador crea categoría, microapp (validación, FAQ, imagen; SVG rechazado), producto, plan con capacidades, precios (formato español), publica y un cliente lo contrata y usa**; código y composición bloqueados tras vender; promociones y cupones; suspensión, reactivación y revocación de licencias; todas las páginas de `/admin/comercial` cargan; auditoría. |
| `IsolationAndAccessTest`        | 8  | **Una organización no ve compras de otra** (pedidos, suscripciones, licencias, plazas, facturación, ni forzando la sesión); **usuario sin licencia denegado**; microapp sin ficha deniega a todos; capacidades, límites y permisos de módulo en el servidor; datos del módulo aislados entre organizaciones; CSV sin fórmulas; ids correlativos no sirven (ulid); el panel lista solo lo accesible. |
| `PublicCatalogTest`             | 4  | Búsqueda, filtros y orden; ficha con planes, sin borradores, URL canónica y JSON-LD válido; planes no publicados no se compran por URL; `noindex` salvo configuración. |
| `BundlePurchaseTest`            | 1  | Paquete creado desde el panel (validación de composición), visible y contratable desde la ficha de cada microapp incluida; una compra da una licencia por microapp; al cancelar se pierde el acceso a todas. |
| `PlatformIntegrationTest`       | 4  | Todas las rutas de microapps llevan la cadena obligatoria de middleware; `microapps:check` sin secretos; Stripe sin configurar es OPCIONAL, no error; actualización desde 0.2.0 conserva usuarios y organizaciones. |
| `Unit/Commerce/*`               | 22 | Importes en formato español, sin decimales flotantes, redondeo de porcentajes; validación del bloque `microapp` de `module.json`. |

## A.3 Cómo se ha probado Stripe sin Stripe

`tests/Support/StripeSimulator.php` implementa la interfaz HTTP de la
librería oficial de Stripe. El código de NimiApps es **el mismo** que en
producción (`StripeGateway`, `StripeCatalog`, `WebhookProcessor`...); solo
cambia a dónde van las peticiones. El simulador reproduce clientes,
productos, precios, cupones, sesiones de Checkout, suscripciones (periodos,
prueba, renovación, reintentos, cancelación, cambios de plan con
prorrateo), facturas, intentos de pago, reembolsos y portal, la
idempotencia de Stripe y la firma de webhooks con el mismo algoritmo.

Límite: el simulador lo he escrito yo a partir de la documentación de
Stripe. Si Stripe real se comporta distinto en algún detalle, solo lo
descubrirán las pruebas de la Parte B.

## A.4 Recorrido real en el navegador

Chromium controlado por Playwright contra `php artisan serve` + MariaDB,
con un servidor local del simulador (página de pago falsa que envía los
webhooks firmados a la aplicación). Capturas en `pruebas-e2e/`.

| Paso | Resultado |
|------|-----------|
| El administrador crea la microapp desde el navegador (capacidades importadas del módulo) | ✓ |
| Crea el producto, 4 planes (gratis, mensual/anual, con prueba, con plazas) con precios, y publica todo | ✓ |
| Catálogo y ficha en móvil 390 px sin desbordamiento horizontal | ✓ |
| Un cliente sin licencia abre `/apps/demo`: 403 | ✓ |
| El cliente contrata (9,90 €) y llega a la página de pago | ✓ |
| Vuelve sin pagar: sigue sin acceso | ✓ |
| Paga: el pedido se completa por webhook + lectura de la API; usa la microapp y ve su contratación | ✓ |
| La propietaria de una organización contrata el plan Equipo | ✓ |
| Un miembro sin plaza: 403 en la microapp y en la facturación | ✓ |
| La propietaria pulsa «Dar acceso»: el miembro entra y tiene la capacidad «exportar» | ✓ |
| El administrador ve pedidos, eventos de Stripe y licencias | ✓ |

Errores de consola: solo las respuestas 403 esperadas y un 404 (icono del
simulador).

## A.5 Ensayo de la actualización 0.2.0 → 0.3.0

Sobre una copia de la 0.2.0 en `/home/nimiapss/public_html/staging.nimiapps.com`,
con **un usuario sin privilegios** `nimiapss`, MariaDB 10.6.28,
`APP_ENV=production`, cachés de `optimize` activas, `.htaccess` propios y
datos (2 usuarios, 1 organización con un miembro, 1 administrador):

| Prueba | Resultado |
|--------|-----------|
| `sha256sum -c` del ZIP | OK |
| `actualizar.sh --simular` | «Todo listo», 541 nuevos y 40 modificados, nada cambiado |
| Cambio local en `routes/web.php` | Detectado: «Modificado localmente», no copia nada |
| Sin modo mantenimiento | Se niega |
| Como `root` | Se niega |
| Actualización real | 581 archivos copiados y verificados; copia en `~/nimiapps-respaldos/actualizacion-ent003-*` |
| `.env`, `.htaccess`, `public/.htaccess`, logs | Idénticos (sha256) |
| Repetir el script | «ya están instalados», no hace nada |
| `migrate --force` | 7 migraciones `DONE` |
| `microapps:check` | `NimiApps 0.3.0 (ENT-003)`, «instalación correcta», Stripe `[OPCIONAL]` |
| `sha256sum -c CHECKSUMS.sha256` | Integridad correcta |
| HTTP: `/estado`, `/aplicaciones`, acceso con un usuario de la 0.2.0, `/panel`, `/admin/comercial` | 200; `/apps/demo` 403 sin licencia; webhook sin claves 503 |
| Vuelta atrás (15.2): `migrate:rollback --step=7` + `restaurar-archivos.sh` | 0.2.0 de nuevo, `CHECKSUMS.sha256` de la 0.2.0 correcto, usuarios y espacios intactos, tablas de ENT-003 eliminadas |

## A.6 Incidencias encontradas y corregidas durante las pruebas

- Consultas de licencias con columna `status` ambigua en MariaDB (unión con
  otras tablas).
- Asignación masiva bloqueada en varios modelos comerciales al crear
  licencias, capacidades y promociones.
- Cancelar → reactivar → cancelar en el mismo segundo reutilizaba la clave
  de idempotencia de Stripe.
- Paso a plan gratuito marcado erróneamente como pago duplicado.
- Las plazas ocupadas por exmiembros contaban para el límite.
- Parámetros de URL en forma de lista producían error 500 (ahora 400).
- La ficha mostraba «Desde X €/mes» en microapps solo de pago único.
- Los datos estructurados JSON-LD de la ficha salían corruptos (Blade
  interpretaba `@context` como directiva).
- Los paquetes se listaban en la ficha pero no tenían botón para
  contratarlos.

## A.7 Lo que no he podido probar

- **Stripe real** (todo lo de la Parte B.4–B.8).
- Apache, PHP-FPM, HTTPS y los `.htaccess` reales de cPanel.
- Concurrencia real con varios procesos PHP simultáneos contra MariaDB (la
  protección por bloqueo de fila y reclamación condicional está probada de
  forma secuencial, simulando el otro proceso).
- Impuestos con Stripe Tax, métodos de pago distintos de tarjeta y SCA/3D
  Secure reales.

## A.8 Observaciones conocidas (decisiones, no errores)

- Solo el propietario de una organización contrata, cambia de plan o cancela.
- Las disputas (contracargos) no se gestionan en NimiApps; se ven en Stripe.
- No se puede pasar de una suscripción a un pago único sin cancelar antes.
- El reembolso de una suscripción no retira el acceso: hay que cancelarla.
- El módulo de demostración va habilitado para poder probar; hay que
  desactivarlo antes de abrir al público.
- La microapp de documentación no está construida (solo sus contratos).

---

# Parte B · Pruebas pendientes en cPanel (staging)

Usar **claves de prueba** de Stripe. Tarjetas de prueba: `4242 4242 4242 4242`
(correcta), `4000 0000 0000 9995` (fondos insuficientes),
`4000 0025 0000 3155` (pide autenticación 3D Secure). Fecha futura y
cualquier CVC.

## B.1 Actualización y diagnóstico

- [ ] Pasos 3–10 del manual sin errores; `--simular` limpio.
- [ ] `migrate` muestra las 7 migraciones.
- [ ] `microapps:check`: 0.3.0, sin `[ERROR]`; Stripe `[OPCIONAL]` antes del paso 11.
- [ ] `/estado` con `"version":"0.3.0"`; usuarios y organizaciones previos entran.
- [ ] `https://staging.nimiapps.com/.env` y `/vendor/stripe/` siguen dando 403/404.

## B.2 Panel comercial sin Stripe

- [ ] Crear categoría, ficha de `demo-licencias` (con imagen), «Traer capacidades del módulo».
- [ ] Producto, plan gratuito y plan de pago con precio mensual y anual; publicar.
- [ ] El catálogo y la ficha se ven bien en móvil y escritorio.
- [ ] Contratar el plan gratuito: acceso inmediato a `/apps/demo`.
- [ ] El plan de pago avisa de que los pagos no están activados y no crea pedido.

## B.3 Configuración de Stripe (modo prueba)

- [ ] Paso 11 del manual; `microapps:check` dice «Modo de pruebas» y «Pagos activos».
- [ ] Tras la primera compra (B.4), Stripe → Webhooks muestra respuestas 200 y los eventos aparecen «procesados» en `/admin/comercial/eventos`.

## B.4 Compra individual

- [ ] Compra mensual con 4242: página de Stripe con el importe correcto, regreso, pedido «Completado», licencia activa, acceso.
- [ ] Volver desde Stripe sin pagar: sin acceso.
- [ ] Tarjeta 9995: Stripe rechaza, sin acceso.
- [ ] Tarjeta 3155: autenticación y acceso tras completarla.
- [ ] Plan con prueba gratis: no cobra al inicio, licencia «en prueba».
- [ ] Cupón: el descuento coincide en NimiApps y en Stripe.
- [ ] Pago único: acceso y, si tiene días de acceso, fecha de fin correcta.
- [ ] En Stripe aparecen cliente, producto y precio creados por NimiApps.

## B.5 Organización

- [ ] La propietaria compra un plan con plazas; un miembro sin plaza no entra; «Dar acceso» y entra; «Quitar acceso» y deja de entrar.
- [ ] Un administrador de la organización no puede comprar; un miembro no ve Contrataciones.
- [ ] Con dos organizaciones, ninguna ve los pedidos ni licencias de la otra.

## B.6 Ciclo de vida (Stripe → Billing → Relojes de prueba, o acciones manuales)

- [ ] Renovación pagada amplía la licencia.
- [ ] Renovación fallida: «pago pendiente» con acceso durante 7 días, después sin acceso.
- [ ] Cancelar a final de periodo desde el panel y reactivar.
- [ ] Ampliar y reducir plan; cambiar mensual ↔ anual; importes prorrateados en Stripe.
- [ ] «Métodos de pago y facturas» abre el portal de Stripe.

## B.7 Administración de ventas

- [ ] Reembolso parcial y total desde `/admin/comercial/pagos`; estado actualizado por webhook.
- [ ] Cancelar suscripción de inmediato: el cliente pierde el acceso.
- [ ] «Sincronizar con Stripe» y «Reprocesar» un evento.
- [ ] Concesión manual, suspensión, reactivación y revocación de una licencia.
- [ ] Auditoría registra todo lo anterior.

## B.8 Robustez

- [ ] Reenviar un evento desde Stripe → Webhooks: no duplica nada.
- [ ] Pagar en dos pestañas el mismo plan: el segundo queda marcado «duplicado».
- [ ] Desactivar temporalmente el endpoint en Stripe, comprar, reactivarlo y reenviar: el pedido se completa (también al recargar la página de regreso).

## B.9 Diagnóstico final

- [ ] `microapps:check` sin `[ERROR]`, sin eventos fallidos ni pedidos atascados.
- [ ] `storage/logs/` sin errores nuevos.
