# Edição de Rota — `dia_efetivo` da solicitação 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.

> **Commits:** This repo has a standing rule — **never `git commit`/`git push` without the user's explicit go-ahead**. The commit steps below are the intended boundaries, but the executor MUST pause and ask before actually committing. Treat each "Commit" step as "stage + request approval to commit".

**Goal:** A solicitação de edição de rota passa a carregar um `dia_efetivo` igual ao `romaneios.dia_efetivo` do romaneio editado, e é agrupada por esse dia em `central.php`.

**Architecture:** Snapshot capturado na criação da edição. A data efetiva entra pelo contrato de callback (padrão V2 Registry+Interface) através de uma interface opcional separada — o núcleo genérico nunca consulta `romaneios`. Um serviço fino resolve a data via Registry; o Laboratório injeta no `make()`; o espelho copia; a listagem agrupa via `COALESCE`.

**Tech Stack:** PHP 7.x/8.x, PostgreSQL (`dev_gx_goldie` para integração), ModelInteligencia (Sondas V2), testes standalone CLI (`*TddTest.php` puro / `*IntegrationTest.php` com DB).

---

## Context the engineer needs

- **Sinal genérico desacoplado.** `componentes/Missoes/Logistica/sinais/Edicaorota/` não conhece Romaneio. Só `Adapters/Romaneio/Callbacks/RomaneioVeiculoEdicaoRotaCallback.php` pode tocar tabelas de romaneio. **Não viole isso.**
- **Sondas são schema-driven.** `EdicaoRotaSonda` e `SolicitacaoSonda` têm `self::$fields = []` / `self::$required = []`. Não há código de Sonda para mudar — basta a coluna existir no banco e a chave ser passada no `make()`.
- **Sem SQL cru para writes.** Use `->update()`/`->create()`/`make()+save()` dos models. Exceção: DDL de migration (arquivos `.sql`) e leituras/cleanup em testes de integração (padrão estabelecido — ver `CriarEdicaoRotaLaboratorioIntegrationTest.php`).
- **Sem migration runner.** Migrations são arquivos `.sql` versionados aplicados ao banco manualmente. Aplicamos via PDO (`Connect`) num passo explícito. `ADD COLUMN IF NOT EXISTS` é idempotente.
- **Convenção de teste TddTest:** script PHP CLI, `require_once` direto dos arquivos sob teste (sem autoload pesado), helper `assert` simples, `exit(0|1)`. Rodar: `php <arquivo>`.
- **Convenção IntegrationTest:** bootstrap de `$_SESSION` na raiz **e** em `$_SESSION['dashboard']`, `(new Connect)->getInstance(null, null, 'dev_gx_goldie')` para reads/cleanup. Roda só em `dev_gx_goldie`.

## File Structure

| Arquivo | Responsabilidade |
|---|---|
| `Edicaorota/Callbacks/EdicaoRotaDiaEfetivoResolverInterface.php` *(novo)* | Contrato **opcional**: `resolverDiaEfetivo(int): ?string`. Não toca a interface principal. |
| `Edicaorota/Callbacks/EdicaoRotaDiaEfetivoResolver.php` *(novo)* | Serviço fino: Registry → `instanceof` → chama o resolver → normaliza/try-catch. Dependências leves (sem ModelInteligencia) → unit-testável. |
| `Edicaorota/Adapters/Romaneio/Callbacks/RomaneioVeiculoEdicaoRotaCallback.php` *(mod)* | Passa a implementar a interface opcional: `rv.id → romaneio_id → romaneios.dia_efetivo`. Único ponto que conhece Romaneio. |
| `Edicaorota/Laboratorios/CriarEdicaoRotaLaboratorio.php` *(mod)* | Chama o serviço resolver e injeta `dia_efetivo` (nullable) no `make()`. |
| `Edicaorota/Adapters/Solicitacoes/EspelhoSolicitacaoGlobalAdapter.php` *(mod)* | `montarPayloadCriacao()` copia `dia_efetivo` do edição. |
| `Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesSolicitacoes.php:674` *(mod)* | Filtro global `COALESCE(dia_efetivo, data_cadastro::date) = :dia`. |
| `Edicaorota/Migrations/003_dia_efetivo.sql` *(novo)* | `ALTER TABLE edicoes_rota ADD COLUMN IF NOT EXISTS dia_efetivo DATE;` |
| `Solicitacoes/Migrations/002_dia_efetivo.sql` *(novo)* | `ALTER TABLE solicitacoes_globais ADD COLUMN IF NOT EXISTS dia_efetivo DATE;` |
| `Edicaorota/Testes/Callbacks/EdicaoRotaDiaEfetivoResolverTddTest.php` *(novo)* | Unit do serviço resolver. |
| `Edicaorota/Testes/Adapters/Solicitacoes/EspelhoSolicitacaoGlobalAdapterTddTest.php` *(mod)* | + asserts de `dia_efetivo`. |
| `Edicaorota/Testes/Adapters/Romaneio/RomaneioDiaEfetivoIntegrationTest.php` *(novo)* | Integração ponta-a-ponta em `dev_gx_goldie`. |

