# Separação por Endereço WMS — Plano técnico

> Skills carregadas: `genesis-arquitetura` (Sinais/Sondas/Laboratórios/Strategies V2),
> `genesis-callbacks-adapters` (decisão: **não** criar novo Registry/Adapter — reusar o
> `ExecucaoStrategyFactory` que já é o ponto de extensão de variantes), `genesis-sondas-db`
> (reuso das Sondas WMS, **sem** migration nova), `genesis-api-handler` (dispatch por
> `sateliteGxData` no `App`), `genesis-views-i18n` (front + `show()` nas 3 línguas).
> Decisão do dono (2026-07-02) aplicada: **na variante WMS o percurso físico do endereço vence
> a prioridade de rota do romaneio** — a ordenação é puramente por código de endereço.

## Resumo de decisão arquitetural

Não nasce Sinal novo, nem `TipoMissao` novo, nem tabela/migration nova. A "separação v2" é uma
**variante de execução** (`SEPARACAO:WMS`) que se pluga no `ExecucaoStrategyFactory` já existente,
marcada por missão em `missao_logistica.configuracoes` (JSON, sem coluna nova). A criação continua
usando o par `SeparacaoStrategy` (designação) / `GestaoMissoesSeparacao`; o snapshot de endereço/origem
por item vai em `dados_adicionais` (JSONB existente, ao lado do snapshot de códigos de barras). A
resolução do endereço reusa `WmsMovimentoSonda::mapaLocalizacaoConfirmadaPorProduto()` e a validação
na execução reusa `WmsLocalizacaoSonda::buscarPorCodigo()`. Padrão V2 dominante: **Strategy + snapshot
congelado na criação + zero I/O no hot path** (a variante ordena por dado já materializado no item).

## Sinal de referência

- **Imitar (fluxo endereço-primeiro / bipagem por estado)**:
  `/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/AlocacaoWms/` — em especial
  `App/html/alocacaowms/item.js` (roteamento do scanner por **estado do card**, não por prefixo do
  código: `estado.enderecoAtivo` → bip é produto; sem endereço → bip é localização),
  `Laboratorios/ConsultarEnderecoLaboratorio.php` (aviso `fonte='local'`),
  `Sondas/WmsMovimentoSonda.php` e `Sondas/WmsLocalizacaoSonda.php`.
- **Imitar (snapshot congelado na criação)**:
  `GestaoMissoesBase::enriquecerCodigosBarras()` em
  `/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/Designacoes/Laboratorios/GestaoMissoesBase.php`
  — mesmo padrão (1 query batch na criação, grava em `dados_adicionais`, execução lê do snapshot).
- **Imitar (variante lida de `configuracoes`)**:
  `OperacaoTipo::fromConfig()` em
  `/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/AlocacaoWms/Enums/OperacaoTipo.php`
  — precedente de resolver comportamento a partir do JSON `configuracoes` da missão.
- **Não imitar**: `SeparacaoEnderecamentoStrategy` **não** é o alvo a copiar apesar do nome —
  ela é a separação **por andar** atual (heurística `identificarAndar()`, roteirização LIFO por
  rota). A variante WMS substitui essa lógica de andar/rota por ordenação por código de endereço.
  Também **não** imitar o legado `SEPARACAO:ENDERECO` do factory (é apelido do andar-based, colisão
  de nome que a spec pediu para evitar — por isso a chave nova é `SEPARACAO:WMS`, não `:ENDERECO`).

## Estrutura proposta

### Novos arquivos

```
componentes/Constelacoes/Logistica/MissaoLogistica/Strategies/Execucao/
└── SeparacaoWmsStrategy.php                 # variante SEPARACAO:WMS (extends SeparacaoExecucaoStrategy)

componentes/Missoes/Logistica/sinais/AlocacaoWms/Laboratorios/
└── ResolverEnderecoProdutoLaboratorio.php   # batch produto_id -> {endereco_wms|null} (wrap do mapa WMS)

componentes/Missoes/Logistica/sinais/App/Laboratorios/
└── ValidarEnderecoSeparacao.php             # valida bip de endereço (ativo + == snapshot) + extrato

componentes/Missoes/Logistica/sinais/App/html/separacao/
├── endereco_wms.js                          # state machine endereço-primeiro (parcial, incluída pelo item.js)
└── endereco_wms.php                         # badge de endereço/origem no card (parcial)

Testes:
componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Strategies/Execucao/
└── SeparacaoWmsStrategyTddTest.php
componentes/Missoes/Logistica/sinais/AlocacaoWms/Testes/Laboratorios/
└── ResolverEnderecoProdutoLaboratorioTddTest.php
componentes/Missoes/Logistica/sinais/App/Testes/Laboratorios/          (criar se não existir)
└── ValidarEnderecoSeparacaoTddTest.php
```

