# SmartPlus Delivery App

Scaffold inicial de aplicación Flutter Android para motorizados. Está separado del backend y del frontend web de SmartPlus.

## Estado

Incluye una base ejecutable para:

- Login del repartidor.
- Consulta de pedidos asignados.
- Detalle de cliente, dirección, teléfono y total.
- Aceptar pedido.
- Aceptar un pedido e iniciar automÃ¡ticamente la ruta activa.
- Cierre automatico de la ruta cuando todos los pedidos son terminales y los cobros estan resueltos.
- Captura de ubicación mientras la ruta está activa.
- Confirmar entrega con GPS opcional.
- Registrar cobro en efectivo o Yape.
- Cola local de eventos y ubicaciones para sincronización offline con reintento.
- Registro opcional de token Firebase Cloud Messaging por dispositivo.
- Evidencia fotográfica de cobros Yape mediante multipart.
- Notificación persistente durante una ruta activa en Android.

## Jornada laboral del dispositivo corporativo

La app crea o recupera una jornada al iniciar sesiÃ³n. La ubicaciÃ³n continÃºa
aunque no haya pedidos, usa un servicio Android foreground con notificaciÃ³n
visible, y se detiene al cerrar sesiÃ³n o mediante el cierre administrativo.
La jornada solo existe cuando el tenant tiene activo
`business_turns.value = water_distributor`. Si el tenant no tiene la migraciÃ³n
nueva, la app conserva temporalmente el rastreo asociado a la ruta.

## Contrato usado

Los paths están centralizados en `lib/core/api_contract.dart` y únicamente usan el contrato `/api/delivery/*`:

```text
POST /api/login
GET  /api/delivery/orders
POST /api/delivery/orders/{id}/accept
POST /api/delivery/orders/{id}/status
POST /api/delivery/orders/{id}/deliver
POST /api/delivery/orders/{id}/payment
GET  /api/delivery/routes/current
POST /api/delivery/routes/{id}/location
POST /api/delivery/devices
POST /api/delivery/tracking/start
GET  /api/delivery/tracking/current
POST /api/delivery/tracking/locations
POST /api/delivery/tracking/stop
```

El backend local ya incluye estos endpoints. Antes de integrar contra producción hay que ejecutar las migraciones tenant y confirmar las respuestas con un usuario repartidor de prueba.

## Ejecutar

Requisitos:

- Flutter 3.41.x o compatible.
- Dart 3.11.x.
- Android SDK y un dispositivo Android.
- JDK compatible con la versión de Gradle generada por Flutter.

```powershell
cd C:\laragon\www\smartplus\mobile\delivery_app
flutter pub get
flutter analyze
flutter run --dart-define=SMARTPLUS_API_URL=https://demo.sistemax.net.pe
```

Para un entorno local, usa el host accesible desde el dispositivo Android; `localhost` dentro del teléfono no apunta a Laragon.

## Firebase y ubicación

Las notificaciones con la aplicación cerrada requieren configuración real de Firebase en cada entorno. No se debe reutilizar ni subir una clave de otro tenant.

1. Registrar el paquete Android `pe.smartplus.smartplus_delivery_app` en Firebase y descargar `google-services.json` en `android/app/`.
2. Crear una cuenta de servicio con permiso Firebase Cloud Messaging API y guardar el JSON fuera del repositorio.
3. Configurar en el servidor tenant `FCM_SERVICE_ACCOUNT_FILE=/ruta/segura/service-account.json`.
4. Reiniciar el worker/FPM después de cambiar la configuración y asignar un pedido de prueba.

Si falta cualquiera de esos archivos, la app puede sincronizar mientras está
abierta, pero Android no puede obtener el token FCM o el servidor no puede
enviar notificaciones cuando está cerrada. En producción SmartPlus ambos
artefactos ya están configurados: el APK usa el proyecto
`smartplus-delivery-production` y los contenedores montan la cuenta de servicio
fuera del repositorio.

Antes de publicar:

El build release se detiene si detecta el Firebase local `smartplus-delivery-local`.
Para una compilacion local explicitamente autorizada usa
`SMARTPLUS_ALLOW_LOCAL_FIREBASE_RELEASE=true`; para produccion reemplaza primero
`google-services.json` por el proyecto Firebase autorizado.

1. Agregar el `google-services.json` del proyecto Firebase autorizado.
2. Inicializar `Firebase.initializeApp()` en `main.dart` con la configuración del entorno.
3. Registrar el token FCM en `POST /api/delivery/devices`; la app lo actualiza cuando Firebase renueva el token.
4. La ruta activa usa `AndroidSettings` con notificación foreground persistente, intervalo de 30 segundos y filtro de 50 metros.
5. Revisar permisos Android 13/14+, política de privacidad y explicación dentro de la app.

