# Drive (ride-hailing)

O módulo Drive adiciona um fluxo completo de **ride-hailing** ao app: o usuário pede corrida como **passageiro** ou entra no **modo motorista** para aceitar solicitações por perto. O mapa usa **Mapbox**; rota e preço são calculados no **servidor**, não no cliente.

- **Três backends.** Firebase (Cloud Functions + triggers), Supabase (Edge Function + SQL) e API REST (endpoints documentados no README do patch) expõem o mesmo contrato.
- **Sem papéis fixos.** Qualquer usuário logado pode pedir corrida. Quem preenche o onboarding de motorista ganha um perfil `drivers/{uid}` e pode ficar online.
- **Preço = estimativa.** O valor na tela vem do backend (tarifa base + km + minuto). O kit **não processa pagamento**. Para cobrar de verdade, integre Stripe ou outro gateway no seu servidor.

> **Atenção:** o preço exibido é apenas uma estimativa. Pagamento real exige integração futura (o kit já traz [Stripe web](https://kasy.dev/docs/funcionalidades/stripe) como módulo separado).

## Ligar / desligar

Drive não vem no modo Rápido por padrão. Para adicionar num projeto existente:

```
kasy add drive
```

O comando pergunta o token Mapbox (`pk.…`) e os coeficientes de preço, grava `MAPBOX_ACCESS_TOKEN` no `.env`, semeia `DRIVE_*` no ambiente do servidor e tenta gravar o token como secret (Firebase/Supabase).

```
kasy remove drive
```

Remove a feature, rotas, dependência Mapbox e flag `withDrive`.

Configure depois a qualquer momento com `kasy configure` (seção "Drive (Ride-hailing)") ou `kasy configure drive`.

## Configuração

### Cliente (Flutter)

| Variável | Onde | Uso |
| --- | --- | --- |
| `MAPBOX_ACCESS_TOKEN` | `.env` / `--dart-define` | Tiles e mapa no app |

Obtenha em [account.mapbox.com](https://account.mapbox.com/access-tokens/). Use token público (`pk.…`).

### Servidor (Firebase / Supabase)

| Variável | Onde | Padrão |
| --- | --- | --- |
| `MAPBOX_ACCESS_TOKEN` | Secret | (obrigatório para rotas) |
| `DRIVE_BASE_FARE` | `functions/.env` ou Supabase secret | `5` |
| `DRIVE_PRICE_PER_KM` | idem | `1.5` |
| `DRIVE_PRICE_PER_MIN` | idem | `0.25` |
| `DRIVE_CURRENCY` | idem | `USD` |
| `DRIVE_ALLOW_SELF_ACCEPT` | idem | *(omitido)* |

`DRIVE_ALLOW_SELF_ACCEPT=true` só no projeto de **kit / dogfood** (Firebase): deixa a mesma conta pedir e aceitar a própria corrida. Em produção **não defina** essa variável (ou use qualquer valor diferente de `true`). Sem ela, o servidor rejeita auto-aceite e também esconde o próprio pedido na lista do motorista.

No **Supabase**, o equivalente é a setting SQL `app.drive_allow_self_accept = 'on'` (migrations `20240101000018`–`20`). Sem isso, `accept_ride` e `find_nearby_ride_requests` bloqueiam/escondem o próprio pedido.

No Firebase, `kasy configure drive` grava os coeficientes em `functions/.env` e o token via `firebase functions:secrets:set MAPBOX_ACCESS_TOKEN`. No Supabase, os mesmos valores viram `supabase secrets set`.

No backend **API REST**, coloque os coeficientes no `.env` do seu servidor e implemente os endpoints descritos no README do patch.

## Testar em debug

1. Rode `kasy run` com `withDrive` ligado.
2. Abra **Configurações** e toque no tile **Drive**.
3. No menu lateral do Drive, ligue **Teste automatizado** (só aparece com `ENV` diferente de `prod`).
4. Fluxo em **uma conta** (GPS simulado, backend real):
   - Fique online como motorista (o GPS do teste nasce perto do corredor de demo em Lima).
   - Alterne para passageiro: a home já vem com pickup e destino do corredor preenchidos.
   - Peça a corrida → no app bar **Voltar** (com o teste ligado a corrida **não** cancela; você vai para o motorista) → aceite o pedido.
   - Depois do aceite, o carro anda sozinho na polyline do Mapbox até o embarque (libera "Passageiro a bordo") e depois até o destino (libera "Concluir").
   - O mesmo Voltar vale depois de aceitar / em andamento: abre a navegação do motorista daquela corrida em vez do diálogo de cancelar.
5. Desligue **Teste automatizado** quando terminar. Em produção o Voltar volta a exigir cancelamento, o GPS volta a ser o do aparelho, e o servidor bloqueia auto-aceite sem `DRIVE_ALLOW_SELF_ACCEPT=true`.

Sem o interruptor (ou com `ENV=prod`), o comportamento seguro é o padrão: GPS real via Geolocator, Voltar na tela de acompanhamento abre o diálogo de cancelar só enquanto a corrida ainda pode ser cancelada (`requested` / `accepted`). Depois do embarque, Voltar só sai para a home do Drive e mantém a corrida aberta.

### GPS no browser

Com **Teste automatizado** ligado, o Drive **não** pede permissão de localização do browser: a posição vem do simulador interno. Com o interruptor desligado, o Drive pede localização no toque de **Configurações → Drive** (gesto do usuário; o Chrome costuma exigir isso). Sem permissão, o mapa fica no placeholder até o usuário liberar. Em web, se o GPS demorar, o pin aparece assim que o browser entregar o fix.

Quer testar proximidade **sem** o modo automatizado? DevTools → More tools → Sensors (ou emulador Android/Xcode) e injete lat/lng perto do pickup/destino.

### Raio de busca (motorista ↔ pedido)

O motorista online só vê (e o trigger só notifica) pedidos a até **5 km** do pickup (`SEARCH_RADIUS_METERS = 5000` em `functions/src/drive/drive_functions.ts` e `triggers.ts`; no Supabase, o mesmo valor na SQL `find_nearby_ride_requests`). Quer outro raio pro seu mercado? Altere essa constante nos backends e faça deploy. Hoje **não** há variável de ambiente pra isso.

Os CTAs da corrida ativa ("Passageiro a bordo" / "Concluir corrida") exigem o motorista a até **100 m** do embarque ou do destino (só no app, não no servidor).

Sem `MAPBOX_ACCESS_TOKEN`, as telas abrem mas o mapa não carrega tiles. Sem deploy do backend, a solicitação de corrida falha ao chamar o servidor.

## Onde ver no Firebase Console

O Drive **não** grava motorista nem corrida na aba **Authentication**. Lá só existe a conta (anônima ou com email) e o **User UID**. Os dados do módulo ficam no **Firestore** do mesmo projeto Firebase que o app usa (`firebase_options` / `google-services.json`).

| O que você procura | Onde olhar | Observação |
| --- | --- | --- |
| Conta que pediu ou aceitou corrida | **Authentication → Users** | Copie o **User UID** |
| Cadastro de motorista (veículo) | **Firestore → `drivers/{uid}`** | O `{uid}` é o mesmo da Authentication |
| Corridas criadas | **Firestore → `rides/`** | Criadas pela callable `requestRide` (cliente só lê) |
| Token de push do aparelho | **Firestore → `users/{uid}/devices/`** | Não é nome do motorista |

**Passo a passo:** abra o projeto certo no console → **Authentication** → copie o UID da conta de teste → **Firestore Database** → coleção **`drivers`** → documento com id = esse UID (marca, modelo, cor, placa, `status`). Corridas: coleção **`rides`**.

**Teste automatizado:** só o GPS é simulado no app. Pedido, aceite e status **sempre** passam pelo backend. Se `rides/` estiver vazio, confira: functions do Drive deployadas, `MAPBOX_ACCESS_TOKEN` no secret, e se a região do app (`drive_api.dart`) bate com a região do deploy (`functionsRegion` no `kit_setup.json`).

No **Supabase**, o equivalente é o schema `drive` nas migrations (`drivers`, `rides`). No console: **Table Editor** nessas tabelas, com o `uid` do usuário autenticado.

## Fluxo na UI

| Tela | Rota (nome) | Papel |
| --- | --- | --- |
| Entrada do Drive | `drive` | Intro + escolha de papel na primeira vez; depois abre a home do papel salvo (passageiro fica aqui) |
| Acompanhar | `driveRideTracking` | Passageiro: status em tempo real |
| Onboarding motorista | `driveDriverOnboarding` | Primeiro cadastro do veículo |
| Home motorista | `driveDriverHome` | Online/offline, overlay de corridas próximas |
| Corrida ativa (motorista) | `driveDriverRide` | Navegação até concluir |

O papel escolhido (passageiro/motorista) fica salvo; a troca é pelo menu lateral do Drive.

### Regras importantes do fluxo

- **Motorista só fica online com o veículo cadastrado.** O switch abre o onboarding se faltar.
- **Corridas próximas = consulta no servidor** (`listNearbyRideRequests`), com raio padrão de **5 km** do GPS publicado do motorista até o pickup (não é listener aberto em todos os pedidos).
- **Botões do motorista são liberados por proximidade GPS:** "Embarquei o passageiro" só habilita a até 100 m do ponto de embarque; "Concluir corrida" só a até 100 m do destino.
- **Passageiro só cancela enquanto** a corrida está `requested` ou `accepted`; depois do embarque não dá mais.
- **Durante uma corrida o motorista não recebe novas solicitações**; recusar uma corrida só a esconde para aquele motorista.
- **O servidor valida tudo de novo** nos 3 backends: aceitar só corrida `requested` (o primeiro motorista ganha), iniciar/concluir só pelo motorista designado e na ordem certa, cancelar só antes de terminar.
- **Auto-aceite (mesma conta) é bloqueado por padrão.** Só libera com `DRIVE_ALLOW_SELF_ACCEPT=true` no servidor **e** o interruptor **Teste automatizado** no menu (dev). Em app publicado, deixe os dois desligados.

Push notifications disparam quando uma corrida é criada ou muda de status (implementação varia por backend).

## Backend de referência

| Backend | Implementação |
| --- | --- |
| Firebase | `functions/src/drive/drive_functions.ts` (callables) + `triggers.ts` |
| Supabase | Edge Function `request-ride` + migration `20240101000017_drive.sql` |
| API REST | `POST /drive/rides`, `POST /drive/rides/{id}/accept`, etc. |

Depois de configurar secrets, faça deploy:

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

## Produção

Checklist mínimo:

1. Token Mapbox no `.env` do app **e** como secret do servidor
2. Coeficientes `DRIVE_*` ajustados à sua região/moeda
3. **Sem** `DRIVE_ALLOW_SELF_ACCEPT=true` (ou remova a linha)
4. Interruptor **Teste automatizado** desligado (some com `ENV=prod`)
5. Functions ou migration deployadas
6. Permissão de localização testada em iOS e Android reais
7. Estratégia de pagamento definida (Stripe ou outro), se for cobrar

O kit não inclui split de pagamento motorista/plataforma. Isso fica no seu backend quando integrar cobrança.

## Removendo Drive

`kasy remove drive` desliga o módulo e remove código gerado. Faça commit antes se tiver customizado telas do Drive.
