# EdicaoRota — Timer de Sessão do Motorista — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Exibir um countdown sincronizado com `expira_em` na tela do motorista, invalidar a sessão ao zerar e redirecionar para "Sessão Expirada".

**Architecture:** `expira_em` já existe em `edicoes_rota`. O controller passa o valor para a view de edição como variável JS. O JS faz countdown local; ao zerar chama `acao=expirar_sessao` (nova ação JSON) que limpa `$_SESSION`, depois recarrega a página — o back-end agora detecta `expira_em < now()` e renderiza a view `sessao_expirada` diretamente.

**Tech Stack:** PHP 8, JavaScript vanilla (sem dependências), layout `_layout.php` existente.

---

## Arquivos

| Ação | Arquivo |
|------|---------|
| Modificar | `componentes/Missoes/Logistica/sinais/Edicaorota/Edicaorota.php` |
| Criar | `componentes/Missoes/Logistica/sinais/Edicaorota/html/sessao_expirada.php` |
| Modificar | `componentes/Missoes/Logistica/sinais/Edicaorota/html/editar_rota.php` |

---

## Task 1: Nova ação `expirar_sessao` no controller

**Arquivo:** `componentes/Missoes/Logistica/sinais/Edicaorota/Edicaorota.php`

- [ ] **Step 1: Adicionar o branch `expirar_sessao` em `start()`**

Localizar o bloco (linhas 140-147):
```php
            } elseif ($acao === 'reordenar_direto') {
                $novaRota = json_decode((string) ($_POST['nova_rota'] ?? '[]'), true) ?: [];
                $resultado = $this->processarReordenarDireto($token, $novaRota);
                while (ob_get_level() > 0) { ob_end_clean(); }
                header('Content-Type: application/json; charset=utf-8');
                echo json_encode($resultado);
                exit;
            }
```

Substituir por:
```php
            } elseif ($acao === 'reordenar_direto') {
                $novaRota = json_decode((string) ($_POST['nova_rota'] ?? '[]'), true) ?: [];
                $resultado = $this->processarReordenarDireto($token, $novaRota);
                while (ob_get_level() > 0) { ob_end_clean(); }
                header('Content-Type: application/json; charset=utf-8');
                echo json_encode($resultado);
                exit;
            } elseif ($acao === 'expirar_sessao') {
                if (session_status() === PHP_SESSION_NONE) { session_start(); }
                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;
            }
```

- [ ] **Step 2: Testar a ação manualmente via curl**

```bash
# Substitua <TOKEN> por um token válido de uma edição aberta no banco.
curl -s -X POST \
  "http://dev-galpaotms.goldieapp.com.br/edicao-rota/<TOKEN>" \
  -d "acao=expirar_sessao&token=<TOKEN>" \
  -H "Content-Type: application/x-www-form-urlencoded"
```

Saída esperada: `{"ok":true}`

