# Campo ativo + edição na execução Alocação WMS — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Dar ao operador controle explícito de qual campo (produto/localização) recebe o próximo bipe e permitir trocar um campo já preenchido antes de confirmar.

**Architecture:** Introduz `campoAtivo` explícito (default neutro) e uma função pura `_decidirAlvo(estado)` que centraliza o roteamento; `_processarCodigo` passa a usá-la, com substituição do campo ativo e, no caso neutro+ambos-cheios, troca-por-bipe-de-produto ou toast. Tap em qualquer card ativa o campo; `✕` limpa e ativa. Só interação — sem tocar persistência/backend.

**Tech Stack:** JS/jQuery (item.js), CSS (item.php), node (teste da função pura).

## Global Constraints

- Não mudar lógica de alocação/backend, `_atualizarBotaoConfirmar`, `_enviarSugestao`, confirmar, setters de persistência de valor.
- Bipar um produto conhecido sempre o seleciona (princípio "bipar = selecionar produto"), inclusive com ambos preenchidos.
- Sem botão "recomeçar".
- `.wms-scan-field.ativo` (destaque indigo) já existe no CSS — reusar aplicando a classe.
- Verificação: TDD em `_decidirAlvo` (node) + `node --check` + `php -l` + smoke manual.
- Commits terminam com: `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`

---

## File Structure

- **Modify:** `componentes/Missoes/Logistica/sinais/App/html/alocacaowms/item.js` — `_decidirAlvo`, `_processarCodigo`, `_ativarCampo`/`_bindCamposTap`, setters, `_bindLimparBotoes`, `txt`, `_mostrarToast`, default `campoAtivo`.
- **Modify:** `componentes/Missoes/Logistica/sinais/App/html/alocacaowms/item.php` — CSS `.wms-toast.info`.
- **Create (teste):** `componentes/Missoes/Logistica/sinais/App/Testes/Html/DecidirAlvoTest.js`.

---

## Task 1: Função pura `_decidirAlvo` + TDD

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/App/html/alocacaowms/item.js`
- Create: `componentes/Missoes/Logistica/sinais/App/Testes/Html/DecidirAlvoTest.js`

**Interfaces:**
- Produz: `_decidirAlvo(st)` onde `st = {campoAtivo, produtoCodigo, localizacaoCodigo}` → `'produto' | 'localizacao' | null`.

- [ ] **Step 1: Escrever o teste falhando**

Create `componentes/Missoes/Logistica/sinais/App/Testes/Html/DecidirAlvoTest.js`:

```javascript
'use strict';

/**
 * Testa a função pura _decidirAlvo extraída do item.js real (sem replicar a lógica).
 * Roda com: node <este arquivo>
 */
const fs = require('fs');
const path = require('path');

const itemJs = fs.readFileSync(
    path.join(__dirname, '../../html/alocacaowms/item.js'), 'utf8'
);
const m = itemJs.match(/function _decidirAlvo\s*\([\s\S]*?\n {4}\}/);
if (!m) {
    console.error('[FAIL] _decidirAlvo não encontrado em item.js');
    process.exit(1);
}
eval(m[0]); // define _decidirAlvo no escopo local

let pass = 0, fail = 0;
function check(cond, msg) {
    if (cond) { pass++; console.log('[PASS] ' + msg); }
    else      { fail++; console.log('[FAIL] ' + msg); }
}

check(_decidirAlvo({campoAtivo: null, produtoCodigo: '',    localizacaoCodigo: ''})   === 'produto',     'neutro + produto vazio -> produto');
check(_decidirAlvo({campoAtivo: null, produtoCodigo: 'ABC', localizacaoCodigo: ''})   === 'localizacao', 'neutro + produto cheio -> localizacao');
check(_decidirAlvo({campoAtivo: null, produtoCodigo: 'ABC', localizacaoCodigo: 'A1'}) === null,          'neutro + ambos cheios -> null');
check(_decidirAlvo({campoAtivo: 'produto',     produtoCodigo: 'ABC', localizacaoCodigo: 'A1'}) === 'produto',     'ativo produto (cheio) -> produto');
check(_decidirAlvo({campoAtivo: 'localizacao', produtoCodigo: '',    localizacaoCodigo: ''})   === 'localizacao', 'ativo localizacao -> localizacao');

console.log('\nRESULTADO: PASS=' + pass + ' FAIL=' + fail);
process.exit(fail > 0 ? 1 : 0);
```

- [ ] **Step 2: Rodar e ver falhar**

Run: `node componentes/Missoes/Logistica/sinais/App/Testes/Html/DecidirAlvoTest.js`
Expected: `[FAIL] _decidirAlvo não encontrado em item.js` + exit 1.

- [ ] **Step 3: Adicionar `_decidirAlvo` no item.js**

No `item.js`, inserir a função imediatamente antes de `function _processarCodigo` (atualmente ~linha 141):

```javascript
    // Decide o campo-alvo do próximo código: campo ativo explícito vence;
    // senão, primeiro campo vazio; se ambos preenchidos, neutro (null).
    function _decidirAlvo(st) {
        if (st.campoAtivo === 'produto' || st.campoAtivo === 'localizacao') {
            return st.campoAtivo;
        }
        if (!st.produtoCodigo) return 'produto';
        if (!st.localizacaoCodigo) return 'localizacao';
        return null;
    }

