# Matriz de validación del aplicativo de reparto

> **Corte canónico posterior al despliegue — 2026-10-21.** Las notas que
> indiquen que falta la cuenta FCM o la migración de `smart_demo` pertenecen a
> cortes históricos. Actualmente producción usa el release `134cde35`, la
> cuenta de servicio FCM está montada desde almacenamiento privado compartido,
> las migraciones de `website_id=5` están aplicadas y el APK release de
> producción fue instalado en `373ead9f`. El preflight pasó con disco al 88%.

> Aún no se certifica en este corte un pedido nuevo completo desde POS hasta
> Caja con la APK cerrada/bloqueada; esa es una prueba funcional pendiente, no
> un indicio de que FCM o las migraciones sigan sin configurar.

## Corte vigente 2026-10-20

La evidencia histórica de este documento distingue pruebas locales, simuladas y pruebas contra demo. En el corte actual, el tenant `smart_demo` ya tiene aplicada la migración `delivery_order_item_lines` y el APK release carga pedidos y sus detalles en el dispositivo físico `373ead9f`. El login, registro del dispositivo, consulta de pedidos y consulta de ruta respondieron HTTP 200.

La notificación FCM con la aplicación cerrada sigue sin declararse probada en demo: el host tiene configurado `FCM_SERVICE_ACCOUNT_FILE`, pero el archivo de cuenta de servicio no está presente en el host ni en el contenedor FPM. Las pruebas FCM históricas con transporte falso no sustituyen esa verificación externa.

Entorno: `demo.smartplus.test` y dispositivo Android conectado por ADB.
Producción: no modificada.

Prueba de dispositivo local: APK debug compilado con `SMARTPLUS_API_URL=http://192.168.100.9` y `SMARTPLUS_TENANT_HOST=demo.smartplus.test`; la pantalla cargo pedidos sin el error de URL.

## Comprobado localmente

| Área | Resultado | Evidencia |
|---|---|---|
| GPS al confirmar entrega | Comprobado | El APK local registró `order_delivered` con latitud, longitud, precisión y fuente `device`; el backend valida coordenadas juntas. |
| Acciones offline | Comprobado | En dispositivo real se cargó el pedido, se cortó la red y se encolaron aceptar, cobro en efectivo y entrega con GPS; al recuperar internet las tres acciones sincronizaron y la cola quedó vacía. Las pruebas Flutter también cubren estados, ruta y respuestas `success:false`. |
| Evidencia Yape offline | Implementado | La foto se copia al almacenamiento persistente antes de encolarse. |
| Permisos de cobro | Implementado | Caja/administrador valida; el repartidor solo reporta su propio cobro. |
| Foto de Yape | Restringida | Solo caja/administrador o repartidor asignado pueden descargarla. |
| Acceso web del repartidor | Implementado | El middleware bloquea el back office web para usuarios marcados como repartidor; el cierre de sesión permanece disponible. |
| Estados de entrega | Comprobado | No entregado, reprogramado, cancelado y rechazo del pedido requieren observación; el APK local registró un rechazo con motivo y evento `order_rejected`. |
| Rastreo | Comprobado con reapertura | En una ruta temporal `route_id=4` se capturó GPS, se reinició físicamente el teléfono y, al abrir SmartPlus, el servicio volvió a primer plano y registró otro punto con precisión 16.60. La ruta activa se persiste, la pantalla verifica su salud cada 30 segundos y el receptor conserva la recuperación pendiente si Android bloquea el arranque silencioso. |
| Notificación FCM | Comprobado | Con la aplicación cerrada desde recientes y la pantalla bloqueada, Android mostró la notificación y al tocarla abrió el pedido correcto. |
| Trazabilidad FCM | Implementado | Intentos, reintentos, errores de envío/configuración, `message_id` y apertura. |
| Recaudación | Implementado | Resumen de efectivo, Yape y total por ruta. |
| APK debug/release | Comprobado | El APK debug compila, instala y abre; el release compila y pasa verificación de firma v2 con un firmante. |

| Interfaz movil | Comprobado | Skeleton de carga, refresco sin parpadeo, aviso persistente de actualizacion, estados con icono/color, animaciones breves de ruta y proteccion contra overflow de productos. |
| Pantalla pequena | Comprobado automaticamente | El detalle pasa widget test a 360x640 con nombre de producto largo; el dispositivo tambien se probo temporalmente a 360x640 y volvio a 1080x2340. |

