# Review — bipagem-codigo-barras-fator

**Base**: working tree em `dev_team` (sem `main...HEAD` — fluxo sem merge p/ main)
**HEAD**: working tree (modificados rastreados + arquivos novos não rastreados)
**Arquivos tocados**: 10 modificados + 3 novos (2 untracked + Sonda já existente estendida)
**Linhas**: +376 / -48 (rastreados) + ~600 (test novo + migration)

## Sumário

Implementação de altíssima aderência ao techplan e aos padrões V2. A Sonda imita `OtpVerificacaoSonda` à risca (`find()->order()->fetch()`, `customerControl` ligado, sanitização correta no `IN(...)` via `intval`, `floatval` no `fator`); o Satélite propaga o fator pelo objeto que flui (sem estado mutável), consulta a Sonda **antes** dos campos do produto (precedência da tabela correta), não regrediu `validarLimites`/lote, e enriquece o payload do extrato. Presenter monta `codigosBarras` com 1 query/missão (sem N+1). Migration idempotente com `block NOT NULL` sem `DEFAULT`. Front aplica fator via `codeMap[cleanCode].quantidade`, valida por `_codigosSet`, bloqueia AC6.3. **Zero raw SQL em código de produção.** Todos os `php -l`/`node --check`/JSON válidos.

Não há blocker. O único ponto que merece correção antes da PR é o **i18n desconectado** (Major): as 5 chaves foram adicionadas nos 3 idiomas com tradução real, porém **nenhuma está cabeada** ao código — o PHP usa strings interpoladas sem `show()` e com texto que diverge da chave registrada (`%s` vs `{$var}`, maiúscula/minúscula), e o JS usa literais (`'Prod. sem código...'` abreviado) que não batem com a chave `"Produto sem código cadastrado!"`. O techplan previu que as mensagens ficariam hardcoded, mas o inventário i18n entregue dá falsa impressão de cobertura. Recomendação: **Merge após ajustar o i18n** (ou aceitar com TODO explícito) — os resíduos do teste são nits.

## Issues

### 🔴 Blocker (0 issues)

Nenhum.

### 🟡 Major (1)

#### M1 — Chaves i18n adicionadas mas não cabeadas; texto-fonte diverge do código real
**Onde**:
- `linguagens/{pt-br,en,es}.json` (5 chaves novas, hashes `e5743610…`, `53e94261…`, `0faa05ea…`, `1d38f3e5…`, `ade1782b…`)
- `componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesBipagem.php:149,175,287`
- `componentes/Missoes/Logistica/sinais/App/html/separacao/item.js:2218,2228`
- `componentes/Missoes/Logistica/sinais/App/html/carregamento/item.js:2821,2831,2868`

**Problema**: As 5 entradas i18n estão corretas e traduzidas (EN/ES reais, não cópias). Mas **nenhuma é efetivamente usada**:
1. **PHP não chama `show()`** (`grep show( MissoesBipagem.php` = 0). As mensagens são strings com interpolação direta:
   - `:149` → `"Código incorreto! ({$codigo}) não pertence a ..."` vs chave registrada `"Código incorreto! (%s) não pertence a %s"` (placeholder `%s` nunca preenchido por `sprintf`/`show`).
   - `:175` → `"Código inválido: Produto não cadastrado ({$codigo})"` (P maiúsculo) vs chave `"Código inválido: produto não cadastrado (%s)"` (p minúsculo).
   - `:287` → `"Quantidade excedida! Limite: {$qtdEsperada}"` vs chave `"Quantidade excedida! Limite: %s"`.
2. **JS: o `show()` que aparece nesses arquivos é o `.show()` do jQuery** (DOM), não a i18n. Os literais de erro não passam por i18n e, pior, **divergem do texto da chave**:
   - separação/carregamento `:2228/:2831/:2868` → `'Prod. sem código cadastrado!'` (abreviado) vs chave `"Produto sem código cadastrado!"`.
   - carregamento `:2821` → `'Código incorreto!'` (não existe chave), enquanto a chave `"Código não pertence a este item!"` lista o `carregamento/item.js` em `solicitacoes` — mas o carregamento **não** usa esse texto (só a separação `:2218` usa).