### Arquivos alterados

- `/home/goldie/www/genesis/componentes/Constelacoes/Logistica/MissaoLogistica/Strategies/Execucao/ExecucaoStrategyFactory.php`
  — registrar `'SEPARACAO:WMS' => SeparacaoWmsStrategy::class`; em `createFromMissao()`, quando não
  vier `$ordenacao` e o tipo for `SEPARACAO`, **derivar a variante de `missao->configuracoes`**
  (chave `variante_separacao`). É o que faz a variante persistir e dirigir o **hot path**
  (`ordenarCandidatosStrategy()` chama `createFromMissao($missao)` sem `$ordenacao`).
- `/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/Designacoes/Laboratorios/GestaoMissoesBase.php`
  — adicionar hook `protected function enriquecerLocalizacaoWms(array $itens, array $data): array`
  (default: retorna `$itens` inalterado) e chamá-lo em `processarEnvio()` logo após
  `enriquecerCodigosBarras()` (passo 5.1.5).
- `/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/Designacoes/Laboratorios/GestaoMissoesSeparacao.php`
  — sobrescrever `enriquecerLocalizacaoWms()` (só age quando `variante_separacao == 'WMS'`); em
  `getConfiguracoesAdicionais()` gravar `configuracoes['variante_separacao'] = 'WMS'` quando o gestor
  escolher o modo; expor a contagem de cobertura (itens sem endereço WMS) na resposta de criação.
- `/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/App/App.php`
  — no `start()`, novo ramo `sateliteGxData === 'ValidarEnderecoSeparacao'` que instancia
  `App\Laboratorios\ValidarEnderecoSeparacao` e devolve via `galaxiaResposta(...)` (espelha os ramos
  `SolicitarAcessoRetira` / `SolicitarAcessoPesagem` já existentes).
- `/home/goldie/www/genesis/componentes/Constelacoes/Logistica/MissaoLogistica/Enums/AcaoExtrato.php`
  — novo case `ENDERECO_VALIDADO = 'ENDERECO_VALIDADO'` (registro auditável da validação — RN14/US5.8).
- `/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/App/html/separacao/item.php`
  — no card, ramificar exibição de localização por `dados_adicionais.origem_localizacao`
  (`wms` → badge com código real + selo WMS; `legado` → localização atual + selo legado); emitir
  `data-origem-localizacao` e `data-endereco-wms` para a state machine.
- `/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/App/html/separacao/item.js`
  — incluir a state machine endereço-primeiro (arquivo `endereco_wms.js`) para itens `origem=wms`.
- `/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/Designacoes/html/designar_unificado.php`
  + `/home/goldie/www/genesis/componentes/Missoes/Logistica/sinais/Designacoes/forms/enviar_unificado.php`
  — novo seletor de modo de separação (Andar × Endereço WMS) na criação; input submetido
  `variante_separacao` (default vazio = andar).
- `/home/goldie/www/genesis/linguagens/Distribuicao/pt-br.json` + `en.json` + `es.json`
  — strings novas via `show()` (badge de endereço, selo WMS/legado, erro de bipagem de endereço,
  aviso de fallback, rótulo do modo na criação). Chaves auto-harvest (md5 do texto PT literal;
  ver memória `galaxia_i18n_show_md5`) — **não** inventar chave legível.

## Sondas e schema

**Nenhuma tabela nova, nenhuma Sonda nova, nenhuma migration.** A feature é 100% aditiva sobre
colunas JSON já existentes e Sondas WMS já existentes:

| Dado | Onde mora | Sonda/coluna |
|---|---|---|
| Modo escolhido (variante) | `missao_logistica.configuracoes` (JSONB) → `variante_separacao='WMS'` | — (sem coluna nova) |
| Endereço resolvido + origem (snapshot p/ item) | `missao_logistica_itens.dados_adicionais` (JSONB) → `endereco_wms`, `origem_localizacao` | — |
| Registro da validação na execução | `extrato` da missão (`registrarExtrato` + `AcaoExtrato::ENDERECO_VALIDADO`); opcionalmente `dados_coletados.enderecos_validados[]` | — |
| Resolução "endereço confirmado por produto" | leitura | `WmsMovimentoSonda::mapaLocalizacaoConfirmadaPorProduto()` (existente) |
| Validação "endereço ativo no catálogo" | leitura | `WmsLocalizacaoSonda::buscarPorCodigo()` (existente, só `ativo=1`) |
| Fallback legado | leitura | `produtos.codigo_enderecamento` (via `dados_adicionais.localizacao` já preenchido em `buscarItensDoPedido`) |

**Tenant / `block_*`**: `WmsMovimentoSonda` e `WmsLocalizacaoSonda` já são `customerControl=true`,
então resolução e validação já vêm escopadas por cliente sem trabalho extra — coerente com o
"sem gate por empresa, mas isolado por tenant" da spec.

## Endpoints / fluxo

| Momento | Gatilho | Handler / entrada | Laboratório / Strategy | Auth |
|---|---|---|---|---|
| Criação (snapshot) | envio da missão de separação com `variante_separacao=WMS` | `Designacoes` → `GestaoMissoesSeparacao::enriquecerLocalizacaoWms()` | `ResolverEnderecoProdutoLaboratorio` (batch) | sessão |
| Execução (fila/próximo item) | `obterProximoItem` (hot path) | `MissoesExecucao::ordenarCandidatosStrategy` | `SeparacaoWmsStrategy::ordenarCandidatosParaExecucao` (lê snapshot, **0 query nova**) | sessão |
| Execução (bip de endereço) | scanner em card `origem=wms` sem endereço confirmado | `App::start()` ramo `sateliteGxData='ValidarEnderecoSeparacao'` | `App\Laboratorios\ValidarEnderecoSeparacao` | sessão |
| Execução (bip de produto) | após endereço confirmado | fluxo atual inalterado (`MissoesBipagem`, snapshot códigos + fator) | — (intocado, RN11) | sessão |

Fluxo do card (US5), espelhando `alocacaowms/item.js`:
1. Item `origem=legado` → estado direto `aguardando_produto` (sem bip de endereço — AC5.6).
2. Item `origem=wms` → estado `aguardando_endereco`: o bip é roteado por **estado do card** (AC5.5)
   para `ValidarEnderecoSeparacao`, que (a) resolve `WmsLocalizacaoSonda::buscarPorCodigo` (só ativo,
   AC5.4), (b) compara normalizado com `dados_adicionais.endereco_wms` do item (AC5.2/5.3),
   (c) registra `ENDERECO_VALIDADO` no extrato (US5.8), (d) responde ok/erro. Sucesso → card vira
   `aguardando_produto` e libera a bipagem do produto (mecânica atual). Erro → feedback, não libera.

## Padrões de extensibilidade

**Reusa o Registry existente; não cria um novo.** `ExecucaoStrategyFactory::$strategies` já é um mapa
`TIPO:VARIANTE → Strategy` (padrão V2 de variante). A spec tem **um** fluxo novo, então a extensão
correta e mínima é: 1 classe `SeparacaoWmsStrategy` + 1 entrada no mapa + leitura da variante em
`configuracoes`. **Não** introduzir novo Adapter/Registry (como `Callbacks/Registry` do Edicaorota)
— aquilo existe porque há múltiplos módulos consumindo; aqui há um único consumidor (a fila de
separação). Herança: `SeparacaoWmsStrategy extends SeparacaoExecucaoStrategy extends BaseExecucaoStrategy`
para reaproveitar `carregarDadosDeEnderecamento`/`finalizarOrdenacao`/LIFO sem duplicação.

