# 12 - Armadilhas Conhecidas

> Cada item desta lista **já causou bug real** ou horas de depuração neste projeto. Leia antes de mexer na área correspondente. Formato: sintoma → causa → solução → prevenção.
>
> Relacionados: [11 - Guia de Manutenção](11-guia-de-manutencao.md), [05 - Banco de Dados](05-banco-de-dados.md), [03 - Regras de Negócio](03-regras-de-negocio.md).

## ORM / ModelInteligencia (Sondas)

### 1. `find()` sem `->fetch()` é sempre truthy
- **Sintoma:** um guard `if ($sonda->find(...))` passa sempre, mesmo sem registro no banco.
- **Causa:** `find()` retorna o próprio objeto (fluente), não o resultado. Só `->fetch()` materializa.
- **Solução:** `if ($sonda->find(...)->fetch())`.
- **Prevenção:** em code review, todo `find(` sem `fetch(` na mesma expressão é suspeito.

### 2. `findById()`/`data()` vêm embrulhados em array
- **Sintoma:** acessar `$obj->campo` falha ou retorna null depois de `findById()`.
- **Causa:** na Sonda V2, `findById()` e `data()` retornam `[obj]` (array com um objeto). `findAny()` e ModelDb **não** embrulham.
- **Solução:** `$registro = $sonda->findById($id)[0] ?? null;`
- **Prevenção:** conhecer a assimetria; não assumir consistência entre métodos de leitura.

### 3. `$required` como lista quebra o `create()`
- **Sintoma:** insert de Sonda V2 falha silenciosamente ou com erro obscuro.
- **Causa:** declarar `$required` com lista de campos ativa validação que conflita com o fluxo de create.
- **Solução:** manter `$required` vazio na Sonda e validar no Validator do Laboratório.

### 4. `update()` retorna rowCount — use como guard CAS
- **Nota:** `update()` retorna o número de linhas afetadas. Isso permite *compare-and-swap*: `UPDATE ... WHERE status = 'pendente'` e checar `rowCount === 1` para garantir transição atômica de status. Se você ignora o retorno, perde a proteção contra corrida.

### 5. Colunas JSON voltam como string crua
- **Sintoma:** `$config->is_retira` é sempre truthy/errado; iterar sobre coluna JSON falha.
- **Causa:** o `prepareObject` da Sonda **não** decodifica colunas JSON — elas chegam como string.
- **Solução:** `json_decode($obj->configuracoes ?? '{}')` explicitamente em todo ponto de leitura.

### 6. V1 `__get` mágico
- **Sintoma:** em Inteligências V1, `$obj->campo` funciona "de graça" mas `$obj->data` se comporta estranho.
- **Causa:** `ModelInteligencia` tem `__get` que resolve `$this->data->$name`. Para pegar o objeto completo use o **método** `$obj->data()`.

### 7. Tabela nova sem colunas de auditoria/block quebra o `create()`
- **Sintoma:** `create()` falha só em teste de integração (nunca em análise estática).
- **Causa:** o framework injeta colunas de auditoria e `block_*` em todo insert. Se a tabela não as tem, o SQL quebra.
- **Solução:** toda migration de tabela com escrita via Sonda espelha o conjunto completo de colunas de `wms_movimentos` (auditoria + block).
- **Prevenção:** rodar teste de integração de create antes de considerar a migration pronta. As colunas `block_*` **não** levam `DEFAULT 1` — o framework injeta o valor.

## Banco de dados / ERP legado

### 8. `pedidos.comodato` é NULL em quase tudo (ERP)
- **Sintoma:** filtro `comodato = 0` faz sumir praticamente todos os pedidos.
- **Causa:** NULL não é igual a 0 em SQL.
- **Solução:** `COALESCE(comodato, 0) = 0`.

### 9. `numero_pedido` NÃO é único (ERP)
- **Sintoma:** guard ou join por `numero_pedido` pega o pedido errado.
- **Causa:** o ERP reusa números entre empresas/anos.
- **Solução:** cruzar sempre por `codigomd5` (ERP) ↔ `importacao_id` (Genesis).

