# Edição de Rota — `dia_efetivo` da solicitação = `dia_efetivo` do romaneio

**Data:** 2026-05-19
**Sinal:** `Logistica/Edicaorota` (+ `Logistica/Solicitacoes`, leitura em `Constelacoes/Logistica/MissaoLogistica`)
**Abordagem escolhida:** A (snapshot no momento da criação da edição, via contrato de callback)

## Problema

Uma edição de rota é espelhada em `solicitacoes_globais` (`tipo = 'EDICAO_ROTA'`) e
listada em `central.php` por `MissoesSolicitacoes::lerSolicitacoes()`. No ramo das
solicitações **globais**, o filtro de data é
`MissoesSolicitacoes.php:674` → `data_cadastro::date = :dia` (data de criação).
Todo o resto da central é agrupado pelo `dia_efetivo` do romaneio.

Consequência: se um romaneio é efetivo em 20/05 mas o motorista submete a edição
em 19/05, a solicitação aparece sob 19/05. O usuário que está em `central.php`
olhando 20/05 (a data efetiva do romaneio) **não vê** a solicitação.

## Objetivo

A solicitação de edição de rota deve carregar um `dia_efetivo` igual ao
`romaneios.dia_efetivo` do romaneio cuja rota está sendo editada, e ser agrupada
por esse dia em `central.php` — exatamente o eixo (`romaneios.dia_efetivo`) que a
central já usa para agrupar todo o resto. Decisão do usuário: a fonte autoritativa
é `romaneios.dia_efetivo` (não `processos_status_movimentacoes.dia_efetivo` nem o
`data_mapa` da UI).

## Restrição arquitetural (V2 desacoplado)

O núcleo genérico — `CriarEdicaoRotaLaboratorio`, `EspelhoSolicitacaoGlobalAdapter`,
`SolicitacaoSonda`, `EdicaoRotaSonda` — **não pode** conhecer `romaneios`.
Apenas o adapter Romaneio (`Adapters/Romaneio/Callbacks/RomaneioVeiculoEdicaoRotaCallback`)
pode tocar nas tabelas de romaneio. A resolução da data efetiva entra pelo
contrato de callback (padrão Registry+Interface), nunca por query do núcleo.

## Design

### 1. Schema (2 migrations versionadas)

- `Edicaorota/Migrations/003_dia_efetivo.sql`
  `ALTER TABLE edicoes_rota ADD COLUMN IF NOT EXISTS dia_efetivo DATE;`
- `Solicitacoes/Migrations/002_dia_efetivo.sql`
  `ALTER TABLE solicitacoes_globais ADD COLUMN IF NOT EXISTS dia_efetivo DATE;`

Coluna **nullable**, sem `DEFAULT`. Não há colunas `block_*` novas (a tabela já
existe). `EdicaoRotaSonda` e `SolicitacaoSonda` são schema-driven
(`self::$fields = []`, `self::$required = []`) — **nenhuma alteração de código de
Sonda é necessária**; basta a coluna existir e a chave ser passada no `make()`.

### 2. Contrato de callback — capability opcional

Nova interface fina, separada da principal (não força implementadores
existentes/futuros a mudar):

```
Edicaorota/Callbacks/EdicaoRotaDiaEfetivoResolverInterface.php
    resolverDiaEfetivo(int $callbackReferenciaId): ?string   // 'Y-m-d' ou null
```

`EdicaoRotaCallbackInterface` permanece intacta. Callbacks que não implementam a
nova interface simplesmente não contribuem com data (resultado: `null`).

`RomaneioVeiculoEdicaoRotaCallback` passa a implementar também a nova interface:

1. `RomaneioVeiculo->findById($callbackReferenciaId)` → pega `romaneio_id`
2. `Romaneio->findById($romaneioId)` → retorna `dia_efetivo`
3. Qualquer falha/ausência → retorna `null` (degrada para fallback)

Tudo via `findById` do model — **nunca SQL cru**.

### 3. Caminho de escrita — `CriarEdicaoRotaLaboratorio`

Após a validação, antes do `EdicaoRotaSonda->make([...])`:

- Se `EdicaoRotaCallbackRegistry::temRegistro($callback_modulo)`, resolver o
  callback; se `instanceof EdicaoRotaDiaEfetivoResolverInterface`, chamar
  `resolverDiaEfetivo((int) $entrada['callback_referencia_id'])`.
- Adicionar `'dia_efetivo' => $diaEfetivo` (pode ser `null`) ao payload do `make()`.