---

## Task 1: Interface opcional do resolver

**Files:**
- Create: `componentes/Missoes/Logistica/sinais/Edicaorota/Callbacks/EdicaoRotaDiaEfetivoResolverInterface.php`

- [ ] **Step 1: Criar a interface**

```php
<?php

namespace GalaxiaMissoes\Logistica\sinais\Edicaorota\Callbacks;

/**
 * Capability OPCIONAL de um callback de edição de rota.
 *
 * Um adapter de domínio (Romaneio, Coleta, ...) que tenha um "dia efetivo"
 * operacional implementa também este contrato. Quem não implementa
 * simplesmente não contribui com data (a edição fica sem dia_efetivo e
 * a listagem cai no fallback de data_cadastro).
 *
 * NÃO faz parte de EdicaoRotaCallbackInterface — adicionar capability
 * aqui não força nenhum callback existente a mudar.
 */
interface EdicaoRotaDiaEfetivoResolverInterface
{
    /**
     * Resolve o dia efetivo (formato 'Y-m-d') a partir do
     * callback_referencia_id do domínio dono. Retorna null se não
     * houver data resolvível (referência inexistente, sem data, etc.).
     */
    public function resolverDiaEfetivo(int $callbackReferenciaId): ?string;
}
```

- [ ] **Step 2: Sanity check de sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Edicaorota/Callbacks/EdicaoRotaDiaEfetivoResolverInterface.php`
Expected: `No syntax errors detected`

- [ ] **Step 3: Commit** *(pausar e pedir aprovação antes de commitar)*

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/Callbacks/EdicaoRotaDiaEfetivoResolverInterface.php
git commit -m "feat(edicaorota): add optional EdicaoRotaDiaEfetivoResolverInterface"
```

---

## Task 2: Serviço resolver (TDD, unit)

**Files:**
- Create: `componentes/Missoes/Logistica/sinais/Edicaorota/Callbacks/EdicaoRotaDiaEfetivoResolver.php`
- Test: `componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Callbacks/EdicaoRotaDiaEfetivoResolverTddTest.php`

- [ ] **Step 1: Escrever o teste que falha**

Create `componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Callbacks/EdicaoRotaDiaEfetivoResolverTddTest.php`:

```php
<?php

require_once __DIR__ . '/../../Callbacks/EdicaoRotaCallbackInterface.php';
require_once __DIR__ . '/../../Callbacks/EdicaoRotaCallbackRegistry.php';
require_once __DIR__ . '/../../Callbacks/EdicaoRotaDiaEfetivoResolverInterface.php';
require_once __DIR__ . '/../../Callbacks/EdicaoRotaDiaEfetivoResolver.php';

use GalaxiaMissoes\Logistica\sinais\Edicaorota\Callbacks\EdicaoRotaCallbackInterface;
use GalaxiaMissoes\Logistica\sinais\Edicaorota\Callbacks\EdicaoRotaCallbackRegistry;
use GalaxiaMissoes\Logistica\sinais\Edicaorota\Callbacks\EdicaoRotaDiaEfetivoResolverInterface;
use GalaxiaMissoes\Logistica\sinais\Edicaorota\Callbacks\EdicaoRotaDiaEfetivoResolver;

$falhas = 0;
$assert = function (bool $c, string $m) use (&$falhas) {
    echo ($c ? "✅ [PASS] " : "❌ [FAIL] ") . $m . "\n";
    if (!$c) $falhas++;
};

// Callback que resolve data
class FakeComData implements EdicaoRotaCallbackInterface, EdicaoRotaDiaEfetivoResolverInterface
{
    public function aoAprovar(object $e): bool { return true; }
    public function aoRejeitar(object $e): bool { return true; }
    public function resolverDiaEfetivo(int $id): ?string
    {
        if ($id === 999) return null;
        if ($id === 555) return '2026-05-20 00:00:00'; // com hora — deve normalizar
        return '2026-05-20';
    }
}

// Callback SEM a capability opcional
class FakeSemData implements EdicaoRotaCallbackInterface
{
    public function aoAprovar(object $e): bool { return true; }
    public function aoRejeitar(object $e): bool { return true; }
}

// Callback que lança
class FakeQueLanca implements EdicaoRotaCallbackInterface, EdicaoRotaDiaEfetivoResolverInterface
{
    public function aoAprovar(object $e): bool { return true; }
    public function aoRejeitar(object $e): bool { return true; }
    public function resolverDiaEfetivo(int $id): ?string { throw new \RuntimeException('boom'); }
}

EdicaoRotaCallbackRegistry::reset();
EdicaoRotaCallbackRegistry::registrar('com_data', FakeComData::class);
EdicaoRotaCallbackRegistry::registrar('sem_data', FakeSemData::class);
EdicaoRotaCallbackRegistry::registrar('lanca', FakeQueLanca::class);

$assert(EdicaoRotaDiaEfetivoResolver::resolver('com_data', 7) === '2026-05-20',
    'callback com capability retorna a data');
$assert(EdicaoRotaDiaEfetivoResolver::resolver('com_data', 555) === '2026-05-20',
    'data com hora é normalizada para 10 chars (Y-m-d)');
$assert(EdicaoRotaDiaEfetivoResolver::resolver('com_data', 999) === null,
    'resolver null do callback → null');
$assert(EdicaoRotaDiaEfetivoResolver::resolver('sem_data', 7) === null,
    'callback sem a interface opcional → null');
$assert(EdicaoRotaDiaEfetivoResolver::resolver('lanca', 7) === null,
    'callback que lança → null (não propaga)');
$assert(EdicaoRotaDiaEfetivoResolver::resolver('nao_registrado', 7) === null,
    'módulo não registrado → null');
$assert(EdicaoRotaDiaEfetivoResolver::resolver('com_data', 0) === null,
    'referencia_id <= 0 → null');
$assert(EdicaoRotaDiaEfetivoResolver::resolver('', 7) === null,
    'módulo vazio → null');

echo $falhas === 0 ? "\nTODOS OK\n" : "\n{$falhas} FALHAS\n";
exit($falhas === 0 ? 0 : 1);
```

