# Testes — bipagem-cota-minima

## Cobertura

| Componente | Tipo | Path | Status |
|---|---|---|---|
| `ScanCotaLaboratorio::calcular` | TDD unit | `Testes/Laboratorios/ScanCotaLaboratorioTddTest.php` | pass (14 casos) |
| `ScanCotaLaboratorio::contarBipaveis` | TDD unit | `Testes/Laboratorios/ScanCotaLaboratorioTddTest.php` | pass (9 casos) |
| `ScanCotaLaboratorio::itemQualificado` | TDD unit | `Testes/Laboratorios/ScanCotaLaboratorioTddTest.php` | pass (6 casos) |
| `ScanCotaLaboratorio::itemManchado` | TDD unit | `Testes/Laboratorios/ScanCotaLaboratorioTddTest.php` | pass (3 casos) |
| `ScanCotaLaboratorio::itemBipavel` | TDD unit | `Testes/Laboratorios/ScanCotaLaboratorioTddTest.php` | pass (4 casos) |
| `ScanCotaLaboratorio::lerSnapCota` | TDD unit | `Testes/Laboratorios/ScanCotaLaboratorioTddTest.php` | pass (6 casos) |
| `ScanCotaLaboratorio::validarFinalizacao` | TDD unit | `Testes/Laboratorios/ScanCotaLaboratorioTddTest.php` | pass (15 casos) |
| `ScanCotaLaboratorio::regraAtivaParaFluxo` | TDD unit | `Testes/Laboratorios/ScanCotaLaboratorioTddTest.php` | pass (8 casos) |
| Migration `missao_scan_cota_config` | integration | `Testes/Laboratorios/ScanCotaIntegrationTest.php` | pass |
| Migration backfill counters | integration | `Testes/Laboratorios/ScanCotaIntegrationTest.php` | pass |
| Snapshot no banco (`lerSnapCota` com dado real) | integration | `Testes/Laboratorios/ScanCotaIntegrationTest.php` | pass |
| `validarFinalizacao` com itens do banco | integration | `Testes/Laboratorios/ScanCotaIntegrationTest.php` | pass |
| Backstop `MissoesStatus::finalizarMissao` recusa | integration | `Testes/Laboratorios/ScanCotaIntegrationTest.php` | pass |
| Extrato `SCAN_COTA_VIOLADA` gravado (AC4.4) | integration | `Testes/Laboratorios/ScanCotaIntegrationTest.php` | pass |
| Backstop pass com `scan_cota.ativo=false` (AC4.5) | integration | `Testes/Laboratorios/ScanCotaIntegrationTest.php` | pass |
| Imutabilidade do snapshot após criação (AC6.2) | integration | `Testes/Laboratorios/ScanCotaIntegrationTest.php` | pass |
| `ScanCotaConfigSonda::obterConfig` default desligado (AC5.2) | TDD Sonda | `Testes/Sondas/ScanCotaConfigSondaTddTest.php` | pass |
| `ScanCotaConfigSonda::salvarConfig` insert path | TDD Sonda | `Testes/Sondas/ScanCotaConfigSondaTddTest.php` | pass |
| `ScanCotaConfigSonda::salvarConfig` update path (upsert idempotente) | TDD Sonda | `Testes/Sondas/ScanCotaConfigSondaTddTest.php` | pass |
| `ScanCotaConfigSonda` isolamento tenant: empresa B nao ve A (AC5.3) | TDD Sonda | `Testes/Sondas/ScanCotaConfigSondaTddTest.php` | pass |
| `ScanCotaConfigSonda::obterConfig` guard empresaId<=0 | TDD Sonda | `Testes/Sondas/ScanCotaConfigSondaTddTest.php` | pass |
| `ScanCotaConfigSonda::salvarConfig` guard empresaId<=0 | TDD Sonda | `Testes/Sondas/ScanCotaConfigSondaTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) gate dev: user nao-dev recebe erro | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) gate dev: user dev passa | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) validacao pct nao-numerico | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) validacao pct ausente | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) validacao pct > 100 | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) validacao pct < 0 | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) pct 0 e 100 sao validos | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) toggle ativo=2 → erro | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) toggle ativo_separacao=2 → erro | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) toggle ativo_carregamento=2 → erro | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) empresa nao identificada → erro | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) save feliz: persiste e retorna scanCotaConfig | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) save idempotente (upsert) | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) opcao invalida → erro | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) leitura sem empresa → erro | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |
| `ScanCotaConfig` Lab (Admin) leitura feliz: retorna scanCotaConfig | TDD Lab Admin | `sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php` | pass |

| `GestaoMissoesBase::congelarScanCota` — 9 itens UN c/ código (N=9, meta=1) | integration | `Testes/Laboratorios/CongelarScanCotaIntegrationTest.php` | pass |
| `GestaoMissoesBase::congelarScanCota` — itens sem código (N=0 → ativo=false) | integration | `Testes/Laboratorios/CongelarScanCotaIntegrationTest.php` | pass |
| `GestaoMissoesBase::congelarScanCota` — ativo_carregamento=0 + tipo CARREGAMENTO | integration | `Testes/Laboratorios/CongelarScanCotaIntegrationTest.php` | pass |
| `GestaoMissoesBase::congelarScanCota` — sem config no banco → ativo=false | integration | `Testes/Laboratorios/CongelarScanCotaIntegrationTest.php` | pass |
| `GestaoMissoesBase::congelarScanCota` — itens KG excluídos de N; mix KG+UN | integration | `Testes/Laboratorios/CongelarScanCotaIntegrationTest.php` | pass |

## Gaps de cobertura (e justificativa)

- **`GestaoMissoesBase::congelarScanCota` — ponto de chamada em `processarEnvio`** — O pipeline completo de criação de missão (`processarEnvio`) tem >10 dependências (ERP, pedidos, WMS) impossíveis de serem isoladas. O método `congelarScanCota` em si agora tem cobertura direta via `_CongelarProbe` (5 cenários, incluindo o caso N=0 que gerou o bug reportado). O ponto de chamada em `processarEnvio` continua coberto por smoke manual.
- **`MissaoPresenter::montarScanCotaFront` / `bipavel`/`manchado` por item** — lógica de presenter (PHP → HTML), sem regra de negócio própria; derivada diretamente das funções do Laboratório (já cobertas por unit). Testar a view exigiria renderizar PHP completo com sessão web — fora do padrão standalone.
- **Front JS (bloqueio +/-, modal AC3)** — fora do escopo de testes automatizados conforme anti-padrões do projeto; coberto por smoke manual documentado no 04-frontend.md.
- **`buildScanCotaDataForLuz` wiring de `missao->configuracoes` na Luz** — débito d1 apontado no ciclo 3 do review: o fix do `find()->fetch()` foi validado por inspeção da cadeia + prova de forma; não há teste automatizado guardando `missao->configuracoes !== null` no caminho de re-render pós-bipagem. Recomenda-se (futuro) um teste de handler que asserte o retorno do helper.
- **Tela Admin — view/form/JS** — HTML, JS e form do sinal Admin (`scan_cota_config.php`, `scan_cota_config.js`) são camadas de apresentação/frontend; fora do escopo de testes standalone PHP.

## Output da execucao

```
=== TDD unit (ScanCotaLaboratorioTddTest) ===

php componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Laboratorios/ScanCotaLaboratorioTddTest.php

====================================================
TDD — ScanCotaLaboratorio (unit, sem banco)
====================================================

--- calcular() ---
[PASS] calcular(25,10): meta_itens=3 (AC1.1)
[PASS] calcular(25,10): cota_manuais=22 (AC1.1)
[PASS] calcular(25,0): meta=0 (AC1.2 pct=0)
[PASS] calcular(25,0): cota=0 (AC1.2 pct=0)
[PASS] calcular(0,10): meta=0 (AC1.2 N=0)
[PASS] calcular(0,10): cota=0 (AC1.2 N=0)
[PASS] calcular(5,100): meta=5 (cap meta≤N, AC1.3)
[PASS] calcular(5,100): cota=0
[PASS] calcular(5,200): meta capped a N=5 (AC1.3)
[PASS] calcular(5,200): cota=0
[PASS] calcular(10,33): meta=ceil(3.3)=4 (ceil)
[PASS] calcular(10,33): cota=6
[PASS] calcular(1,10): meta=1 (ceil 0.1→1)
[PASS] calcular(1,10): cota=0

--- contarBipaveis() ---
[PASS] contarBipaveis([]): N=0
[PASS] contarBipaveis([UN+cod]): N=1
[PASS] contarBipaveis([KG+cod]): N=0 (AC1.4 exclui pesagem)
[PASS] contarBipaveis([UN,codigos=[]): N=0 (AC1.4 exclui sem-código)
[PASS] contarBipaveis([sem dados_adicionais]): N=0
[PASS] contarBipaveis([GR]): N=0 (pesagem)
[PASS] contarBipaveis([MT]): N=0 (pesagem)
[PASS] contarBipaveis(mix 2UN+1KG+1semCod): N=2 (AC1.4)
[PASS] contarBipaveis([objeto json]): N=1 (suporte objeto)

--- itemQualificado() ---
[PASS] itemQualificado(scan=3,qtd=3,manual=0): true
[PASS] itemQualificado(manual=1): false (manchado, AC4.3)
[PASS] itemQualificado(scan<qtd): false (incompleto)
[PASS] itemQualificado(qtd=0): false (sem quantidade)
[PASS] itemQualificado(NULL counters): false (seguro via floatval)
[PASS] itemQualificado(scan>qtd,manual=0): true (scan>=qtd)

--- itemManchado() ---
[PASS] itemManchado(1): true
[PASS] itemManchado(0): false
[PASS] itemManchado(sem campo): false (COALESCE null→0)

--- itemBipavel() ---
[PASS] itemBipavel(UN+cod): true
[PASS] itemBipavel(KG+cod): false (pesagem)
[PASS] itemBipavel(UN,codigos=[]): false (sem código)
[PASS] itemBipavel(array dados_adicionais): true

--- lerSnapCota() ---
[PASS] lerSnapCota(json str): retorna array ativo=true
[PASS] lerSnapCota: meta_itens=3
[PASS] lerSnapCota: cota_manuais=22
[PASS] lerSnapCota(array direto): retorna ativo=false
[PASS] lerSnapCota(sem scan_cota): null
[PASS] lerSnapCota(sem configuracoes): null

--- validarFinalizacao() ---
[PASS] validarFinalizacao(sem snap): ok=true (AC4.5)
[PASS] validarFinalizacao(ativo=false): ok=true (AC4.5)
[PASS] validarFinalizacao(meta=0): ok=true
[PASS] validarFinalizacao(3/3 qualificados): ok=true
[PASS] validarFinalizacao: qualificados=3
[PASS] validarFinalizacao: faltam=0
[PASS] validarFinalizacao(2/3 qualificados): ok=false (AC4.2)
[PASS] validarFinalizacao: qualificados=2
[PASS] validarFinalizacao: faltam=1
[PASS] validarFinalizacao: meta=3
[PASS] validarFinalizacao(0/3 qualificados): ok=false
[PASS] validarFinalizacao: qualificados=0
[PASS] validarFinalizacao: faltam=3
[PASS] validarFinalizacao(1 UN qualif + KG ignorado): ok=true
[PASS] validarFinalizacao: qualificados=1 (KG ignorado)

--- regraAtivaParaFluxo() ---
[PASS] regraAtivaParaFluxo(master=0): false p/ SEPARACAO
[PASS] regraAtivaParaFluxo(master=0): false p/ CARREGAMENTO
[PASS] regraAtivaParaFluxo(sep=1): true p/ SEPARACAO
[PASS] regraAtivaParaFluxo(carg=0): false p/ CARREGAMENTO
[PASS] regraAtivaParaFluxo(sep=0): false p/ SEPARACAO
[PASS] regraAtivaParaFluxo(carg=1): true p/ CARREGAMENTO
[PASS] regraAtivaParaFluxo(INVENTARIO): false (tipo sem cota)
[PASS] regraAtivaParaFluxo(tipo=""): false

====================================================
RESULTADO: PASS=65  FAIL=0
TODOS OS TESTES PASSARAM


=== Integration (ScanCotaIntegrationTest) ===

php componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Laboratorios/ScanCotaIntegrationTest.php

====================================================
INTEGRATION — ScanCota (dev_gx_goldie)
====================================================

--- SCHEMA: missao_scan_cota_config ---
[PASS] tabela missao_scan_cota_config existe
[PASS] missao_scan_cota_config tem coluna 'id'
[PASS] missao_scan_cota_config tem coluna 'ativo'
[PASS] missao_scan_cota_config tem coluna 'pct'
[PASS] missao_scan_cota_config tem coluna 'ativo_separacao'
[PASS] missao_scan_cota_config tem coluna 'ativo_carregamento'
[PASS] missao_scan_cota_config tem coluna 'block_empresa_id'
[PASS] missao_scan_cota_config tem coluna 'block_filial_id'
[PASS] índice único uq_scan_cota_empresa existe (AC5.3)

--- SCHEMA: backfill quantidade_bipada_scan/manual ---
[PASS] missao_logistica_itens tem coluna quantidade_bipada_scan
[PASS] missao_logistica_itens tem coluna quantidade_bipada_manual

--- SNAPSHOT + validarFinalizacao ---
[PASS] Seed: produto sentinela criado
[PASS] Seed: missão com snap ativo criada
[PASS] Missão encontrada no banco
[PASS] lerSnapCota retorna array (snapshot persistido, AC6.1)
[PASS] snapshot.ativo=true
[PASS] snapshot.meta_itens=1
[PASS] snapshot.pct=10.0
[PASS] Seed: item1 (manchado) criado
[PASS] Seed: item2 (manchado) criado
[PASS] validarFinalizacao(manchados): ok=false (backstop recusaria, AC4.2)
[PASS] validarFinalizacao: qualificados=0
[PASS] validarFinalizacao: meta=1 (leu do snapshot)
[PASS] validarFinalizacao: faltam=1

--- BACKSTOP via MissoesStatus ---
[PASS] MissoesStatus::finalizarMissao retorna error (backstop recusou, AC4.2)
[PASS] Mensagem de erro nao esta vazia (AC4.2 — texto i18n via show())
[PASS] Extrato SCAN_COTA_VIOLADA gravado (AC4.4)
[PASS] Extrato payload.meta=1
[PASS] Extrato payload.qualificados=0
[PASS] Extrato payload.faltam=1
[PASS] Missão permanece em_andamento após recusa do backstop

--- BACKSTOP: scan_cota.ativo=false passa sem recusa (AC4.5) ---
[PASS] Seed: missão com snap inativo criada
[PASS] validarFinalizacao(ativo=false): ok=true mesmo com itens manchados (AC4.5)

--- SNAPSHOT: imutabilidade após criação (AC6.2) ---
[PASS] Snapshot ainda presente após releitura (AC6.2)
[PASS] Snapshot.meta_itens=1 inalterado (AC6.2)
[PASS] Snapshot.pct=10.0 inalterado (AC6.2)

====================================================
RESULTADO: PASS=36  FAIL=0
TODOS OS TESTES PASSARAM


=== TDD Sonda (ScanCotaConfigSondaTddTest) ===

php componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Sondas/ScanCotaConfigSondaTddTest.php

====================================================
TDD — ScanCotaConfigSonda (isolamento de tenant, dev_gx_goldie)
====================================================

--- obterConfig: empresa sem linha → default desligado ---
[PASS] obterConfig(sem linha): retorna objeto
[PASS] obterConfig(sem linha): ativo=0 (AC5.2 — default desligado)
[PASS] obterConfig(sem linha): id=null (objeto default, sem registro)
[PASS] obterConfig(sem linha): não cria linha no banco

--- salvarConfig: primeiro save → insert (1 linha criada) ---
[PASS] salvarConfig(insert): retorna true
[PASS] salvarConfig(insert): 1 linha criada no banco

--- salvarConfig: update path (upsert idempotente) ---
[PASS] linha do insert já presente (pré-condição para testar update)
[PASS] salvarConfig(update): retorna true
[PASS] salvarConfig(update): ainda exatamente 1 linha (sem duplicata — upsert correto)
[PASS] obterConfig(após update): pct=60.0
[PASS] obterConfig(após update): ativo_separacao=0
[PASS] obterConfig(após update): ativo_carregamento=1
[PASS] obterConfig(após update): block_empresa_id correto

--- Isolamento de tenant: empresa B não vê config de empresa A (AC5.3) ---
[PASS] isolamento: empresa B não tem linha (pré-condição)
[PASS] isolamento: obterConfig(empresaB) retorna ativo=0 (não vazou config de A)
[PASS] isolamento: obterConfig(empresaB) retorna id=null (sem registro próprio)
[PASS] isolamento: empresa B tem sua própria linha (seed PDO)
[PASS] isolamento: config de A inalterada após seed de B (pct=60.0)
[PASS] isolamento: empresa A ainda lê apenas seu próprio block_empresa_id
[PASS] isolamento: empresa B lê seu próprio pct=50.0 (não o 60.0 de A)
[PASS] isolamento: empresa B tem seu próprio block_empresa_id

--- Guards de entrada: empresaId <= 0 ---
[PASS] salvarConfig(empresaId=0): retorna false (guard)
[PASS] salvarConfig(empresaId=-1): retorna false (guard)
[PASS] obterConfig(empresaId=0): retorna default ativo=0 (guard)
[PASS] obterConfig(empresaId=0): id=null (não consulta banco)

====================================================
RESULTADO: PASS=25  FAIL=0
TODOS OS TESTES PASSARAM


=== TDD Lab Admin (ScanCotaConfigTddTest) ===

php componentes/Missoes/Logistica/sinais/Admin/Testes/Laboratorios/ScanCotaConfigTddTest.php

====================================================
TDD — ScanCotaConfig Lab (Admin, dev_gx_goldie)
====================================================

--- gate dev-only: user nao-dev recebe erro ---
[PASS] gate(user=999): notification_class=error
[PASS] gate(user=999): mensagem contém "restrit"

--- gate dev-only: user dev (id=1) passa ---
[PASS] gate(user=1): chegou na validação de pct (gate não barrou)
[PASS] gate(user=1): mensagem NÃO é "Acesso restrito"

--- validação pct: nao-numérico → erro ---
[PASS] pct(nao-numerico): notification_class=error
[PASS] pct(nao-numerico): mensagem contém "percentual"
[PASS] pct(ausente): notification_class=error

--- validação pct: fora de 0-100 → erro ---
[PASS] pct(101): notification_class=error
[PASS] pct(101): mensagem contém "percentual"
[PASS] pct(-1): notification_class=error

--- validação pct: 0 e 100 são válidos ---
[PASS] pct(0): não gerou erro de percentual
[PASS] pct(100): não gerou erro de percentual

--- validação toggles: valor invalido → erro ---
[PASS] toggle(ativo=2): notification_class=error
[PASS] toggle(ativo=2): mensagem contém 'inválido'
[PASS] toggle(ativo_separacao=2): notification_class=error
[PASS] toggle(ativo_separacao=2): mensagem contém 'inválido'
[PASS] toggle(ativo_carregamento=2): notification_class=error
[PASS] toggle(ativo_carregamento=2): mensagem contém 'inválido'

--- empresa nao identificada → erro ---
[PASS] empresa=0: notification_class=error
[PASS] empresa=0: mensagem contém "empresa"

--- save feliz: persiste via Sonda e retorna scanCotaConfig ---
[PASS] save feliz: notification_class=success
[PASS] save feliz: response tem chave scanCotaConfig
[PASS] save feliz: scanCotaConfig é objeto
[PASS] save feliz: scanCotaConfig.ativo=1
[PASS] save feliz: scanCotaConfig.pct=42.5
[PASS] save feliz: scanCotaConfig.ativo_separacao=1
[PASS] save feliz: scanCotaConfig.ativo_carregamento=0
[PASS] save feliz: 1 linha criada no banco
[PASS] save 2x: notification_class=success (upsert)
[PASS] save 2x: ainda 1 linha (sem duplicata)
[PASS] save 2x: pct atualizado para 55.0

--- opcao invalida → erro ---
[PASS] opcao(invalida): notification_class=error
[PASS] opcao(invalida): mensagem contém "inválid"

--- leitura: empresa nao identificada → erro ---
[PASS] leitura(sem empresa): notification_class=error

--- leitura feliz: devolve scanCotaConfig ---
[PASS] leitura feliz: notification_class=success
[PASS] leitura feliz: response tem chave scanCotaConfig
[PASS] leitura feliz: scanCotaConfig é objeto
[PASS] leitura feliz: pct=55.0 (dado persistido do save anterior)

====================================================
RESULTADO: PASS=38  FAIL=0
TODOS OS TESTES PASSARAM
```

## Output da execucao — CongelarScanCotaIntegrationTest (novo)

```
php componentes/Constelacoes/Logistica/MissaoLogistica/Testes/Laboratorios/CongelarScanCotaIntegrationTest.php

====================================================
INTEGRATION — congelarScanCota (dev_gx_goldie)
empresa sentinela: 999700
====================================================

[PASS] resolverEmpresaIdAtual() retorna 999700 (sessão configurada corretamente)

--- Cenário 1: ativo=1, pct=10, sep=1 + 9 itens UN com código ---
[PASS] Cenário 1: ativo=true
[PASS] Cenário 1: fluxo=SEPARACAO
[PASS] Cenário 1: denominador_n=9
[PASS] Cenário 1: meta_itens=1 (ceil 0.9)
[PASS] Cenário 1: cota_manuais=8 (9-1)
[PASS] Cenário 1: pct=10.0

--- Cenário 2: mesma config + codigos_barras=[] (N=0) → ativo=false ---
[PASS] Cenário 2: ativo=false (N=0 — produtos sem código → cota desligada por design)
[PASS] Cenário 2: denominador_n ausente quando ativo=false

--- Cenário 3: ativo_carregamento=0 + tipo=CARREGAMENTO → ativo=false ---
[PASS] Cenário 3: ativo=false (ativo_carregamento=0 → regraAtivaParaFluxo=false)
[PASS] Cenário 3b: ativo=false (INVENTARIO não tem cota por design)

--- Cenário 4: sem config no banco para empresaId → ativo=false ---
[PASS] Cenário 4: ativo=false (empresa sem linha → default desligado, AC5.2)

--- Cenário 5: itens KG com código → excluídos de N; N=0 → ativo=false ---
[PASS] Cenário 5: ativo=false (todos itens KG → N=0; pesagem excluída do denominador)
[PASS] Cenário 5b (mix KG+UN): ativo=true
[PASS] Cenário 5b: denominador_n=2 (só UN contam)
[PASS] Cenário 5b: meta_itens=1
[PASS] Cenário 5b: cota_manuais=1

====================================================
RESULTADO: PASS=17  FAIL=0
TODOS OS TESTES PASSARAM
```

## Bugs encontrados

Nenhum bug de código de produção encontrado neste ciclo. Todos os testes passam.

### Ciclos anteriores (bugs já corrigidos)

#### BUG #2 (ciclo 1 — corrigido antes do ciclo 2)

**Componente**: `ScanCotaConfigSonda::salvarConfig` — caminho de insert

**Sintoma original**: `salvarConfig()` retornava `true` mas nenhuma linha era gravada no banco (insert path usava `make()`/`save()` que não existem em `ModelInteligencia`).

**Status**: Corrigido. O insert agora usa `$this->create($dadosInsert)` diretamente. O teste `ScanCotaConfigSondaTddTest.php` confirma PASS=25 / FAIL=0, incluindo a asserção `salvarConfig(insert): 1 linha criada no banco`.