```

- [ ] **Step 4: Rodar e ver passar**

Run: `node componentes/Missoes/Logistica/sinais/App/Testes/Html/DecidirAlvoTest.js`
Expected: `RESULTADO: PASS=5 FAIL=0`

- [ ] **Step 5: Commit**

```bash
git add componentes/Missoes/Logistica/sinais/App/html/alocacaowms/item.js \
        componentes/Missoes/Logistica/sinais/App/Testes/Html/DecidirAlvoTest.js
git commit -m "$(cat <<'EOF'
feat(app): função pura _decidirAlvo (roteamento de campo na Alocação WMS)

Centraliza a decisão do campo-alvo do próximo código (ativo explícito ->
primeiro vazio -> neutro). TDD: DecidirAlvoTest (5/5, node, extrai do real).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
)"
```

---

## Task 2: Wire do modelo de campo ativo (processarCodigo, tap, clear, toast)

**Files:**
- Modify: `componentes/Missoes/Logistica/sinais/App/html/alocacaowms/item.js`
- Modify: `componentes/Missoes/Logistica/sinais/App/html/alocacaowms/item.php` (CSS toast info)

**Interfaces:**
- Consome: `_decidirAlvo` (Task 1), `_buscarItemPorCodigo`, `_selecionarItemPorElemento`, `_setProdutoCodigo`, `_setLocalizacao`, `_limparProduto`, `_limparLocalizacao`, `_toggleInputManual`, `_focarScannerInput`, `_mostrarToast`.
- Produz: `_ativarCampo(tipo)`; comportamento de campo ativo + edição.

Sem teste automatizado novo (DOM/interação); a lógica pura já é coberta na Task 1. Verificação: `node --check` + `php -l` + smoke manual.

- [ ] **Step 1: `campoAtivo` default neutro**

No `item.js`, trocar:
```javascript
        campoAtivo:         'produto', // 'produto' | 'localizacao' | null
```
por:
```javascript
        campoAtivo:         null, // 'produto' | 'localizacao' | null (alvo do próximo código)
```

- [ ] **Step 2: Adicionar `txt.toqueCampo`**

No `item.js`, no objeto `txt`, após `limpoCampo:        'Campo limpo.',` adicionar:
```javascript
        toqueCampo:        'Toque no campo que deseja trocar.',
```

- [ ] **Step 3: Reescrever `_processarCodigo`**

No `item.js`, substituir toda a função `_processarCodigo` atual (do `function _processarCodigo(codigo, origem) {` até o `}` de fechamento) por:

```javascript
    function _processarCodigo(codigo, origem) {
        if (!codigo) return;
        var alvo = _decidirAlvo(estado);

        if (alvo === 'produto') {
            // substitui o produto (se já houver) e re-resolve pela lista
            if (estado.produtoCodigo || estado.produtoId) {
                _limparProduto();
            }
            var encontrado = _buscarItemPorCodigo(codigo);
            if (encontrado) {
                _selecionarItemPorElemento(encontrado);
                estado.produtoOrigem = origem;
            } else {
                _setProdutoCodigo(codigo, origem);
            }
            return;
        }

        if (alvo === 'localizacao') {
            estado.localizacaoCodigo = ''; // permite substituição
            _setLocalizacao(codigo, origem);
            return;
        }

        // alvo === null → ambos preenchidos, neutro:
        // bipe de produto conhecido troca o produto; senão orienta a tocar no campo.
        var encNeutro = _buscarItemPorCodigo(codigo);
        if (encNeutro) {
            _limparProduto();
            _selecionarItemPorElemento(encNeutro);
            estado.produtoOrigem = origem;
        } else {
            _mostrarToast('info', txt.toqueCampo);
        }
    }
```

- [ ] **Step 4: Resetar `campoAtivo` ao preencher (setters)**

No `item.js`, em `_setProdutoCodigo`, após `estado.produtoOrigem  = origem;` adicionar:
```javascript
        estado.campoAtivo = null;
```
Em `_setLocalizacao`, após `estado.localizacaoOrigem = origem;` adicionar:
```javascript
        estado.campoAtivo = null;
```
Em `_selecionarItemPorElemento`, após `estado.produtoOrigem   = estado.produtoOrigem || 'codigo_barras';` adicionar:
```javascript
        estado.campoAtivo = null;
```

- [ ] **Step 5: `_ativarCampo` + `_bindCamposTap` (tocar qualquer card)**

No `item.js`, substituir a função `_bindCamposTap` atual por:

```javascript
    function _ativarCampo(tipo) {
        estado.campoAtivo = tipo;
        $('#campo-produto, #campo-localizacao').removeClass('ativo');
        $('#campo-' + tipo).addClass('ativo');
        _focarScannerInput();
    }

    function _bindCamposTap() {
        $('#campo-produto').on('click', function () {
            _ativarCampo('produto');
            _toggleInputManual('produto');
        });
        $('#campo-localizacao').on('click', function () {
            _ativarCampo('localizacao');
            _toggleInputManual('localizacao');
        });
    }
