# Liberar Motorista UX (intl-tel-input + Reenviar) 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:** No modal "Liberar rota para motorista" da Central da Logística, trocar o input de telefone por `intl-tel-input` (bandeira/DDI/validação, default Brasil) e fazer o botão "Enviar para o motorista" virar "Reenviar para o motorista" após sucesso no canal `exibir_tela`, com clique re-gerando o OTP da mesma edição.

**Architecture:** Mudança 100% frontend em um único arquivo (`componentes/Missoes/Logistica/sinais/Central/html/central.php`). Plugin `intl-tel-input@18.2.1` carregado via jsdelivr (mesmo padrão de `bootstrap`/`quill` no tema `genezes`). Estado de "enviado" (`enviadoExibirTela`) e instância singleton do plugin (`liberarMotoristaIti`) ficam no escopo da IIFE existente em L.3857-4470. Reenvio reusa `edicoesPorRota[rotaId].token` e chama `POST /api/v1/edicao-rota/{token}/otp/gerar` sem criar nova edição.

**Tech Stack:** PHP view (HTML inline) + jQuery + Tailwind utility classes + FontAwesome + plugin `intl-tel-input` 18.x via CDN. Sem mudanças backend, sem mudanças em testes PHP existentes. Sem infraestrutura JS de testes — verificação é manual em browser (rotina padrão do projeto pra esse tipo de view).

**Spec:** `docs/superpowers/specs/2026-05-08-liberar-motorista-ux-design.md`

---

## File Structure

Único arquivo modificado:
- `componentes/Missoes/Logistica/sinais/Central/html/central.php` — view com HTML do modal + bloco `<script>` que controla o fluxo de liberação para motorista (handlers em `'.btn-liberar-motorista'`, `'#liberar-motorista-confirmar'`, `'#liberar-motorista-fechar, #liberar-motorista-cancelar'`).

Linhas-chave já no arquivo (mantidas como referência ao longo do plano):
- L.1 — abre `<style>` inline (vamos adicionar `<link>`/`<script>` do plugin **acima** dela).
- L.2473-2476 — div com input `#liberar-motorista-telefone`.
- L.3857-4470 — bloco `<script>` em IIFE `(function () { "use strict"; ... })();` que cuida de toda a integração com `/api/v1/edicao-rota`.
  - L.3862-3863 — declaração de `edicoesPorRota` e `pollTimer` (vamos adicionar nossas variáveis logo após).
  - L.3972-3996 — handler `'.btn-liberar-motorista'` (abre modal).
  - L.4002-4004 — handler `'#liberar-motorista-fechar, #liberar-motorista-cancelar'` (fecha modal).
  - L.4007-4126 — handler `'#liberar-motorista-confirmar'` (submit do modal).

---

## Task 1: Carregar plugin intl-tel-input no topo da view

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Central/html/central.php` (L.1, antes do `<style>`)

- [ ] **Step 1: Inserir `<link>` e `<script>` do plugin acima do `<style>` na linha 1**

Estado atual da L.1:
```html
<style>
    /* ==========================================================================
       ESTILOS ESPECÍFICOS DO CENTRAL - Mapa de Logística
       Estilos gerais (g-*) estão em kitgenesis.css
       ========================================================================== */
```

Substituir por:
```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/intl-tel-input@18.2.1/build/css/intlTelInput.css">
<script src="https://cdn.jsdelivr.net/npm/intl-tel-input@18.2.1/build/js/intlTelInput.min.js"></script>

<style>
    /* ==========================================================================
       ESTILOS ESPECÍFICOS DO CENTRAL - Mapa de Logística
       Estilos gerais (g-*) estão em kitgenesis.css
       ========================================================================== */
```

- [ ] **Step 2: Verificação manual no browser**

Abrir a tela `Logistica/Central` (rota usual de Central de Logística) com DevTools aberto. No console:
```js
typeof window.intlTelInput
```
Esperado: `"function"` (plugin carregou). Se retornar `"undefined"`, conferir se o `<script>` foi inserido **fora** de qualquer bloco PHP/template e se o jsdelivr está acessível na rede do ambiente.

- [ ] **Step 3: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Central/html/central.php
git commit -m "feat(central): load intl-tel-input plugin in liberar-motorista view"
```

