# Review — separacao-endereco-wms

**Base**: `b792d225` (checkpoint pré-feature)
**HEAD**: `b9ca647f2` (branch `dev_team`)
**Arquivos tocados (em escopo)**: 20 código + 3 idiomas (`linguagens/{pt-br,en,es}.json`)
**Linhas**: +2168 / -8

> Escopo revisado: apenas os arquivos declarados em `03-backend.md`, `04-frontend.md` e
> `05-testes.md`. Arquivos de `IntegracaoWms/Tradutores`, `Inventariociclico` e
> `PRD/integracao-wms-desacoplada` (sessão paralela) foram **ignorados** por instrução.

## Sumário

Implementação de altíssima aderência ao techplan e aos padrões V2. A "separação v2" nasceu
exatamente como o plano previu: **variante de execução** (`SEPARACAO:WMS`) plugada no
`ExecucaoStrategyFactory` existente, snapshot congelado na criação em `dados_adicionais` (espelhando
`enriquecerCodigosBarras`), zero migration/coluna/tabela nova, hot path preservado (ordenação lê só o
snapshot — provado 0-I/O em teste unit), e nenhum Registry/Adapter novo (correto: consumidor único).
Os três travas do TL foram respeitadas: validação de endereço é gate de front + extrato (não trava
`validarConclusao`), o `obterProximoItem` não ganhou query nova, e a separação por andar continua
byte-a-byte igual quando `variante_separacao` está ausente. i18n completo e realmente traduzido nos 3
idiomas. `php -l` limpo em 18/18 PHP; os 2 testes unit puros passam localmente (25 asserts).
**0 blockers.** Recomendação: **merge**, com 3 majors que entram com TODO (nenhum é de correção
imediata) — o principal é a decisão de bypass do dispatcher no front, que vale reconciliar.

## Issues

### 🔴 Blocker (0 issues)

Nenhum. Sem raw SQL, sem `extends Inteligencia` em classe nova, sem migration (feature é 100%
aditiva sobre colunas JSON existentes — confirmado), controllers/Laboratórios nomeados corretamente,
lógica de negócio em Laboratórios/Strategy (não no controller), i18n nos 3 idiomas, e todo Laboratório
novo tem teste.

### 🟡 Major (3)

#### M1 — Leitura do item via model V1 `Inteligencias\MissaoLogisticaItem` em código novo
**Onde**: `componentes/Missoes/Logistica/sinais/App/Laboratorios/ValidarEnderecoSeparacao.php:6` (import) e `:62-63` (`new MissaoLogisticaItem(); ->find('id = :id', ...)->fetch()`)
**Problema**: O Laboratório novo lê `missao_logistica_itens` pelo model V1 `MissaoLogistica\Inteligencias\MissaoLogisticaItem`, contrariando a memória `feedback_sondas_v2` ("código novo usa só Sondas").
**Por que é major (e não blocker)**: O blocker literal do checklist é *estender* `Inteligencia` — aqui a classe não estende nada disso (é um consumo/leitura). Além disso: (a) é **read-only** (o único write é o `registrarExtrato()` do trait canônico do módulo, não raw SQL); (b) `Inteligencias\MissaoLogisticaItem` é o model dominante de item em todo o módulo MissaoLogistica (`MissoesBipagem`, `MissoesExecucao`, etc.) e **não existe** um Sonda de leitura de item por id na Constelação (o `MissaoLogisticaItemSonda` que existe é read-model do Sinal `Dashboard` — puxá-lo daqui seria acoplamento cross-Sinal pior). Empiricamente funciona (o integration `ValidarEnderecoSeparacaoTddTest` passa 17 asserts).
**Como corrigir**: Aceitável entrar com TODO. Alinhamento V2 ideal, quando houver apetite: expor um `findById`/`buscarPorId` num Sonda de item na própria Constelação `MissaoLogistica/Sondas/` e migrar este consumo (e idealmente os siblings) para ele. Não bloquear o merge por isso — o custo/risco de criar infra de Sonda fora de escopo supera o ganho agora.