### 10. Timestamps do ERP são UTC
- **Sintoma:** horários exibidos com 3h de diferença.
- **Solução:** converter com `DateTime` + timezone `America/Sao_Paulo`; **não** usar `strtotime`/`date` cru.

### 11. Nunca raw SQL para writes
- **Regra:** writes em lote usam o `update()` do model, nunca `Connect::getInstance()` com SQL manual — o SQL manual pula multi-tenant, auditoria e block, e já causou dados órfãos.

## Frontend / Views

### 12. Cache de JS: dois regimes diferentes
- **Sintoma:** editou o JS e o navegador continua servindo o antigo.
- **Causa/Solução dupla:**
  - Views renderizadas por `GalaxiaRoute::visual()` — o JS pareado é versionado **automaticamente** via `filemtime`; basta salvar o arquivo.
  - Modais do módulo de **metas** (e outros com `<script src>` manual) — versão é o `?v=N` **manual** no PHP; tem que bumpar a cada edição do JS.

### 13. `overflow-x: hidden` quebra scroll vertical
- **Solução:** para bloquear scroll horizontal use `overflow-x: clip` — `hidden` transforma o elemento em scroll container e engole o sticky/scroll vertical.

### 14. jQuery `.data()` cacheia
- **Sintoma:** widget lê valor antigo de `data-*` mesmo depois do atributo mudar no DOM.
- **Solução:** ler com `.attr('data-x')` quando o atributo é atualizado dinamicamente (caso real: widget de sugestão de EAN).

### 15. Mobile: largura real ≠ largura do body
- **Nota (tema genezes):** no mobile o body mede 375px mas `window.innerWidth` é 512 — 512 é a largura real para elementos full-width. Gantt ApexCharts no mobile precisa clampar `xaxis.min/max` na janela real de atividade via `matchMedia`.

### 16. SortableJS: classes empilhadas e drag em mobile
- O original recebe `chosen+ghost+drag` juntos: estilizar chosen com `:not(.sortable-ghost)` e usar `filter`/`outline` (não `transform`) no fallback. Long-press + scroll manual: `touch-action: none`, `delay: 500`, checar `Sortable.active`.

### 17. Google Maps `loading=async`
- **Sintoma:** primeira chamada ao `DirectionsService` estoura.
- **Causa:** `script.onload` resolve antes da lib existir.
- **Solução:** usar `google.maps.importLibrary()`.

## Framework / Roteamento

### 18. Sinal linkado de dentro do SPA recebe método de render, não `start()`
- **Sintoma:** `Fatal: undefined method ::Html()` ao navegar para um Sinal a partir do SPA de outro App.
- **Causa:** o link interno chama o método de render (ex.: `Html`) direto no controller.
- **Solução:** implementar `__call` que delega para `start()`.

### 19. View "index" tem nome do controller em minúsculo
- Convenção: `EdicaoRota` → `html/edicaorota.php`. Nome errado = view não encontrada sem erro claro.

### 20. i18n: a chave é o md5 do texto PT
- `show("texto PT literal")` — nunca inventar chave legível. Runtime de tradução por setor em `galaxia/linguagens`; sempre verificar no browser porque o harvest é automático.

## Testes

### 21. Bootstrap de integração precisa de `$_SESSION` em dois lugares
- `SessionGalaxia::has()` só checa o grupo — o bootstrap de teste precisa setar `$_SESSION` **na raiz E em `$_SESSION['dashboard']`**, senão o teste falha em auth de formas confusas.
- Testes de integração **só** rodam no banco `dev_gx_goldie`.

## Processo

### 22. Nunca commitar sem pedido explícito
- O fluxo do time: commits/pushes só quando o usuário pede. Branch de trabalho é `dev_team`; **não há merge para `main`** — reviews não devem usar `git diff main...HEAD`.

### 23. Pedido de "PRD" = só o documento
- Quando pedem um PRD, o entregável é `PRD/<feature>/00-prd.md` — não começar a implementar.
