# Drive (ride-hailing)

El módulo Drive agrega un flujo completo de **ride-hailing**: el usuario pide viaje como **pasajero** o entra en **modo conductor** para aceptar solicitudes cercanas. El mapa usa **Mapbox**; la ruta y el precio se calculan en el **servidor**, no en el cliente.

- **Tres backends.** Firebase (Cloud Functions + triggers), Supabase (Edge Function + SQL) y API REST (endpoints documentados en el README del patch) exponen el mismo contrato.
- **Sin roles fijos.** Cualquier usuario logueado puede pedir viaje. Quien completa el onboarding de conductor obtiene un perfil `drivers/{uid}` y puede quedar online.
- **Precio = estimación.** El monto en pantalla viene del backend (tarifa base + km + minuto). El kit **no procesa pagos**. Para cobrar de verdad, integra Stripe u otra pasarela en tu servidor.

> **Atención:** el precio mostrado es solo una estimación. El pago real exige integración futura (el kit ya trae [Stripe web](https://kasy.dev/docs/funcionalidades/stripe) como módulo separado).

## Activar / desactivar

Drive no viene en el modo Rápido por defecto. Para agregarlo en un proyecto existente:

```
kasy add drive
```

Pide el token Mapbox (`pk.…`) y los coeficientes de precio, escribe `MAPBOX_ACCESS_TOKEN` en `.env`, siembra `DRIVE_*` en el entorno del servidor e intenta guardar el token como secret (Firebase/Supabase).

```
kasy remove drive
```

Quita la feature, rutas, dependencia Mapbox y la flag `withDrive`.

Configura después en cualquier momento con `kasy configure` (sección "Drive (Ride-hailing)") o `kasy configure drive`.

## Configuración

### Cliente (Flutter)

| Variable | Dónde | Uso |
| --- | --- | --- |
| `MAPBOX_ACCESS_TOKEN` | `.env` / `--dart-define` | Tiles y mapa en la app |

Obtén en [account.mapbox.com](https://account.mapbox.com/access-tokens/). Usa token público (`pk.…`).

### Servidor (Firebase / Supabase)

| Variable | Dónde | Predeterminado |
| --- | --- | --- |
| `MAPBOX_ACCESS_TOKEN` | Secret | (obligatorio para rutas) |
| `DRIVE_BASE_FARE` | `functions/.env` o Supabase secret | `5` |
| `DRIVE_PRICE_PER_KM` | igual | `1.5` |
| `DRIVE_PRICE_PER_MIN` | igual | `0.25` |
| `DRIVE_CURRENCY` | igual | `USD` |
| `DRIVE_ALLOW_SELF_ACCEPT` | igual | *(omitido)* |

`DRIVE_ALLOW_SELF_ACCEPT=true` solo para **kit / dogfood** (Firebase): la misma cuenta puede pedir y aceptar su propio viaje. En produccion **no definas** esa variable (o usa cualquier valor distinto de `true`). Sin ella, el servidor rechaza el auto-aceptar y tambien oculta la propia solicitud en la lista del conductor.

En **Supabase**, el equivalente es la setting SQL `app.drive_allow_self_accept = 'on'` (migrations `20240101000018`–`20`). Sin eso, `accept_ride` y `find_nearby_ride_requests` bloquean/ocultan la propia solicitud.

En Firebase, `kasy configure drive` escribe los coeficientes en `functions/.env` y el token vía `firebase functions:secrets:set MAPBOX_ACCESS_TOKEN`. En Supabase, los mismos valores pasan a `supabase secrets set`.

En backend **API REST**, pon los coeficientes en el `.env` de tu servidor e implementa los endpoints del README del patch.

## Probar en debug

1. Ejecuta `kasy run` con `withDrive` activo.
2. Abre **Configuración** y toca el tile **Drive**.
3. En el menu lateral de Drive, activa **Prueba automatizada** (solo aparece si `ENV` no es `prod`).
4. Flujo con **una cuenta** (GPS simulado, backend real):
   - Ponte online como conductor (el GPS de prueba nace cerca del corredor de demo en Lima).
   - Cambia a pasajero: la home ya trae pickup y destino del corredor rellenados.
   - Pide el viaje → en la barra **Volver** (con la prueba activa el viaje **no** se cancela; saltas al conductor) → acepta la solicitud.
   - Tras aceptar, el auto camina solo por la polyline de Mapbox hasta el embarque (libera "Pasajero a bordo") y luego hasta el destino (libera "Completar").
   - El mismo Volver vale despues de aceptar / en curso: abre la navegacion del conductor de ese viaje en vez del dialogo de cancelar.
5. Desactiva **Prueba automatizada** al terminar. En produccion, Volver vuelve a pedir cancelar, el GPS vuelve a ser el del aparato, y el servidor bloquea el auto-aceptar sin `DRIVE_ALLOW_SELF_ACCEPT=true`.

Sin el interruptor (o con `ENV=prod`), el comportamiento seguro es el predeterminado: GPS real via Geolocator; Volver en el seguimiento abre el dialogo de cancelar solo mientras el viaje aun se puede cancelar (`requested` / `accepted`). Despues del embarque, Volver solo sale a la home de Drive y deja el viaje abierto.

### GPS en el navegador

Con **Prueba automatizada** activa, Drive **no** pide permiso de ubicacion del navegador: la posicion viene del simulador interno. Con el interruptor apagado, Drive pide ubicacion en el toque de **Ajustes → Drive** (gesto del usuario; Chrome suele exigirlo). Sin permiso, el mapa se queda en el placeholder hasta que el usuario lo permita. En web, si el GPS tarda, el pin aparece en cuanto el navegador entrega el fix.

Quieres probar proximidad **sin** el modo automatizado? DevTools → More tools → Sensors (o emulador Android/Xcode) e inyecta lat/lng cerca del pickup/destino.

### Radio de busqueda (conductor ↔ solicitud)

El conductor online solo ve (y el trigger solo notifica) solicitudes a hasta **5 km** del pickup (`SEARCH_RADIUS_METERS = 5000` en `functions/src/drive/drive_functions.ts` y `triggers.ts`; en Supabase, el mismo valor en el SQL `find_nearby_ride_requests`). Quieres otro radio para tu mercado? Cambia esa constante en los backends y haz deploy. Hoy **no** hay variable de entorno para eso.

Los CTAs del viaje activo ("Pasajero a bordo" / "Completar viaje") exigen al conductor a hasta **100 m** del embarque o del destino (solo en la app, no en el servidor).

Sin `MAPBOX_ACCESS_TOKEN`, las pantallas abren pero el mapa no carga tiles. Sin backend deployado, la solicitud de viaje falla al llamar al servidor.

## Donde ver en Firebase Console

Drive **no** guarda conductor ni viaje en la pestaña **Authentication**. Ahi solo esta la cuenta (anonima o con email) y el **User UID**. Los datos del modulo estan en **Firestore** del mismo proyecto Firebase que usa la app (`firebase_options` / `google-services.json`).

| Que buscas | Donde mirar | Nota |
| --- | --- | --- |
| Cuenta que pidio o acepto un viaje | **Authentication → Users** | Copia el **User UID** |
| Registro de conductor (vehiculo) | **Firestore → `drivers/{uid}`** | El `{uid}` coincide con Authentication |
| Viajes creados | **Firestore → `rides/`** | Creados por la callable `requestRide` (el cliente solo lee) |
| Token push del dispositivo | **Firestore → `users/{uid}/devices/`** | No es el nombre del conductor |

**Paso a paso:** abre el proyecto correcto → **Authentication** → copia el UID de la cuenta de prueba → **Firestore Database** → coleccion **`drivers`** → documento con id = ese UID (marca, modelo, color, placa, `status`). Viajes: coleccion **`rides`**.

**Prueba automatizada:** solo el GPS se simula en la app. Pedido, aceptacion y estado **siempre** pasan por el backend. Si `rides/` esta vacio, revisa: functions de Drive deployadas, `MAPBOX_ACCESS_TOKEN` en secrets, y si la region del app (`drive_api.dart`) coincide con la del deploy (`functionsRegion` en `kit_setup.json`).

En **Supabase**, el equivalente es el schema `drive` de las migrations (`drivers`, `rides`). En el console: **Table Editor** en esas tablas, con el `uid` del usuario autenticado.

## Flujo en la UI

| Pantalla | Ruta (nombre) | Rol |
| --- | --- | --- |
| Entrada de Drive | `drive` | Intro + elección de rol la primera vez; luego abre la home del rol guardado (el pasajero queda aquí) |
| Seguimiento | `driveRideTracking` | Pasajero: estado en vivo |
| Onboarding conductor | `driveDriverOnboarding` | Primer registro del vehículo |
| Home conductor | `driveDriverHome` | Online/offline, overlay de viajes cercanos |
| Viaje activo (conductor) | `driveDriverRide` | Navegar hasta completar |

El rol elegido (pasajero/conductor) queda guardado; el cambio se hace desde el menú lateral de Drive.

### Reglas clave del flujo

- **El conductor solo puede ponerse online con el vehículo registrado.** El switch abre el onboarding si falta.
- **Viajes cercanos = consulta en el servidor** (`listNearbyRideRequests`), radio por defecto de **5 km** desde el GPS publicado del conductor hasta el pickup (no es un listener abierto de todas las solicitudes).
- **Los botones del conductor se habilitan por proximidad GPS:** "Recogí al pasajero" solo se habilita a máximo 100 m del punto de embarque; "Completar viaje" solo a máximo 100 m del destino.
- **El pasajero solo puede cancelar mientras** el viaje está `requested` o `accepted`; después de abordar ya no es posible.
- **Durante un viaje el conductor no recibe nuevas solicitudes**; rechazar un viaje solo lo oculta para ese conductor.
- **El servidor vuelve a validar todo** en los 3 backends: aceptar solo un viaje `requested` (gana el primer conductor), iniciar/completar solo el conductor asignado y en orden, cancelar solo antes de un estado terminal.
- **El auto-aceptar (misma cuenta) esta bloqueado por defecto.** Solo funciona con `DRIVE_ALLOW_SELF_ACCEPT=true` en el servidor **y** el interruptor **Prueba automatizada** en el menu (dev). En una app publicada, deja ambos apagados.

Las notificaciones push se disparan cuando se crea un viaje o cambia de estado (la implementación varía por backend).

## Backend de referencia

| Backend | Implementación |
| --- | --- |
| Firebase | `functions/src/drive/drive_functions.ts` (callables) + `triggers.ts` |
| Supabase | Edge Function `request-ride` + migration `20240101000018_drive.sql` |
| API REST | `POST /drive/rides`, `POST /drive/rides/{id}/accept`, etc. |

Después de configurar secrets, haz deploy:

- **Firebase:** `kasy deploy`
- **Supabase:** `supabase db push` y `supabase functions deploy request-ride`

## Producción

Checklist mínimo:

1. Token Mapbox en el `.env` de la app **y** como secret del servidor
2. Coeficientes `DRIVE_*` ajustados a tu región/moneda
3. **Sin** `DRIVE_ALLOW_SELF_ACCEPT=true` (o quita la linea)
4. Interruptor **Prueba automatizada** apagado (desaparece con `ENV=prod`)
5. Functions o migration deployadas
6. Permiso de ubicación probado en iOS y Android reales
7. Estrategia de pago definida (Stripe u otro) si vas a cobrar

El kit no incluye reparto de pago conductor/plataforma. Eso queda en tu backend al integrar cobro.

## Quitar Drive

`kasy remove drive` apaga el módulo y quita código generado. Haz commit antes si personalizaste pantallas de Drive.