---

## Task 2: Trocar HTML do input de telefone para `type="tel"` sem placeholder

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Central/html/central.php` (L.2473-2476)

- [ ] **Step 1: Substituir o `<div>` do telefone**

Estado atual L.2473-2476:
```html
            <div>
                <label class="g-texto-rotulo mb-1 block">Telefone do motorista</label>
                <input type="text" id="liberar-motorista-telefone" class="g-input w-full" placeholder="+55 11 99999-0000" autocomplete="off">
            </div>
```

Substituir por:
```html
            <div>
                <label class="g-texto-rotulo mb-1 block">Telefone do motorista</label>
                <input type="tel" id="liberar-motorista-telefone" class="g-input w-full" autocomplete="off">
            </div>
```

Mudanças exatas:
- `type="text"` → `type="tel"`.
- Remove `placeholder="+55 11 99999-0000"`.
- Tudo o mais (id, classe `g-input w-full`, `autocomplete="off"`) preservado.

- [ ] **Step 2: Verificação manual**

Abrir o modal "Liberar rota para motorista" (botão de avião na linha de uma rota com veículo + roteirizada). Confirmar que o campo de telefone aparece — ainda **sem** bandeira, porque o init do plugin só vai entrar na Task 4. Nesse ponto o input está literalmente "type=tel sem placeholder". Não há regressão funcional.

- [ ] **Step 3: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Central/html/central.php
git commit -m "refactor(central): switch motorista phone input to type=tel"
```

---

## Task 3: Adicionar variáveis de estado e helper `resetBotaoLiberar`

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Central/html/central.php` (após L.3863)

- [ ] **Step 1: Inserir variáveis e helper após `let pollTimer = null;`**

Estado atual L.3861-3864:
```js
    // Estado por rota: { [rotaId]: { edicaoId, token, status, expira_em, expirado } }
    const edicoesPorRota = {};
    let pollTimer = null;

    const ICONS = {
```

Substituir por:
```js
    // Estado por rota: { [rotaId]: { edicaoId, token, status, expira_em, expirado } }
    const edicoesPorRota = {};
    let pollTimer = null;

    // Modal "Liberar rota para motorista":
    // - liberarMotoristaIti: instância singleton do plugin intl-tel-input no campo telefone.
    // - enviadoExibirTela: true depois do 1º envio bem-sucedido no canal exibir_tela
    //   (ativa o modo "Reenviar" do botão confirmar). Resetado no abrir/fechar do modal.
    let liberarMotoristaIti = null;
    let enviadoExibirTela = false;

    function resetBotaoLiberar() {
        $('#liberar-motorista-confirmar')
            .html('<i class="fas fa-paper-plane mr-2"></i>Enviar para o motorista');
    }

    const ICONS = {
```

- [ ] **Step 2: Verificação no console**

Recarregar a página da Central. No DevTools console:
```js
// Não há acesso direto às variáveis (estão dentro da IIFE), mas confirmar que não houve erro de sintaxe:
document.querySelectorAll('script').length // > 0
```
Conferir aba "Console" do DevTools — não deve haver erros de SyntaxError ou ReferenceError mencionando `central.php` ou as linhas inseridas.

- [ ] **Step 3: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Central/html/central.php
git commit -m "refactor(central): add liberar-motorista state vars and resetBotaoLiberar helper"
```

---

## Task 4: Adicionar `ensureItiInit()` e chamar no abrir do modal

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Central/html/central.php` (após o helper `resetBotaoLiberar` adicionado na Task 3, e dentro do handler `.btn-liberar-motorista` em L.~3972-3996)

- [ ] **Step 1: Adicionar `ensureItiInit()` logo após `resetBotaoLiberar`**

Estado atual (após Task 3, dentro do bloco que acabamos de mexer):
```js
    function resetBotaoLiberar() {
        $('#liberar-motorista-confirmar')
            .html('<i class="fas fa-paper-plane mr-2"></i>Enviar para o motorista');
    }

    const ICONS = {
```

Substituir por:
```js
    function resetBotaoLiberar() {
        $('#liberar-motorista-confirmar')
            .html('<i class="fas fa-paper-plane mr-2"></i>Enviar para o motorista');
    }

    function ensureItiInit() {
        if (liberarMotoristaIti) return liberarMotoristaIti;
        if (typeof window.intlTelInput !== 'function') return null; // plugin não carregou (CDN offline)
        const input = document.getElementById('liberar-motorista-telefone');
        if (!input) return null;
        liberarMotoristaIti = window.intlTelInput(input, {
            initialCountry: 'br',
            preferredCountries: ['br', 'pt', 'us', 'ar', 'uy', 'py'],
            separateDialCode: true,
            nationalMode: true,
            autoPlaceholder: 'aggressive',
            utilsScript: 'https://cdn.jsdelivr.net/npm/intl-tel-input@18.2.1/build/js/utils.js',
        });
        return liberarMotoristaIti;
    }

    const ICONS = {
```

- [ ] **Step 2: Atualizar handler `.btn-liberar-motorista` para resetar estado e inicializar plugin**

Estado atual L.3993-3995 (final do handler `.btn-liberar-motorista`):
```js
        $('#liberar-motorista-rota-info').text('Rota #' + rotaIdEmEdicao);
        $('#liberar-motorista-feedback').addClass('hidden').empty().removeClass('bg-green-900/30 text-green-300 bg-red-900/30 text-red-300');
        $('#liberar-motorista-modal').removeClass('hidden');
    });
```

Substituir por:
```js
        $('#liberar-motorista-rota-info').text('Rota #' + rotaIdEmEdicao);
        $('#liberar-motorista-feedback').addClass('hidden').empty().removeClass('bg-green-900/30 text-green-300 bg-red-900/30 text-red-300');

        enviadoExibirTela = false;
        resetBotaoLiberar();

        $('#liberar-motorista-modal').removeClass('hidden');
        ensureItiInit();
    });
