# Implementacao frontend — bipagem-cota-minima

## Loop de correcao pos-review (06-review.md) — Ciclo 3

### B4 — `buildScanCotaDataForLuz`: `find($id)` sem `->fetch()` corrigido

**Arquivo**: `componentes/Missoes/Logistica/sinais/App/Handlers/SeparacaoHandler.php`
**Metodo**: `buildScanCotaDataForLuz` (linha ~585)

**Diagnostico**: `$mModel->find($missaoId)` passava um `int` diretamente ao
`ModelInteligencia::find()`, que e um query builder e retorna `$this` sem executar
o SELECT. `$this->data` nunca e populado, entao `$missao->configuracoes` vinha
`null`. Consequencia: `MissaoPresenter::montarScanCotaFront($missao)` recebia
`configuracoes=null` → retornava `ativo=false` → todo card re-renderizado apos
a 1a bipagem saía com `data-scan-cota-ativo="0"`, desligando a cota silenciosamente
do 2o card em diante.

**Fix** (1 linha): substituído por WHERE explicito + fetch(), espelhando o padrao
correto ja usado em `:189-192` do mesmo arquivo:

```php
// antes (errado):
$missao = $mModel->find($missaoId);

// depois (correto):
$missao = $mModel->find("id = :id", "id={$missaoId}")->fetch();
```

`fetch()` sem `$all=true` retorna o objeto hydratado diretamente (nao em array),
portanto nenhum desembrulho necessario — diferente do `findById()` que embrulha em
`[$response]`.

**Escopo**: os dois `reencaminheLuz` (`:519` apos bipagem de item e `:563` via
observatorio) e o bloco de `endereco_alocacao` em `Luz_SeparacaoItemCard` (`:386-394`)
todos chamam `buildScanCotaDataForLuz` — os tres caminhos sao corrigidos por esta
unica mudanca.

**`endereco_alocacao` (`:386-394`)**: NAO era um `find()` independente — e o proprio
`buildScanCotaDataForLuz` chamado via `$scanCotaData = $this->buildScanCotaDataForLuz(...)`.
Portanto ja estava coberto pelo fix acima; sem alteracao adicional necessaria.

**php -l**: limpo.

---

## Loop de correcao pos-review (06-review.md) — Ciclo 2

### B1 (ciclo 2) — Fila única: fix movido para o caminho real de renderizacao (Luz)

**Diagnóstico do ciclo 1**: o fix anterior foi aplicado no bloco `else` do `modoFilaUnica`
em `separacao/item.php` (linhas 518-530). Esse bloco é código morto: o handler força
`modoFilaUnica => true` (SeparacaoHandler.php:217), então a separacao sempre entra no
`if ($modoFilaUnica)` e renderiza a Luz `SeparacaoItemCard`. O `else` nunca executa.
Resultado: `data-scan-cota-ativo="0"` → `ExecucaoCard.initScanCota()` saía com
`disponivel = Infinity` → bloqueio de tempo real inerte na separação.

**Fix ciclo 2** — 3 arquivos alterados:

**`separacao/item.php`** (bloco `if ($modoFilaUnica)`, linha ~415):
- Adicionado `'missao' => $missao` e `'jsItensData' => $jsItensData ?? []` na chamada
  `$galaxia->luz('SeparacaoItemCard', [...])`, para que a view da Luz receba os dados
  necessários para calcular `scan_cota.disponivel`.
- Removido o bloco `scan_cota` morto do `else` (modo livre), que nunca era executado.

**`luz/separacao/item_card.php`** (view da Luz):
- Adicionadas as declarações de segurança `$missao = $missao ?? null` e
  `$jsItensData = $jsItensData ?? []` no topo (logo após `$tipoCard`).
- Adicionado bloco `$scanCotaFila` (closure IIFE) que chama
  `MissaoPresenter::montarScanCotaFront($missao)` e reconstrói `disponivel =
  manuais - count(bipaveis manchados)` a partir dos itens com `bipavel && manchado`.