**Por que é major (não blocker)**: o comportamento funcional não regrediu (mensagens já eram hardcoded antes desta feature) e o techplan §i18n explicitou "as mensagens hoje estão hardcoded… detalhe de mecânica i18n fica com a skill na implementação". Mas a skill `genesis-views-i18n` manda "toda string visível passa por `show()`" e as 3 línguas devem refletir o texto real. Entregar um dicionário que não casa com o código vira débito ilusório (parece coberto, não está) e quebra a tradução de fato.

**Como corrigir** (escolher uma via, ser consistente):
- **Via mínima (recomendada p/ não estourar escopo)**: alinhar o **texto-fonte** das chaves ao texto literal real do código (ex.: registrar `"Prod. sem código cadastrado!"` e `"Código incorreto!"`/`"Código não reconhecido!"`/`"Código desconhecido!"`), e corrigir os `solicitacoes` (remover `carregamento/item.js` da chave "não pertence a este item"; ele usa "Código incorreto!"). Assim o harvester de i18n e as traduções batem com o que o usuário vê.
- **Via completa (se quiser cabear de verdade)**: no PHP, trocar interpolação por `show()` + `sprintf` — ex.:
  ```php
  return (new Message())->error(sprintf(show('Código inválido: produto não cadastrado (%s)'), $codigo))->render();
  ```
  e padronizar maiúsculas. No JS, se houver helper i18n client-side disponível, usá-lo; senão manter literal mas garantir que a chave registrada == literal.
- Em qualquer via: padronizar o texto de "produto sem código" para **um** literal único nos dois arquivos JS (hoje `'Prod. sem código cadastrado!'`) e refletir nas 3 línguas.

### 🟢 Minor (2)

#### m1 — Resíduo de seed no teste: item inserido com `produto_id` inválido e deletado em seguida
**Onde**: `componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Laboratorios/FatorBipagemIntegrationTest.php:261-303`
**Problema**: Insere um item com `produto_id => $produtoSentinelaId . 999` (concatenação string → id inexistente), com comentários de "Na verdade precisa de produto diferente / Recriando…", e depois deleta esse item em `:302-303`. É um vai-e-volta de seed que ficou no arquivo — gera escrita inútil no banco e confunde quem lê. Não afeta o resultado (o item certo é recriado em `:306-315`).
**Como corrigir**: remover o INSERT de `:262-271` e o DELETE de `:302-303`; criar o `produtoBlqId` (`:277-288`) **antes** e inserir o item de qtd=7 já com o `produto_id` correto. Limpa ~15 linhas.

#### m2 — Expressão morta `… ? 0 : 0` no caso 7
**Onde**: `componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Laboratorios/FatorBipagemIntegrationTest.php:542-544`
**Problema**: `$extratoNrAntes = (int) $pdo->prepare(...)->execute([...]) ? 0 : 0;` — `execute()` retorna bool; o ternário `? 0 : 0` resulta sempre `0` independentemente, e a linha é imediatamente sobrescrita pela contagem correta em `:545-547`. É um no-op que ainda dispara um `execute()` desnecessário.
**Como corrigir**: apagar `:542-544` (a contagem correta já está logo abaixo). Inofensivo, mas é código morto que filtra na revisão.

### ⚪ Nit (2)

#### n1 — `solicitacoes` da chave i18n apontam para arquivo que não usa a string
**Onde**: `linguagens/{pt-br,en,es}.json` chave `e5743610…` ("Código não pertence a este item!")
**Problema**: lista `carregamento/item.js` em `solicitacoes`, mas o carregamento usa `'Código incorreto!'`. (Coberto pela correção de M1; anotado separadamente porque é um detalhe de metadados.)

#### n2 — Migration `block_*` auxiliares nullable diverge da convenção genérica da skill
**Onde**: `componentes/Constelacoes/Produtos/CodigosBarra/Migrations/001_create_produtos_codigos_barras.sql:35-40`
**Problema**: a skill `genesis-sondas-db` mostra `block_empresa_id INT NOT NULL`; aqui `block_empresa_id/usuario/filial/setor` são nullable e só `block` é `NOT NULL`. **Está correto e intencional** — o techplan (§2) e o comentário do arquivo justificam: a tabela já existe no `dev_gx_goldie` populada pelo ERP com esse schema real, e a migration é idempotente (`IF NOT EXISTS`, no-op onde já existe). O regra crítica do `feedback_block_columns` (sem `DEFAULT 1`) **foi respeitada**. Apenas registrando que a divergência é consciente; sem ação.

