# Auditoría vigente del plan SmartPlus Reparto de Agua

Fecha de corte: 2026-10-21. Este documento reemplaza cualquier estado anterior
que indique que la asignación de `PD01-8` devolvía HTTP 500.

## Regla global de activación

Las funciones exclusivas de reparto se habilitan únicamente si, en la conexión
del tenant, existe un registro:

```text
business_turns.value = water_distributor
business_turns.active = true
```

Cuando el giro está inactivo, no se deben aceptar login de repartidor, lectura
o mutación de pedidos de reparto, GPS, notificaciones nuevas, pagos del
repartidor, bidones, puntos ni configuración de presentaciones. Los datos
históricos no se eliminan.

La regla está aplicada en `DeliveryBusinessTurnGuard`, el middleware
`delivery.business_turn`, `DeliveryController`,
`OrderNoteController`, los servicios de bidones, pagos, puntos, presentaciones,
el job de notificaciones y el cliente Android. Está cubierta por pruebas de
gate con el giro apagado.

## Matriz punto por punto

| Punto del plan | Estado actual | Evidencia | Pendiente real |
|---|---|---|---|
| Resolver empresa por RUC antes del login | Implementado y probado local | `DeliveryTenantController@resolveRuc`, pantalla de login en dos pasos | Probar con segundo tenant real |
| Mantener tenant al cerrar sesión | Implementado | tenant guardado localmente; no se cambia empresa desde pedidos | Confirmar visualmente logout/login en otro tenant |
| Usuario ligado a sucursal | Implementado | login exige `establishment_id` y devuelve sucursal fija | Prueba runtime con dos sucursales |
| Marcar usuario como repartidor | Implementado y reforzado | campo `users.delivery_driver`, formulario, `UserResource` y rechazo backend si el giro está inactivo | Validación visual final del formulario |
| Bloquear web/backoffice al repartidor | Implementado y probado local | middleware `delivery.driver.web`; logout queda permitido | E2E visual contra release activo |
| Impedir POS, caja, pagos, clientes y comprobantes al repartidor | Implementado y probado local | `delivery.driver.web` bloquea el backoffice y `delivery.driver.api` bloquea los grupos API genéricos; las rutas móviles quedan separadas | Repetir matriz completa contra el release desplegado |
| Crear pedido de reparto | Implementado | POS y `OrderNoteController`; el modo solo se activa con giro de agua | Pedido demo ya creado |
| Dirección, teléfono, referencia y coordenadas | Implementado | `person_addresses`, validación de cliente y campos del pedido | Varias direcciones del mismo cliente |
| Google Maps por tenant | Implementado | `tenant_settings.google_maps_browser_key`, clave no global por defecto | Verificar restricciones de cada tenant |
| Asignar repartidor | Implementado y probado localmente | bloqueo de fila, versión renderizada, `PD01-8` HTTP 200, repartidor QA vinculado | Repetir con dos usuarios simultáneos contra el próximo release |
| No cambiar repartidor fuera de Pendiente | Implementado y probado localmente | actualización condicional, conflicto obsoleto HTTP 409 y prueba `DeliveryAssignmentNotificationTest` | Confirmar dos usuarios en runtime contra el próximo release |
| No asignar sin outbox válido | Implementado y probado local/runtime | devuelve 503 antes de mutar si faltan tabla o columnas; demo tiene outbox alineado | Repetir en tenant aislado sin migración |
| Notificación FCM | Implementado y probado en runtime | outbox enviado por worker; reintento ante token FCM antiguo; notificación visible con pantalla bloqueada | Segundo dispositivo y prueba de caída real de Firebase |
| Abrir pedido correcto desde notificación | Implementado y probado con pantalla desbloqueada | `PD01-8` abrió su detalle; con pantalla bloqueada `PD01-10` llegó a la bandeja protegida | Confirmar toque posterior al desbloqueo en el dispositivo |
| Mostrar productos, presentación, cantidad y total | Implementado y probado en runtime | APK mostró `agua`, cantidad 1, S/ 7.00 y total | Pedido con presentación no legacy |
| Aceptar pedido | Implementado y probado en runtime | pedido pasó a `en_route` | Rechazo y reprogramación |
| Iniciar ruta automáticamente | Implementado y probado en runtime | ruta 3 pasó a `active` al aceptar; no existe botón manual de inicio | Cierre de ruta completa |
| Estados de reparto | Implementado y probado parcialmente en runtime | `pending → en_route → delivered`, `not_delivered`, `rescheduled`, `rejected` y `cancelled` verificados en demo; las dos últimas transiciones aún requieren validación visual desde APK | Prueba visual móvil de `rejected` y `cancelled` |
| GPS solo durante ruta activa | Implementado y probado parcialmente | servicio Android foreground, puntos persistidos y apagado ante ruta inactiva o giro desactivado | Pantalla bloqueada y recuperación completa |
| Ver última ubicación desde Caja | Implementado en API/UI | `/delivery/routes/active`, `latestLocation`, estado synced/stale/offline | E2E visual durante ruta activa |
| Confirmar entrega desde APK | Implementado y probado en runtime | `PD01-8` entregado con coordenadas del dispositivo | Confirmación sin GPS permitido |
| Adicionales, faltantes y sustituciones | Implementado en código | líneas ajustadas, aprobación y sustitución en API/APK | E2E con diferencia de cantidades |
| Cobro efectivo y vuelto | Implementado y probado en runtime | S/ 7.00 reportado, recibido S/ 7.00, vuelto S/ 0.00 | Probar vuelto positivo y liquidación |
| Yape/Plin/transferencia con evidencia | Implementado en código | método, referencia, archivo y revisión posterior | Foto real y validación de Caja |
| Crédito/no cobrado | Implementado en código | estados y validaciones específicas | Confirmar política comercial |
| Evitar doble cobro | Implementado y probado local | idempotencia, locks y estado único | Dos dispositivos contra runtime |
| Caja valida o rechaza cobro | Implementado y probado parcialmente | Caja mostró pendiente; bloqueó validar sin comprobante | Generar comprobante demo y validar/rechazar |
| No mostrar “Generar comprobante” después de generado | Implementado en UI | `can_generate` y texto `Ya generado` | Verificar boleta de este pedido |
| Cierre automático de ruta | Implementado y probado localmente | `DeliveryRouteAutoFinisher` cierra con los cinco estados terminales (`delivered`, `not_delivered`, `rescheduled`, `cancelled`, `rejected`); efectivo y bidones se liquidan después | Ruta real con liquidación de efectivo/bidones |
| Bidones: catálogo, QR y asociación | Implementado y probado local | servicios, permisos, asociación idempotente | Cámara y QR físico |
| Estados de bidón | Implementado en código | lleno, vacío, dañado, prestado, vendido y perdido | Validación física completa |
| Carga y liquidación de bidones | Implementado en código | `RouteContainerSettlementService` | Ruta real conciliada |
| Saldo de bidones por cliente | Implementado en código | `ContainerDepositService`, saldos y movimientos | Cliente con varios intercambios |
| Producto maestro y presentaciones | Implementado y probado local | `item_unit_types`, snapshots y fallback legacy | Presentación real en POS |
| Precio por presentación/lista/sucursal | Implementado y probado local | `PresentationPriceResolver` | Validación comercial por tenant |
| Puntos por presentación | Implementado y probado local | reglas y ledger por presentación | Regla real en POS |
| Canje de 11 puntos | Implementado técnicamente | campaña y recompensa por presentación | Confirmar producto/recompensa comercial |
| No duplicar puntos/canjes | Implementado y probado local | locks, ledger e idempotencia | Concurrencia en demo |
| Migraciones reversibles | Implementado y probado local | `TenantMigrationChainTest` y migraciones tenant | Staging antes de rollback productivo |
| Deploy seguro | Implementado y verificado en producción | lock, preflight, no-build, healthchecks, release y rollback; producción sirve `6717aa3` | Mantener pruebas E2E externas pendientes |