`ResolverEnderecoProdutoLaboratorio` fica no Sinal `AlocacaoWms` (dono das Sondas WMS) para manter o
conhecimento WMS encapsulado e reutilizável — o `GestaoMissoesSeparacao` (Sinal `Designacoes`) apenas
o consome, exatamente como a designação do AlocacaoWms já consome `WmsMovimentoSonda`. O fallback
legado (`codigo_enderecamento`) é decidido no `GestaoMissoesSeparacao` (concern de separação), não no
Laboratório WMS.

## Ordenação da variante (decisão do dono 2026-07-02)

`SeparacaoWmsStrategy` ordena **puramente pelo código do endereço** do snapshot; ignora
`_prioridade_rota`. Consequência positiva: dispensa `buscarRoteirizacao()` (que faz I/O de
romaneio) no caminho da variante — reforço extra do hot path.
- Itens `origem=wms`: `usort` por `dados_adicionais.endereco_wms` (comparação natural do código —
  `strnatcasecmp`, para `A03-P2-N1` ordenar coerentemente); desempate por nome/id (canônico do Base).
- Itens `origem=legado`: sempre ao **fim** da fila, agrupados à parte (AC4.2), sem intercalar.
- Ler o snapshot dos candidatos já carregados → **nenhuma query nova por pull** (AC2.4/AC4.4).
- Andar (default) intocado: sem `variante_separacao`, o factory resolve a Strategy atual (AC4.3/AC8).

## Ordem de implementação (sequencial)

1. `ExecucaoStrategyFactory` — registrar `SEPARACAO:WMS` + leitura de `variante_separacao` de
   `configuracoes`. Teste unit do factory (variante ⇄ Strategy).
2. `SeparacaoWmsStrategy` — ordenação por código de endereço + legado no fim + 0 I/O. TDD
   `SeparacaoWmsStrategyTddTest`.
3. `ResolverEnderecoProdutoLaboratorio` (Sinal AlocacaoWms) — batch `mapaLocalizacaoConfirmadaPorProduto`.
   TDD `ResolverEnderecoProdutoLaboratorioTddTest`.
4. Hook `enriquecerLocalizacaoWms` em `GestaoMissoesBase` + override em `GestaoMissoesSeparacao`
   (snapshot `endereco_wms`/`origem_localizacao` + contagem de cobertura). Integração de criação.
5. `AcaoExtrato::ENDERECO_VALIDADO` + `App\Laboratorios\ValidarEnderecoSeparacao` + ramo em `App.php`.
   TDD `ValidarEnderecoSeparacaoTddTest` (ativo/inativo, match/mismatch, registro no extrato).
6. Front: `designar_unificado.php`/`enviar_unificado.php` (seletor de modo) + `separacao/item.php`
   e `item.js` (badge + state machine endereço-primeiro) + `endereco_wms.php/.js`.
7. i18n `linguagens/Distribuicao/{pt-br,en,es}.json` (harvest das `show()` novas).

## Riscos e decisões não-óbvias

1. **Factory hoje ignora a variante da missão no hot path** — `ordenarCandidatosStrategy` chama
   `createFromMissao($missao)` sem `$ordenacao`, então sempre cai no default `SEPARACAO`. Mitigação:
   a variante passa a ser derivada de `configuracoes.variante_separacao` **dentro** do factory, de
   forma que a variante persista e dirija a missão do início ao fim (AC1.3) sem tocar os callers.
   Regressão-zero: sem a chave, o comportamento é idêntico ao atual.
2. **Validação de endereço não pode virar trava de conclusão** — US7 (escape) permite concluir por
   ocorrência/pular mesmo sem achar o produto. Decisão: a validação de endereço é **gate de front +
   registro no extrato**, NÃO um bloqueio em `validarConclusao()`. Assim o escape (fluxo de
   ocorrência existente) continua funcionando e nada trava o vínculo missão-operador (AC5.9/AC7.2).
3. **Defasagem do snapshot (realocação no meio da missão)** — aceita no MVP (decisão do PO). O card
   pode apontar endereço antigo; o operador resolve pelo escape (US7). Sem re-resolução por pull
   (preserva hot path). Documentar como evolução futura (re-resolver ao bipar divergente / botão
   "atualizar endereço").