## Registro historico de pruebas manuales

Las pruebas que originalmente figuraban como pendientes fueron ejecutadas y sus
resultados actuales estan documentados en `Gate de release` mas abajo. El listado
historico se conserva para distinguir las comprobaciones hechas en dispositivo,
emulador, transporte FCM simulado y concurrencia de base de datos.

Estado actual al 2026-10-19: no quedan bloqueadores funcionales locales. La caida
real de Firebase no se provoca contra ningun entorno; se valida mediante fallos
controlados del transporte y debe confirmarse con monitoreo durante un canario.

De esta lista, los permisos y la pantalla pequena ya fueron comprobados. Los unicos
pendientes de ejecucion externa son el arranque silencioso tras reinicio, una caida
controlada de Firebase y la concurrencia real de dos solicitudes.

El servicio tambien marca recuperacion pendiente si Android revoca el permiso de
ubicacion mientras existe una ruta activa, y la app vuelve a solicitar la recuperacion
al regresar al primer plano.

La marca solo se limpia despues de que el servicio confirma que pudo registrar las
actualizaciones GPS; un arranque fallido ya no se considera recuperado.

1. Arranque silencioso del servicio durante `BOOT_COMPLETED` sin abrir la app. La recuperación al reiniciar y volver a abrir SmartPlus ya fue comprobada; Android puede impedir el arranque silencioso de un FGS de ubicación sin permiso de ubicación permanente.
2. Rechazar permisos de ubicación/notificaciones y comprobar la guía a Configuración. Comprobado: tras dos rechazos Android abrió la configuración de la aplicación; los permisos fueron restaurados al finalizar.
3. Firebase temporalmente no disponible: comprobado con transporte local falso; no se interrumpio Firebase real.
4. Dos asignaciones simultáneas y dos cobros simultáneos.
5. Prueba en un teléfono pequeño y con teclado abierto.

La API rechazó un token inválido con HTTP 401. La asignación repetida del mismo
repartidor no creó un segundo intento FCM para la misma versión de asignación.

## Resultado de la validacion automatizada final

- `flutter analyze`: sin hallazgos.
- `flutter test`: 7 pruebas pasaron, incluyendo cola offline y detalle a 360x640.
- PHP lint de los archivos de reparto: sin errores.
- `php artisan test tests/Unit/DeliveryDriverWebRestrictionTest.php`: 2 pruebas pasaron.
- `php artisan test tests/Unit/DeliveryRouteResourceTest.php`: 1 prueba pasÃ³; una ruta
  sin cobro asociado ya no produce HTTP 500 al iniciar.
- `php artisan test`: 12 pruebas pasaron.
- APK release: construido y verificado con firma APK v2, un firmante.
- El APK local usa el proyecto Firebase `smartplus-delivery-local`; no debe
  distribuirse ni promoverse a produccion sin reemplazar la configuracion Firebase.
- El guard de Gradle bloquea ese release por defecto; el build local solo paso con
  `SMARTPLUS_ALLOW_LOCAL_FIREBASE_RELEASE=true` de forma explicita.
- APK debug local: instalado en `R5CX1069ZWD`, abrio la pantalla de pedidos usando
  `http://192.168.100.9` y `demo.smartplus.test`, sin error de URL.
- Playwright: `/pos` y `/order-notes/delivery-orders` siguen sin acceso para el
  repartidor; la consola del panel local no reporto errores.
- FCM: el backend reintenta hasta tres veces tanto la obtencion del token OAuth
  como el envio del mensaje; los intentos quedan registrados por dispositivo.

No se simulo ninguna indisponibilidad sobre produccion. La prueba del segundo
dispositivo, la asignacion repetida y los dos cobros simultaneos ya fueron comprobados;
queda opcionalmente un teclado fisico, que no bloquea el flujo movil principal.

La prueba con `am force-stop` no es equivalente a cerrar desde recientes: Android puede
bloquear FCM para una aplicación detenida forzosamente. Esa variante se documenta como
limitación del sistema y no como cierre normal de la aplicación.

La validación Playwright con `repartidor.demo@smartplus.test` confirmó que tanto `/pos`
como `/order-notes/delivery-orders` responden "Acceso denegado"; la API móvil permanece
separada para el repartidor.