La captura Android se inicia automáticamente al abrir la jornada al iniciar sesión, no al aceptar el primer pedido de la
ruta, muestra una notificación persistente y se detiene al cerrar la jornada o
cuando el backend la cierra. No existe una operación manual de **Iniciar ruta**
en el contrato actual. Usa un intervalo de 30 segundos y un filtro de 50 metros.
Android puede detener el proceso si el usuario fuerza el cierre o aplica
restricciones agresivas de batería; eso debe probarse en el dispositivo objetivo
antes de publicar.

Nota vigente: con la migraciÃ³n de jornadas aplicada, el servicio se inicia al
abrir la jornada y no espera la aceptaciÃ³n de un pedido. La ruta sigue siendo
automÃ¡tica, pero comparte la sesiÃ³n de jornada.

### Prueba local desde un telefono Android

Usa la IP LAN de la computadora y conserva el host del tenant. `localhost` dentro del telefono no apunta a Laragon:

```powershell
flutter run `
  --dart-define=SMARTPLUS_API_URL=http://192.168.100.9 `
  --dart-define=SMARTPLUS_TENANT_HOST=demo.smartplus.test
```

La IP debe cambiarse por la IP LAN real del equipo que ejecuta Laragon. No uses estos valores para el APK de produccion.

## Regla vigente de jornada

La jornada comienza automáticamente al iniciar sesión en el APK; no existe un
botón ni una pantalla manual de jornada para el motorizado. La ruta se crea o
reutiliza cuando acepta pedidos dentro de esa jornada. El servicio Android
foreground se detiene cuando administración cierra la jornada, al cerrar sesión
o cuando el backend informa que el giro de agua ya no está activo.

El bloque histórico inferior que describe iniciar el GPS al aceptar un pedido
queda supersedido por esta regla y se conserva solo como referencia de la ruta.

## Contrato vigente de ruta

La ruta se crea o reutiliza automÃ¡ticamente al aceptar pedidos dentro de la
jornada. El contrato actual no contiene `POST /api/delivery/routes/{id}/start`;
la ubicaciÃ³n pertenece a la sesiÃ³n de jornada y se detiene cuando la jornada
queda cerrada.

## Riesgos y pendientes

Nota actualizada: la cola offline ya cubre ubicaciones, aceptacion, estados, cobros y entregas; cada accion se reintenta al recuperar internet y conserva una clave de idempotencia.

Esta nota es la regla vigente: las acciones de pedido y cobro tambien pueden quedar
encoladas sin conexion. Antes de publicar, se debe configurar el tenant/Firebase,
usar la firma release y repetir la matriz en el dispositivo objetivo.

- El backend local incluye los endpoints `/api/delivery/*`; producción requiere despliegue y migración tenant antes de usar la app.
- Las respuestas JSON podrían requerir ajustar `DeliveryOrder.fromJson` cuando se confirme el contrato real.
- Las ubicaciones, acciones de pedido y cobros se almacenan en la cola nativa
  cuando no hay señal y se reintentan al recuperar internet. Cada evento usa
  una clave de idempotencia; si el servidor responde conflicto o rechazo, la
  aplicación conserva el resultado para revisión y no lo aplica nuevamente.
- FCM requiere configuración Firebase por entorno y permiso de notificaciones en Android 13+.
- La captura de ubicación puede fallar por permisos, GPS, ahorro de batería o cobertura; la entrega no debe bloquearse por eso.
- La evidencia Yape se toma con cámara y se envía como `evidence`; si el repartidor cancela la foto, el cobro no se envía.
- No se debe probar cobros reales ni publicar la aplicación fuera del entorno
  autorizado. La autenticación por tenant, autorización del repartidor y
  bloqueo server-side contra doble cobro/entrega ya están implementados; falta
  validarlos en el flujo E2E del dispositivo objetivo.

## Respuesta al giro desactivado

Si el servicio Android recibe `403` al consultar la ruta actual, lo interpreta
como `water_distributor` desactivado, detiene el servicio foreground y elimina
la cola local de ubicaciones de esa ruta. Los errores temporales de red se
conservan para reintento y no se confunden con una desactivación del giro.

## Estructura

```text
lib/
  core/       tema, cliente HTTP y contrato de endpoints
  modelos/    pedidos y estados
  pantallas/  login, pedidos, detalle y ruta
  servicios/  API, ubicación, FCM y cola offline
```