## Evidencia runtime actual

- Release activo: `f44d691160b5f4926dd11fef7fdab3bcb27ecd84`.
- Symlink efectivo: `/var/www/html/smart -> /var/www/html/smart-release-f44d6911`.
- PHP de FPM: 7.4.33; se corrigió el job que usaba sintaxis PHP 8.
- Disco: 89%; el preflight bloquea desde 90%.
- Salud: `mariadb_smartplus`, `redis_smartplus`, `fpm_smartplus`,
  `worker_smartplus`, `scheduling_smartplus` y `nginx_smartplus` saludables.
- Pedido controlado: `PD01-8`, total S/ 7.00, `agua` x1.
- Ruta: id 3, repartidor id 4, estado `active`.
- Ubicaciones persistidas: precisión observada entre 19.67 m y 39.44 m.
- Entrega: estado `delivered`, coordenadas de dispositivo persistidas.
- Cobro: `delivery_order_payments`, método `cash`, estado `reported`.
- Despliegue: preflight aprobado con disco 89%, RAM disponible 3.6 GiB,
  swap 50%, sin OOM reciente ni procesos de compilación; `RUN_MIGRATIONS=0`.
- Verificación posterior: HTTP 200 y marcador del commit visible en FPM,
  worker, scheduler y Nginx; los seis contenedores quedaron saludables.
- En Caja, `PD01-8` permanece correctamente como `Pendiente de validación`;
  la validación sin comprobante fue rechazada con el mensaje de seguridad
  esperado. La pantalla solo muestra `Ya generado` cuando el pedido tiene
  comprobante; en el intento controlado de emitirlo, el sistema exigió el
  destino del pago y no se creó un comprobante incompleto.
- En el APK físico `373ead9f`, una incidencia de `PD01-3` se almacenó sin
  conexión, se reinstaló el APK con la normalización de estados y la cola
  legacy se sincronizó con HTTP 200. El pedido terminó en `not_delivered` con
  observación persistida; el error anterior era `notDelivered` (camelCase),
  incompatible con el contrato API `not_delivered`.

