# Review — integracao-wms-desacoplada (tradutores `movimentacao` + `inventario` + refactor da base + fixes de bootstrap)

**Base**: `dev_team` @ HEAD `661db59f`-descendente (working tree commitado por sessão paralela em `b792d225a`+3 da feature alheia `separacao-endereco-wms`; **review por conteúdo atual**, não por range — `project_git_workflow`)
**Escopo**: 5 fontes de tradutor (1 base nova + 2 novos + Alocacao refatorado + Factory) + 5 arquivos de teste + 2 fixes de bootstrap (uncommitted) + 3 docs
**`php -l`**: limpo nos 12 PHP tocados (tradutores, testes, cron, HookEstorno) — sweep re-executado aqui
**Suítes reportadas** (não reexecutadas por mim — dependem de `dev_gx_goldie`): sinal 405/0, E2E 108/0, Config 47/0, Migrations 41/0, HookEstorno 4/0 e CronEstorno verdes pós-fix; baseline residual = 12 fails do `ConfirmarAlocacaoValidator` (poluição de fixture da feature paralela, fora de escopo, pinada nas rodadas anteriores)

## Sumário

Entrega madura e coesa. O refactor extraiu `OperacaoWmsOrdemTradutorBase` com o mesmo racional do
`OperacaoAdapterBase` do domínio irmão (defaults compartilhados + hooks abstratos para a diferença real),
tornou `traduzir()` **`final`** (trava o fluxo compartilhado; filho só varia `getOrdemTipo()`+`montarSelecao()`)
e **hoistou o B1 (`requer_aprovacao='0'`) para a base** — um único ponto valendo para os 3 tradutores.
`Movimentacao` ficou uma subclasse fina em paridade com `Alocacao`; `Inventario` traz uma matriz de subtipo
bem desenhada (explícito > inferência; ambíguo/vazio/inválido → **rejeição com motivo, nunca default
silencioso**), coberta exaustivamente por 9 casos de teste. Os 2 fixes de bootstrap do orquestrador são
mínimos, idempotentes, no lugar certo, e ainda **despinam 2 vermelhos pré-existentes** da baseline
(`HookEstorno` fatal, `CronEstorno`). Cobertura E2E real (GRUPOS F/G: criar→dedup→report via HTTP).