A classe continua sem conhecer Romaneio (fala só com o Registry/Interface).
Resolução envolta em try/catch — falha de resolução não impede criar a edição.

### 4. Caminho de espelho — `EspelhoSolicitacaoGlobalAdapter`

`montarPayloadCriacao(object $edicao)` adiciona:
`'dia_efetivo' => $edicao->dia_efetivo ?? null`.

Cópia pura do snapshot já gravado em `edicoes_rota`. O adapter continua sendo o
único ponto que conhece `solicitacoes_globais` e continua sem conhecer Romaneio.
`sincronizar()` não muda (só mexe em status).

### 5. Caminho de listagem — `MissoesSolicitacoes::lerSolicitacoes()`

Linha 674, ramo global:
`data_cadastro::date = :dia` → `COALESCE(dia_efetivo, data_cadastro::date) = :dia`

EDICAO_ROTA passa a agrupar pelo dia efetivo do romaneio; **todos os outros tipos
globais** (que continuam com `dia_efetivo` NULL) mantêm exatamente o
comportamento atual via o fallback do `COALESCE`.

## Edge cases

| Situação | Resultado |
|---|---|
| Callback não-romaneio (sem resolver) | `dia_efetivo` = NULL → COALESCE cai em `data_cadastro` (comportamento atual) |
| Romaneio sem `dia_efetivo` | resolver retorna NULL → idem acima |
| `romaneios_veiculos` / `romaneios` não encontrado | resolver retorna NULL → idem acima |
| Espelho recriado (idempotência) | `criar()` continua idempotente; payload inclui o mesmo `dia_efetivo` |
| Romaneio muda `dia_efetivo` depois | snapshot é imutável (gravado na criação); comportamento intencional |

## Arquivos afetados

| Arquivo | Mudança |
|---|---|
| `Edicaorota/Migrations/003_dia_efetivo.sql` | **novo** — ADD COLUMN |
| `Solicitacoes/Migrations/002_dia_efetivo.sql` | **novo** — ADD COLUMN |
| `Edicaorota/Callbacks/EdicaoRotaDiaEfetivoResolverInterface.php` | **novo** — interface opcional |
| `Edicaorota/Adapters/Romaneio/Callbacks/RomaneioVeiculoEdicaoRotaCallback.php` | implementa a nova interface |
| `Edicaorota/Laboratorios/CriarEdicaoRotaLaboratorio.php` | resolve e injeta `dia_efetivo` no `make()` |
| `Edicaorota/Adapters/Solicitacoes/EspelhoSolicitacaoGlobalAdapter.php` | copia `dia_efetivo` no payload |
| `Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesSolicitacoes.php:674` | filtro `COALESCE(...)` |

Sem alteração em `EdicaoRotaSonda` / `SolicitacaoSonda` (schema-driven).

## Testes

**Unit / payload (`Edicaorota/Testes/`, `*TddTest.php`):**
- `EspelhoSolicitacaoGlobalAdapter::montarPayloadCriacao` inclui `dia_efetivo`
  copiado de `$edicao->dia_efetivo` (e `null` quando ausente).
- `RomaneioVeiculoEdicaoRotaCallback::resolverDiaEfetivo` — com mock/fixture:
  retorna a data do romaneio; retorna `null` quando `romaneios_veiculos` ou
  `romaneios` ausente / sem `dia_efetivo`.
- `CriarEdicaoRotaLaboratorio`: callback sem o resolver interface → `dia_efetivo`
  não quebra a criação (fica `null`).

**Integração (`dev_gx_goldie`, bootstrap de sessão padrão):**
- Romaneio com `dia_efetivo = D` e um `romaneios_veiculos`; criar edição via
  `CriarEdicaoRotaLaboratorio` com `callback_modulo='romaneio_veiculo'` →
  `edicoes_rota.dia_efetivo = D`.
- Criar espelho → `solicitacoes_globais.dia_efetivo = D`.
- `lerSolicitacoes(dia = D)` retorna a solicitação; `lerSolicitacoes(dia = data
  de criação ≠ D)` **não** a retorna.
- Tipo global sem `dia_efetivo` continua agrupado por `data_cadastro` (regressão).

## Fora de escopo

- Backfill de edições/solicitações já existentes (coluna nasce NULL → fallback).
- Mudança de UI em `central.php` / `missao_solicitacoes.php` (continua exibindo
  `data_cadastro` no card; apenas o agrupamento por dia muda).
- Resolver de data efetiva para outros domínios de callback (Coleta, etc.).