La correccion del APK detiene el servicio foreground, elimina la cola GPS y
limpia la ruta local cuando el API responde `403` por giro de agua inactivo,
incluyendo el caso en que el `403` llegue durante el envio de ubicaciones.
El APK release se recompilo, se instalo en `373ead9f` y abrio correctamente;
la respuesta `403` real aun falta probarla desactivando el giro en un tenant
aislado.

En el mismo APK, `PD01-4` se reporto como `rescheduled` con observacion
persistida y desaparecio de la lista activa. La ruta permanecio `active`
porque todavia tiene cobros no resueltos, confirmando la condicion adicional
del cierre automatico.

La regla de activacion tambien se reforzo en el login web y en el endpoint de
usuarios: un repartidor no puede iniciar sesion ni ser creado/editado con ese
indicador si `water_distributor` esta inactivo. La prueba de gate web quedo
incluida en `DeliveryBusinessTurnGateTest`.

Prueba externa adicional: con la aplicacion fuera de recientes y la pantalla
bloqueada, Android mostro `Nuevo pedido asignado` para `PD01-10`, con `S/ 8.26`
y `1 producto(s)`. El toque fue detenido por el PIN del dispositivo; el
deep-link ya habia sido confirmado con la pantalla desbloqueada.

En el tenant demo se verificaron tambien las transiciones terminales desde el
panel: `PD01-9` paso a `rejected` y `PD01-10` paso a `cancelled`, ambos con
observacion y respuesta HTTP 200. La prueba confirma el contrato backend; no
sustituye la validacion visual de esos estados dentro del APK.

Playwright confirmo tambien la representacion administrativa: `PD01-10`
aparece como `Cancelado` y `PD01-9` como `Rechazado` en la tabla de pedidos,
con repartidor, direccion y total visibles.

## Pruebas ejecutadas

```text
php artisan test --no-coverage       119 passed
flutter analyze                       No issues found
flutter test (mobile/delivery_app)    19 passed
npm run lint                          passed
npm run prod                          terminado; regeneró `public/js/app.js` y `public/mix-manifest.json`
git diff --check                      passed
```

Playwright demo E2E: 4 pruebas aprobadas (smoke, lectura y seguridad).

## Pendientes que impiden declarar terminado

1. Generar el comprobante de `PD01-8` y completar validación/rechazo en Caja.
2. Ejecutar Yape con foto, rechazo y reintento idempotente.
3. La notificación con APK fuera de recientes y pantalla bloqueada ya fue
   recibida para `PD01-10`; falta confirmar el toque que abre el pedido en un
   dispositivo cuyo desbloqueo pueda completarse durante la prueba. El
   deep-link con pantalla desbloqueada ya fue confirmado.
Nota de dispositivo fÃ­sico: el APK obtuvo token FCM, mantuvo
`DeliveryTrackingService` como foreground con la pantalla bloqueada y mostrÃ³
la notificaciÃ³n real de `PD01-10` con monto y cantidad. El toque fue ejecutado,
pero falta completar el desbloqueo del equipo para confirmar el deep-link.

Posteriormente se compilÃ³ la APK corregida en una copia de trabajo ubicada en
D: y se instalÃ³ sobre el dispositivo `373ead9f`. Firebase inicializÃ³, el token
FCM fue obtenido y el servicio foreground arrancÃ³ sin excepciÃ³n fatal.

4. Confirmar replay offline de GPS/cobros y duplicidad con una segunda
   operación; el replay de estados, incluido un evento legacy, ya quedó
   validado.
5. Completar la prueba visual móvil de estados rechazado y cancelado;
   `not_delivered` y `rescheduled` ya quedó validado en el dispositivo y
   `rejected`/`cancelled` fueron verificados en runtime web/demo.
6. Completar una ruta con cierre automático y liquidación de efectivo/bidones.
7. Validar QR físico y estados físicos de bidones.
8. Validar presentación, precio y campaña real de 11 puntos.
9. Ejecutar runtime con `water_distributor.active = false` en tenant de prueba,
   incluyendo la detencion del servicio GPS ante respuesta `403`.
10. Verificar el bundle generado contra el release servido después del próximo
    despliegue; localmente `npm run prod` ya terminó y regeneró
    `public/js/app.js` y `public/mix-manifest.json`, pero esos artefactos todavía
    no están desplegados.

Hasta completar esos puntos, el estado correcto es **implementado y
parcialmente certificado**, no completamente terminado.

## Deploy actual verificado — 2026-10-21

- Release desplegado: `6717aa3af5d074e55e383ffd0a353796070ca1ea`.
- Symlink efectivo: `/var/www/html/smart -> /var/www/html/smart-release-6717aa3`.
- Se ejecutó primero el preflight; bloqueó correctamente al 90% y continuó
  después de liberar espacio hasta 89%.
- Se desplegó con `RUN_MIGRATIONS=0`, `docker compose up -d --no-build` y sin
  ejecutar Composer, NPM ni Webpack en producción.
- FPM, worker, scheduler y Nginx verificaron el mismo marcador; MariaDB,
  Redis, FPM, worker, scheduler y Nginx quedaron saludables.
- `https://sistemax.net.pe/login` y `https://demo.sistemax.net.pe/login`
  respondieron HTTP 200.