Nenhum blocker, nenhum major. O único ajuste que recomendo antes de plugar um WMS real é de **contrato
partner-facing**: o README §4 (entrada) não avisa que `endereco_ref` é informativo em `movimentacao`
(ponto #3 da tarefa) — está documentado internamente, mas não onde o integrador do WMS lê. **Merge.**

## Issues

### 🔴 Blocker (0) · 🟡 Major (0)

Nenhum. Checklist hard varrido: sem raw SQL de write (tradutores só leem via `Produto`/`WmsLocalizacaoSonda`/`MissaoLogistica` e delegam a persistência ao `GestaoMissoesAlocacaoWms`); não estende Inteligencia V1; Factory+enum de mapa fechado mantido; lógica de negócio nos tradutores/Laboratório (não em controller/handler); rejeição sempre com motivo persistido; testes cobrindo cada tradutor novo (unit + E2E). i18n N/A (superfície de webhook/máquina).

### 🟢 Minor (1)

#### m1 — Contrato de entrada (README §4) não avisa que `endereco_ref` é informativo em `movimentacao` (ponto #3)
**Onde**: `IntegracaoWms/README.md:107-129` (§4 "Contrato — entrada"), o exemplo de item traz `"endereco_ref": "<wms_localizacoes.codigo>"` sem ressalva.
**Problema (ponto 3 da tarefa)**: internamente está claríssimo que origem/destino do envelope são **só informativos** em `movimentacao` — a missão nasce endereço-primeiro/sessão livre e o operador escolhe origem×destino na execução (`MovimentacaoOrdemTradutor` docblock:20-27; `14-backend-tradutores.md:45-46,121` tem seção dedicada). Mas o **README** — o documento contract-facing que um integrador de WMS lê — mostra `endereco_ref` no item e **não** diz que, para `movimentacao`, ele não pré-endereça nada (é usado só p/ validação de identidade a montante). Um parceiro pode shipar uma integração esperando movimentação **pré-dirigida** (origem→destino do envelope honrados) e se surpreender. A assimetria fica gritante porque o §5 (saída) **menciona** `localizacao_codigo_origem/destino` no report — reforçando a leitura errada de que a entrada também os honraria.
**Como corrigir**: uma linha no §4 do README: para `alocacao`/`movimentacao`, `endereco_ref` (e um eventual `endereco_destino_ref`) é **informativo/validado, não pré-endereça** — o operador vincula endereços na execução; hoje não há campo de item para pré-fixá-los (mesma limitação do desvio #4 da Fase 3). Barato e evita expectativa quebrada no parceiro. Não bloqueia (comportamento correto e testado; é só doc do contrato).

### ⚪ Nit (2)

- **n1 — Mensagem de rejeição de subtipo INVÁLIDO diz "informe 'subtipo'" embora ele tenha sido informado.** `InventarioOrdemTradutor.php:74-80`: um `subtipo='xyz'` cai na mesma mensagem do caso "indeterminado" ("informe 'subtipo' ('produtos'|'endereco') ... ou envie itens não-ambíguos"). O comportamento é **certo** (rejeita, sem default silencioso — provado no teste seção 9), só a mensagem é um pouco imprecisa para o caso "valor inválido informado". Cosmético; o teste só exige conter "escopo". Se quiser, distinguir "subtipo 'xyz' não reconhecido" do "sem escopo".
- **n2 — Acoplamento tácito: `operacao_tipo` emitido que não esteja em `OperacaoAdapterFactory::operacoesSuportadas()` é rebaixado silenciosamente para `alocacao`.** `Designacoes/.../GestaoMissoesAlocacaoWms.php:106-109` (arquivo compartilhado/alheio): se um tradutor emitir um `operacao_tipo` cujo adapter não esteja registrado, a missão nasce como `alocacao` em vez de ser rejeitada. **Não é disparável hoje** (as 4 operações — `alocacao`/`movimentacao`/`inventario_produtos`/`inventario_endereco` — estão todas registradas pela feature multi-tipo já em HEAD, e os testes G1/F1 fixam `operacao_tipo`), mas é uma dependência implícita dos tradutores nesse feature. Só registro — o comportamento mora no arquivo compartilhado, não nos tradutores.

## Verificação dos pontos de atenção pedidos

1. **Refactor da base / paridade do `Alocacao`** — ✅ `traduzir()` é **`final`** na base e monta o mesmo `$data` de antes (mesmas chaves, `requer_aprovacao='0'`, `resolverProdutoIds`, `localizarMissaoPorReferencia`); `AlocacaoOrdemTradutor` virou subclasse fina (`getOrdemTipo`+`montarSelecao` com `operacao_tipo='alocacao'`). Comportamento idêntico por construção — o `AlocacaoOrdemTradutorTddTest`/GRUPO D não precisaram mudar. **B1 na base** (`OperacaoWmsOrdemTradutorBase.php:72`) vale para os 3, confirmado por assert `requer_aprovacao=0` em cada suíte (tradutor unit + F1 + G1).
2. **`InventarioOrdemTradutor` (matriz de subtipo + executabilidade)** — ✅ explícito vence inferência (`:52-55`); inferência: produto_ref→`produtos`, só endereco_ref→`endereco`, mistura→ambíguo (`inferirSubtipo`); vazio/ambíguo/**inválido** → rejeição com motivo (`:74-80`), sem criar missão — 9 casos no `InventarioOrdemTradutorTddTest` (inclui subtipo inválido não "salvo" por item de produto claro, seção 9). **Executabilidade**: o caminho `inventario_endereco` reusa **exatamente** o `GestaoMissoesAlocacaoWms` que o painel de Designação usa (`buscarItens()` retorna `[]`; escopo em `configuracoes.inventario_alvos` no formato `{tipo:'endereco', ref:<codigo>}` via `resolverAlvosEndereco()`), logo a missão é **estruturalmente idêntica** a uma criada pelo painel → executável pela tela existente por construção. Confirmei que `inventario_alvos` é **write-only/informativo** (nenhum leitor na execução; a execução é sessão livre dirigida por `operacao_tipo`), então não há dependência de formato quebrável. G1 (E2E) prova `operacao_tipo='inventario_endereco'` + missão vinculada + report.
3. **`MovimentacaoOrdemTradutor` (origem/destino informativos)** — ✅ no código e nos docs internos (docblock:20-27; 14-backend §dedicada). **Ressalva**: falta no README partner-facing → **m1**.
4. **Fix do cron (bootstrap)** — ✅ posição correta: `require autoload` (`:40`) → `IntegracaoWms::bootstrap()` (`:54`) → uso das hook strategies (`:150-151`, dentro do loop de tenant). **Idempotente**: `bootstrap()` guarda com `temRegistro('galpao'/'http_wms')` antes de `registrar()`. **Sem efeito colateral**: só popula o registry estático que ninguém mais preenche em CLI; em web o guard evita dupla-registração. O docblock do `bootstrap()` (que afirmava que o cron chamava) agora é verdade. O fix gêmeo no `HookEstornoTddTest` é a mesma chamada para o teste que instancia as strategies direto — **despina o fatal pré-existente da baseline** (ganho líquido).
5. **Factory + tipos** — ✅ registra `alocacao`/`movimentacao`/`inventario`; `separacao` fica fora (bloqueio de **decisão de produto** documentado em `14-backend §"Bloqueio: separacao"` + README §7.1). `TraducaoOrdemFactoryTddTest` confirma os 3 resolvem e `separacao` **ainda lança**; `tiposSuportados()` == exatamente os 3. Receiver seção 6 e E2E B2/B3/GRUPO C migraram para `separacao` como o "tipo sem tradutor" (armadilha móvel documentada). Seção 4b nova prova a **ordem do pipeline**: identidade (`Endereço desconhecido`) barra **antes** do tradutor rodar (motivo não menciona "escopo").

## Pontos fortes

- **Extração da base com `traduzir()` `final`**: tranca o fluxo compartilhado (criação via template method, stub de `$galaxiaRoute`, B1, localização da missão) num só lugar; filhos não conseguem divergir o core, só declaram `getOrdemTipo()`+`montarSelecao()`. Espelha `OperacaoAdapterBase` — escolha V2 consistente com o domínio irmão.
- **B1 hoistado para a base**: a correção do blocker da Fase 3 passou a valer automaticamente para `movimentacao`/`inventario` sem repetição — e é guardada por assert de regressão em três suítes.
- **Matriz de subtipo do inventário**: explícito > inferência, e todo caso indeterminável (vazio/ambíguo/inválido) **rejeita com motivo** em vez de adivinhar — exatamente o "nunca meio pronta" do techplan. Cobertura de 9 casos, incluindo o sutil "subtipo inválido não é salvo por um item de produto claro".
- **Fixes de bootstrap corretos e idempotentes** que, de quebra, **reduzem a baseline vermelha** (HookEstorno fatal + CronEstorno) — o cron real de report agora funciona em CLI (antes, todo push falharia com "provedor não registrado", apesar do docblock afirmar o contrário).
- **E2E real dos novos tipos** (GRUPOS F/G): criar via HTTP assinado → `traduzida` + missão vinculada → dedup não duplica → ciclo de report fecha em `reportada`. Consistente com o padrão do GRUPO D (deriva `AprovacaoGestor` do estado real, não hardcoda).

## Observações (fora do escopo desta rodada)

- **`ConfirmarAlocacaoValidatorTddTest` (12 fails)** segue pré-existente/alheio (poluição de fixture dinâmica da feature multi-tipo), pinada nas rodadas anteriores — não é regressão desta entrega.
- **Bug de sessão do `WebhookEndpointSonda::findByChannel()`** (Api/Sondas, Fase 1) continua valendo como pendência para produção multi-tenant real — smoke `curl` de verdade contra o servidor antes do 1º WMS. Não bloqueia.

## Recomendação

✅ **Merge.** Zero blockers, zero majors. Refactor da base limpo (com `traduzir()` final e B1 centralizado), 2 tradutores novos corretos e bem testados (unit + E2E), matriz de inventário robusta, e fixes de bootstrap que ainda melhoram a baseline. Endereçar o **m1** (uma linha no README §4 sobre `endereco_ref` informativo em `movimentacao`) antes de onboardar um WMS parceiro — é doc de contrato, não código. Nits n1/n2 opcionais. Arquitetura fiel às referências V2 (Otp/OperacaoAdapter) e coerente com as rodadas anteriores desta feature.