- [ ] **Step 3: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/Edicaorota.php
git commit -m "feat(edicaorota): add expirar_sessao JSON action"
```

---

## Task 2: Check de expiração no controller + passar `expira_em` para a view

**Arquivo:** `componentes/Missoes/Logistica/sinais/Edicaorota/Edicaorota.php`

- [ ] **Step 1: Adicionar check de expiração antes do bloco de sessão em `telaMotorista()`**

Localizar (linha ~234):
```php
        // Pode editar — depende de ter validado OTP.
        if ($this->sessaoMotoristaValida($token)) {
```

Substituir por:
```php
        // Pode editar — mas primeiro checa se o prazo já expirou.
        if (!empty($edicao->expira_em) && strtotime((string) $edicao->expira_em) < time()) {
            if (session_status() === PHP_SESSION_NONE) { session_start(); }
            unset($_SESSION['edicao_rota_' . $token]);
            return $this->renderMotorista('html/sessao_expirada', [
                'token'        => $token,
                'status_label' => $statusLabel,
                'status_css'   => $statusCss,
                'expira_em'    => (string) $edicao->expira_em,
            ], null);
        }

        // Pode editar — depende de ter validado OTP.
        if ($this->sessaoMotoristaValida($token)) {
```

- [ ] **Step 2: Passar `expira_em` para a view `editar_rota`**

Localizar (linhas 267-274):
```php
            return $this->renderMotorista('html/editar_rota', [
                'token'          => $token,
                'rota_atual'     => $rotaAtual,
                'origem'         => $origem,
                'niveis_cubagem' => $niveisCubagem,
                'partida'        => $partida,
                'resumo'         => $resumo,
            ], $mensagem);
```

Substituir por:
```php
            return $this->renderMotorista('html/editar_rota', [
                'token'          => $token,
                'rota_atual'     => $rotaAtual,
                'origem'         => $origem,
                'niveis_cubagem' => $niveisCubagem,
                'partida'        => $partida,
                'resumo'         => $resumo,
                'expira_em'      => isset($edicao->expira_em) ? (string) $edicao->expira_em : null,
            ], $mensagem);
```

- [ ] **Step 3: Verificar que uma edição com `expira_em` no passado não carrega mais o formulário**

Abrir no browser a URL de um token com `expira_em` no passado. A página deve mostrar "Sessão Expirada" (Task 3 precisar existir primeiro — faça Task 3 antes de testar esta step).

- [ ] **Step 4: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/Edicaorota.php
git commit -m "feat(edicaorota): block expired sessions server-side and pass expira_em to edit view"
```

---

## Task 3: View `html/sessao_expirada.php`

**Arquivo:** `componentes/Missoes/Logistica/sinais/Edicaorota/html/sessao_expirada.php` *(novo)*

- [ ] **Step 1: Criar o arquivo**

```php
<?php
/**
 * Tela exibida quando o prazo de edição da rota expirou.
 *
 * Variáveis esperadas:
 *   $token:        string
 *   $status_label: string
 *   $status_css:   string
 *   $expira_em:    string|null  (YYYY-MM-DD HH:MM:SS)
 */
$statusLabel = $status_label ?? '';
$statusCss   = $status_css   ?? 'draft';
$expiraEm    = $expira_em    ?? null;

$expiraFmt = '';
if ($expiraEm) {
    try {
        $dt = new \DateTime($expiraEm);
        $diasSemana = ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sáb'];
        $expiraFmt = $diasSemana[(int) $dt->format('w')]
                   . ' ' . $dt->format('d/m')
                   . ' às ' . $dt->format('H:i');
    } catch (\Throwable $e) {
        $expiraFmt = '';
    }
}

ob_start();
?>
<div class="card sessao-expirada-card">
  <div class="card-secao-label">
    <i class="card-secao-icone" aria-hidden="true">⏱</i>
    <span class="card-secao-texto">Sessão Encerrada</span>
  </div>

  <div class="sessao-expirada-icone" aria-hidden="true">⏰</div>
  <p class="sessao-expirada-titulo">Tempo para editar a rota expirou</p>
  <p class="sessao-expirada-sub">
    O prazo de edição desta rota <?= $expiraFmt ? 'terminou em <strong>' . htmlspecialchars($expiraFmt) . '</strong>' : 'já terminou' ?>.
    Nenhuma alteração pode ser feita.
  </p>

  <?php if ($statusLabel): ?>
  <div class="sessao-expirada-status">
    <span class="sessao-expirada-rotulo">Status da edição</span>
    <span class="badge <?= htmlspecialchars($statusCss) ?>"><?= htmlspecialchars($statusLabel) ?></span>
  </div>
  <?php endif; ?>
</div>

<style>
  .sessao-expirada-card { text-align: center; padding: 28px 20px; }
  .sessao-expirada-icone {
    font-size: 3rem;
    margin: 12px 0 8px;
    opacity: 0.85;
  }
  .sessao-expirada-titulo {
    margin: 0 0 10px;
    font-size: 1.2rem;
    font-weight: 700;
    color: #ffffff;
  }
  .sessao-expirada-sub {
    margin: 0 0 18px;
    color: var(--muted);
    font-size: 0.95rem;
    line-height: 1.5;
  }
  .sessao-expirada-sub strong { color: #ffffff; }
  .sessao-expirada-status {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 10px 16px;
    border-radius: 10px;
    background: rgba(139, 92, 246, 0.08);
    border: 1px solid var(--border);
    font-size: 0.88rem;
  }
  .sessao-expirada-rotulo {
    color: var(--muted);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 0.7rem;
  }
</style>
<?php
$conteudo = ob_get_clean();
$titulo   = 'Sessão Expirada';
include __DIR__ . '/_layout.php';
```

- [ ] **Step 2: Verificar visualmente no browser**

Acesse um token com `expira_em` no passado. A página deve exibir:
- Header "Edição de Rota"
- Card com ícone ⏰, título "Tempo para editar a rota expirou"
- Data/hora formatada
- Badge de status

- [ ] **Step 3: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/html/sessao_expirada.php
git commit -m "feat(edicaorota): add sessao_expirada view"
```

---

## Task 4: Timer widget em `editar_rota.php`

**Arquivo:** `componentes/Missoes/Logistica/sinais/Edicaorota/html/editar_rota.php`

- [ ] **Step 1: Adicionar leitura da variável `$expiraEm` no bloco de variáveis do topo**

Localizar (linhas 13-18):
```php
$token         = $token ?? '';
$rotaAtual     = $rota_atual ?? [];
$origem        = $origem ?? null;
$niveisCubagem = $niveis_cubagem ?? [];
$partida       = $partida ?? null;
$resumo        = $resumo ?? null;
```

Substituir por:
```php
$token         = $token ?? '';
$rotaAtual     = $rota_atual ?? [];
$origem        = $origem ?? null;
$niveisCubagem = $niveis_cubagem ?? [];
$partida       = $partida ?? null;
$resumo        = $resumo ?? null;
$expiraEm      = isset($expira_em) && $expira_em ? (string) $expira_em : null;
```

- [ ] **Step 2: Adicionar o widget HTML e CSS antes das tabs**

Localizar a primeira linha de HTML do conteúdo:
```php
ob_start();
?>
<div class="paineis-tabs" role="tablist" aria-label="Alternar entre lista e mapa">
```

Substituir por:
```php
ob_start();
?>
<?php if ($expiraEm): ?>
<div id="rotaTimerWrap" class="rota-timer-wrap" role="timer" aria-live="polite" aria-label="Tempo restante para editar a rota">
  <span class="rota-timer-icone" aria-hidden="true">⏱</span>
  <span id="rotaTimerDisplay" class="rota-timer-display">--:--</span>
</div>
<?php endif; ?>

<div class="paineis-tabs" role="tablist" aria-label="Alternar entre lista e mapa">
```

- [ ] **Step 3: Adicionar CSS do timer logo antes do `</style>` em `_layout.php`**

Abrir `componentes/Missoes/Logistica/sinais/Edicaorota/html/_layout.php`.

Localizar a linha do `</style>` que fecha o bloco `<style>` principal (linha 1267, antes de `</head>`):
```css
  }
</style>
</head>
```

Substituir por:
```css
  }

  /* ============ Timer de sessão do motorista ============ */
  .rota-timer-wrap {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 7px;
    padding: 7px 12px;
    border-radius: 10px;
    margin-bottom: 10px;
    font-size: 0.92rem;
    font-weight: 700;
    border: 1px solid var(--border);
    background: rgba(139, 92, 246, 0.08);
    color: var(--primary-soft);
    transition: background 0.3s, border-color 0.3s, color 0.3s;
    position: sticky;
    top: 0;
    z-index: 10;
  }
  .rota-timer-icone { font-style: normal; flex-shrink: 0; }
  .rota-timer-display { font-variant-numeric: tabular-nums; letter-spacing: 0.04em; }

  .rota-timer-verde   { color: #4ade80; border-color: rgba(74,222,128,0.35); background: rgba(74,222,128,0.08); }
  .rota-timer-amarelo { color: #fcd34d; border-color: rgba(252,211,77,0.35);  background: rgba(252,211,77,0.08);  }
  .rota-timer-vermelho {
    color: #f87171;
    border-color: rgba(248,113,113,0.45);
    background: rgba(248,113,113,0.12);
    animation: rota-timer-pulse 1.2s ease-in-out infinite;
  }
  .rota-timer-expirado { color: #fb923c; border-color: rgba(251,146,60,0.4); background: rgba(251,146,60,0.1); animation: none; }

  @keyframes rota-timer-pulse {
    0%, 100% { opacity: 1; }
    50%       { opacity: 0.55; }
  }
  @media (prefers-reduced-motion: reduce) {
    .rota-timer-vermelho { animation: none; }
  }

  /* Overlay de expiração — escurece tudo e mostra mensagem central */
  .rota-timer-overlay {
    position: fixed;
    inset: 0;
    z-index: 99999;
    background: rgba(5, 5, 15, 0.88);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    animation: rota-overlay-fade 0.3s ease-out;
  }
  @keyframes rota-overlay-fade { from { opacity: 0; } to { opacity: 1; } }
  .rota-timer-overlay-box {
    text-align: center;
    max-width: 320px;
  }
  .rota-timer-overlay-icone { font-size: 3rem; margin-bottom: 12px; }
  .rota-timer-overlay-titulo {
    font-size: 1.2rem;
    font-weight: 700;
    color: #ffffff;
    margin-bottom: 8px;
  }
  .rota-timer-overlay-sub {
    font-size: 0.92rem;
    color: var(--muted);
    line-height: 1.5;
  }
</style>
</head>
```

- [ ] **Step 4: Emitir `window.__rotaExpiraEm` no bloco de variáveis JS existente**

Localizar no `editar_rota.php` (próximo ao final, antes do primeiro `<script>`):
```php
<script>
window.__rotaOrigem      = <?= json_encode($origem, JSON_UNESCAPED_UNICODE) ?: 'null' ?>;
window.__rotaPartidaData = <?= json_encode($partidaData) ?>;
// __rotaPartida = ISO 'YYYY-MM-DDTHH:MM' montado a partir do input do motorista.
// Vira null enquanto ele não preencher o horário (e o resumo/conflito ficam '—').
window.__rotaPartida     = <?= $partidaHora !== '' ? json_encode($partidaData . 'T' . $partidaHora) : 'null' ?>;
</script>
```

Substituir por:
```php
<script>
window.__rotaOrigem      = <?= json_encode($origem, JSON_UNESCAPED_UNICODE) ?: 'null' ?>;
window.__rotaPartidaData = <?= json_encode($partidaData) ?>;
// __rotaPartida = ISO 'YYYY-MM-DDTHH:MM' montado a partir do input do motorista.
// Vira null enquanto ele não preencher o horário (e o resumo/conflito ficam '—').
window.__rotaPartida     = <?= $partidaHora !== '' ? json_encode($partidaData . 'T' . $partidaHora) : 'null' ?>;
window.__rotaExpiraEm    = <?= json_encode($expiraEm) ?>;
window.__rotaToken       = <?= json_encode($token) ?>;
</script>
```

- [ ] **Step 5: Adicionar o bloco JS do timer ao final de `editar_rota.php`**

Logo antes da linha final `$conteudo = ob_get_clean();`, adicionar:

```php
<?php if ($expiraEm): ?>
<script>
(function () {
  var expiraMs = null;
  try {
    // Postgres retorna 'YYYY-MM-DD HH:MM:SS'; Date() precisa de 'T' como separador.
    expiraMs = new Date(String(window.__rotaExpiraEm).replace(' ', 'T')).getTime();
    if (isNaN(expiraMs)) expiraMs = null;
  } catch (_) {}

  if (!expiraMs) return; // expira_em inválido: não exibe timer

  var wrap    = document.getElementById('rotaTimerWrap');
  var display = document.getElementById('rotaTimerDisplay');
  if (!wrap || !display) return;

  function corClasse(segundosRestantes) {
    if (segundosRestantes <= 0)   return 'rota-timer-expirado';
    if (segundosRestantes <= 120) return 'rota-timer-vermelho'; // < 2 min
    if (segundosRestantes <= 600) return 'rota-timer-amarelo';  // < 10 min
    return 'rota-timer-verde';
  }

  function aplicarCor(cls) {
    wrap.classList.remove('rota-timer-verde', 'rota-timer-amarelo', 'rota-timer-vermelho', 'rota-timer-expirado');
    wrap.classList.add(cls);
  }

  function formatarTempo(segundos) {
    if (segundos <= 0) return '00:00';
    var m = Math.floor(segundos / 60);
    var s = segundos % 60;
    return String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
  }

  function tick() {
    var restantes = Math.max(0, Math.floor((expiraMs - Date.now()) / 1000));
    display.textContent = formatarTempo(restantes);
    aplicarCor(corClasse(restantes));
    if (restantes <= 0) {
      clearInterval(intervalo);
      expirar();
    }
  }

  function expirar() {
    // 1. Mostra overlay
    var overlay = document.createElement('div');
    overlay.className = 'rota-timer-overlay';
    overlay.innerHTML =
      '<div class="rota-timer-overlay-box">' +
        '<div class="rota-timer-overlay-icone">⏰</div>' +
        '<div class="rota-timer-overlay-titulo">Tempo esgotado</div>' +
        '<div class="rota-timer-overlay-sub">Aguarde, encerrando a sessão…</div>' +
      '</div>';
    document.body.appendChild(overlay);

    // 2. Invalida sessão no servidor
    var dados = new URLSearchParams();
    dados.append('acao', 'expirar_sessao');
    dados.append('token', window.__rotaToken || '');
    fetch(window.location.href, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: dados.toString(),
    })
    .catch(function () {})
    .finally(function () {
      // 3. Recarrega — back-end agora detecta expira_em < now() e renderiza sessao_expirada
      window.location.reload();
    });
  }

  // Primeira tick imediata + intervalo de 1 s
  tick();
  var intervalo = setInterval(tick, 1000);
})();
</script>
<?php endif; ?>
```

- [ ] **Step 6: Verificar o timer no browser**

Abrir a URL de uma edição com sessão OTP válida. O timer deve aparecer no topo com cor verde, contando regressivamente. Para testar a expiração rapidamente, abra uma edição cuja `expira_em` esteja a poucos minutos no futuro ou edite o banco para setar um valor próximo.

- [ ] **Step 7: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/html/editar_rota.php \
        componentes/Missoes/Logistica/sinais/Edicaorota/html/_layout.php
git commit -m "feat(edicaorota): add countdown timer to driver edit view with session expiry"
```

---

## Verificação Final

- [ ] **Cenário 1 — Timer contando:** Acessar URL com sessão OTP válida e `expira_em` no futuro → timer aparece verde no topo, cores mudam conforme urgência
- [ ] **Cenário 2 — Expiração pelo timer:** Aguardar (ou forçar via banco) o countdown zerar → overlay aparece → página recarrega → exibe "Sessão Expirada"
- [ ] **Cenário 3 — Acesso direto após expirado:** Acessar a URL sem JS (ou após limpar cookie de sessão) com `expira_em` no passado → back-end renderiza `sessao_expirada` diretamente
- [ ] **Cenário 4 — Edição sem `expira_em`:** Acessar edição sem `expira_em` no banco → timer não aparece, comportamento anterior inalterado
- [ ] **Commit final de documentação**

```bash
git add docs/superpowers/specs/2026-05-26-edicaorota-timer-sessao-design.md \
        docs/superpowers/plans/2026-05-26-edicaorota-timer-sessao.md
git commit -m "docs: add spec and plan for edicaorota session timer"
```