- Se eliminó únicamente el release antiguo e inactivo
  `/var/www/html/smart-release-134cde35` (661 MB); se conservaron el activo
  anterior, el rollback y el release nuevo.
- No se ejecutaron migraciones. El release anterior queda disponible para
  rollback mediante el symlink y el script de despliegue.

## Verificación adicional de dispositivos y demo — 2026-10-21

- El APK `pe.smartplus.smartplus_delivery_app` está instalado en los dos
  dispositivos físicos `373ead9f` (RMX2170) y `R5CX1069ZWD` (SM-S928B).
- Ambos dispositivos están actualmente conectados como `device` por ADB; el
  estado `offline` del segundo equipo quedó resuelto reiniciando el servicio
  ADB.
- Ambos tienen concedidos `POST_NOTIFICATIONS`, `ACCESS_FINE_LOCATION` y
  `ACCESS_COARSE_LOCATION`; la actividad `MainActivity` está abierta en ambos.
- El primer equipo conserva la sesión del repartidor y muestra `Mis pedidos`.
- El segundo equipo muestra correctamente `Identifica tu empresa` y solicita
  el RUC antes de las credenciales. Esto es el comportamiento esperado del
  login multi-tenant, no un error de navegación.
- La sesión web de `demo.sistemax.net.pe` fue verificada con Playwright. Las
  pruebas read-only de contexto, pedidos, liquidaciones y sanitización de
  credenciales pasaron `2/2`; no se modificaron pedidos.
- No se puede certificar todavía la notificación entre dos dispositivos ni la
  concurrencia real: el segundo equipo no tiene tenant/sesión iniciada y el
  commit local `ed24c3cd` aún no está desplegado en el release activo.

### Actualización posterior: login y notificación en segundo dispositivo

- El segundo dispositivo completó el login con el tenant `demo` y el usuario
  `Repartidor QA Android`.
- La asignación controlada de `PD01-15` al usuario 4 respondió HTTP 200 y el
  worker entregó una notificación Android real con título `Nuevo pedido
  asignado` y texto `Pedido PD01-15 · S/ 8.26 · 1 producto(s)`.
- El APK mostró el pedido en ambos datos funcionales esperados: `1x agua`,
  dirección y total `S/ 8.26`.
- Esta evidencia certifica login, registro FCM, asignación y recepción de
  notificación en el segundo dispositivo. No certifica todavía la apertura
  desde pantalla bloqueada ni la concurrencia, porque el release demo aún no
  contiene el commit local `ed24c3cd`.

### Prueba concurrente contra el release demo actual

- Se enviaron dos asignaciones simultáneas de `PD01-15`, con la misma versión
  `updated_at` y el mismo repartidor esperado.
- El release demo respondió HTTP 200 en ambas solicitudes: una para el usuario
  4 y otra para el usuario 5. El pedido terminó nuevamente en el usuario 4.
- Resultado: la prueba **no pasa** en el release actualmente servido y confirma
  que la corrección de concurrencia de `ed24c3cd` todavía no está desplegada en
  demo/producción. El código local sí contiene el `lockForUpdate`, la versión
  condicional y la prueba que espera conflicto HTTP 409.
- No se debe declarar protegida la asignación en runtime hasta desplegar el
  commit corregido y repetir esta misma prueba esperando un único éxito y un
  conflicto 409.

### Acción requerida para continuar

La prueba de login y notificación del segundo equipo ya fue completada. La
prueba de concurrencia debe hacerse contra un release que contenga el control
de versión del commit `ed24c3cd`; no debe darse por certificada solo con la
asignación HTTP 200 del release anterior.

## Estado de publicación

La revisión local actual contiene los cambios funcionales y el bundle generado;
la corrección más reciente quedó registrada en el commit `f105ce8c`. Todavía no
existe un release productivo con este commit. El servidor se verificó solo como
estado de referencia: sirve `/var/www/html/smart-release-f44d6911`, tiene
los contenedores SmartPlus saludables y el disco al 89 %. Por tanto, las
pruebas locales no deben presentarse como evidencia de que este diff ya está
activo en producción.

### Corrección adicional de seguridad — 2026-10-21

Se cerró la brecha entre la restricción web y la API: un usuario marcado como
`delivery_driver` ahora recibe HTTP 403 en los grupos API genéricos autenticados
(POS, caja, clientes, documentos y operaciones generales). Las rutas dedicadas
`/api/delivery/*` no usan esta restricción y siguen disponibles para el APK,
pero continúan protegidas por `auth:api`, `locked.tenant` y
`delivery.business_turn`. La nueva prueba
`DeliveryDriverApiRestrictionTest` verifica tanto el rechazo del repartidor
como el acceso normal de un usuario no repartidor.

Esta corrección fue validada localmente con PHP lint, `git diff --check` y los
dos tests unitarios del middleware. Todavía requiere una prueba E2E contra el
release desplegado para certificar que el runtime usa estas rutas y no una
versión anterior.

## Correccion aplicada en esta auditoria

