# Validação automática de código de barras + fator na bipagem — Spec

## Contexto e problema

Um produto tem o código de barras principal em `produtos.codigo_barras` e pode ter **múltiplos** códigos adicionais em `produtos_codigos_barras` (EAN unitário, DUN de caixa, etc.), cada um com uma coluna **`fator`** = quantas unidades aquele código representa (ex.: DUN de caixa com `fator = 10`). Essa tabela já é populada pelo ERP via `Produtos/Sincronizacoes/CodigosBarras` (idempotente, testada) e tem a Sonda V2 `ProdutoCodigoBarraSonda`.

Hoje, nas missões de **separação** e **carregamento** (Missão Logística, sinal `App`), a bipagem:

1. **Ignora `produtos_codigos_barras`.** A resolução do código olha apenas 4 campos do próprio produto — `id`, `codigo_barras`, `codigo_alternativo`, `codigo_produto`:
   - Backend: `MissoesBipagem::resolverProduto` (`componentes/Constelacoes/Logistica/MissaoLogistica/Satelites/MissoesBipagem.php:115-143`) e `codigoPertenceAoProduto` (`:176-182`).
   - Frontend: `codigos_validos` montado nos Presenters (`SeparacaoPresenter.php:58`, `MissaoPresenter.php:97`, `PesagemPresenter.php:58`, `html/shared/components/v2/execucao_bipagem.php:106`).
2. **Nunca aplica o `fator`.** Toda bipagem soma `+1`:
   - Backend: `biparItem` chama `alterarQuantidade($item->id, 1, …)` (`MissoesBipagem.php:91`).
   - Frontend: no caminho "item já selecionado", `processarScan` chama `ajustarQuantidade(1, …)` (separação `item.js:2200`, carregamento `item.js:2810`).

**Infra parcial já existe** e deve ser reaproveitada (não recriada):
- O frontend já tem um formato de mapa código→quantidade: `buildCodeMap` consome `item.codigosBarras = { "codigo": quantidade }` → `codeMap[codigo] = {itemId, quantidade}` (separação `item.js:697-737`, carregamento `:881-925`). No caminho "nenhum item selecionado", o scan **já** aplica essa quantidade: `ajustarQuantidade(quantidade, …)` (`:2227` / `:2850`). Porém **os Presenters nunca preenchem `codigosBarras`**, então o mapa fica vazio e tudo cai no formato legado (`codigos_validos`, `+1`).
- `ajustarQuantidade` (`item.js:1971`) **já bloqueia quando excede o teto** do item (`:2002-2012`, item pesável KG é isento) e **já registra 1 evento de extrato** com `quantidade_delta` (`:2092`).
- O backend `alterarQuantidade` (`MissoesBipagem.php:206`) já grava **1 registro** de extrato por chamada, com `delta` no payload (`:285-296`).

Ou seja: o requisito "1 leitura = N unidades, mas **1** registro de log" é naturalmente atendido por uma única chamada com `delta = fator`; o trabalho é **alimentar o fator** (back e front) e **corrigir os pontos que ainda somam `+1` fixo**, validando contra todos os códigos cadastrados.

## Objetivo

Nas missões de separação e carregamento:
1. Validar o código bipado automaticamente contra **todos** os códigos cadastrados do produto — incluindo `produtos_codigos_barras` — mantendo a operação manual.
2. Aplicar o **`fator`**: 1 leitura de um código de `fator = N` soma **N** unidades ao item e gera **1** (um) registro de log, não N.

## Escopo