## Pontos fortes

- **Sonda imita `OtpVerificacaoSonda` perfeitamente** (`ProdutoCodigoBarraSonda.php:143-208`): `find($crit,$params)->order('id ASC')->fetch(false/true)`, `urlencode` nos params, `(int)` no `produtoId`, `customerControl` mantido ligado (AC1.5), `floatval($row->fator)` resolvendo o gotcha do `NUMERIC` vir como string do PDO pgsql. `IN(...)` saneado com `array_filter(array_map('intval', …))` — sem injeção (segue a skill `genesis-sondas-db`).
- **Precedência do fator correta nos dois caminhos** do `resolverProduto` (`MissoesBipagem.php:127-180`): Sonda consultada **antes** dos campos do produto (estrito e amplo) → tabela vence (regra §1/AC1.1). Fallback preserva AC1.2/AC1.3/AC1.4 com as mensagens originais.
- **Propagação sem estado mutável** (decisão travada respeitada): fator anexado ao produto em `resolverProduto`, propagado ao item em `buscarItemDaMissao:208-211`, lido em `biparItem:93-96`. Assinaturas de `alterarQuantidade`/`atualizarItemNoBanco` ganharam `$tipoCodigoBarras = null` no fim → compatível com `adicionarQuantidade`/`reduzirQuantidade` (manual permanece `±1`, US5).
- **`validarLimites` e o lote não regrediram** (`:270-290`, `:454-580`): KG isento, teto bloqueia não-pesável, cap defensivo + `deltaEfetivo` intactos. Manual continua unitário.
- **Presenter sem N+1** (`MissaoPresenter.php:88-90`): 1× `listarPorProdutos` antes do loop; `codigosBarras` montado com campos do produto (fator 1) primeiro e Sonda por último (precedência). `codigos_validos` mantido (compat). `execucao_bipagem.php` preserva `codigosBarras` no merge.
- **Front fiel ao contrato**: `q = codeMap[cleanCode].quantidade || 1` (separação `:2206`, carregamento `:2811`), validação por `_codigosSet` com lazy-init de paridade, guard `Object.keys().length > 0` no `buildCodeMap`, bloqueio AC6.3 com `validarCodigo` ligado, `fator` adicionado ao extrato **sem renomear `quantidade_delta`** (o lote depende dele). Default `validarCodigo=true` no carregamento (`:54` e fallback `:466-468`), `$validarCodigo=true` em `carregamento/item.php` enquanto `execucao_bipagem.php` mantém default `false` p/ preservar `retorno` (fora de escopo) — exatamente como pedido.
- **Teste de integração robusto**: gate `dev_gx_goldie`, `$_SESSION` nos 2 lugares (`feedback_integration_tests`), cleanup em `finally`, dados sentinela com prefixo descartável e `block=999777`. Assertivas checam retorno, estado no banco, **contagem de extrato (1 log/leitura)** e **payload (`delta`/`fator`/`tipo_codigo_barras`)** — cobrindo AC1.1/1.5, AC2.2, AC3.1/3.2, AC4.1/4.3/4.4. Isolamento de tenant (1d) e `fator` float-não-string (2c/2d) testados explicitamente. `referencia_id` (coluna real do extrato, confirmada em `RegistraExtratoTrait`) usado corretamente nas queries — assertivas não passam à toa.

## Recomendação

⚠️ **Voltar ao dev para um ajuste pequeno (i18n M1)** — ou **✅ Merge com TODO** se o time aceitar o dicionário i18n como inventário aspiracional documentado. Não há blocker; o backend/front/teste estão prontos. Sugiro:
1. Corrigir M1 (alinhar texto-fonte das chaves ao literal real e ajustar `solicitacoes`) — é a única coisa que afeta usuário final (tradução).
2. Limpar m1/m2 no teste (opcional, mas barato e melhora legibilidade do TDD).
3. n2 já está justificado; nenhuma ação.
