# Implementação frontend — Movimentação WMS

## Views criadas

- `componentes/Missoes/Logistica/sinais/App/html/operacaowms/movimentacao.php` — tela de execução do operador, 4 estados (iniciar / em_andamento / concluido / erro), fluxo B1
- `componentes/Missoes/Logistica/sinais/App/html/operacaowms/movimentacao.js` — JS pareado, IIFE, expõe `window.MovimentacaoWms`

## Forms

Nenhum form separado nesta feature. A confirmação é feita via AJAX direto do JS (sem `<form>` POST clássico), igual ao inventário de produtos.

## i18n

Chaves adicionadas nas 3 linguagens (`linguagens/pt-br.json`, `linguagens/en.json`, `linguagens/es.json`) — 30 chaves novas:

| Chave md5 | PT-BR | EN | ES |
|---|---|---|---|
| `4d835b6d...` | Bipe a origem | Scan the origin | Escanee el origen |
| `f2de9690...` | Bipe o destino | Scan the destination | Escanee el destino |
| `58aaa1bc...` | Consultando produto... | Searching product... | Consultando producto... |
| `e749f207...` | Produto não encontrado. Verifique o código. | Product not found. Check the code. | Producto no encontrado. Verifique el código. |
| `3ba11902...` | Informe a quantidade | Enter the quantity | Informe la cantidad |
| `f49e7aad...` | Quantidade deve ser maior que zero | Quantity must be greater than zero | La cantidad debe ser mayor que cero |
| `407fa200...` | Origem e destino devem ser diferentes | Origin and destination must be different | Origen y destino deben ser diferentes |
| `2fb2b5e1...` | Informe o endereço de origem | Enter the origin address | Informe la dirección de origen |
| `5e7d5386...` | Informe o produto | Enter the product | Informe el producto |
| `e7383e0d...` | Informe o endereço de destino | Enter the destination address | Informe la dirección de destino |
| `ff424469...` | Confirmando... | Confirming... | Confirmando... |
| `c4724a63...` | Confirmar movimentação | Confirm movement | Confirmar movimiento |
| `52a155ce...` | Movimentação registrada | Movement registered | Movimiento registrado |
| `53539d03...` | Erro ao confirmar movimentação. | Error confirming movement. | Error al confirmar movimiento. |
| `2b2fd1f2...` | Erro de rede. Tente novamente. | Network error. Try again. | Error de red. Inténtelo de nuevo. |
| `894f35ed...` | Carregando movimentos... | Loading movements... | Cargando movimientos... |
| `e0eb96f3...` | Nenhum movimento registrado ainda | No movements registered yet | Ningún movimiento registrado aún |
| `05f6f442...` | Movimentação WMS | WMS Movement | Movimiento WMS |
| `083ecb3c...` | Bipe a origem, o produto, informe a quantidade e bipe o destino | Scan the origin, the product... | Escanee el origen, el producto... |
| `e2e61c23...` | Iniciar Movimentação | Start Movement | Iniciar Movimiento |
| `5906675a...` | Movimentação concluída | Movement completed | Movimiento completado |
| `a33d808d...` | Todos os movimentos foram registrados | All movements have been registered | Todos los movimientos han sido registrados |
| `f9133fbe...` | Movimentos registrados | Registered movements | Movimientos registrados |
| `13c3bac8...` | Finalizar sessão | End session | Finalizar sesión |
| `6e4e2fb0...` | Limpar origem | Clear origin | Limpiar origen |
| `955e1c92...` | Limpar destino | Clear destination | Limpiar destino |
| `e6f48d24...` | Endereço de origem | Origin address | Dirección de origen |
| `adfc1ba1...` | Endereço de destino | Destination address | Dirección de destino |
| `a503845d...` | Sim (> 0) | Yes (> 0) | Sí (> 0) |
| `a5133f36...` | obrigatório > 0 | required > 0 | obligatorio > 0 |

## Endpoints consumidos

Mesmo endpoint das demais operações WMS:

- `POST /missao/Logistica/AlocacaoWms` com `sinalGxData: 'consultarProduto'`
  - payload: `{ missao_id, codigo_barras }`
  - resposta: `{ ok: true, produto: { produto_id, nome, codigo_produto, codigo_barras } }` ou `{ ok: false, erro: "..." }`

- `POST /missao/Logistica/AlocacaoWms` com `sinalGxData: 'confirmar'`
  - payload: `{ missao_id, produto_id|codigo_barras, localizacao_codigo_origem, localizacao_codigo_destino, quantidade, origem_leitura }`
  - ATENÇÃO: não envia `localizacao_codigo` legado
  - resposta: `{ ok: true }` ou `{ ok: false, erro: "mensagem específica" }`

- `POST /missao/Logistica/AlocacaoWms` com `sinalGxData: 'listar'`
  - payload: `{ missao_id }`
  - resposta: `{ ok: true, movimentos|itens: [...] }` — linhas brutas de `wms_movimentos` contendo `localizacao_codigo_origem` e `localizacao_codigo_destino`