- ✅ Faz parte:
  - Dois métodos de leitura na `ProdutoCodigoBarraSonda` (resolver por código; listar por produtos), respeitando "Constelação = só model".
  - **Migration versionada idempotente** de `produtos_codigos_barras` (a tabela existe no banco dev e é populada pelo ERP, mas não há migration no repo).
  - Presenters passam a montar `codigosBarras = { "codigo": fator }` por item (códigos do produto = fator 1 + registros de `produtos_codigos_barras`), mantendo `codigos_validos` por compatibilidade.
  - Frontend (`separacao/item.js` **e** `carregamento/item.js`): corrigir o incremento do caminho "item já selecionado" para usar a quantidade/fator do `codeMap` (não `+1`); validar contra as chaves do mapa; garantir validação automática **sempre ligada** nos dois fluxos.
  - Backend `MissoesBipagem`: `resolverProduto` consulta a Sonda para resolver produto + fator; `biparItem` aplica o **fator** como delta; o bloqueio por teto (`validarLimites`) cobre o "fator que excede o pedido"; enriquecer o payload do extrato com `fator`/`tipo_codigo_barras`.
  - Testes: unit/TDD (resolução de fator e bloqueio) + integração da Sonda contra `dev_gx_goldie`.
- ❌ Fora de escopo (confirmado com o solicitante):
  - **Código morto** de códigos de barras: a Inteligência V1 `ProdutoCodigoBarra` (singular, aponta p/ tabela inexistente), o `CodigosBarra/sql/produto_codigo_barra.sql` e o Satélite `ProdutosCodigosBarra` que a consome — **ficam intactos**.
  - **Toggle** para desligar a validação automática numa missão.
  - **Override** explícito para forçar um item com código divergente.
  - Fluxo dedicado de **digitação manual** do código (o input de scan já aceita digitação; não há tela/regra nova para isso).
  - Aplicar fator no **ajuste manual +/-** (esse permanece unitário).
  - Alterar o cadastro/CRUD de códigos de barras ou a sincronização do ERP (já prontos).
  - Conferência (`conferencia*`) e inventário — fora; a feature é separação e carregamento.

## User stories

### US1 — Validação automática contra todos os códigos cadastrados
**Como** operador de separação/carregamento **eu quero** que qualquer código de barras cadastrado do produto (principal, alternativo, SKU **e** os de `produtos_codigos_barras`) seja aceito automaticamente **para** não precisar conferir manualmente se o item bate.

**Critérios de aceite:**
- AC1.1: Bipar um código presente em `produtos_codigos_barras` (do produto do item, no tenant) é aceito e creditado ao item correto.
- AC1.2: Os códigos do próprio produto (`codigo_barras`, `codigo_alternativo`, `codigo_produto`, `id`) continuam aceitos (fator 1).
- AC1.3: Bipar um código que não pertence a nenhum produto da missão é rejeitado com erro (comportamento atual preservado).
- AC1.4: Bipar um código de um produto que existe mas **não** está na missão retorna "produto não faz parte desta missão" (comportamento atual preservado).
- AC1.5: A resolução é multi-tenant: só considera registros do `block` da sessão (via `customerControl`).

### US2 — Fator na bipagem (1 leitura = N unidades)
**Como** operador **eu quero** que ao bipar um código de caixa (ex.: `fator = 10`) o item seja creditado em 10 unidades de uma vez **para** acelerar a separação/carregamento de volumes fechados.

**Critérios de aceite:**
- AC2.1: Bipar 1× um código de `fator = N` soma exatamente **N** à quantidade bipada do item.
- AC2.2: Bipar um código de `fator = 1` (ou código do produto) soma 1, como hoje.
- AC2.3: O fator vale tanto no fluxo online (backend `biparItem`) quanto no modo offline (frontend aplica e envia o `quantidade_delta` correto no lote).
- AC2.4: O feedback visual (número, barra de progresso, som/efeito) reflete o incremento de N de uma vez.

### US3 — Um registro de log por leitura
**Como** gestor/auditor **eu quero** que cada leitura de código gere **1** registro no extrato, com a quantidade resultante **para** o histórico refletir leituras reais (e não N linhas para uma caixa de N).