```

- [ ] **Step 6: `✕` limpa e ativa o campo**

No `item.js`, substituir `_bindLimparBotoes` por:

```javascript
    function _bindLimparBotoes() {
        $('#btn-limpar-produto').on('click', function (e) {
            e.stopPropagation();
            _limparProduto();
            _ativarCampo('produto');
        });
        $('#btn-limpar-localizacao').on('click', function (e) {
            e.stopPropagation();
            _limparLocalizacao();
            _ativarCampo('localizacao');
        });
    }
```

- [ ] **Step 7: Ramo `info` no `_mostrarToast`**

No `item.js`, em `_mostrarToast`, trocar a linha do ícone:
```javascript
        $icone.attr('class', 'bi ' + (tipo === 'sucesso' ? 'bi-check-circle-fill sucesso' : 'bi-x-circle-fill erro'));
```
por:
```javascript
        $icone.attr('class', 'bi ' + (tipo === 'sucesso' ? 'bi-check-circle-fill sucesso' : (tipo === 'info' ? 'bi-info-circle-fill info' : 'bi-x-circle-fill erro')));
```

- [ ] **Step 8: CSS `.wms-toast.info` no item.php**

No `item.php`, após a linha `.wms-toast i.erro    { color: #ef4444; }` adicionar:
```css
    .wms-toast.info    { border-color: rgba(79, 70, 229, 0.4); }
    .wms-toast i.info  { color: #818cf8; }
```

- [ ] **Step 9: Verificar sintaxe**

Run: `node --check componentes/Missoes/Logistica/sinais/App/html/alocacaowms/item.js && php -l componentes/Missoes/Logistica/sinais/App/html/alocacaowms/item.php && echo OK`
Expected: `OK`

Run: `node componentes/Missoes/Logistica/sinais/App/Testes/Html/DecidirAlvoTest.js`
Expected: `RESULTADO: PASS=5 FAIL=0` (a função pura segue íntegra)

- [ ] **Step 10: Smoke manual + commit**

Recarregar Apache (`sudo -n systemctl reload apache2`). No coletor/dev, em `app/alocacaowms/<id>`:
1. Bipar produto → localização sem tocar: continua funcionando (auto-avança).
2. Com ambos preenchidos, tocar no card Produto (fica destacado) → bipar novo produto → substitui.
3. Tocar na Localização → bipar → substitui a localização.
4. Ambos preenchidos, bipar um produto conhecido (sem tocar) → troca o produto; bipar código ambíguo → toast "Toque no campo que deseja trocar.".
5. `✕` limpa o campo e o deixa ativo (próximo bipe recoloca).

```bash
git add componentes/Missoes/Logistica/sinais/App/html/alocacaowms/item.js \
        componentes/Missoes/Logistica/sinais/App/html/alocacaowms/item.php
git commit -m "$(cat <<'EOF'
feat(app): seleção de campo ativo + edição na execução Alocação WMS

Tocar qualquer card o torna alvo do próximo bipe (mantém valor); bipe
substitui o campo ativo. Neutro+ambos-cheios: bipe de produto conhecido
troca; código ambíguo -> toast. ✕ limpa e ativa. Sem tocar backend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
EOF
)"
```

---

## Self-Review

**Spec coverage:**
- `_decidirAlvo` pura + 5 casos (CA 6) → Task 1. ✔
- Bipar em sequência sem tocar (CA 1) → Task 2 (default null + empty-first). ✔
- Tocar card preenchido ativa + substitui (CA 2, 3) → Task 2 (Steps 3, 5). ✔
- Neutro+ambos: produto conhecido troca; ambíguo → toast (CA 4) → Task 2 (Step 3). ✔
- ✕ limpa e ativa (CA 5) → Task 2 (Step 6). ✔
- Confirmar/backend inalterados (CA 7) → nenhuma tarefa toca. ✔

**Placeholder scan:** Sem TBD/TODO; todo passo mostra código completo. ✔

**Type consistency:** `_decidirAlvo(st)` retorna `'produto'|'localizacao'|null` (Task 1) e é consumido em `_processarCodigo` (Task 2 Step 3) com os mesmos valores; `_ativarCampo(tipo)` definido na Step 5 e usado na Step 6; `estado.campoAtivo` lido em `_decidirAlvo` e setado nos setters/clears/tap consistentemente. ✔

**Riscos conhecidos:**
- `_toggleInputManual` alterna (toca 2× no mesmo card oculta o manual). Comportamento existente, aceitável; a ativação (`campoAtivo`/`.ativo`) é aplicada de qualquer forma na Step 5.
- Caminho manual (Enter/blur em `_bindInputManuais`) chama `_setProdutoCodigo`/`_setLocalizacao`/`_selecionarItemPorElemento` direto → o reset de `campoAtivo` nos setters (Step 4) cobre esse caminho também.