El manejo de apertura desde notificaciÃ³n ahora conserva los identificadores
recibidos antes de que `OrdersScreen` monte sus listeners. Esto evita perder el
deep-link en un arranque en frÃ­o desde la notificaciÃ³n; el APK drena esos
identificadores al cargar la pantalla de pedidos.

Las rutas web de seguimiento, asignacion, estados, pagos y bidones ahora usan
el middleware `delivery.business_turn`. El middleware responde 403 antes de
ejecutar el controlador cuando `water_distributor` no existe o esta inactivo:
JSON para API y vista 403 para web. Esto evita que una ruta web futura omita el
gate del giro. La prueba `DeliveryBusinessTurnGateTest` cubre el rechazo comun.

El auto-cierre tambien fue alineado con el flujo operativo: cuando todos los
pedidos llegan a un estado terminal, la ruta se completa y el GPS se detiene
sin esperar la validacion de Caja. El repartidor puede reportar un cobro
pendiente sobre una ruta completada; la liquidacion de efectivo y bidones queda
como conciliacion posterior e idempotente.

Tambien se cerro el bypass del endpoint generico de documentos: un pedido de
reparto ya no puede generar comprobante cuando el giro de agua esta apagado;
los comprobantes de pedidos normales no se bloquean por esta regla.

La trazabilidad fisica de bidones tambien fue reforzada: los movimientos de
un bidon identificado registran condicion anterior y posterior cuando las
columnas existen, actualizan la condicion actual del bidon y conservan
compatibilidad con esquemas legacy. La pantalla comercial ahora expone
sucursal, prioridad, vigencia y rangos de cantidad para las reglas de precio.

Playwright contra demo.sistemax.net.pe verifico sin mutaciones: smoke 2/2,
consulta read-only de contexto/estados/liquidaciones 1/1 y sanitizacion de
credenciales internas 1/1. La prueba con `--project=chromium` no aplica porque
el proyecto no esta definido en la configuracion local; la ejecucion correcta
usa la configuracion del repositorio.

## Actualizacion de auditoria posterior

- Verificacion SSH mas reciente: release activo `/var/www/html/smart-release-f44d6911`,
  disco raiz al 85% y los seis contenedores SmartPlus saludables. El diff local
  de esta auditoria aun no esta desplegado.
- Se corrigio la exclusividad de bidones fisicos: una unidad no puede tener dos
  salidas simultaneas (entregado, prestado o vendido), ni salir nuevamente si su
  estado fisico ya esta con el cliente, vendido, perdido, dañado o en reparacion.
  Los retornos solo se aceptan desde estados compatibles.
- La prueba `ContainerMovementServiceTest` ahora cubre el segundo movimiento de
  salida y queda con 11 casos aprobados.
- La auditoria movil actual reporta 18 pruebas Flutter en el codigo presente;
  debe actualizarse el conteo despues de la siguiente ejecucion completa.
- Sigue pendiente la prueba fisica de dos dispositivos, la caida real de FCM,
  Yape con evidencia, la ruta completa con liquidacion y el gate real con el
  giro `water_distributor` apagado.
- Revalidacion posterior: `php artisan test --no-coverage` termino con 113
  pruebas aprobadas; `php -l` paso para los PHP modificados y `git diff --check`
  no reporto errores de espacios.

## Estado posterior al despliegue

- Commit desplegado: `2ab13d2a9070310e217119b0d3c68dd499c3ba2a`.
- Symlink efectivo: `/var/www/html/smart -> /var/www/html/smart-release-2ab13d2a`.
- El release fue desplegado con `RUN_MIGRATIONS=0`; no se compilo Webpack,
  NPM ni Composer en produccion.
- FPM, worker, scheduler y Nginx verificaron el mismo marcador de release;
  MariaDB y Redis quedaron saludables.
- HTTP real: `sistemax.net.pe/login` y `demo.sistemax.net.pe/login` devolvieron
  200; Playwright post-deploy aprobo 4/4 pruebas de smoke, lectura y seguridad.
- El plan sigue parcialmente certificado: el despliegue backend ya esta
  comprobado, pero permanecen las pruebas fisicas de segundo dispositivo,
  FCM caido, Yape con evidencia, ruta liquidada y gate real con el giro apagado.
- Correccion posterior desplegada en `bd3e5044`: la liquidacion ahora descuenta
  tambien los bidones prestados del saldo final de la ruta; la prueba de
  conciliacion cubre entregados, devueltos, dañados, vendidos, prestados y
  perdidos.

## Estado verificado contra produccion y dispositivo

Esta seccion reemplaza las referencias historicas anteriores a releases que ya
no son el activo efectivo.

- Repositorio local limpio en `9cda1c57aaa2f7ee05f81985325b879ab1c7bbe9`.
- Servidor SmartPlus verificado por SSH: `serversoftlte` (`5.161.118.178`).
- Symlink efectivo: `/var/www/html/smart -> /var/www/html/smart-release-9cda1c57`.
- Marcador servido por FPM, worker, scheduler y Nginx: `9cda1c57aaa2f7ee05f81985325b879ab1c7bbe9`.
- Los cuatro contenedores de aplicacion montan `/var/www/html/smart`; el
  almacenamiento compartido permanece separado en `smart-base-20261020T1815Z`.