## Decisões de UX tomadas

### Tema visual (violeta/índigo)
Cor `#7c3aed` (violeta) para distinguir de:
- âmbar `#d97706` (inventário_produtos)
- teal (inventário_endereço — se existir)
- índigo `#4f46e5` (alocação padrão)

Implementado via variáveis CSS `:root` (sem cor hardcoded na lógica).

### Fluxo B1: heurística de bip por etapa
O scanner determina o campo destino do código bipado pela sequência:
1. Override por toque (`campoAtivo`) vence tudo
2. Origem vazia → vai para origem
3. Origem ok, produto vazio → resolve como produto (consultarProduto)
4. Produto ok, quantidade > 0, destino vazio → vai para destino
5. Produto ok, quantidade não preenchida → toast "Informe a quantidade" + foca no input
6. Todos preenchidos → toast de lembrete "Confirmar movimentação"

### Quantidade > 0 (diferente do inventário)
O inventário aceita `quantidade >= 0`; a movimentação exige `> 0`. O input tem `min="0.001" step="0.001"`. O guard client-side rejeita 0 e vazio com toast específico antes de enviar. O backend (`MovimentacaoAdapter::validarOperacao`) aplica a mesma regra como segunda barreira.

### Guard no clique (5 checagens em ordem)
Mensagens específicas por campo faltante (não uma mensagem genérica):
1. Origem faltando → "Informe o endereço de origem" + shake no campo
2. Produto faltando → "Informe o produto" + shake no campo
3. Quantidade inválida → "Quantidade deve ser maior que zero" + shake no campo qtd
4. Destino faltando → "Informe o endereço de destino" + shake no campo
5. Origem == Destino → "Origem e destino devem ser diferentes" + shake nos dois campos

### Botão Confirmar (habilitação dinâmica)
Habilitado apenas quando: origem ≠ vazia AND produto resolvido AND quantidade > 0 AND destino ≠ vazio AND origem ≠ destino. Desabilitado enquanto `enviando = true`.

### Pós-sucesso (decisão append)
Toast de sucesso → `_limparTudo()` (reseta os 4 campos; foco volta ao scanner) → `_carregarLista()` (recarrega histórico da sessão). Cada confirmação é um movimento independente (append-only).

### Lista da sessão
Exibe linhas brutas de `wms_movimentos` (com `localizacao_codigo_origem` e `localizacao_codigo_destino`) em formato "ORIG → DEST" com badges coloridos (violeta para origem, verde para destino). A lista normaliza tanto arrays diretos quanto objetos agrupados (fallback de compatibilidade com o que `listar` devolver). Filtra por `operacao_tipo === 'movimentacao'` se o endpoint misturar operações.

### i18n via bloco `#mov-i18n`
Espelha o padrão `#inv-i18n` do inventário. Strings declaradas no PHP via `show('texto PT')` (chave = md5), emitidas como JSON no bloco, lidas pelo JS no boot. Fallback hardcoded em inglês no JS caso o bloco não exista.

## Smoke test sugerido

1. **Designação:** Criar Operação WMS, selecionar "Movimentação" no `#select-operacao-wms` → deve aparecer como opção (adapter registrado no factory).
2. **Abrir missão:** `/missao/Logistica/App` → selecionar missão de movimentação → deve renderizar `movimentacao.php` (tela violeta, não âmbar).
3. **Estado iniciar:** missão não-iniciada deve mostrar tela de início com botão "Iniciar Movimentação"; clicar → estado muda para `em_andamento`.
4. **Fluxo completo:** Bip CORREDOR-A-01 (origem) → bip produto (resolve nome) → digitar quantidade 5 → bip CORREDOR-B-02 (destino) → Confirmar → toast "Movimentação registrada: 5" → campos limpos → lista mostra "CORREDOR-A-01 → CORREDOR-B-02 / 5".
5. **Guard origem==destino:** preencher os 4 campos com origem = destino → botão permanece desabilitado; se forçar clique via DevTools → toast "Origem e destino devem ser diferentes" + shake em ambos os campos.
6. **Guard quantidade zero:** digitar 0 no campo quantidade → botão desabilitado; forçar clique → toast "Quantidade deve ser maior que zero".
7. **Produto não encontrado:** bip de código inexistente → toast "Produto não encontrado. Verifique o código." + shake no campo produto; origem permanece preenchida.
8. **Append-only:** confirmar 3 movimentos distintos → lista deve mostrar 3 linhas separadas com pares origem→destino corretos.
9. **i18n:** mudar idioma para EN → todos os rótulos da tela em inglês; ES → espanhol.
10. **Finalizar:** clicar botão de flag (finalizar sessão) → missão finalizada; tela de conclusão exibe total de movimentos.
