# Edição de Rota — substituir Maps JS pago por Embed API gratuita

**Data:** 2026-05-20
**Sinal:** `componentes/Missoes/Logistica/sinais/Edicaorota`
**Tela afetada:** `editar_rota.php` (drag-drop de paradas com preview de rota)

## Contexto

A tela `editar_rota.php` hoje carrega o **Google Maps JavaScript SDK** direto no browser e usa o **Directions API** para desenhar a rota a cada reordenação de parada. As duas APIs são cobradas:

- **Maps JavaScript API (Dynamic Maps)** — ~US$ 7 / 1.000 loads acima do free tier (28.5k/mês)
- **Directions API** — US$ 5–10 / 1.000 requests acima do free tier (~40k/mês), e cada drag dispara uma chamada (com debounce de 200ms, mas ainda assim N chamadas por edição)

O custo escala linearmente com o uso operacional. Para uma operação que reedita rotas várias vezes ao dia, isso vira faturamento mensal recorrente que não traz valor incremental — o motorista só precisa visualizar a rota, sem interação custosa no mapa.

## Objetivo

Eliminar o consumo de APIs pagas do Google Maps Platform na tela de edição de rota, mantendo:

1. Visualização inline da rota com a ordem atual das paradas.
2. Link rápido para abrir a rota no Google Maps nativo (app no celular, web no desktop) — com voz/navegação para o motorista.
3. Atualização automática da visualização conforme o usuário reordena paradas.

## Decisão arquitetural

Substituir o SDK + Directions por:

- **`<iframe>` com Maps Embed API** (gratuita, sem cota cobrada por Google) para o preview inline.
- **`<a target="_blank">` com Google Maps URLs** (sem chave, sem cobrança) para o botão "Abrir no Google Maps".

JavaScript da página passa a apenas montar duas strings de URL e atualizar `iframe.src` + `a.href` com debounce de 500ms a cada reorder.

## Arquitetura nova

```
Browser
   │
   ├── carrega editar_rota.php (HTML + JS leve, sem SDK do Maps)
   │
   ├── reorder de parada (drag-drop existente)
   │     │
   │     └── debounce 500ms → reconstrói embedUrl + appUrl
   │           ├── iframe.src = embedUrl
   │           └── a#abrirNoApp.href = appUrl
   │
   └── iframe carrega de google.com/maps/embed/v1/directions
         (Google renderiza, sem custo para nós)
```

Componentes:

| Antes | Depois |
|---|---|
| `<script src="maps.googleapis.com/maps/api/js?...">` (Maps JS SDK) | _removido_ |
| `<div id="mapaRota">` + `new google.maps.Map(...)` | `<iframe id="mapaRota" loading="lazy">` |
| `DirectionsService().route(...)` (pago) | URL Embed API montada em JS (grátis) |
| `gm.Marker`, `gm.Polyline`, `gm.DirectionsRenderer` | _removidos_ — Google renderiza dentro do iframe |
| Fallback `Polyline` reta quando Directions falha | _não aplicável_ — Embed API entrega ou não entrega |
| Limite de 25 waypoints (Directions standard) | 20 waypoints (Embed API) — ver fallback abaixo |

## Componentes da UI

```
┌──────────────────────────────────────┐
│  [Lista de paradas drag-drop]        │  ← inalterado
├──────────────────────────────────────┤
│  <iframe id="mapaRota" …>            │
│    Mapa renderizado pelo Google      │
│    via Maps Embed API (grátis)       │
│  </iframe>                           │
├──────────────────────────────────────┤
│  [🗺️ Abrir no Google Maps]           │  ← <a id="abrirNoApp" target="_blank">
│  Aviso de paradas sem coordenada     │  ← mantém o <p id="avisoMapa">
└──────────────────────────────────────┘
```

## URLs

### Embed (iframe, gratuita)

```
https://www.google.com/maps/embed/v1/directions
  ?key={KEYMAPSGOOGLE}
  &origin={origem.lat},{origem.lng}
  &destination={ultima.lat},{ultima.lng}
  &waypoints={lat,lng|lat,lng|…}
  &mode=driving
```

Quando a lista tem 0 paradas com coordenada, troca para modo `place`:

```
https://www.google.com/maps/embed/v1/place
  ?key={KEYMAPSGOOGLE}
  &q={origem.lat},{origem.lng}
```

### Abrir no app (sem chave)

```
https://www.google.com/maps/dir/?api=1
  &origin={origem.lat},{origem.lng}
  &destination={ultima.lat},{ultima.lng}
  &waypoints={lat,lng|lat,lng|…}
  &travelmode=driving
```

No mobile, o Google abre direto o app nativo (deep link). No desktop, abre `maps.google.com` em nova aba.

## Edge cases

| Situação | Iframe | Botão "Abrir no app" |
|---|---|---|
| Galpão sem coordenada | Escondido, aviso "Mapa indisponível — galpão sem coordenada cadastrada" | Desabilitado |
| 0 paradas com coordenada | Modo `place` mostrando só o galpão | Desabilitado |
| 1 parada | `directions` com origin + destination, sem waypoints | Idem |
| 2–20 paradas | `directions` com origin + (N-1) waypoints + última como destination | Idem |
| 21+ paradas | `directions` com primeiras 20 + aviso "Mapa mostra as 20 primeiras paradas. Use 'Abrir no Google Maps' para a rota completa." | Inclui todas (até o limite prático do Google Maps URLs, ~25) |
| Paradas individuais sem coordenada | Filtradas + aviso "X paradas não aparecem no mapa (sem coordenada cadastrada)" — mantém comportamento atual | Idem (também filtra) |

