# EdicaoRota — Timer de Sessão do Motorista

**Data:** 2026-05-26
**Status:** Aprovado para implementação

## Contexto

A página pública do motorista (`/edicao-rota/<token>`) permite reordenar paradas e enviar a rota para aprovação. O gestor configura um tempo máximo de edição (campo "Tempo (minutos)" no modal "Liberar rota para motorista") que é salvo em `edicoes_rota.tempo_minutos` e `edicoes_rota.expira_em`. Esse valor já alimenta o countdown "17 MIN" exibido no painel do gestor em Central/central.php.

**Problema:** a sessão do motorista não expira automaticamente — o link permanece acessível mesmo após `expira_em`. Não há feedback visual do tempo restante na tela do motorista.

## Objetivo

1. Exibir um timer de contagem regressiva na tela de edição da rota, sincronizado com `expira_em`.
2. Ao zerar: invalidar a sessão no front-end e no back-end, redirecionando para uma tela de "Sessão Expirada".
3. Garantir que o back-end rejeite qualquer acesso à view de edição após `expira_em`.

## Arquivos Afetados

```
componentes/Missoes/Logistica/sinais/Edicaorota/
  Edicaorota.php                   ← mudanças no controller
  html/editar_rota.php             ← timer widget + JS
  html/_layout.php                 ← sem mudança (timer fica em editar_rota)
  html/sessao_expirada.php         ← nova view
```

## Design

### Seção 1 — Fluxo de dados

`Edicaorota.php::telaMotorista()` já carrega `$edicao` via `findByToken($token)`. O campo `expira_em` (TIMESTAMP) existe na tabela mas não é passado para a view de edição.

**Mudança:** adicionar `'expira_em' => $edicao->expira_em` ao array de vars enviado para `html/editar_rota`. A view emite:

```php
window.__rotaExpiraEm = <?= json_encode($expiraEm) ?>;
// ex: "2026-05-26T15:30:00"
```

Se `expira_em` for `null` (edições antigas), a variável é emitida como `null` e o timer não aparece.

### Seção 2 — Mudanças no back-end

#### 2a. Checagem de expiração em `telaMotorista()`

Antes do bloco `if ($this->sessaoMotoristaValida($token))`, adicionar:

```php
if (!empty($edicao->expira_em) && strtotime((string) $edicao->expira_em) < time()) {
    unset($_SESSION['edicao_rota_' . $token]);
    return $this->renderMotorista('html/sessao_expirada', [
        'token'        => $token,
        'status_label' => $statusLabel,
        'status_css'   => $statusCss,
        'expira_em'    => $edicao->expira_em,
    ], null);
}
```

Isso garante que, mesmo com sessão PHP ativa, o motorista não consiga acessar o formulário de edição após o prazo.

#### 2b. Nova ação `expirar_sessao`

Em `start()`, antes do bloco `elseif ('submeter_rota')`:

```php
elseif ($acao === 'expirar_sessao') {
    unset($_SESSION['edicao_rota_' . $token]);
    while (ob_get_level() > 0) { ob_end_clean(); }
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode(['ok' => true]);
    exit;
}
```

Chamada pelo front-end via `fetch` quando o timer zera. Não precisa de validação extra: limpar uma sessão já expirada é idempotente.

#### 2c. Nova view `html/sessao_expirada.php`

Usa `_layout.php`. Exibe:
- Badge de status atual da edição (ex: "Aguardando motorista")
- Mensagem: "O tempo para editar a rota expirou."
- Timestamp de expiração formatado (`$expira_em`)
- Sem formulário, sem ação, sem botão

### Seção 3 — Componente de timer (front-end em `editar_rota.php`)

#### 3a. PHP — variável de contexto

```php
$expiraEm = isset($expira_em) && $expira_em ? (string) $expira_em : null;
```

Emitida como `window.__rotaExpiraEm` antes dos scripts existentes.

#### 3b. HTML — widget do timer

Inserido no topo de `editar_rota.php`, antes do `.paineis-tabs`:

```html
<div id="rotaTimerWrap" class="rota-timer-wrap oculto" role="timer" aria-live="off">
  <span class="rota-timer-icone">⏱</span>
  <span id="rotaTimerDisplay" class="rota-timer-display">--:--</span>
</div>
```

Posicionado com `position: sticky; top: 0; z-index: 10` para ser visível durante o scroll.

**Estados visuais:**
- `rota-timer-verde` — > 10 min restantes
- `rota-timer-amarelo` — 2–10 min
- `rota-timer-vermelho` — < 2 min (com animação `pulse`)
- `rota-timer-expirado` — ao zerar (laranja, parado)

#### 3c. JS — lógica do countdown

```
setInterval (1s):
  restante = Math.floor((expiraMs - Date.now()) / 1000)
  se restante <= 0 → disparar expiração
  senão → atualizar display MM:SS + classe de cor
```

**Fluxo de expiração:**
1. Parar `setInterval`
2. Atualizar widget para estado `expirado`
3. Mostrar overlay escuro com mensagem "Tempo esgotado, aguarde..."
4. `fetch` POST `acao=expirar_sessao` + `token`
5. `window.location.reload()` (a page agora renderiza `sessao_expirada`)

Se `window.__rotaExpiraEm` for `null` ou o parse falhar: timer não aparece, sem erro.

## Fora do Escopo

- Extensão do prazo pelo gestor (não solicitado)
- Notificação por push/SMS ao motorista sobre tempo restante
- Animação de transição entre a tela de edição e a de expirada
- Alteração do `OtpConfig::registrar` (validade do código OTP é separada do timer de sessão)

## Critérios de Aceitação

1. Timer aparece na tela de edição quando `expira_em` está definido, contando regressivamente em MM:SS
2. Cores mudam conforme urgência (verde → amarelo → vermelho pulsante)
3. Ao zerar: overlay aparece, sessão é invalidada via AJAX, página recarrega mostrando "Sessão Expirada"
4. Se motorista tentar acessar a URL após expiração (sem JS): back-end detecta `expira_em < now()` e renderiza `sessao_expirada` diretamente
5. Se `expira_em` for `null` (edições antigas): timer não aparece, comportamento atual mantido
6. O timer é sincronizado com o mesmo `expira_em` que alimenta o countdown do gestor em Central