- `mariadb_smartplus`, `redis_smartplus`, `fpm_smartplus`, `worker_smartplus`,
  `scheduling_smartplus` y `nginx_smartplus` reportaron `healthy`.
- El disco raiz esta al 87%, debajo del bloqueo configurado en 90%; no se
  eliminaron volumenes, backups ni imagenes.
- `demo.sistemax.net.pe/login` devolvio HTTP 200 desde el servidor.
- Playwright read-only post-verificacion: 4 pruebas aprobadas.
- Suite PHP completa: 113 pruebas aprobadas. El APK actualizado se compilo con
  `build-number 2`, se instalo en el dispositivo `373ead9f` y conserva permisos
  de notificaciones, ubicacion y servicio de ubicacion en primer plano.
- FCM quedo verificado en el runtime: la cuenta de servicio existe dentro de
  FPM/worker/scheduler, el proyecto es `smartplus-delivery-production`, hay 2
  tokens activos y 5 eventos de outbox con envio aceptado por Firebase. Los
  tokens `NotRegistered` fueron revocados automaticamente.

El plan sigue parcialmente certificado. Requieren evidencia externa adicional:
segundo dispositivo y concurrencia real, caida real de Firebase, Yape/Plin con
foto y revision de Caja, ruta completa con liquidacion, QR fisico, prueba
offline con la aplicacion cerrada y el gate real con `water_distributor.active`
apagado. La implementacion esta protegida por el gate y cubierta por pruebas
automatizadas, pero esas condiciones no deben marcarse como pruebas E2E reales
hasta ejecutarlas.

## Corte canónico posterior: 2026-10-21

- Se corrigió el cambio administrativo de estado: los estados `cancelled`,
  `rejected`, `rescheduled` y `not_delivered` ahora solicitan observación en
  los dos componentes web antes de llamar a `delivery-status`. Esto alinea la
  interfaz con la validación obligatoria del backend y evita el HTTP 422 que
  ocurría al seleccionar `Cancelado` sin motivo.
- Commit funcional: `8cf5f9ece18c910b4d9e0f98c0481cf98163f4f6`.
- Release activo verificado: `/var/www/html/smart-release-8cf5f9ec`.
- El deploy pasó el preflight con RAM disponible superior al mínimo, swap al
  50%, disco al 87%, sin OOM reciente ni procesos Webpack/NPM activos. Se usó
  `RUN_MIGRATIONS=0`, `docker compose up -d --no-build` y no se compiló en
  producción.
- FPM, worker, scheduler y Nginx sirven el mismo marcador del commit; MariaDB,
  Redis, FPM, worker, scheduler y Nginx quedaron saludables.
- Playwright post-deploy: 4 pruebas aprobadas. El bundle servido contiene la
  nueva cadena `Observación de reparto`.
- Prueba FCM externa completada con pedido demo `PD01-11`: asignación real,
  aplicación fuera de pantalla/Recientes, notificación con pedido, monto y
  cantidad, apertura del pedido correcto y registro `opened_at`. El pedido fue
  cancelado después con observación de prueba.

La certificación global sigue parcial únicamente por las pruebas externas que
requieren otra condición física o de servicio: segundo dispositivo y
concurrencia, caída real de Firebase, Yape/Plin con foto y revisión de Caja,
ruta completa con liquidación, QR físico, offline con aplicación cerrada y
prueba runtime del gate con `water_distributor.active = false` en un tenant
aislado. No se deben presentar como terminadas solo por las pruebas unitarias.

## Corte canonico de liquidacion de ruta: 2026-10-21

- La prueba E2E controlada completo asignacion, aceptacion, inicio automatico de
  ruta, captura de ubicacion, cobro en efectivo, entrega y cierre automatico
  para `PD01-12`.
- Se detecto y corrigio un error real de produccion al liquidar efectivo: el
  servicio usaba promocion de propiedades de PHP 8, pero FPM ejecuta PHP 7.4.
- Correccion desplegada: `37b4a25365b8009cc580126e761449e489ae809f`.
- Release activo: `/var/www/html/smart-release-37b4a253`; FPM, worker,
  scheduler y Nginx verificaron el mismo marcador; no hubo migraciones ni
  compilaciones en produccion.
- El APK mostro `Liquidacion enviada a caja.` y el backend acepto la
  liquidacion sin duplicarla. Playwright consulto
  `/order-notes/delivery-cash-settlements` y encontro la ruta 5 como
  `submitted`, esperado S/8.26, recibido S/8.26 y diferencia S/0.00.
- La captura de efectivo de la prueba fue S/8.26, igual al esperado. El
  intento automatizado de S/10.00 no certifica el vuelto porque termino
  enviando S/8.26 como recibido y `10.00` como referencia; debe repetirse con
  selectores inequivocos.

La certificacion global continua parcial por segundo dispositivo/concurrencia,
caida real de Firebase, Yape/Plin con foto y revision de Caja, QR fisico,
offline con aplicacion cerrada y gate runtime con el giro desactivado.