## Mudanças concretas

### `editar_rota.php`

1. Trocar `<div id="mapaRota">` por `<iframe id="mapaRota" loading="lazy" referrerpolicy="no-referrer-when-downgrade" allowfullscreen>` no bloco HTML do mapa.
2. Adicionar `<a id="abrirNoApp" class="…" target="_blank" rel="noopener">Abrir no Google Maps</a>` abaixo do iframe.
3. Substituir o bloco `<script>` (linhas ~157-357) por uma versão enxuta (~80 linhas) que:
   - Expõe `KEYMAPSGOOGLE` via PHP no JS: `var KEYMAPS = <?= json_encode(KEYMAPSGOOGLE) ?>;` (reaproveita o padrão já em uso na linha 85 com `window.__rotaOrigem`).
   - Reaproveita `window.__rotaOrigem` (já injetado na linha 85) para coordenadas do galpão — sem mudar a injeção PHP.
   - Função `montarUrls()` que percorre `<li>` da lista, filtra paradas sem coord, monta as duas URLs (embed + dir).
   - Função `redesenhar()` que chama `montarUrls()` e atualiza `iframe.src` + `a.href` + texto do `<p id="avisoMapa">`.
   - `window.__rotaMapaRedesenhar` com debounce de 500ms — **mesma interface pública do código atual**, então o módulo de reorder não precisa mudar.
4. Remover a linha `$comMapa = true;` (linha 14) — flag não tem mais função.

### `_layout.php`

5. Remover o bloco `<?php if ($comMapa && defined('KEYMAPSGOOGLE')): ?> <script async defer src="maps.googleapis.com/…"> <?php endif ?>` (linhas 221-223). Outras views que incluem `_layout.php` (`solicitar_codigo.php`, `validar_codigo.php`, `status.php`) não usam mapa, então não perdem nada.
6. Remover a variável `$comMapa` e seu docstring (linhas 9 e 15). Verificação prévia: a flag só é referenciada no bloco do `<script>` (linha 221) — o "split layout CSS" mencionado no docstring antigo não é controlado por essa variável. Sem caller na codebase além das 4 views do Edicaorota, e nenhuma delas precisa do flag depois desta mudança.

### Configuração GCP

7. Garantir no Google Cloud Console (projeto que detém `KEYMAPSGOOGLE`):
   - **Maps Embed API** habilitada.
   - **Maps JavaScript API** e **Directions API** podem ser desabilitadas depois do deploy (defesa em profundidade — evita que código novo volte a usar acidentalmente).
   - Restrição da chave por HTTP referrer mantida/configurada (a chave fica exposta no `<iframe src>`).

## Pré-condições para implementação

- Maps Embed API habilitada no projeto GCP que detém `KEYMAPSGOOGLE`.
- Confirmado que `_layout.php` é incluído só pelas 4 views do Edicaorota (já verificado: `solicitar_codigo.php`, `validar_codigo.php`, `editar_rota.php`, `status.php`).

## Testes

Como esta tela é HTML/JS sem lógica de domínio nova, não há teste unitário a escrever. Verificação é manual:

- [ ] Galpão sem coord → iframe escondido, botão desabilitado, aviso correto.
- [ ] 0 paradas → iframe mostra o galpão (modo `place`).
- [ ] 1 parada → iframe mostra origem → destino, sem waypoints.
- [ ] 5 paradas → iframe mostra rota completa.
- [ ] 25 paradas → iframe mostra "primeiras 20" com aviso, botão abre app com todas.
- [ ] Reorder por drag → iframe atualiza após 500ms.
- [ ] Botão "Abrir no Google Maps" no celular → abre app nativo.
- [ ] Botão no desktop → abre `maps.google.com` em nova aba.
- [ ] Paradas sem coord → contadas no aviso, ausentes no mapa.

## Trade-offs

**Ganhos**
- Custo direto zero (Embed API e Maps URLs são gratuitos sob a política do Google Maps Platform).
- Página fica mais leve (não baixa ~300KB do SDK).
- Menos código JS (~250 linhas a menos em `editar_rota.php`).

**Perdas**
- Estilo escuro customizado do mapa some — o iframe usa o estilo padrão do Google.
- Sem fallback de polyline reta quando o Google não encontra rota — o iframe mostra erro do próprio Google.
- Limite de waypoints cai de 25 para 20 no preview inline (botão "Abrir no app" mantém ~25).
- Pequeno piscar do iframe ao trocar `src` durante reorder (mitigado pelo debounce de 500ms).

## Fora de escopo

- Substituir Maps em outras telas do Genesis que eventualmente usem (esta spec cobre só `Edicaorota`).
- Cachear ou armazenar rotas previamente calculadas.
- Adicionar polyline custom em cima do iframe (Google não permite).
- Mudar comportamento do drag-drop de paradas em si.