- Passado `'scan_cota' => $scanCotaFila` no array do `execucaoCard()`.

**`SeparacaoHandler.php`** (handler da Luz):
- Adicionado `use GalaxiaConstelacoes\Logistica\MissaoLogistica\Presenters\MissaoPresenter`.
- Adicionado método privado `buildScanCotaDataForLuz(int $missaoId): array` que busca
  `$missao` via `MissaoLogistica::find()` e `$jsItensData` via
  `MissoesExecucao::lerItens` + `MissaoPresenter::montarJsonFrontendBase()`.
- `Luz_SeparacaoItemCard`: refatorado para usar `buildScanCotaDataForLuz` em vez da
  query manual de gondola; agora popula `$data['missao']` e `$data['jsItensData']`
  para qualquer re-render via autoload da Luz (após navegação/refresh).
- `biparItemSeparacao` (reencaminheLuz após bipagem completa): `array_merge` com
  `buildScanCotaDataForLuz` para que o card seguinte também receba `scan_cota`
  correto com `disponivel` já decrementado.
- `handleObservatorioMissoesBipagem` (observador do `biparItem`): idem — `array_merge`
  com `buildScanCotaDataForLuz`.

### B2 — Decremento manual bloqueava/consumia cota indevidamente
**Fix**: Gate de cota restrito a `delta > 0` nos tres fluxos:
- `execucao_card.js::decrementarQuantidade()` — removido todo o bloco de cota; decremento
  vai direto para `_executarDecrementoManual()`.
- `carregamento/item.js::ajustarQuantidade()` — `isManualAction` renomeado para
  `isManualIncremento = (codigo === 'MANUAL' && delta > 0)`.
- `separacao/item.js::ajustarQuantidade()` — idem.

### Nits
- **m3**: removidos todos os `console.log('[ScanCota]...')` dos 3 arquivos JS.
- **n1**: `mostrarAvísoCotaBloqueada` renomeado para `mostrarAvisoCotaBloqueada`
  (sem acento, typo corrigido) e todas as chamadas atualizadas nos 3 arquivos.
- **n2**: modal de `mostrarModalUltimaCota` em `carregamento/item.js` refatorado
  para usar `var(--bg-card)`, `var(--color-primary)`, `var(--color-warning)`,
  `var(--text-primary)`, `var(--text-muted)`, `var(--border-color)` — alinhado
  com o padrao de `separacao/item.js`.

---

## Views / componentes modificados

Nenhuma view nova foi criada. O trabalho foi inteiramente de modificacao de
componentes compartilhados ja existentes e de correcao de uma pendencia apontada
pelo backend.

| Arquivo modificado | O que mudou |
|---|---|
| `componentes/Missoes/Logistica/sinais/App/html/shared/components/v2/execucao_bipagem.php` | Adicionado bloco `<script id="data-scan-cota-i18n" type="application/json">` logo apos os 4 hidden inputs `data-scan-cota-*` (AC2.6 i18n para o JS). |
| `componentes/Missoes/Logistica/sinais/App/html/shared/components/execucao/execucao_card.php` | Adicionados `<input id="data-item-bipavel">` e `<input id="data-item-manchado">` (estado por item no fila unica) + bloco i18n identico ao acima. |
| `componentes/Missoes/Logistica/sinais/App/html/separacao/item.php` | Corrigida pendencia do backend: passagem de `scan_cota => MissaoPresenter::montarScanCotaFront(...)` no array de config do `execucaoCard()`. |

## JS modificados (inline — sem cache-bust externo)

Os tres arquivos JS sao incluidos inline via PHP (`include __DIR__ . '/...js'` ou
`$galaxia->visual()`), portanto nao ha `?v=N` para bumpar.

### `execucao_bipagem.php` (componente CarregamentoV2 / SeparacaoV2 — modo execucao_bipagem)

Nao contem JS proprio; a logica esta em `carregamento/item.js` e `separacao/item.js`.

### `execucao_card.js` (fila unica — SeparacaoItemCard / Luz Dinamica)