## E2E efectivo y vuelto: `PD01-13`

- Pedido creado en demo con `agua` x1, total S/8.26 y asignado a
  `Repartidor QA Android`.
- El APK renovado recibio la notificacion FCM `Nuevo pedido asignado` con
  pedido, monto y cantidad; al tocarla abrio `PD01-13`.
- Aceptar el pedido cambio el estado a `en_route` e inicio la ruta sin boton
  manual.
- El APK registro efectivo recibido de S/10.00 y mostro vuelto S/1.74.
- El backend persistio `amount=8.26`, `received=10.00` y `change=1.74`.
- La entrega termino en `delivered` con coordenadas del dispositivo y la ruta
  se cerro automaticamente.
- La liquidacion de la ruta 6 quedo en Caja como `submitted`, con esperado
  S/8.26, recibido S/8.26 y diferencia S/0.00.

## Corte vigente posterior al gate: 2026-10-21

- El release funcional `4065a92b9196629e288e38cb50daaf2969da9eca` quedo
  desplegado en `/var/www/html/smart-release-4065a92b`.
- El preflight bloqueo correctamente el primer intento por un marcador invalido;
  el release activo no se modifico. Tras corregir el artefacto, el deploy paso
  con `RUN_MIGRATIONS=0`, sin Composer, NPM, Webpack ni `--build`.
- FPM, worker, scheduler y Nginx sirven el mismo marcador; los seis
  contenedores SmartPlus quedaron `healthy`; `/login` respondio HTTP 200 y el
  release anterior `smart-release-37b4a253` permanece disponible.
- El APK conectado `373ead9f` continuo en `MainActivity`; al pulsar Atrás desde
  la pantalla de ruta regreso a `Mis pedidos` sin cerrar la aplicación.
- La version instalada es `versionCode=2`, `targetSdk=36`.

La certificacion funcional global sigue parcial por pruebas que necesitan
segundo dispositivo, caida real de Firebase, foto real de Yape/Plin y revision
de Caja, QR fisico, offline con la aplicacion cerrada y un tenant aislado con
`water_distributor.active = false`. No se simulan como E2E reales.

## Corte vigente de gate y despliegue: 2026-10-21

- Se eliminaron los ultimos accesos directos a
  `BusinessTurn::isWaterDistributor()` dentro del modulo de reparto. El
  controlador movil, seguimiento administrativo, pedidos y vista web usan el
  `DeliveryBusinessTurnGuard` con la conexion configurada del tenant.
- La suite PHP completa paso `116/116`; PHP 7.4 en FPM valido los controladores
  desplegados sin errores de sintaxis.
- El commit funcional `2e11235087ac9797b186cd1e7947f89e4c59bf34` quedo activo
  en `/var/www/html/smart-release-2e112350`.
- El deploy uso `RUN_MIGRATIONS=0` y `docker compose up -d --no-build`; no se
  ejecutaron Composer, NPM ni Webpack en produccion.
- FPM, worker, scheduler y Nginx sirven el mismo marcador; MariaDB, Redis,
  FPM, worker, scheduler y Nginx estan `healthy`; `/login` respondio HTTP 200.
- Playwright smoke en demo paso `2/2`; el release anterior
  `/var/www/html/smart-release-4065a92b` permanece disponible para rollback.

El plan queda implementado y protegido por el giro activo, pero la
certificacion E2E total sigue pendiente solo en las condiciones fisicas o
externas listadas arriba.

## Evidencia mas reciente: commit `31900a3f` y release `smart-release-31900a3f`

- Se centralizo el gate del giro de agua en login, usuarios, POS, catalogo de bidones, QR, items y presentaciones; no quedan llamadas directas a `BusinessTurn::isWaterDistributor()` en `app`, `Modules` ni `routes`.
- El guard usa la conexion tenant configurada y bloquea por defecto cuando el giro esta inactivo o la tabla no existe.
- Se verificaron siete archivos modificados por SHA-256 entre local y servidor. El release contiene `vendor/autoload.php` y `public/js/app.js`.
- Preflight: RAM disponible 3620 MiB, swap 51%, disco 89%, sin OOM reciente y sin procesos webpack/npm activos. El deploy no ejecuto migraciones ni build.
- Los seis contenedores reales (`mariadb_smartplus`, `redis_smartplus`, `fpm_smartplus`, `worker_smartplus`, `scheduling_smartplus` y `nginx_smartplus`) quedaron saludables. `/login` respondio 200 y el marker servido coincide con `31900a3f68cfa5967bef45ef866bef76225306f6`.
- PHP: `116 passed`; lint PHP 7.4 en FPM, worker y scheduler sin errores.
- Rollback conservado: `/var/www/html/smart-release-2e112350`.

No se considera certificada la parte que requiere un segundo dispositivo, concurrencia real, una caida real de Firebase, captura y revision de Yape/Plin, QR fisico, offline con la app cerrada o un tenant aislado con el giro apagado.

## Correccion final verificada: `9e4304fa`