### Evidencia adicional de reinicio real

El 2026-10-18 se reinicio el telefono con una ruta activa y SmartPlus sin abrirse.
El `DeliveryTrackingBootReceiver` recibio el arranque y solicito recuperar la ruta,
pero Android bloqueo el `ForegroundService` de ubicacion desde segundo plano porque la
aplicacion tiene permiso de ubicacion mientras esta en uso. `last_location_at` no avanzo
durante ese intervalo, pero se mostro la notificacion `SmartPlus: reanudar ruta` para
que el repartidor abra la aplicacion y reactive el seguimiento. Este es el comportamiento
seguro esperado sin solicitar rastreo permanente; no se considera captura GPS silenciosa.

## Corte de auditoría vigente 2026-10-20

Este bloque prevalece sobre las evidencias históricas anteriores. En el entorno
actual `demo.sistemax.net.pe`, Playwright confirmó que el listado de pedidos y
el contexto responden HTTP 200, pero la creación de un pedido nuevo falló antes
de asignarlo por la ausencia de `order_note_items.item_unit_type_id` en
`smart_demo`. Por ello no se declara completado el flujo actual
POS → asignación → FCM → APK → ruta → entrega → cobro.

La cuenta `FCM_SERVICE_ACCOUNT_FILE` está referenciada en la configuración del
servidor, pero el archivo no está disponible en host/FPM. Las pruebas históricas
con transporte falso y las evidencias de otro corte no sustituyen una prueba
FCM real con el servidor vigente. La notificación con APK cerrada permanece
pendiente de configuración externa.

El APK físico vigente `373ead9f` sí fue instalado, abrió `MainActivity`, cargó
pedidos asignados y mostró producto, cantidad, precio, total, dirección y
contacto. También pasó detalle → retroceso y mostró el inicio automático de ruta
sin botón manual. No se pudo repetir aceptación nueva porque el pedido nuevo no
se creó.

La migración pendiente es:

```text
database/migrations/tenant/2026_10_19_000013_tenant_add_item_unit_type_id_to_order_note_items.php
```

## Gate de release

### Validacion reproducible adicional 2026-10-18

- FCM indisponible: se probaron tres respuestas HTTP 503 mediante un transporte local falso. El servicio consumio las tres respuestas, reintento tres veces y dejo la excepcion controlada; una secuencia 503, 503, 200 tambien termino correctamente en el tercer intento. No se interrumpio Firebase real.
- Concurrencia de asignacion: dos procesos locales ejecutaron simultaneamente la misma actualizacion condicional sobre un pedido de demo. Los resultados fueron `1` y `0`; solo una asignacion pudo ganar. El pedido fue restaurado a su estado original despues de la prueba.
- Segundo dispositivo: el AVD local `sysfarma_demo` arranco como `emulator-5554`, se conecto por ADB, hizo ping a `192.168.100.9`, instalo el APK release, inicio sesion, registro su token FCM y abrio `MainActivity` sin excepcion fatal. Con la app en segundo plano recibio `Pedido PD01-2 · S/ 8.00 · 1 producto(s)`; al tocar la notificacion abrio el detalle `PD01-2`. El intento quedo `sent`, con `attempts=1` y `opened_at` registrado. El pedido de demo fue restaurado despues.
- Asignacion repetida: con dos dispositivos registrados, la primera asignacion genero 2 intentos (uno por dispositivo) y repetir la misma asignacion mantuvo el total en 2; no se genero una segunda notificacion para la misma version.
- Dispositivo fisico: `R5CX1069ZWD` volvio a estado ADB `device`, tiene instalado `pe.smartplus.smartplus_delivery_app` y abrio `MainActivity` correctamente.
- Concurrencia de cobros: dos solicitudes simultaneas de efectivo para el mismo pedido
  terminaron en un HTTP 200 y un HTTP 422 por duplicidad; la base dejo exactamente un
  pago reportado con `amount_reported=8.00`. El pago de prueba fue eliminado y el pedido,
  la ruta y sus relaciones fueron restaurados.

El build release está configurado para usar una clave propia mediante:

- `android/key.properties`
- un keystore local ignorado por Git

Si falta la clave, el build se detiene explícitamente. No se debe usar la firma debug para publicar.