- [ ] **Step 2: Rodar o teste e ver falhar**

Run: `php componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Callbacks/EdicaoRotaDiaEfetivoResolverTddTest.php`
Expected: FAIL — fatal error "Failed opening required ... EdicaoRotaDiaEfetivoResolver.php" (o serviço ainda não existe).

- [ ] **Step 3: Implementar o serviço**

Create `componentes/Missoes/Logistica/sinais/Edicaorota/Callbacks/EdicaoRotaDiaEfetivoResolver.php`:

```php
<?php

namespace GalaxiaMissoes\Logistica\sinais\Edicaorota\Callbacks;

/**
 * Resolve o dia efetivo de uma edição de rota consultando o callback
 * registrado para o módulo. Best-effort: qualquer ausência/erro vira null
 * (a edição fica sem dia_efetivo e a listagem cai no fallback).
 *
 * O núcleo genérico fala só com este serviço; nunca conhece Romaneio.
 */
class EdicaoRotaDiaEfetivoResolver
{
    public static function resolver(string $modulo, int $callbackReferenciaId): ?string
    {
        if ($modulo === '' || $callbackReferenciaId <= 0) {
            return null;
        }
        if (!EdicaoRotaCallbackRegistry::temRegistro($modulo)) {
            return null;
        }

        try {
            $callback = EdicaoRotaCallbackRegistry::resolver($modulo);
        } catch (\Throwable $e) {
            error_log('[EdicaoRotaDiaEfetivoResolver] resolver registry: ' . $e->getMessage());
            return null;
        }

        if (!$callback instanceof EdicaoRotaDiaEfetivoResolverInterface) {
            return null;
        }

        try {
            $dia = $callback->resolverDiaEfetivo($callbackReferenciaId);
        } catch (\Throwable $e) {
            error_log('[EdicaoRotaDiaEfetivoResolver] resolverDiaEfetivo: ' . $e->getMessage());
            return null;
        }

        if ($dia === null || $dia === '') {
            return null;
        }
        return substr((string) $dia, 0, 10);
    }
}
```

- [ ] **Step 4: Rodar o teste e ver passar**

Run: `php componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Callbacks/EdicaoRotaDiaEfetivoResolverTddTest.php`
Expected: `TODOS OK`, exit 0.

- [ ] **Step 5: Commit** *(pausar e pedir aprovação)*

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/Callbacks/EdicaoRotaDiaEfetivoResolver.php componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Callbacks/EdicaoRotaDiaEfetivoResolverTddTest.php
git commit -m "feat(edicaorota): add EdicaoRotaDiaEfetivoResolver service + TDD"
```

---

## Task 3: Espelho copia `dia_efetivo` (TDD, unit)

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Edicaorota/Adapters/Solicitacoes/EspelhoSolicitacaoGlobalAdapter.php`
- Test: `componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Adapters/Solicitacoes/EspelhoSolicitacaoGlobalAdapterTddTest.php`