- Las consultas repetidas del giro en puntos y precios fueron sustituidas por `DeliveryBusinessTurnGuard`; la regla de activacion queda centralizada tambien en los servicios internos.
- Se reemplazaron dos usos de `match` incompatibles con PHP 7.4 en los servicios de precios y movimientos de bidones.
- El release activo es `/var/www/html/smart-release-9e4304fa` y el marker servido por FPM, worker, scheduler y Nginx coincide con `9e4304fa4a74dbfddda97eb06afcd78cd8ffa015`.
- Lint PHP 7.4 en los servicios modificados: correcto; seis contenedores saludables; `/login`: HTTP 200.
- `php artisan test --no-coverage`: `116 passed`.
- Playwright demo: smoke `2/2`, lectura/seguridad `2/2`.
- El release anterior `/var/www/html/smart-release-82421f77` permanece para rollback.

La certificacion total sigue pendiente solo en pruebas que requieren condiciones
externas: segundo dispositivo/concurrencia, caida real de Firebase, Yape/Plin
con foto y revision, QR fisico, offline con app cerrada y tenant aislado con
el giro apagado.

## Actualizacion de evidencia posterior al deploy: 2026-10-21

- El repositorio local quedo limpio en `efced366`, con la rama remota alineada.
- Produccion sirve `/var/www/html/smart-release-6717aa3` mediante el symlink
  `/var/www/html/smart`, con marker
  `6717aa3af5d074e55e383ffd0a353796070ca1ea`.
- Los contenedores reales `mariadb_smartplus`, `redis_smartplus`,
  `fpm_smartplus`, `worker_smartplus`, `scheduling_smartplus` y
  `nginx_smartplus` quedaron saludables. El disco esta en 89% y se preservaron
  releases anteriores para rollback.
- Los endpoints externos `https://sistemax.net.pe/login` y
  `https://demo.sistemax.net.pe/login` respondieron HTTP 200.
- Playwright demo paso `demo-delivery-readonly.spec.js` y
  `demo-delivery-security.spec.js`: `2/2`.
- La prueba concurrente real sobre `PD01-15`, usando la misma version de
  asignacion, devolvio exactamente una respuesta `200` y una `409`; el pedido
  quedo asignado a `Repartidor QA Android`, en estado `pending`.

La concurrencia de asignacion queda certificada en el release activo. Siguen
pendientes las pruebas que requieren condiciones externas o hardware adicional:
caida real de Firebase, Yape/Plin con captura y revision de Caja, QR fisico,
offline con APK cerrada, tenant con el giro inactivo, presentaciones reales y
campana de 11 puntos, varias direcciones fisicas y liquidacion completa de
efectivo y bidones.

## Correccion de exposicion de documentos: 2026-10-21

- Se detecto que varias rutas publicas de impresion y descarga estaban fuera
  del bloqueo del repartidor.
- Se aplico `delivery.driver.web` a las rutas de PDFs, tickets, notas,
  pedidos, compras, cotizaciones y pedidos de reparto.
- El middleware identifica tanto sesion web como token API del tenant.
- Los invitados y usuarios normales mantienen sus enlaces publicos; un
  repartidor recibe HTTP 403.
- Se agregaron pruebas unitarias para repartidor bloqueado y visitante
  anonimo permitido.
- Suite PHP posterior al cambio: `121 passed`.

Este cambio aun requiere incorporarse al siguiente release de produccion; no
se ha ejecutado un deploy por este ajuste en esta auditoria. La suite completa
posterior al cierre de esta brecha quedo en `123 passed`.

## Corte operativo corregido: 2026-10-21

El texto anterior queda superado por la verificacion posterior al deploy:

- Produccion sirve `/var/www/html/smart-release-4c482863`.
- El marker completo es `4c482863963fb59df72e694ca041cfa25461c8f4`.
- La cache de rutas fue limpiada en `fpm_smartplus` despues de extraer el
  release. Un repartidor autenticado recibio HTTP 403 en impresion publica y
  en la lista heredada de pedidos.
- Los seis contenedores SmartPlus quedaron saludables, el disco esta en 89%,
  y no se ejecutaron migraciones ni compilaciones.
- Playwright paso 2/2 y la suite PHP quedo en `123 passed`.

## Correccion del fail-closed del giro: 2026-10-21

`DeliveryBusinessTurnGuard` ahora captura errores de conexion o consulta y
devuelve `false`; si el tenant no puede confirmar el registro
`water_distributor` activo, las funciones de reparto quedan bloqueadas en vez
de habilitarse o fallar de forma ambigua. Se agrego una prueba con una
conexion invalida y la suite completa quedo en `123 passed`.

## Release posterior verificado: 2026-10-21

- El commit `09eaf448aa246271790ddc8512bf7e0e9e9382fc` quedo desplegado en
  `/var/www/html/smart-release-09eaf448`.
- FPM, worker, scheduler y Nginx sirven el mismo marker completo.
- MariaDB, Redis, FPM, worker, scheduler y Nginx quedaron saludables.
- El deploy paso el preflight con disco al 89%, sin migraciones, Composer,
  NPM, Webpack ni `--build`.
- `/login` respondio HTTP 200 y Playwright paso 2/2.