4. **Normalização do código de endereço** — o bip do scanner pode vir com espaços/case diferentes do
   catálogo. Mitigação: normalizar (`trim`+upper) tanto no snapshot quanto na comparação e no
   `buscarPorCodigo`; centralizar a normalização num único ponto do Laboratório.
5. **Ordenação por string do código ≠ percurso físico serpentina** — MVP ordena pelo código
   (`strnatcasecmp`). Se a operação tiver percurso físico que o código não expressa, é refinamento
   pós-MVP (já sinalizado na spec como `(confirmar)`), não bloqueia.
6. **Cobertura parcial** — missão mista `wms`+`legado` é válida (RN4). O aviso na criação é
   informativo e não bloqueia (US3); a contagem sai do próprio passo de resolução (sem query extra).

## Breaking changes

Nenhum. Tudo é aditivo: sem migration, sem coluna nova, sem alterar a Strategy/telas/bipagem da
separação por andar (que continua default e byte-a-byte igual quando `variante_separacao` está
ausente). As Sondas WMS ganham apenas leitura; a mecânica de bipagem de produto e o claim
multi-operador (`SKIP LOCKED`) ficam intocados (RN11/US8).

## Plano de teste

- **Unit (TDD)**:
  - `SeparacaoWmsStrategyTddTest` — ordena por código; legado no fim; não emite query nova; default
    (andar) inalterado quando sem variante.
  - `ResolverEnderecoProdutoLaboratorioTddTest` — produto com/sem alocação confirmada → `wms`/`legado`;
    usa a última confirmada.
  - `ValidarEnderecoSeparacaoTddTest` — endereço inativo rejeita (AC5.4); mismatch rejeita e não
    libera (AC5.3); match libera e grava extrato (US5.8).
  - Factory: `variante_separacao='WMS'` → `SeparacaoWmsStrategy`; ausente → Strategy atual.
- **Integration (contra `dev_gx_goldie`)** — bootstrap com `$_SESSION` na raiz **e** em
  `$_SESSION['dashboard']` (ver memórias `integration_test_session_groups` /
  `feedback_integration_tests`): criação de missão WMS gera snapshot em `dados_adicionais`
  (endereço + origem por item); `obterProximoItem` numa missão WMS devolve fila por endereço sem
  I/O adicional de resolução; validação de endereço registra no extrato.
- **Manual (smoke que o usuário roda)** — criar missão de separação escolhendo "Endereço WMS" com
  base parcial → ver aviso de cobertura; executar no app: bipar endereço (ativo) libera produto,
  endereço errado dá erro, item legado vai direto ao produto; conferir card mostrando `A03-P2-N1`
  com selo WMS vs legado; criar uma missão **sem** o modo e confirmar separação por andar idêntica.

## Out of scope (confirmando a spec)

- **Não grava movimento de saída nem baixa saldo local WMS** — variante é só leitura das tabelas WMS
  (decisão do dono 2026-07-02). Baixa de saldo = evolução futura.
- **Não reserva estoque / anti-oversell** — separação não reserva nem decrementa (reafirma 2026-06-05).
- **Não lê saldo oficial do WMS externo** — sem read API; fonte é o registro local (`fonte='local'`)
  com aviso padrão sempre que saldo for exibido.
- **Não migra nem remove `codigo_enderecamento`** — segue como fonte da v1 e fallback da variante.
- **Não cria gate por empresa** — escolha por missão, disponível a todos.
- **Não mexe em carregamento, conferência, pesagem ou outras missões** — só a separação ganha a variante.
- **Não muda a mecânica de bipagem do produto** (snapshot códigos + fator + `MissoesBipagem`) nem o
  claim multi-operador (`SKIP LOCKED`).
- **Não gerencia layout de armazém** — `wms_localizacoes` é só consultada/validada, não editada.
- **Acréscimos técnicos ao out-of-scope**: sem migration/coluna nova; sem novo Sinal/`TipoMissao`;
  sem novo Registry/Adapter; a validação de endereço não é trava de conclusão (é gate de front +
  extrato), preservando o escape por ocorrência.
```