#### M2 — Frontend re-implementa o dispatch Galaxia à mão para driblar `atualizarPagina()`
**Onde**: `componentes/Missoes/Logistica/sinais/App/html/separacao/endereco_wms.js:117-179` (`_validar`: monta a URL `systemUrl + '/missao/' + sinalRocket + '/' + sinalRoute`, faz `$.ajax` direto e re-parseia `data-acao-dados`) combinado com `componentes/Missoes/Logistica/sinais/App/App.php:254-263` (o ramo devolve `->atualizarPagina()->feito()`).
**Problema**: O backend copiou o padrão `SolicitarAcessoRetira/Pesagem` incluindo `->atualizarPagina()` (que força `location.reload()` no pipeline padrão). Como um reload por bip de endereço quebra a UX de US5/AC5.3, o front **não** usa `SendGalaxiaPost`/`dispararAcaoGalaxia`: reconstrói a rota do framework na mão a partir de `data-sinalrocket`/`data-sinalroute` e posta via `$.ajax`. Fica uma contradição (backend pede reload, front ignora de propósito) e um acoplamento frágil — se o shape de rota `/missao/{rocket}/{route}` ou o parsing de `data-acao-dados` mudar no framework, isto quebra em silêncio, sem o dispatcher pegar.
**Por que é major**: É uma decisão de arquitetura documentada e funcional (não é bug), mas fura o padrão V2 de dispatch. A própria feature já demonstra o caminho "limpo" para ler resposta fora do pipeline padrão sem reload: o `hookRespostaCoberturaWms()` envolvendo `RequestGalaxiaAct` (`designar_unificado.js:1001-1017`).
**Como corrigir**: Preferir a correção no backend: **remover `->atualizarPagina()`** do ramo `ValidarEnderecoSeparacao` em `App.php` (este endpoint nunca quer reload) e disparar pelo dispatcher padrão — lendo `endereco_confirmado` via o mesmo padrão de wrap de `RequestGalaxiaAct` que a feature já usa para `cobertura_wms`. Entra com TODO se não der para reconciliar agora, mas registrar o débito.