**Critérios de aceite:**
- AC3.1: Bipar 1× um código de `fator = 10` cria **1** registro de extrato com `delta = 10` (não 10 registros).
- AC3.2: O registro identifica o `codigo_bipado`, o `metodo_entrada = scan` e passa a incluir o `fator` e o `tipo_codigo_barras` aplicados.
- AC3.3: A ação do extrato continua sendo `BIPAGEM_SCAN` (ou a granular já existente conforme o método).

### US4 — Bloqueio quando o fator excede o pedido
**Como** operador **eu quero** que, se uma caixa (fator N) ultrapassar o que falta no item, a leitura seja **bloqueada** **para** não separar/carregar a mais do que o pedido.

**Critérios de aceite:**
- AC4.1: Se `quantidade_bipada + fator > quantidade pedida` (item não pesável), a leitura é rejeitada e **nada** é somado.
- AC4.2: O operador recebe feedback de erro (vibração/som/alerta de volume máximo) coerente com o bloqueio de teto atual.
- AC4.3: Itens pesáveis (KG e afins) seguem sem teto e o fator de caixa **não** se aplica a eles (comportamento atual de peso preservado).
- AC4.4: Após o bloqueio, o operador pode completar com código unitário (fator 1) ou com o ajuste manual +/-.

### US5 — Operação manual preservada
**Como** operador **eu quero** continuar usando os botões **+/-** para ajustar a quantidade **para** cobrir exceções sem depender do leitor.

**Critérios de aceite:**
- AC5.1: Os botões +/- continuam funcionando, somando/subtraindo **1** por toque (o fator não se aplica ao ajuste manual).
- AC5.2: O ajuste manual continua sendo contabilizado como entrada manual (`quantidade_bipada_manual` / `metodo_entrada` manual) e gera seu próprio registro de extrato.

### US6 — Validação automática sempre ligada
**Como** gestor **eu quero** que a validação automática de código valha por padrão em separação **e** carregamento **para** ter comportamento previsível em toda a operação.

**Critérios de aceite:**
- AC6.1: A separação mantém a validação ligada (já é o default).
- AC6.2: O carregamento passa a ter a validação ligada por padrão (hoje o default é desligado em `carregamento/item.js:54`).
- AC6.3: Item sem nenhum código cadastrado, com validação ligada, bloqueia a leitura com mensagem de "produto sem código cadastrado" (comportamento já existente no carregamento; alinhar na separação).

## Personas envolvidas

| Persona | Papel | Ponto de contato |
|---|---|---|
| Operador de separação | Executor (picking) | Bipa itens na tela `separacao/item` |
| Operador de carregamento | Executor (loading) | Bipa itens na tela `carregamento/item` |
| Gestor logístico | Decisor operacional | Acompanha conclusão/divergências |
| ERP / Sincronização | Origem dos dados | Popula `produtos_codigos_barras` (fator) — já existente |
| Time de devs Genesis | Mantenedor | `MissoesBipagem`, Presenters, `item.js`, Sonda |

## Dados / entidades-chave (alto nível)

- **`produtos_codigos_barras`** (Sonda `ProdutoCodigoBarraSonda`, db `gx_goldie`): `id`, `produto_id`, `codigo_barras`, `tipo_codigo_barras` (default `proprio`), **`fator`** (NUMERIC default 1), `block` (tenant), refs ERP (`codigomd5`, `produto_codigomd5`). **Fonte da verdade do fator.**
- **`produtos`** (Inteligência `Produto`): `codigo_barras`, `codigo_alternativo`, `codigo_produto`, `id`, `tipo_venda`/`unidade` (define pesável).
- **`missao_logistica_item`** (`MissaoLogisticaItem`): `quantidade` (pedida), `quantidade_bipada`, `quantidade_bipada_scan`, `quantidade_bipada_manual`, `produto_id`, `status`.
- **`missao_logistica_extrato`** (append-only): 1 registro por evento; payload com `delta`, `codigo_bipado`, `metodo_entrada` → **passa a incluir `fator` e `tipo_codigo_barras`**.