- Estado `scanCota` adicionado ao objeto `ExecucaoCard`.
- Flags `_itemAtualBipavel` e `_itemAtualManchado`.
- `initScanCota()`: le `data-scan-cota-*` e `data-item-bipavel/manchado`; define
  `disponivel = manuaisInicial` (fila unica re-renderiza por item, estado e fresco).
- `verificarCotaParaManual()`: retorna `'ok' | 'ultimo' | 'bloqueado'`.
- `consumirCota()`: marca `_itemAtualManchado = true`, decrementa `disponivel`.
- `mostrarModalUltimaCota(onConfirm)`: overlay confirmacao (AC3) reaproveitando
  classes `.mini-confirm` ja presentes no CSS do componente.
- `mostrarAvísoCotaBloqueada()`: exibe `#alerta-max` com mensagem i18n (AC2.2).
- `incrementarQuantidade()` refatorado: checa cota antes de delegar a
  `_executarIncrementoManual()`.
- `decrementarQuantidade()` refatorado: idem, delega a `_executarDecrementoManual()`.
- `_atualizarEstadoBotoesManual()`: atualiza opacidade de `#btn-qty-mais` /
  `#btn-qty-menos` quando cota esta bloqueada.

### `carregamento/item.js` (CarregamentoV2)

- Estado `scanCota` adicionado.
- `initScanCota()` chamado como passo 8 do `initAsync`. Le `data-scan-cota-*`,
  parseia i18n, reconstroi `disponivel` contando `item.bipavel && item.manchado`
  em todo `itensData` (AC2.6 — idempotente no refresh).
- `verificarCotaParaManual(itemId)`, `consumirCota(itemId)`,
  `mostrarModalUltimaCota(onConfirm)`, `mostrarAvísoCotaBloqueada()`.
- `ajustarQuantidade(delta, codigo)` refatorado: para `codigo === 'MANUAL'` checa
  cota antes de delegar a `_executarAjuste(delta, codigo)` (corpo original).

### `separacao/item.js` (SeparacaoV2)

- Estado `scanCota` adicionado.
- `initScanCota()` chamado como passo 7 do `initAsync` (mesmo padrao do
  carregamento — AC2.6 missao-wide).
- Metodos `verificarCotaParaManual`, `consumirCota`, `mostrarModalUltimaCota`,
  `mostrarAvísoCotaBloqueada` adicionados entre `iniciarAutoSave` e `bindEvents`.
- `ajustarQuantidade` refatorado com o mesmo padrao do carregamento; corpo original
  movido para `_executarAjuste`.

## Forms

Nenhum form criado nesta feature (logica inteiramente JS/PHP inline).

## i18n

Todas as 10 chaves abaixo ja foram adicionadas pelo backend nas 3 linguas
(`pt-br.json`, `en.json`, `es.json`). O frontend apenas as consume via
`show()` no PHP e via `JSON.parse('#data-scan-cota-i18n')` no JS.

| Chave | Texto pt-br |
|---|---|
| `scan_cota_manual_bloqueado` | "Cota de manuais esgotada; bipe este item." |
| `scan_cota_aviso_ultima_titulo` | "Ultima cota disponivel" |
| `scan_cota_aviso_ultima_texto` | "Ao confirmar, os itens bipaveis restantes so poderao ser concluidos por scanner." |
| `scan_cota_aviso_confirmar` | "Confirmar" |
| `scan_cota_aviso_cancelar` | "Cancelar" |
| `scan_cota_finalizacao_recusada` | "Cota minima de bipagem nao atingida: faltam {faltam} item(s) por scanner." |
| `scan_cota_config_master` | "Cota minima de bipagem" |
| `scan_cota_config_pct` | "Percentual minimo de itens por scanner (%)" |
| `scan_cota_config_separacao` | "Aplicar na separacao" |
| `scan_cota_config_carregamento` | "Aplicar no carregamento" |

## Endpoints consumidos