- [ ] **Step 1: Adicionar asserts que falham ao teste existente**

No arquivo `EspelhoSolicitacaoGlobalAdapterTddTest.php`, **depois da linha 27** (após o bloco `$edicaoSemMot`) e **antes** da linha `echo $falhas === 0 ? ...`, inserir:

```php

// --- dia_efetivo ---
$edicaoComDia = (object) ['id' => 70, 'identificador_motorista' => 'Ana', 'dia_efetivo' => '2026-05-20'];
$p3 = EspelhoSolicitacaoGlobalAdapter::montarPayloadCriacao($edicaoComDia);
$assert(($p3['dia_efetivo'] ?? 'AUSENTE') === '2026-05-20', 'dia_efetivo copiado do edicao');

$p4 = EspelhoSolicitacaoGlobalAdapter::montarPayloadCriacao((object) ['id' => 71]);
$assert(array_key_exists('dia_efetivo', $p4) && $p4['dia_efetivo'] === null,
    'sem dia_efetivo no edicao → chave presente e null');
```

- [ ] **Step 2: Rodar o teste e ver falhar**

Run: `php componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Adapters/Solicitacoes/EspelhoSolicitacaoGlobalAdapterTddTest.php`
Expected: FAIL — `[FAIL] dia_efetivo copiado do edicao` (chave ausente no payload atual).

- [ ] **Step 3: Implementar — adicionar `dia_efetivo` ao payload**

Em `EspelhoSolicitacaoGlobalAdapter.php`, no método `montarPayloadCriacao`, o `return` atual (linhas 24-32) deve passar a incluir a chave. Substituir o array de retorno por:

```php
        return [
            'referencia_tipo' => self::REFERENCIA_TIPO,
            'referencia_id'   => $id,
            'tipo'            => 'EDICAO_ROTA',
            'solicitante_id'  => 0,
            'motivo'          => $motivo,
            'status'          => 'pendente',
            'dia_efetivo'     => $edicao->dia_efetivo ?? null,
            'block'           => 1,
        ];
```

- [ ] **Step 4: Rodar o teste e ver passar**

Run: `php componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Adapters/Solicitacoes/EspelhoSolicitacaoGlobalAdapterTddTest.php`
Expected: `TODOS OK`, exit 0.

- [ ] **Step 5: Commit** *(pausar e pedir aprovação)*

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/Adapters/Solicitacoes/EspelhoSolicitacaoGlobalAdapter.php componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Adapters/Solicitacoes/EspelhoSolicitacaoGlobalAdapterTddTest.php
git commit -m "feat(edicaorota): espelho global carrega dia_efetivo"
```

---

## Task 4: `CriarEdicaoRotaLaboratorio` injeta `dia_efetivo`

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Edicaorota/Laboratorios/CriarEdicaoRotaLaboratorio.php`

> Esta classe persiste via Sonda (DB) → não tem TddTest puro; a cobertura é o IntegrationTest da Task 7. Aqui só fazemos a mudança e o `php -l`.

- [ ] **Step 1: Adicionar o `use` do serviço**

Em `CriarEdicaoRotaLaboratorio.php`, após a linha 8 (`use ...CriarEdicaoRotaValidator;`), adicionar:

```php
use GalaxiaMissoes\Logistica\sinais\Edicaorota\Callbacks\EdicaoRotaDiaEfetivoResolver;
```

- [ ] **Step 2: Resolver e injetar `dia_efetivo` no `make()`**

Em `executar()`, logo após a linha `$expiraEm = date('Y-m-d H:i:s', time() + ($tempoMinutos * 60));` (linha 42) e antes de `$sonda = new EdicaoRotaSonda();`, inserir:

```php
            $diaEfetivo = EdicaoRotaDiaEfetivoResolver::resolver(
                (string) $entrada['callback_modulo'],
                (int) $entrada['callback_referencia_id']
            );
```

Depois, no array passado a `$sonda->make([...])`, adicionar a chave `'dia_efetivo'` logo após `'identificador_motorista'`:

```php
                'identificador_motorista' => $entrada['identificador_motorista'] ?? null,
                'dia_efetivo'             => $diaEfetivo,
                'rota_atual'              => json_encode(array_values($entrada['rota_atual'])),
```

- [ ] **Step 3: Sanity check de sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Edicaorota/Laboratorios/CriarEdicaoRotaLaboratorio.php`
Expected: `No syntax errors detected`

- [ ] **Step 4: Commit** *(pausar e pedir aprovação)*

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/Laboratorios/CriarEdicaoRotaLaboratorio.php
git commit -m "feat(edicaorota): CriarEdicaoRota resolve e grava dia_efetivo"
```

---