## Regras de negócio

1. **Resolução do código:** casa contra os campos do produto (fator 1) **ou** contra `produtos_codigos_barras` (fator da coluna). Quando o mesmo código existir nos dois, a tabela `produtos_codigos_barras` tem **precedência** no fator.
2. **Fator → delta:** 1 leitura de código de fator N soma N e gera 1 registro de extrato com `delta = N`. Nunca gerar N registros.
3. **Bloqueio por teto:** para itens não pesáveis, se a leitura levaria `quantidade_bipada` acima da `quantidade` pedida, a leitura é **bloqueada** e nada é somado (reaproveita `validarLimites` no back e o teto de `ajustarQuantidade` no front).
4. **Pesáveis (KG/G/MT/L/…):** sem teto; o fator de caixa não se aplica (mantêm o tratamento atual).
5. **Manual (+/-):** sempre unitário; o fator não se aplica.
6. **Validação sempre ligada** em separação e carregamento; sem toggle de desligar.
7. **Multi-tenant:** a resolução de código/fator filtra pelo `block` da sessão (`customerControl`).
8. **Offline-first:** o fator vai ao frontend (no `codigosBarras` por item) para feedback imediato e incremento offline; o backend é a autoridade no online e confia no `quantidade_delta` do lote (mantendo o cap defensivo já existente).
9. **Compatibilidade:** itens sem `produtos_codigos_barras` continuam funcionando com os códigos do produto (fator 1); nenhuma regressão no fluxo atual.

## Métricas de sucesso

- Bipar 1× uma caixa de `fator = 10` num item que pede ≥10 credita 10 e mostra "10/…", com **1** linha no extrato (`delta = 10`).
- Bipar uma caixa de `fator = 10` num item que precisa de 7 é **bloqueado**; completar com 7 unitárias funciona.
- Bipar um código de `produtos_codigos_barras` antes não reconhecido passa a ser aceito; um código de outro produto continua recusado.
- Carregamento passa a recusar código que não pertence ao item (validação ligada), igual à separação.
- Nenhuma regressão para produtos sem códigos adicionais.

## Dúvidas em aberto (para o Tech Lead resolver no techplan)

- [ ] **Local da migration versionada**: `Constelacoes/Produtos/CodigosBarra/Migrations/NNN_*.sql` vs. dentro de um sinal. Definir e incluir índices (`produto_id`, `codigo_barras`, `block`) coerentes com o schema real.
- [ ] **Forma exata de alimentar o front**: confirmar `item.codigosBarras = { codigo: fator }` como contrato; na separação a validação em `item.js:2189` usa `codigos_validos` direto — alinhar para validar pelas chaves do mapa (`_codigosSet`), como o carregamento já faz (`:902`).
- [ ] **Origem do fator na resolução estrita do back** (`codigoPertenceAoProduto`): quando o código bate por campo do produto, fator 1; quando bate pela Sonda, fator da tabela — confirmar a ordem de tentativa e o retorno do fator até `biparItem`.
- [ ] **`bipagemLote` / snapshot**: o cap em `MissoesBipagem.php:441-443` capa no pedido em vez de bloquear. Como o front bloqueia antes, manter o cap como defesa — confirmar que não há divergência de contadores `scan`/`manual`.
- [ ] **Default e fonte de config de `validarCodigo` no carregamento** (`item.js:54` e leitura em `:467-469`): mudar o default e checar se alguma config de backend/`data-attribute` precisa acompanhar.
- [ ] **Chaves do payload do extrato**: nomes finais para `fator` e `tipo_codigo_barras` no payload de `registrarExtrato`.
- [ ] **i18n**: mensagens novas/alteradas (ex.: bloqueio por caixa que excede) passam pelas 3 línguas conforme convenção.