#### M3 — Gate + badge só ativam na fila única; modo livre degrada sem a variante (gap vs. US5)
**Onde**: `componentes/Missoes/Logistica/sinais/App/html/separacao/endereco_wms.js:21-29` (doc) e `04-frontend.md` §"Limitação conhecida".
**Problema**: O badge de endereço e o gate "bipe o endereço antes do produto" só aparecem no modo **fila única / Luz Dinâmica** (card server-renderizado por item). No **modo livre** (`SeparaçãoV2` em `separacao/item.js`), o card é montado a partir de `jsItensData` (`MissaoPresenter::montarJsonFrontendBase`, fora de `html/`/`forms/`), que ainda não carrega `origem_localizacao`/`endereco_wms` — então uma missão criada com a variante WMS é separada **sem** validação de endereço nesse modo. US5 não restringe o gate à fila única.
**Por que é major (e não blocker)**: A degradação é **segura** — não há regressão (AC8 intacto) e a validação de endereço é, por design, um gate soft (extrato + front, nunca trava de conclusão — RN9/Risco#2 do techplan), então a ausência não corrompe nada nem trava a fila. Mas é um gap de escopo real frente a US5 que o PO deve assinar.
**Como corrigir**: Entrar com TODO **rastreado**: repassar `dados_adicionais.origem_localizacao`/`endereco_wms` em `MissaoPresenter::montarJsonFrontendBase()` (o dev estimou 1-2 linhas) e ligar o mesmo gate no fluxo `SeparaçãoV2` — o `EnderecoWmsGate._patchScanners()` já tenta envolver `SeparaçãoV2.processarScan`, então falta só o dado chegar ao card do modo livre. Confirmar com o PO se o MVP pode sair com o gate só na fila única.

### 🟢 Minor (3)

#### m1 — `resolverMapa()` lê o mapa inteiro do tenant e filtra em PHP
**Onde**: `componentes/Missoes/Logistica/sinais/AlocacaoWms/Laboratorios/ResolverEnderecoProdutoLaboratorio.php:56` (`mapaLocalizacaoConfirmadaPorProduto()` sem filtro de produto, filtragem por `$ids` no loop `:59-64`).
**Problema**: A Sonda devolve todas as alocações confirmadas do tenant e o Laboratório filtra os `produto_id` pedidos em memória. Para tenants com base WMS grande, é um read amplo.
**Nota**: Roda **na criação** (não no hot path do `obterProximoItem`) e o techplan aceitou explicitamente essa forma ("A Sonda expõe o mapa completo... filtramos aqui"). Só registrar como candidato a `mapaLocalizacaoConfirmadaPorProduto(array $ids = [])` no futuro se a criação ficar lenta. Não bloqueia.

#### m2 — `AcaoExtrato::ENDERECO_VALIDADO` sem teste unit dedicado
**Onde**: `componentes/Constelacoes/Logistica/MissaoLogistica/Enums/AcaoExtrato.php` (novo case + 3 arms de `match()`).
**Problema**: O case novo não tem teste unit direto. Mitigado: os 3 `match()` (`descricao`/`icon`/`categoria`) foram atualizados de forma exaustiva (confirmado no diff — sem risco de `UnhandledMatchError`) e o integration `ValidarEnderecoSeparacaoTddTest` chama `registrarExtrato()` com esse case e passa. Risco residual baixo; aceitável.

#### m3 — IDs (não classes) nos hidden inputs do badge
**Onde**: `componentes/Missoes/Logistica/sinais/App/html/separacao/endereco_wms.php:62-63` (`id="data-origem-localizacao"`, `id="data-endereco-wms"`).
**Problema**: São `id`s, que precisam ser únicos no DOM. Funciona porque a fila única renderiza **um** card por vez (e o `#data-item-id`/`#data-missao-id` que o JS lê já seguem essa mesma premissa em `execucao_card.php:367-368`). Se algum dia dois cards coexistirem na página, o gate lê o primeiro. Consistente com o padrão vizinho, mas vale saber do limite.

### ⚪ Nit (2)

#### n1 — Cor de erro hardcoded sem CSS var
**Onde**: `componentes/Missoes/Logistica/sinais/App/html/separacao/endereco_wms.php:137-139` (`.sep-endereco-wms-hint.erro { color: #ef4444; }`).
**Problema**: Todas as outras cores do partial usam `var(--app-*, #fallback)` (ex.: `var(--app-sucesso, #10b981)`). Só a cor de erro ficou literal. Trocar por algo como `var(--app-erro, #ef4444)` para consistência de tema.

#### n2 — Bloco `<style>` dentro do corpo da função do partial
**Onde**: `componentes/Missoes/Logistica/sinais/App/html/separacao/endereco_wms.php:87-140`.
**Problema**: O `<style>` é re-emitido a cada chamada de `separacaoEnderecoWmsBadge()`. Inofensivo na fila única (um card), mas se o partial for reusado em lista, duplica CSS. Considerar emitir uma vez (guard estático) ou mover para a folha do App.

## Pontos fortes

- **Imitou os canônicos V2 com precisão** (`AlocacaoWms`/`Otp`/`Edicaorota`, não `Designacoes` legado): Strategy + snapshot congelado na criação + variante lida de `configuracoes`, exatamente o par de referência que o techplan indicou.
- **Decisão de extensibilidade correta**: reusou o `ExecucaoStrategyFactory` (`SEPARACAO:WMS` = 1 classe + 1 entrada no mapa + derivação de `configuracoes`) em vez de inventar Registry/Adapter — consumidor único, sem over-engineering (alinhado a `genesis-callbacks-adapters`).
- **Hot path realmente preservado**: `SeparacaoWmsStrategy` herda o `enriquecerItens` no-op do Base e ordena só pelo snapshot (`aplicarOrdenacao` via `strnatcasecmp`, legado ao fim) — o teste unit prova 0-I/O por construção (`require autoload` + classes, sem DB).
- **Snapshot sem clobber**: `enriquecerLocalizacaoWms` espelha byte-a-byte o merge defensivo de `enriquecerCodigosBarras` (mesmo guard `if (!is_array($dados))`), preservando `codigos_barras`/`produto_nome` já gravados — e roda no ponto exato (5.1.5) que o techplan pediu.
- **`AcaoExtrato` à prova de runtime**: os 3 `match()` exaustivos foram todos atualizados no mesmo diff — sem `UnhandledMatchError`.
- **Dispatch do `App.php` idêntico ao padrão**: o ramo `ValidarEnderecoSeparacao` espelha `SolicitarAcessoRetira`/`SolicitarAcessoPesagem` linha a linha.
- **Normalização centralizada**: `ResolverEnderecoProdutoLaboratorio::normalizar()` (trim+upper) é o ponto único reusado tanto no snapshot quanto na validação do bip — evita defasagem de case/espaço (Risco#4 do techplan resolvido).
- **i18n impecável**: 16 chaves novas presentes e **de fato traduzidas** em pt-br/en/es (verificado por md5), com `solicitacoes: ["separacao-endereco-wms"]` — nada de chave só em pt-br nem string visível sem `show()`.
- **Testes bem posicionados**: `Testes/<espelho>/*TddTest.php` com sufixo correto, 65 asserts, e as decisões de escopo (reflection no hook protegido, `produto_id` fictício sem FK) estão documentadas e são razoáveis.
- **Regressão-zero comprovada**: sem a chave `variante_separacao`, factory cai no default, hook faz early-return e o card não emite nada — cobertura de teste explícita para AC1.4/AC8.

## Recomendação

✅ **Merge.** 0 blockers; os 3 majors são débitos rastreáveis, não correções de bloqueio:
- **M2** (bypass do dispatcher) é o que mais vale reconciliar — de preferência tirando `->atualizarPagina()` do ramo no `App.php` e voltando ao dispatcher padrão.
- **M3** (modo livre) precisa de **sign-off do PO** para o MVP sair só com o gate na fila única; o follow-up no `MissaoPresenter` já está mapeado.
- **M1** (model V1) entra com TODO — sem Sonda de item na Constelação, é o pragmático hoje.

A limitação do modo livre (pergunta explícita do TL) é **aceitável e documentada**, não blocker: a validação de endereço é gate soft por design (não trava conclusão), a degradação é sem regressão, e o caminho de correção está escrito. Só exige o aval do PO por ser gap frente a US5.

---

# Ciclo 1 — re-review

**Base**: `2dd905afa` (commit do review original) · **HEAD**: `29f6911b2` (`dev_team`)
**Escopo re-revisado**: só os 8 arquivos de código desta feature + 2 testes novos declarados nas
seções "Correções pós-review (ciclo 1)" de `03-backend.md`/`04-frontend.md`. `IntegracaoWms/Tradutores`,
`Inventariociclico` e `PRD/integracao-wms-desacoplada` (sessão paralela) **ignorados** por instrução.
**Tamanho**: 8 arquivos, +451 / -53.

## Sumário (ciclo 1)

**M1** e **M2** foram resolvidos corretamente. **M3** foi resolvido **só no backend**; a parte de
frontend (o dev optou por implementar o gate no modo livre em vez de entrar com TODO + sign-off do
PO, como o review sugeria) está **escrita mas inerte em runtime**. Ao investigar o wiring novo do
modo livre encontrei um **defeito que impede o gate de endereço de funcionar em QUALQUER modo** — e,
como é pré-existente (nasceu no ciclo 0, não foi pego no review original por ser code-review sem
verificação no browser), ele não é "regressão do ciclo 1", mas **é o que faz o M3 não entregar o que
promete** e derruba também o gate da fila única. Por isso: **1 blocker novo (B1)**. M1/M2 estão
prontos; M3 só fica de pé depois do B1.

Verificações que rodei: `php -l` limpo nos 8 PHP + 2 testes; `node --check` limpo no
`endereco_wms.js`; **teste unit** `MissaoPresenterEnderecoWmsTddTest` -> 14/14 PASS; **teste
integração** `MissaoLogisticaItemSondaTddTest` (dev_gx_goldie) -> **12/12 PASS** (isolamento de
tenant inclusive). O `endereco_confirmado` no `RequestGalaxiaAct`-wrap do M2 é sólido (ver abaixo).
O que **não** consegui provar verde é o runtime do gate no browser — e é justamente ali que está o B1.

## Veredito por major

| Major | Backend | Frontend | Status |
|-------|---------|----------|--------|
| **M1** — model V1 -> Sonda V2 | OK: `MissaoLogisticaItemSonda` na Constelação, boundary idêntico ao V1, 12/12 integração | n/a | **Resolvido** |
| **M2** — dispatcher/reload | OK: `->atualizarPagina()` removido do ramo | OK: `SendGalaxiaPost` + wrap de `RequestGalaxiaAct` (espelha `hookRespostaCoberturaWms`) | **Resolvido** (mas só é exercitado depois do B1) |
| **M3** — gate no modo livre | OK: `MissaoPresenter` expõe `origem_localizacao`/`endereco_wms` no topo, 14/14 unit | FALHA: `_patchModoLivre`/`_patchScanners` dependem de `window['SeparaçãoV2']` = `undefined` | **Backend ok, front inerte -> ver B1** |

## Issues (ciclo 1)

### Blocker (1)

#### B1 — Gate de endereço nunca engata: `window['ExecucaoCard']`/`window['SeparaçãoV2']` são `undefined`
**Onde**: `componentes/Missoes/Logistica/sinais/App/html/separacao/endereco_wms.js:126`
(`const ctrl = window[nome];` no `_patchScanners`), `:147` (`const ctrl = window['SeparaçãoV2'];`
no `_patchModoLivre`) e `:362` (`if (window.ExecucaoCard)` no `_feedback`).
**Problema**: `ExecucaoCard` (`execucao_card.js:8`) e `SeparaçãoV2` (`separacao/item.js:42`) são
declarados como `const` no topo de `<script>` clássicos (ambos incluídos inline via PHP em
`execucao_card.php:1077` e `item.php`). **`const`/`let` no topo de script clássico entram no
*global lexical environment*, NÃO viram propriedade de `window`** — então `window['ExecucaoCard']`
e `window['SeparaçãoV2']` retornam `undefined`. Consequência em cadeia: `_patchScanners` cai no
`return` para os dois controllers -> **`processarScan` nunca é envolvido**; `_patchModoLivre` cai no
`return` -> **`atualizarTelaExecucao` nunca é envolvido** -> `sincronizarItemLivre` **nunca roda**. O
gate não intercepta bip **em nenhum modo**: o produto é bipado direto, sem pedir endereço (US5
inteira não acontece em runtime). Nenhum dos dois controllers chama `EnderecoWmsGate` diretamente,
então não há caminho alternativo — o gate se auto-liga só por esse monkey-patch.
**Por que é blocker (e não "só" o gap de M3 aceito no ciclo 0)**: (a) o review original tratou a
fila única como funcional e o modo livre como o único gap — mas a fila única **também** depende
desse `window[...]` e portanto também não engata; (b) o ciclo 1 construiu **todo** o M3 em cima
desse alicerce e `04-frontend.md` afirma "o gate/badge agora ativa nos DOIS modos", o que não se
sustenta em runtime. Provei o comportamento de `const` vs `window` reproduzindo a semântica de
script clássico com `node:vm` (2 scripts separados no mesmo global: `window.ExecucaoCard` ->
`undefined`, nome nu `ExecucaoCard` -> `object`). Confirmação de 30s no browser: no console de uma
missão de separação, `typeof window.SeparaçãoV2` deve dar `"undefined"` hoje.
**Prova cruzada dentro do próprio diff**: o M2 funciona **porque** `RequestGalaxiaAct` é uma
`async function` de topo (`func.js:1927`) — declaração de função **vai** pro `window` — então
`window.RequestGalaxiaAct` existe e o wrap pega. É o mesmo padrão `window.X` usado para os dois
casos; só funciona no que é `function`, não no que é `const`.
**Como corrigir** (baixo custo, 2 linhas): exportar os controllers ao objeto global —
`window.ExecucaoCard = ExecucaoCard;` ao fim de `execucao_card.js` e
`window.SeparaçãoV2 = SeparaçãoV2;` ao fim de `separacao/item.js`. Alternativa: referenciar por
nome nu com guarda (`typeof SeparaçãoV2 !== 'undefined' ? SeparaçãoV2 : null`) em vez de
`window[...]` — o nome nu **é** visível cross-script (o `vm` confirma). Depois, **validar no
browser** os smoke tests já escritos em `04-frontend.md` (§Smoke test ciclo 1) — hoje eles não têm
como passar. Recomendo tratar o mesmo fix como pré-condição para dar por fechada a fila única do
ciclo 0 também.

### Minor / Observações (3)

#### c1 — `<style>` + i18n do badge duplicados entre as duas funções do partial
**Onde**: `endereco_wms.php` — `separacaoEnderecoWmsSkeletonDinamico()` (novo) re-emite
byte-a-byte o mesmo bloco `<style>` (`.sep-endereco-wms*`) e o mesmo `<script type=application/json>`
de i18n que `separacaoEnderecoWmsBadge()` já tem. São mutuamente exclusivos por card (o branch
`modo_dinamico` dá `return` cedo), então não duplica em runtime — mas são ~60 linhas espelhadas que
vão divergir na próxima edição. Considerar extrair o CSS/i18n para um helper único chamado pelas
duas. Não bloqueia.

#### c2 — Nit n1 do review original ganhou uma segunda cópia
**Onde**: `endereco_wms.php` — a cor de erro hardcoded `.sep-endereco-wms-hint.erro { color: #ef4444; }`
(nit **n1** do review de ciclo 0) foi **copiada** para dentro de `separacaoEnderecoWmsSkeletonDinamico()`.
Agora o mesmo literal está em dois lugares. Ao endereçar n1 (trocar por `var(--app-erro, #ef4444)`),
lembrar de trocar nas duas. Nit.

#### c3 — Dois `MissaoLogisticaItemSonda` com mesmo nome curto em camadas diferentes
**Onde**: novo `Constelacoes/.../MissaoLogistica/Sondas/MissaoLogisticaItemSonda.php` convive com o
já existente `Missoes/.../sinais/Dashboard/Sondas/MissaoLogisticaItemSonda.php` (namespaces distintos
— sem colisão de classe PHP). A colocação nova na **Constelação** é a **correta** (camada de
model/DB compartilhada — `feedback_constelacoes_sem_html`), e a boundary espelha o V1
(`customerControl:true`). Só fica o débito futuro de o Sonda do Dashboard passar a reusar o da
Constelação em vez de manter dois read-models da mesma tabela. Não bloqueia — é o caminho V2 certo.

## Pontos fortes (ciclo 1)

- **M1 impecável**: Sonda no lugar certo (Constelação, não no Sinal), método único `buscarPorId`,
  `propeties.db` definido, boundary de tenant idêntica ao V1, e **teste de integração provando o
  isolamento** (item de outro block -> `null`, 12/12). Exatamente o "Como corrigir" do M1.
- **M2 tecnicamente correto e no padrão**: removeu `->atualizarPagina()` no backend e migrou o front
  para `SendGalaxiaPost` + wrap idempotente de `RequestGalaxiaAct`, espelhando o canônico
  `hookRespostaCoberturaWms`. A decisão de **consumir** a resposta (não delegar) para não disparar
  um toast por bip é bem fundamentada e documentada. O wrap funciona de fato (RequestGalaxiaAct é
  função de topo -> está no `window`).
- **M3 backend sólido e testado**: `montarJsonFrontendBase` expõe os dois campos no topo sem mudar
  assinatura nem regredir os outros Presenters; contrato estável (chave sempre presente, `null`
  quando ausente) coberto por 14 asserts unit incluindo `dados_adicionais` como string JSON crua.
- **Regressão-zero preservada por construção**: `modo_dinamico` default `false` mantém todos os
  ~5 callers de `execucaoCard()` no branch clássico intocado; o watchdog de 10s cobre "sem
  resposta" que o `SendGalaxiaPost` não expõe.

## Recomendação (ciclo 1)

**Voltar pro dev — 1 blocker (B1).**
- **M1**: OK, fechado.
- **M2**: OK, fechado (o mecanismo de dispatch está certo; só não é exercitado enquanto o gate não
  engata — dependência do B1).
- **M3**: backend OK; frontend **não entrega em runtime** até o B1 ser corrigido. O fix é de ~2
  linhas (`window.ExecucaoCard = ...` / `window.SeparaçãoV2 = ...`) + **verificação no browser** dos
  smoke tests já redigidos (que hoje não passariam).
- Minors c1/c2/c3 entram com TODO — nenhum bloqueia.

Observação de processo: o gate de endereço nunca foi validado em browser (nem no ciclo 0). O B1
seria pego em 30s com um smoke test real (`typeof window.SeparaçãoV2` no console). Antes de fechar
qualquer ciclo desta feature, rodar os smoke tests de `04-frontend.md` de verdade — a suíte
automatizada cobre backend/strategy/presenter, mas não toca o wiring JS onde o bug vive.

---

# Ciclo 2 — re-review

**Base**: `9f85df7f1` · **HEAD**: `d0e13f0b2` (`dev_team`)
**Escopo re-revisado**: só os 2 arquivos apontados como fix do B1 —
`html/shared/components/execucao/execucao_card.js` e `html/separacao/item.js`.
`IntegracaoWms/Tradutores`/`Inventariociclico`/`integracao-wms-desacoplada` (sessão paralela)
**ignorados** por instrução.
**Tamanho**: 2 arquivos, +12 / -0 (6 linhas cada, das quais 4 são comentário-âncora ao B1).

## Sumário (ciclo 2)

**B1 resolvido — corretamente e com o custo mínimo prescrito.** O diff é exatamente as 2 linhas que
o ciclo 1 pediu (`window.ExecucaoCard = ExecucaoCard;` e `window.SeparaçãoV2 = SeparaçãoV2;`), cada
uma colocada logo após o objeto fechar e **antes** do respectivo hook de init — puramente aditivas,
sem tocar nenhuma linha do comportamento existente por nome nu, `node --check` limpo nos 3 arquivos.
O gate agora engata de fato no fluxo **fila única / Luz Dinâmica** (o primário), que é onde o B1
mais doía e onde a cadeia de carregamento é 100% síncrona/inline. **0 blockers novos.**

O "risco residual" que o dev documentou (`04-frontend.md` §"Risco residual identificado") é **real e,
na minha leitura do código, mais provável do que 'teórico'** — mas **não é um blocker novo**: afeta
só o **modo livre** (que já era gap aceito com sign-off do PO desde o ciclo 0), degrada de forma
segura (sem regressão, gate soft), e tem fix documentado de baixo custo. Fica como **M4 (major /
follow-up)** com validação obrigatória em browser antes de dar o modo livre por entregue.
**Recomendação: merge do fix do B1.**

## Veredito

| Item | Status |
|------|--------|
| **B1** — `window.ExecucaoCard`/`window.SeparaçãoV2` `undefined` | **RESOLVIDO** para a fila única (fluxo primário). Export presente, ordem de carga síncrona confirmada, zero regressão. |
| **M4 (novo)** — race de cold/warm-load do modo livre | **MAJOR / follow-up.** Não bloqueia o merge; bloqueia declarar o M3-modo-livre como funcional até validar em browser. |

## Confirmação do fix do B1

- **Diff é o "fix mínimo" exato** (`06-review.md` ciclo 1, "Como corrigir", opção 1). `item.js`:
  `window.SeparaçãoV2 = SeparaçãoV2;` inserido após o `const SeparaçãoV2 = {…}` fechar, antes do
  bloco `ValidacaoInicio`. `execucao_card.js`: `window.ExecucaoCard = ExecucaoCard;` inserido após o
  `const ExecucaoCard = {…}` fechar, **antes** do `$(document).ready(...)` que chama
  `ExecucaoCard.init()`. Ordem garante o export antes de qualquer leitura.
- **Nome não-ASCII confere** (`SeparaçãoV2`): o export (`item.js`) e o consumo
  (`endereco_wms.js:147` `window['SeparaçãoV2']`) usam os mesmos code points — sem risco de
  normalização Unicode. `node --check` limpo em `execucao_card.js`, `item.js` e `endereco_wms.js`.
- **Fila única realmente engata agora** (verificado por leitura da cadeia de carga, não por browser):
  `execucao_card.php` inclui `execucao_card.js` (define **e agora exporta** `window.ExecucaoCard`)
  inline/síncrono **antes** de `endereco_wms.js` no mesmo bloco `<script>` por card;
  `EnderecoWmsGate.init()` roda em `$(document).ready` e lê `window.ExecucaoCard` já definido.
  `_patchScanners()` (`endereco_wms.js:126`) enxerga `window['ExecucaoCard']` → envolve
  `processarScan` → gate intercepta o bip. B1 fechado para o fluxo primário.
- **Zero regressão introduzida pelo fix**: as duas linhas são atribuições globais aditivas; tudo que
  já usava `ExecucaoCard`/`SeparaçãoV2` por nome léxico continua idêntico. Diff = 2 hunks, nada mais
  tocado.

## Issues (ciclo 2)

### 🟡 Major (1 — novo)

#### M4 — Modo livre: `EnderecoWmsGate.init()` (ready, sem delay) provavelmente corre antes de `window.SeparaçãoV2` existir (item.js async)
**Onde**: `endereco_wms.js:395-396` (`$(document).ready` chama `init()` **sem** `setTimeout`),
`endereco_wms.js:64-65` (`_patchScanners()`/`_patchModoLivre()`), `:147-149` (lê
`window['SeparaçãoV2']`) e `:121-122` (guard global setado **antes** de checar o controller).
Contexto: `separacao/item.php:740-757` inicializa `SeparaçãoV2` dentro de `$(document).ready` com
`setTimeout(..., 1000)` **exatamente porque** `SeparaçãoV2` (o JS pareado `item.js`, injetado async
por `loadDynamicScripts()`) não está definido em ready-time no modo livre.
**Problema**: O fix do B1 tornou o wiring do modo livre *correto no código* (o export existe), mas
não resolve a **ordem de execução**. `EnderecoWmsGate.init()` roda em `$(document).ready` sem delay;
`window.SeparaçãoV2` só passa a existir depois que `item.js` carrega de forma assíncrona. A própria
espera de **1000ms** que `item.php` usa para o mesmo objeto é a prova, dentro do repo, de que em
ready-time `SeparaçãoV2` costuma **não** existir ainda. Logo `_patchScanners()` e `_patchModoLivre()`
tendem a cair no `return` sem envolver `SeparaçãoV2.processarScan`/`atualizarTelaExecucao`. Pior: o
guard `window.__enderecoWmsGatePatched` (`:122`) é setado **antes** do loop, então mesmo um retry não
re-tentaria `SeparaçãoV2` sem reestruturar o guard para ser por-controller. Resultado provável: o
gate/badge do **modo livre** (o M3 do ciclo 1) continua **inerte** na maioria das cargas — não por
`undefined` de `window` (isso o B1 corrigiu), mas por timing.
**Por que é MAJOR e não blocker novo**: (a) **não atinge o fluxo primário** — a fila única não
depende de `item.js`/`SeparaçãoV2` e engata 100% síncrona com o fix do B1; (b) o modo livre já era
**gap aceito com sign-off do PO** desde o ciclo 0 (M3), então sua não-entrega não é regressão de
escopo comprometido; (c) **degrada com segurança** — sem o patch, o bip vai direto ao produto
(comportamento AC8), e a validação de endereço é gate soft por design (nunca trava
`validarConclusao` — RN9/Risco#2), então nada corrompe nem trava a fila; (d) há **fix documentado e
barato**. Não justifica reter o merge do B1 (fila única) nem re-arquitetar às cegas sem browser.
**Como corrigir** (follow-up, exatamente como o dev delineou): tornar os patches **retentáveis** —
guard **por-controller** (não global) e re-chamar `_patchScanners()`/`_patchModoLivre()` num
`setTimeout(..., ~1200)` dentro de `init()` (depois do prazo de 1000ms que `item.php` já usa pro
mesmo objeto). Entra com TODO **rastreado**, mas com uma pré-condição de processo: o M3-modo-livre
**não pode ser declarado entregue** enquanto o smoke test de browser (abaixo) não passar verde.

## Pontos fortes (ciclo 2)

- **Fix cirúrgico e disciplinado**: exatamente as 2 linhas prescritas, âncora ao `06-review.md B1`
  no comentário, colocação correta (export antes do init), zero colateral. É o tamanho de diff certo
  para um blocker de wiring.
- **Autoconsciência de engenharia exemplar**: o dev *encontrou e documentou* o próprio risco
  residual (race de load-order) em vez de declarar vitória — inclusive apontando o guard global de
  `_patchScanners()` como obstáculo ao retry e propondo o fix por-controller. Isso é o que separa
  "consertei a linha" de "entendi o sistema".
- **Verificação byte-a-byte do nome não-ASCII** (`SeparaçãoV2` via code points) antecipando o
  clássico bug de normalização Unicode NFC/NFD — cuidado acima da média para um fix de 2 linhas.

## Recomendação (ciclo 2)

✅ **Merge do fix do B1.** 0 blockers novos. O blocker do ciclo 1 está fechado para o fluxo primário
(fila única), com prova de ordem de carga e zero regressão.

⚠️ **M4 (race do modo livre) entra como major/follow-up rastreado**, com **duas condições de
processo** antes de dar a feature 100% por concluída:
1. **Validação em browser obrigatória** (nenhum de nós tem runtime aqui): rodar o "Smoke test
   adicional sugerido (ciclo 2)" de `04-frontend.md`, item 2 e **especialmente o item 3 (carga
   fria)**. Checar no console, logo após abrir a tela de modo livre e **antes de interagir**:
   `typeof window.SeparaçãoV2` (esperado `"object"`) **e** `window.__enderecoWmsGateLivrePatched`
   (esperado `true`). Se o segundo ficar `false`/`undefined` com `SeparaçãoV2` já definido, é o M4 se
   materializando — aí o follow-up vira obrigatório antes do sign-off do modo livre.
2. Dada a evidência do `setTimeout(1000)` do próprio `item.php`, **assuma o M4 como provável, não
   raro** — recomendo agendar o fix retentável junto com a validação, não deixá-lo condicionado a
   "se o tester notar".

Para a **fila única** (fluxo primário e o coração do B1): resolvido, pode seguir. O gate do **modo
livre** permanece **não confirmado em runtime** até o smoke test de carga fria passar.