## Task 5: `RomaneioVeiculoEdicaoRotaCallback` implementa o resolver

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/Edicaorota/Adapters/Romaneio/Callbacks/RomaneioVeiculoEdicaoRotaCallback.php`

> DB-bound (lê `romaneios_veiculos` + `romaneios`). Coberto pelo IntegrationTest da Task 7. Aqui: implementação + `php -l`.

- [ ] **Step 1: Importar `Romaneio` e a interface**

Em `RomaneioVeiculoEdicaoRotaCallback.php`, nas linhas de `use` (5-7), adicionar:

```php
use GalaxiaConstelacoes\Romaneios\Crud\Inteligencias\Romaneio;
use GalaxiaMissoes\Logistica\sinais\Edicaorota\Callbacks\EdicaoRotaDiaEfetivoResolverInterface;
```

(O `use ...RomaneioVeiculo;` e `use ...EdicaoRotaCallbackInterface;` já existem — manter.)

- [ ] **Step 2: Declarar a nova interface na classe**

Alterar a declaração da classe (linha 23) de:

```php
class RomaneioVeiculoEdicaoRotaCallback implements EdicaoRotaCallbackInterface
```

para:

```php
class RomaneioVeiculoEdicaoRotaCallback implements EdicaoRotaCallbackInterface, EdicaoRotaDiaEfetivoResolverInterface
```

- [ ] **Step 3: Implementar `resolverDiaEfetivo`**

Adicionar o método dentro da classe, logo após `aoRejeitar()` (depois da linha 76) e antes de `aplicarSequencia()`:

```php
    /**
     * Dia efetivo da edição = dia_efetivo do romaneio dono do veículo.
     * Caminho: romaneios_veiculos.id → romaneio_id → romaneios.dia_efetivo.
     * Qualquer ausência → null (a listagem cai no fallback de data_cadastro).
     */
    public function resolverDiaEfetivo(int $callbackReferenciaId): ?string
    {
        if ($callbackReferenciaId <= 0) {
            return null;
        }

        $veic = (new RomaneioVeiculo())->findById($callbackReferenciaId);
        $veic = is_array($veic) ? ($veic[0] ?? null) : $veic;
        if (empty($veic) || empty($veic->romaneio_id)) {
            return null;
        }

        $romaneio = (new Romaneio())->findById((int) $veic->romaneio_id);
        $romaneio = is_array($romaneio) ? ($romaneio[0] ?? null) : $romaneio;
        if (empty($romaneio) || empty($romaneio->dia_efetivo)) {
            return null;
        }

        return substr((string) $romaneio->dia_efetivo, 0, 10);
    }
```

- [ ] **Step 4: Sanity check de sintaxe**

Run: `php -l componentes/Missoes/Logistica/sinais/Edicaorota/Adapters/Romaneio/Callbacks/RomaneioVeiculoEdicaoRotaCallback.php`
Expected: `No syntax errors detected`

- [ ] **Step 5: Commit** *(pausar e pedir aprovação)*

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/Adapters/Romaneio/Callbacks/RomaneioVeiculoEdicaoRotaCallback.php
git commit -m "feat(edicaorota): RomaneioVeiculo callback resolve dia_efetivo do romaneio"
```

---

## Task 6: Listagem agrupa por `dia_efetivo` (COALESCE)

**Files:**
- Modify: `componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesSolicitacoes.php:674`

> Cobertura: query de regressão no IntegrationTest da Task 7. Aqui: edição de 1 linha + `php -l`.

- [ ] **Step 1: Trocar o filtro do ramo global**

Na linha 674, substituir:

```php
            $ondeGlobal .= " AND (data_cadastro::date = :dia)";
```

por:

```php
            $ondeGlobal .= " AND (COALESCE(dia_efetivo, data_cadastro::date) = :dia)";
```

(As linhas 673 `if ($diaEfetivo) {` e 675 `$paramsGlobal .= "&dia={$diaEfetivo}";` permanecem inalteradas.)

- [ ] **Step 2: Sanity check de sintaxe**

Run: `php -l componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesSolicitacoes.php`
Expected: `No syntax errors detected`

- [ ] **Step 3: Commit** *(pausar e pedir aprovação)*

```bash
git add componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesSolicitacoes.php
git commit -m "fix(central): solicitação global agrupa por COALESCE(dia_efetivo, data_cadastro)"
```

---

## Task 7: Migrations + Integração ponta-a-ponta (`dev_gx_goldie`)

**Files:**
- Create: `componentes/Missoes/Logistica/sinais/Edicaorota/Migrations/003_dia_efetivo.sql`
- Create: `componentes/Missoes/Logistica/sinais/Solicitacoes/Migrations/002_dia_efetivo.sql`
- Test: `componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Adapters/Romaneio/RomaneioDiaEfetivoIntegrationTest.php`