```

Ordem importante: reset antes de mostrar o modal; `ensureItiInit()` depois de `removeClass('hidden')` pra garantir que o input está visível no DOM (alguns layouts do plugin medem dimensões).

- [ ] **Step 3: Verificação manual no browser**

1. Recarregar a Central.
2. Abrir o modal "Liberar rota para motorista".
3. Confirmar visual: o input agora aparece **com bandeira do Brasil** à esquerda e DDI `+55` separado.
4. Clicar na bandeira → dropdown abre, mostrando "Brasil", "Portugal", "Estados Unidos", "Argentina", "Uruguai", "Paraguai" no topo (preferred), seguido da lista completa em ordem alfabética com busca.
5. Selecionar Argentina → DDI muda pra `+54` e o `autoPlaceholder` muda pra exemplo argentino.
6. Selecionar Brasil de novo → volta pra `+55`.
7. Fechar (X ou Cancelar) e reabrir → bandeira começa em Brasil de novo (state reset, mas `liberarMotoristaIti` reusado por singleton — o input mantém o último país; aceitável pra essa task).

Se a bandeira não aparecer: abrir DevTools console, conferir erros. Provavelmente Task 1 falhou no carregamento do plugin.

- [ ] **Step 4: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Central/html/central.php
git commit -m "feat(central): init intl-tel-input on liberar-motorista modal open"
```

---

## Task 5: Resetar estado no fechar/cancelar do modal

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Central/html/central.php` (handler em L.4002-4004 — pode ter shifted após edits anteriores; localizar pelo seletor `'#liberar-motorista-fechar, #liberar-motorista-cancelar'`)

- [ ] **Step 1: Adicionar reset dentro do handler de fechar**

Estado atual (handler na linha que contém `$(document).on('click', '#liberar-motorista-fechar, #liberar-motorista-cancelar', ...)`):
```js
    $(document).on('click', '#liberar-motorista-fechar, #liberar-motorista-cancelar', function () {
        $('#liberar-motorista-modal').addClass('hidden');
    });
```

Substituir por:
```js
    $(document).on('click', '#liberar-motorista-fechar, #liberar-motorista-cancelar', function () {
        $('#liberar-motorista-modal').addClass('hidden');
        enviadoExibirTela = false;
        resetBotaoLiberar();
    });