O frontend nao faz chamadas novas. O fluxo existente e:

- `SendGalaxiaPost` para `finalizarMissao` — ja exibe o `status: error` retornado
  pelo backstop `MissoesStatus::finalizar()` quando a cota nao foi atingida.
  Nenhum tratamento adicional necessario (AC backstop coberto pelo framework).

## Smoke test sugerido

### Pre-requisito

Missao com `scan_cota_ativo = true`, `meta = 80 %`, `N = 5 itens bipaveis`,
`manuais = 1` (ou seja, apenas 1 item pode ser dado manualmente).

### Fluxo carregamento (CarregamentoV2)

1. Abrir `/missao/logistica/app/carregamento/<id>` com missao em andamento.
2. Selecionar um item bipavel. Clicar `+` uma vez.
   - Esperado: modal "Ultima cota disponivel" aparece; clicar Cancelar nao altera qty.
3. Clicar `+` novamente, confirmar no modal.
   - Esperado: quantidade incrementa, `disponivel` passa a 0.
4. Selecionar outro item bipavel. Clicar `+`.
   - Esperado: alerta "#alerta-max" com mensagem i18n `scan_cota_manual_bloqueado`.
   - Quantidade NAO muda.
5. Bipar codigo real do segundo item (scan).
   - Esperado: quantidade incrementa normalmente (bypass da cota).
6. Tentar finalizar com itens bipaveis nao bipados por scanner.
   - Esperado: backend retorna `status: error` com mensagem `scan_cota_finalizacao_recusada`; frontend exibe ao usuario via `SendGalaxiaPost`.

### Fluxo separacao modo livre (SeparacaoV2)

1. Abrir `/missao/logistica/app/separacao/<id>` (modo livre, nao fila unica).
2. Repetir passos 2-5 acima. Comportamento identico.

### Fluxo separacao fila unica (ExecucaoCard) — smoke test do B1/B4

1. Abrir separacao com `modoFilaUnica = true` (padrao — sempre ativo).
2. Inspecionar DOM: `input[name="data-scan-cota-ativo"]` DEVE ter `value="1"` quando a
   regra esta ligada. Se `value="0"`, o fix nao chegou ao caminho ativo.
3. Selecionar item bipavel. Clicar `+` (botao `#btn-qty-mais`).
   - Se cota = 1: modal "Ultima cota disponivel" aparece (com cores via `var(--*)`).
   - Se cota = 0: botao fica com `opacity: 0.4`, clique bloqueia com alerta.
4. Confirmar o modal. Passar para proximo item (bipagem completa — item marcado como
   concluido via biparItem).
   - Esperado (B4): o card do PROXIMO item ainda exibe `data-scan-cota-ativo="1"`
     (nao "0"). O `buildScanCotaDataForLuz` agora busca `missao.configuracoes` real,
     entao `montarScanCotaFront` retorna `ativo=true`.
   - Validar: `data-scan-cota-disponivel` no DOM do novo card deve mostrar 0
     (disponivel decrementado, reconstruido pelo handler).

### Correcao com decremento (B2)

1. Com cota = 0, selecionar item bipavel limpo. Clicar `-`.
   - Esperado: decremento ocorre normalmente (sem modal, sem alerta de cota).
2. Confirmar que cota disponivel NAO muda apos o decremento.

### Refresh idempotency (AC2.6)

1. Executar passo 3 acima no fluxo de carregamento ou separacao (consumir 1 cota).
2. Recarregar a pagina (F5).
3. Clicar `+` em qualquer item bipavel.
   - Esperado: alerta de bloqueio (cota ja foi consumida — estado reconstruido
     server-side via `jsItensData[id].manchado` em carregamento/separacao,
     ou via `data-scan-cota-disponivel` na fila unica).

### Fluxo correcao metodica — verificar sem console.log

1. Abrir DevTools, aba Console.
2. Executar qualquer acao de bipagem manual (+/-) com cota ativa.
   - Esperado: nenhum log `[ScanCota]` aparece no console.