- [ ] **Step 1: Criar a migration do Edicaorota**

Create `componentes/Missoes/Logistica/sinais/Edicaorota/Migrations/003_dia_efetivo.sql`:

```sql
-- =====================================================
-- Migration: dia_efetivo na edição de rota
-- Data: 2026-05-19
-- =====================================================
-- Snapshot do dia efetivo do romaneio (resolvido na criação via callback).
-- Nullable, sem DEFAULT: callbacks sem a capability deixam NULL e a
-- listagem cai no fallback de data_cadastro.
-- =====================================================

ALTER TABLE edicoes_rota
    ADD COLUMN IF NOT EXISTS dia_efetivo DATE;
```

- [ ] **Step 2: Criar a migration do Solicitacoes**

Create `componentes/Missoes/Logistica/sinais/Solicitacoes/Migrations/002_dia_efetivo.sql`:

```sql
-- =====================================================
-- Migration: dia_efetivo na solicitação global
-- Data: 2026-05-19
-- =====================================================
-- Copiado do edicoes_rota.dia_efetivo pelo EspelhoSolicitacaoGlobalAdapter.
-- Nullable: outros tipos globais ficam NULL → COALESCE cai em data_cadastro.
-- =====================================================

ALTER TABLE solicitacoes_globais
    ADD COLUMN IF NOT EXISTS dia_efetivo DATE;
```

- [ ] **Step 3: Aplicar as duas migrations em `dev_gx_goldie`**

Run (idempotente — `IF NOT EXISTS`):

```bash
php -r '
require_once __DIR__."/autoload.php";
$pdo = (new \Galaxia\_Controladores\Connect)->getInstance(null, null, "dev_gx_goldie");
if ($pdo->query("SELECT current_database()")->fetchColumn() !== "dev_gx_goldie") { fwrite(STDERR, "DB errado\n"); exit(1); }
$pdo->exec(file_get_contents(__DIR__."/componentes/Missoes/Logistica/sinais/Edicaorota/Migrations/003_dia_efetivo.sql"));
$pdo->exec(file_get_contents(__DIR__."/componentes/Missoes/Logistica/sinais/Solicitacoes/Migrations/002_dia_efetivo.sql"));
echo "migrations aplicadas\n";
'
```

Expected: `migrations aplicadas`

- [ ] **Step 4: Verificar que as colunas existem**

Run:

```bash
php -r '
require_once __DIR__."/autoload.php";
$pdo = (new \Galaxia\_Controladores\Connect)->getInstance(null, null, "dev_gx_goldie");
foreach ([["edicoes_rota"],["solicitacoes_globais"]] as $t) {
  $c = $pdo->query("SELECT data_type FROM information_schema.columns WHERE table_name = ".$pdo->quote($t[0])." AND column_name = '"'"'dia_efetivo'"'"'")->fetchColumn();
  echo $t[0].": ".($c ?: "FALTANDO")."\n";
}
'
```

Expected:
```
edicoes_rota: date
solicitacoes_globais: date
```

- [ ] **Step 5: Escrever o teste de integração ponta-a-ponta**

Create `componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Adapters/Romaneio/RomaneioDiaEfetivoIntegrationTest.php`:

```php
<?php

/**
 * Integração — dia_efetivo da edição de rota = dia_efetivo do romaneio.
 * Roda só em dev_gx_goldie.
 */

if (php_sapi_name() !== 'cli') {
    die("Apenas via CLI\n");
}

$_SERVER['HTTP_HOST']      = 'dev-genesis.goldieapp.com.br';
$_SERVER['REQUEST_METHOD'] = 'GET';

require_once __DIR__ . '/../../../../../../../autoload.php';

if (session_status() === PHP_SESSION_NONE) {
    @session_start();
}
$info = new stdClass();
$info->userData         = (object) ['id' => 1, 'username' => 'integ_test', 'name' => 'Integration Test'];
$info->customerData     = (object) ['id' => 7];
$info->principalEmpresa = (object) ['empresa_id' => 3];
$info->sectorData       = (object) ['id' => 1, 'inteligencia' => 'dev_gx_goldie'];
$_SESSION['galaxiaUserInfo']              = $info;
$_SESSION['dashboard']['galaxiaUserInfo'] = $info;

use Galaxia\_Controladores\Connect;
use GalaxiaMissoes\Logistica\sinais\Edicaorota\Callbacks\EdicaoRotaCallbackRegistry;
use GalaxiaMissoes\Logistica\sinais\Edicaorota\Adapters\Romaneio\Callbacks\RomaneioVeiculoEdicaoRotaCallback;
use GalaxiaMissoes\Logistica\sinais\Edicaorota\Laboratorios\CriarEdicaoRotaLaboratorio;
use GalaxiaMissoes\Logistica\sinais\Edicaorota\Adapters\Solicitacoes\EspelhoSolicitacaoGlobalAdapter;

class AssertER
{
    public static int $passou = 0;
    public static int $falhas = 0;
    public static function true(bool $c, string $m): void
    {
        if ($c) { self::$passou++; echo "[PASS] {$m}\n"; }
        else    { self::$falhas++; echo "[FAIL] {$m}\n"; }
    }
}

$pdo = (new Connect)->getInstance(null, null, 'dev_gx_goldie');
if ($pdo->query("SELECT current_database()")->fetchColumn() !== 'dev_gx_goldie') {
    fwrite(STDERR, "❌ Não conectou em dev_gx_goldie. Abortando.\n");
    exit(1);
}

echo "====================================================\n";
echo "INTEGRATION — dia_efetivo edição de rota (dev_gx_goldie)\n";
echo "====================================================\n\n";

// Achar um romaneios_veiculos cujo romaneio tenha dia_efetivo
$fix = $pdo->query("
    SELECT rv.id AS rv_id, r.dia_efetivo::text AS dia
    FROM romaneios_veiculos rv
    JOIN romaneios r ON r.id = rv.romaneio_id
    WHERE r.dia_efetivo IS NOT NULL
    ORDER BY rv.id DESC
    LIMIT 1
")->fetch(PDO::FETCH_OBJ);

if (!$fix) {
    echo "⚠️  SKIP: nenhum romaneios_veiculos com romaneio.dia_efetivo em dev_gx_goldie.\n";
    echo "    (sem fixture não dá pra validar o caminho real; rode os TddTests para a lógica pura)\n";
    exit(0);
}

$rvId = (int) $fix->rv_id;
$diaEsperado = substr((string) $fix->dia, 0, 10);
echo "fixture: romaneios_veiculos #{$rvId}  dia_efetivo={$diaEsperado}\n\n";

EdicaoRotaCallbackRegistry::reset();
EdicaoRotaCallbackRegistry::registrar('romaneio_veiculo', RomaneioVeiculoEdicaoRotaCallback::class);

// 1. Resolver direto
$resolvido = (new RomaneioVeiculoEdicaoRotaCallback())->resolverDiaEfetivo($rvId);
AssertER::true($resolvido === $diaEsperado, "resolverDiaEfetivo retorna dia do romaneio ({$resolvido})");
AssertER::true((new RomaneioVeiculoEdicaoRotaCallback())->resolverDiaEfetivo(0) === null,
    'resolverDiaEfetivo(0) → null');

$edicaoId = null;
try {
    // 2. Criar edição → edicoes_rota.dia_efetivo deve bater
    $lab = new CriarEdicaoRotaLaboratorio([
        'callback_modulo'         => 'romaneio_veiculo',
        'callback_referencia_id'  => $rvId,
        'telefone_motorista'      => '11999999999',
        'identificador_motorista' => 'Integ DiaEfetivo',
        'rota_atual'              => [['id' => 1], ['id' => 2]],
    ]);
    AssertER::true(($lab->response['notification_class'] ?? '') === 'success', 'Criar: success');
    $edicaoId = (int) ($lab->response['edicao_id'] ?? 0);
    AssertER::true($edicaoId > 0, 'edicao_id preenchido');

    $row = $pdo->query("SELECT dia_efetivo::text AS d FROM edicoes_rota WHERE id = {$edicaoId}")
               ->fetch(PDO::FETCH_OBJ);
    AssertER::true($row && substr((string) $row->d, 0, 10) === $diaEsperado,
        "edicoes_rota.dia_efetivo = {$diaEsperado} (got " . ($row->d ?? 'null') . ")");

    // 3. Criar espelho → solicitacoes_globais.dia_efetivo deve bater
    $edicaoObj = $pdo->query("SELECT * FROM edicoes_rota WHERE id = {$edicaoId}")->fetch(PDO::FETCH_OBJ);
    EspelhoSolicitacaoGlobalAdapter::criar($edicaoObj);

    $esp = $pdo->query("
        SELECT dia_efetivo::text AS d FROM solicitacoes_globais
        WHERE referencia_tipo = 'edicao_rota' AND referencia_id = {$edicaoId}
        LIMIT 1
    ")->fetch(PDO::FETCH_OBJ);
    AssertER::true($esp && substr((string) $esp->d, 0, 10) === $diaEsperado,
        "solicitacoes_globais.dia_efetivo = {$diaEsperado} (got " . ($esp->d ?? 'null') . ")");

    // 4. Regressão COALESCE: aparece sob o dia do romaneio, NÃO sob a data de criação
    $apareceNoDiaRomaneio = (int) $pdo->query("
        SELECT COUNT(*) FROM solicitacoes_globais
        WHERE referencia_tipo = 'edicao_rota' AND referencia_id = {$edicaoId}
          AND COALESCE(dia_efetivo, data_cadastro::date) = '{$diaEsperado}'
    ")->fetchColumn();
    AssertER::true($apareceNoDiaRomaneio === 1, 'aparece sob o dia_efetivo do romaneio');

    if ($diaEsperado !== date('Y-m-d')) {
        $apareceHoje = (int) $pdo->query("
            SELECT COUNT(*) FROM solicitacoes_globais
            WHERE referencia_tipo = 'edicao_rota' AND referencia_id = {$edicaoId}
              AND COALESCE(dia_efetivo, data_cadastro::date) = '" . date('Y-m-d') . "'
        ")->fetchColumn();
        AssertER::true($apareceHoje === 0, 'NÃO aparece sob a data de criação (hoje)');
    } else {
        echo "[INFO] dia do romaneio == hoje; pulando o anti-asserção de data de criação\n";
    }
} finally {
    // cleanup
    if ($edicaoId) {
        $pdo->exec("DELETE FROM solicitacoes_globais WHERE referencia_tipo = 'edicao_rota' AND referencia_id = {$edicaoId}");
        $pdo->exec("DELETE FROM edicoes_rota WHERE id = {$edicaoId}");
    }
}

echo "\n====================================================\n";
echo "RESULTADO: ✅ " . AssertER::$passou . "  ❌ " . AssertER::$falhas . "\n";
exit(AssertER::$falhas > 0 ? 1 : 0);
```