```

- [ ] **Step 2: Verificação manual**

(Verificação completa do reset só faz sentido depois das Tasks 6-7, quando o botão muda pra "Reenviar". Por enquanto, confirme que o modal continua fechando sem erros via X e via Cancelar.)

1. Recarregar Central.
2. Abrir o modal, fechar via X. Modal some.
3. Reabrir, fechar via Cancelar. Modal some.
4. Sem erros no console.

- [ ] **Step 3: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Central/html/central.php
git commit -m "refactor(central): reset enviadoExibirTela on liberar-motorista modal close"
```

---

## Task 6: Validar e ler telefone via `iti.getNumber()` no submit

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Central/html/central.php` (no handler `'#liberar-motorista-confirmar'`, na linha que contém `const telefone = ($('#liberar-motorista-telefone')...`)

- [ ] **Step 1: Substituir leitura simples por leitura validada**

Estado atual (no handler do confirmar; localizar a linha exata pelo conteúdo):
```js
    $(document).on('click', '#liberar-motorista-confirmar', async function () {
        const $btn = $(this);
        const $feedback = $('#liberar-motorista-feedback');

        const telefone = ($('#liberar-motorista-telefone').val() || '').trim();
        const email    = ($('#liberar-motorista-email').val()    || '').trim();
        const tempo    = parseInt($('#liberar-motorista-tempo').val(), 10) || 30;
        const canal    = $('#liberar-motorista-canal').val() || 'whatsapp';
        const padrao   = $('#liberar-motorista-padrao').is(':checked');

        if (!telefone && !email) {
            mostrarFeedback($feedback, 'erro', 'Informe ao menos um contato (telefone ou e-mail).');
            return;
        }
```

Substituir por:
```js
    $(document).on('click', '#liberar-motorista-confirmar', async function () {
        const $btn = $(this);
        const $feedback = $('#liberar-motorista-feedback');

        let telefone = '';
        const inputRaw = ($('#liberar-motorista-telefone').val() || '').trim();
        if (liberarMotoristaIti && inputRaw) {
            if (!liberarMotoristaIti.isValidNumber()) {
                mostrarFeedback($feedback, 'erro', 'Telefone inválido. Confira DDD e número.');
                return;
            }
            telefone = liberarMotoristaIti.getNumber(); // E.164, ex: +5513997919204
        } else {
            telefone = inputRaw; // fallback: plugin não carregou (CDN offline)
        }

        const email    = ($('#liberar-motorista-email').val()    || '').trim();
        const tempo    = parseInt($('#liberar-motorista-tempo').val(), 10) || 30;
        const canal    = $('#liberar-motorista-canal').val() || 'whatsapp';
        const padrao   = $('#liberar-motorista-padrao').is(':checked');

        if (!telefone && !email) {
            mostrarFeedback($feedback, 'erro', 'Informe ao menos um contato (telefone ou e-mail).');
            return;
        }
```

`telefone` segue sendo usado mais abaixo em `body.telefone_motorista = telefone || email;` e em `destinatario: canal === 'email' ? (email || telefone) : (telefone || email),` — sem mudança nesses pontos.

- [ ] **Step 2: Verificação manual — fluxo feliz**

1. Abrir modal, deixar bandeira em Brasil.
2. Digitar `13997919204` (sem formatação). Plugin formata em tempo real para algo como `(13) 99791-9204`.
3. Selecionar canal "Exibir em tela" (`exibir_tela`).
4. Clicar Enviar. Em DevTools → Network → POST `/api/v1/edicao-rota`, conferir no payload:
   - `telefone_motorista` deve ser `"+5513997919204"` (E.164, sem espaços ou pontuação).
5. Backend deve responder 200; painel código/token/link aparece.

- [ ] **Step 3: Verificação manual — fluxo de erro de validação**

1. Reabrir modal, digitar `12345` no campo (incompleto).
2. Clicar Enviar.
3. Esperado: aparece "Telefone inválido. Confira DDD e número." em `$feedback` (vermelho). Nenhuma chamada a `/api/v1/edicao-rota` foi feita (confirmar na aba Network).

- [ ] **Step 4: Verificação manual — fluxo só email (telefone vazio)**

1. Reabrir modal, deixar telefone vazio, preencher e-mail válido, canal `email`.
2. Clicar Enviar.
3. Esperado: chamada à API normal (telefone vazio é OK, validação `isValidNumber` só dispara com input não-vazio).

- [ ] **Step 5: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Central/html/central.php
git commit -m "feat(central): validate motorista phone via intl-tel-input and send E.164"
```

---

## Task 7: Marcar `enviadoExibirTela=true` e mudar label do botão após sucesso

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Central/html/central.php` (dentro do branch `if (canal === 'exibir_tela' && codigoExibido) { ... }` no handler do confirmar)

- [ ] **Step 1: Adicionar set de estado e label após o `$feedback.html(...)`**

Localizar o branch `if (canal === 'exibir_tela' && codigoExibido) { ... } else { ... }` (atualmente em L.~4061-4107). O bloco `if` termina com o backtick fechando o template literal e um `);`. Estado atual:

```js
            if (canal === 'exibir_tela' && codigoExibido) {
                const linkMotorista = `${window.location.origin}/missao/Logistica/edicaorota/ver_status?token=${encodeURIComponent(token)}`;
                $feedback.removeClass('hidden bg-green-900/30 text-green-300 bg-red-900/30 text-red-300')
                    .addClass('bg-green-900/30 text-green-300')
                    .html(`
                        <div class="space-y-2">
                            <div><strong>Edição #${edicaoId}</strong> — exibir em tela</div>
                            <div class="text-xs space-y-2">
                                <div class="flex items-center gap-2">
                                    <span class="opacity-70 w-16 shrink-0">Código:</span>
                                    <code class="bg-black/30 px-2 py-0.5 rounded text-yellow-300 flex-1">${codigoExibido}</code>
                                    <button type="button"
                                            class="g-botao g-botao-contorno text-[11px] py-0.5 px-2 copiar-exibir-tela"
                                            data-copiar-valor="${codigoExibido}"
                                            data-copiar-rotulo="Código">
                                        <i class="fas fa-copy mr-1"></i>Copiar
                                    </button>
                                </div>
                                <div class="flex items-center gap-2">
                                    <span class="opacity-70 w-16 shrink-0">Token:</span>
                                    <code class="bg-black/30 px-2 py-0.5 rounded break-all flex-1">${token}</code>
                                    <button type="button"
                                            class="g-botao g-botao-contorno text-[11px] py-0.5 px-2 copiar-exibir-tela"
                                            data-copiar-valor="${token}"
                                            data-copiar-rotulo="Token">
                                        <i class="fas fa-copy mr-1"></i>Copiar
                                    </button>
                                </div>
                                <div class="flex items-start gap-2">
                                    <span class="opacity-70 w-16 shrink-0 pt-0.5">Link:</span>
                                    <a href="${linkMotorista}" target="_blank" class="underline text-blue-300 break-all flex-1">${linkMotorista}</a>
                                    <button type="button"
                                            class="g-botao g-botao-contorno text-[11px] py-0.5 px-2 copiar-exibir-tela"
                                            data-copiar-valor="${linkMotorista}"
                                            data-copiar-rotulo="Link">
                                        <i class="fas fa-copy mr-1"></i>Copiar
                                    </button>
                                </div>
                                <div class="opacity-70 text-[11px] pt-1">
                                    Copie e repasse os dados ao motorista. O modal não fecha sozinho — feche em "Cancelar"/×.
                                </div>
                            </div>
                        </div>
                    `);
            } else {
                mostrarFeedback($feedback, 'ok', `Solicitação enviada (edição #${edicaoId}).`);
            }
```

Substituir por (mesmo conteúdo + duas linhas adicionando o estado/label antes do `} else {`):

```js
            if (canal === 'exibir_tela' && codigoExibido) {
                const linkMotorista = `${window.location.origin}/missao/Logistica/edicaorota/ver_status?token=${encodeURIComponent(token)}`;
                $feedback.removeClass('hidden bg-green-900/30 text-green-300 bg-red-900/30 text-red-300')
                    .addClass('bg-green-900/30 text-green-300')
                    .html(`
                        <div class="space-y-2">
                            <div><strong>Edição #${edicaoId}</strong> — exibir em tela</div>
                            <div class="text-xs space-y-2">
                                <div class="flex items-center gap-2">
                                    <span class="opacity-70 w-16 shrink-0">Código:</span>
                                    <code class="bg-black/30 px-2 py-0.5 rounded text-yellow-300 flex-1">${codigoExibido}</code>
                                    <button type="button"
                                            class="g-botao g-botao-contorno text-[11px] py-0.5 px-2 copiar-exibir-tela"
                                            data-copiar-valor="${codigoExibido}"
                                            data-copiar-rotulo="Código">
                                        <i class="fas fa-copy mr-1"></i>Copiar
                                    </button>
                                </div>
                                <div class="flex items-center gap-2">
                                    <span class="opacity-70 w-16 shrink-0">Token:</span>
                                    <code class="bg-black/30 px-2 py-0.5 rounded break-all flex-1">${token}</code>
                                    <button type="button"
                                            class="g-botao g-botao-contorno text-[11px] py-0.5 px-2 copiar-exibir-tela"
                                            data-copiar-valor="${token}"
                                            data-copiar-rotulo="Token">
                                        <i class="fas fa-copy mr-1"></i>Copiar
                                    </button>
                                </div>
                                <div class="flex items-start gap-2">
                                    <span class="opacity-70 w-16 shrink-0 pt-0.5">Link:</span>
                                    <a href="${linkMotorista}" target="_blank" class="underline text-blue-300 break-all flex-1">${linkMotorista}</a>
                                    <button type="button"
                                            class="g-botao g-botao-contorno text-[11px] py-0.5 px-2 copiar-exibir-tela"
                                            data-copiar-valor="${linkMotorista}"
                                            data-copiar-rotulo="Link">
                                        <i class="fas fa-copy mr-1"></i>Copiar
                                    </button>
                                </div>
                                <div class="opacity-70 text-[11px] pt-1">
                                    Copie e repasse os dados ao motorista. O modal não fecha sozinho — feche em "Cancelar"/×.
                                </div>
                            </div>
                        </div>
                    `);

                enviadoExibirTela = true;
                $('#liberar-motorista-confirmar')
                    .html('<i class="fas fa-paper-plane mr-2"></i>Reenviar para o motorista');
            } else {
                mostrarFeedback($feedback, 'ok', `Solicitação enviada (edição #${edicaoId}).`);
            }
```

- [ ] **Step 2: Verificação manual**

1. Abrir modal, telefone válido, canal `exibir_tela`, clicar Enviar.
2. Painel com código/token/link aparece.
3. **Botão muda** de "Enviar para o motorista" para "Reenviar para o motorista" (ícone de avião continua igual).
4. Fechar via Cancelar e reabrir o modal: botão volta a ser "Enviar para o motorista" (Task 5 já cobriu o reset).
5. Repetir o fluxo escolhendo canal `whatsapp` em vez de `exibir_tela` — o modal fecha em 1.2s e o label do botão **não muda visualmente** (correto: o branch só dispara em exibir_tela).

- [ ] **Step 3: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Central/html/central.php
git commit -m "feat(central): switch button to 'Reenviar' after success on canal exibir_tela"
```

---

## Task 8: Branch de "Reenviar" no início do handler do confirmar

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Central/html/central.php` (no início do handler `'#liberar-motorista-confirmar'`, antes do trecho que lê telefone/email atual)

- [ ] **Step 1: Adicionar branch de reenvio antes da leitura comum**

Localizar o handler:
```js
    $(document).on('click', '#liberar-motorista-confirmar', async function () {
        const $btn = $(this);
        const $feedback = $('#liberar-motorista-feedback');

        let telefone = '';
        ...
```

(Após Task 6, esse bloco começa com `$btn`/`$feedback` e em seguida o `let telefone = '';`.)

Substituir por (insere o branch reenviar imediatamente após a declaração de `$btn` e `$feedback`, antes da leitura comum de telefone/email):

```js
    $(document).on('click', '#liberar-motorista-confirmar', async function () {
        const $btn = $(this);
        const $feedback = $('#liberar-motorista-feedback');

        // Branch de reenvio: canal exibir_tela, edição já criada na chamada anterior.
        // Re-chama só /otp/gerar com o token salvo; backend invalida código antigo.
        if (enviadoExibirTela && edicoesPorRota[rotaIdEmEdicao] && edicoesPorRota[rotaIdEmEdicao].token) {
            const token = edicoesPorRota[rotaIdEmEdicao].token;

            let telefoneReenvio = '';
            const inputRaw = ($('#liberar-motorista-telefone').val() || '').trim();
            if (liberarMotoristaIti && inputRaw) {
                if (!liberarMotoristaIti.isValidNumber()) {
                    mostrarFeedback($feedback, 'erro', 'Telefone inválido. Confira DDD e número.');
                    return;
                }
                telefoneReenvio = liberarMotoristaIti.getNumber();
            } else {
                telefoneReenvio = inputRaw;
            }
            const emailReenvio = ($('#liberar-motorista-email').val() || '').trim();

            $btn.prop('disabled', true).addClass('opacity-50');
            try {
                const gerarResp = await api('POST', `/api/v1/edicao-rota/${encodeURIComponent(token)}/otp/gerar`, {
                    canal: 'exibir_tela',
                    destinatario: telefoneReenvio || emailReenvio,
                });
                const novoCodigo = gerarResp && gerarResp.data ? gerarResp.data.codigo_exibido : null;
                if (novoCodigo) {
                    const $codigoEl = $feedback.find('code').first();
                    $codigoEl.text(novoCodigo);
                    $feedback.find('.copiar-exibir-tela[data-copiar-rotulo="Código"]')
                        .attr('data-copiar-valor', novoCodigo);
                    // Flash visual: ring amarelo por 1.2s pra sinalizar atualização.
                    $codigoEl.addClass('ring-2 ring-yellow-300');
                    setTimeout(() => $codigoEl.removeClass('ring-2 ring-yellow-300'), 1200);
                } else {
                    mostrarFeedback($feedback, 'erro', 'Resposta inesperada do servidor — código não atualizado.');
                }
            } catch (e) {
                mostrarFeedback($feedback, 'erro', e.message || 'Falha ao reenviar código.');
            } finally {
                $btn.prop('disabled', false).removeClass('opacity-50');
            }
            return;
        }

        let telefone = '';
        ...
```

(Os `...` no final referenciam o código original que já existia desde a Task 6 — não substituir esses; só inserir o branch acima dele.)

- [ ] **Step 2: Verificação manual — reenvio feliz**

1. Abrir modal, telefone válido, canal `exibir_tela`, Enviar.
2. Anotar o `Código` exibido no painel (ex.: `123456`).
3. Clicar **Reenviar para o motorista**.
4. DevTools → Network: aparece **só** uma chamada nova `POST /api/v1/edicao-rota/{token}/otp/gerar`. **Não** aparece `POST /api/v1/edicao-rota` (não cria edição nova).
5. Painel atualiza:
   - O `<code>` do "Código:" mostra um valor novo (diferente do anterior).
   - O `<code>` recebe um anel amarelo (ring) que some em ~1.2s.
   - O botão Copiar do código agora copia o **novo** código (clicar nele e colar em outro lugar pra confirmar).
6. Token e Link permanecem iguais (mesma edição).

- [ ] **Step 3: Verificação manual — reenvio com telefone editado**

1. Continuando do passo anterior (modal aberto, já reenviou uma vez).
2. Trocar o número no campo telefone (ex.: digitar outro DDD).
3. Clicar **Reenviar**.
4. Esperado: chamada `/otp/gerar` com `destinatario` igual ao novo número (E.164). Painel atualiza com novo código. Sem erro.

- [ ] **Step 4: Verificação manual — reenvio com erro de rede/servidor**

(Cenário opcional — usar DevTools → Network → "Offline" pra simular.)

1. Modal aberto pós-envio, ativar "Offline" no DevTools.
2. Clicar Reenviar.
3. Esperado: aparece mensagem de erro em `$feedback`. Botão volta a ficar habilitado e ainda escrito "Reenviar para o motorista".
4. Voltar online, clicar Reenviar de novo: funciona normalmente.

- [ ] **Step 5: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/Central/html/central.php
git commit -m "feat(central): wire 'Reenviar' button to re-call /otp/gerar with same token"
```

---

## Task 9: Smoke test consolidado e ajuste final

**Files:**
- (apenas verificação; nenhuma edição esperada)

- [ ] **Step 1: Roteiro completo de smoke test**

Recarregar a Central limpa. Executar os cenários abaixo, marcando cada um:

1. **Telefone com bandeira:** abrir modal — bandeira BR + DDI `+55` separado aparecem. Dropdown lista BR/PT/US/AR/UY/PY no topo.
2. **Validação:** digitar `123` → Enviar → aparece "Telefone inválido. Confira DDD e número." Sem chamada à API.
3. **Envio normal (whatsapp):** telefone `13997919204`, canal whatsapp, Enviar → modal fecha em ~1.2s. Network mostra `telefone_motorista: "+5513997919204"`.
4. **Envio exibir_tela:** abrir nova rota, telefone válido, canal `exibir_tela`, Enviar → painel código/token/link aparece, botão muda pra **"Reenviar para o motorista"**.
5. **Reenvio:** clicar Reenviar → só `/otp/gerar` é chamado, código atualiza in-place com flash amarelo, token/link permanecem.
6. **Cancelar + reabrir:** Cancelar → reabrir modal → botão volta a ser **"Enviar para o motorista"**, `enviadoExibirTela` resetado.
7. **Sem telefone (só email):** deixar telefone vazio, preencher e-mail, canal `email`, Enviar → funciona normalmente.
8. **Plugin offline (opcional):** bloquear `cdn.jsdelivr.net` no DevTools → recarregar → modal abre input sem bandeira, Enviar usa raw text → ainda funciona (fallback).

- [ ] **Step 2: Rodar testes de regressão PHP existentes**

A mudança é frontend-only e não toca nenhum arquivo PHP, mas vale rodar a suite de testes pra garantir que o ambiente continua íntegro. Do diretório raiz:

```bash
cd /home/goldie/www/genesis && php tests/unit/Strategies/CarregamentoExecucaoStrategyTest.php
```

(Ou rodar a suite completa com o runner padrão do projeto, conforme a skill `genesis-tests`. Se houver script `bin/phpunit` ou similar, preferir esse.)

Esperado: nenhum teste novo falha por causa dessa mudança. Os 4 testes em `tests/unit/Strategies/` não tocam OTP/Edicaorota/Central.

- [ ] **Step 3: Diff final review**

```bash
git log --oneline main..HEAD -- componentes/Missoes/Logistica/sinais/Central/html/central.php
git diff main...HEAD -- componentes/Missoes/Logistica/sinais/Central/html/central.php | wc -l
```

Esperado: ~8 commits (Tasks 1-8) tocando exclusivamente `central.php`. Diff total relativamente pequeno (~150-200 linhas adicionadas, ~5-10 removidas).

- [ ] **Step 4: (Opcional) PR**

Se o usuário pedir, criar PR. Sem isso, deixar os commits no branch `dev_team`.

---

## Self-review notes

- **Spec coverage:**
  - Plugin loading: Task 1 ✓
  - HTML do input alterado: Task 2 ✓
  - State vars + helper: Task 3 ✓
  - `ensureItiInit` + chamada no abrir: Task 4 ✓
  - Reset no fechar: Task 5 ✓
  - Validação + leitura E.164 no submit: Task 6 ✓
  - Set `enviadoExibirTela=true` + label: Task 7 ✓
  - Branch de reenvio: Task 8 ✓
  - Smoke test: Task 9 ✓
  - Out of scope itens (i18n, persistência de país, e-mail validation, auto-close de outros canais, "Salvar como padrão") não foram implementados — correto.

- **Sem placeholders:** todos os passos têm código completo ou comando exato. Nenhum "TBD"/"adicionar validação"/"similar a task X".

- **Type/identifier consistency:** `liberarMotoristaIti`, `enviadoExibirTela`, `resetBotaoLiberar`, `ensureItiInit`, `edicoesPorRota[rotaIdEmEdicao].token` — usados consistentemente entre Tasks 3-8.

- **Sem testes JS:** projeto não tem infra de teste JS pra essa view; verificações são manuais em browser, alinhado à prática do projeto (visto em commits anteriores como `feat(central): keep liberar-motorista modal open on exibir_tela + copy buttons`).