- [ ] **Step 6: Rodar o teste de integração**

Run: `php componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Adapters/Romaneio/RomaneioDiaEfetivoIntegrationTest.php`
Expected: `RESULTADO: ✅ <n>  ❌ 0`, exit 0. (Ou `⚠️ SKIP` com exit 0 se não houver fixture de romaneio com `dia_efetivo` — nesse caso registrar e investigar antes de prosseguir.)

- [ ] **Step 7: Rodar a suíte TddTest do sinal (regressão)**

Run:
```bash
for f in componentes/Missoes/Logistica/sinais/Edicaorota/Testes/*/*TddTest.php componentes/Missoes/Logistica/sinais/Edicaorota/Testes/*/*/*TddTest.php; do echo "== $f =="; php "$f" || echo "FALHOU: $f"; done
```
Expected: nenhum `FALHOU:`; cada teste termina OK.

- [ ] **Step 8: Commit** *(pausar e pedir aprovação)*

```bash
git add componentes/Missoes/Logistica/sinais/Edicaorota/Migrations/003_dia_efetivo.sql componentes/Missoes/Logistica/sinais/Solicitacoes/Migrations/002_dia_efetivo.sql componentes/Missoes/Logistica/sinais/Edicaorota/Testes/Adapters/Romaneio/RomaneioDiaEfetivoIntegrationTest.php
git commit -m "feat(edicaorota): migrations dia_efetivo + teste de integração ponta-a-ponta"
```

---

## Verificação final (antes de declarar pronto)

- [ ] Migrations aplicadas em `dev_gx_goldie` (Task 7 Step 4 mostra `date`/`date`).
- [ ] `RomaneioDiaEfetivoIntegrationTest` → `❌ 0` (ou SKIP justificado).
- [ ] Todos os `*TddTest.php` do sinal passam (Task 7 Step 7).
- [ ] `php -l` limpo em todos os arquivos PHP modificados/criados.
- [ ] Nenhum SQL cru de **write** fora de migration/cleanup-de-teste.
- [ ] Núcleo genérico (`CriarEdicaoRotaLaboratorio`, `EspelhoSolicitacaoGlobalAdapter`, `EdicaoRotaDiaEfetivoResolver`) **não** importa nada de `GalaxiaConstelacoes\Romaneios`.

## Self-review do plano (executado)

- **Cobertura da spec:** schema (Task 7) · interface opcional (Task 1) · serviço resolver (Task 2) · Romaneio callback implementa (Task 5) · write path injeta (Task 4) · espelho copia (Task 3) · listagem COALESCE (Task 6) · testes unit+integração (Tasks 2,3,7) · edge cases null→fallback (cobertos nos TddTests da Task 2 e na regressão da Task 7). ✔ Sem lacunas.
- **Placeholders:** nenhum TBD/TODO; todo passo tem código/comando completo. ✔
- **Consistência de tipos:** `EdicaoRotaDiaEfetivoResolverInterface::resolverDiaEfetivo(int): ?string` usado igual em Task 1, 2, 5; `EdicaoRotaDiaEfetivoResolver::resolver(string,int): ?string` igual em Task 2 e 4; chave `dia_efetivo` consistente em make()/payload/SQL. ✔
