# TASKS.md — Checklist rastreável

Cada item referencia o arquivo/entidade correspondente. Ver [PLAN.md](PLAN.md) para fases e critérios
de aceite completos.

## Fase 0 — Correção de especificação
- [x] `docs/ESPECIFICACAO_TECNICA_1.0.md` §0.1 — nota de correção custo-base vs. preço de venda
- [x] `docs/ESPECIFICACAO_TECNICA_1.0.md` §2.2 — `produtos.custo_base_unitario`,
      `custos_tamanho_produto` (substitui), `faixas_produto` (só venda)
- [x] `docs/ESPECIFICACAO_TECNICA_1.0.md` §4 — algoritmo do Motor Comercial atualizado (passo 4/4b/5)
- [x] `docs/ESPECIFICACAO_TECNICA_1.0.md` §5 — tabela de bloqueio com condição 4b
- [x] `docs/ESPECIFICACAO_TECNICA_1.0.md` — matriz de rastreabilidade atualizada
- [x] `docs/REQUIREMENTS.md` — mesma correção refletida no resumo executável

## Fase 1 — Repositório e banco
- [x] `package.json`, `tsconfig.json`, `vitest.config.ts`, `.gitignore`, `.env.example`
- [x] `docker-compose.yml`
- [x] `knexfile.js`
- [x] `db/utils/schemaHelpers.js`
- [x] 25 migrations em `db/migrations/001` a `025` (uma por tabela + extensão pgcrypto)
- [x] `db/seeds/001_regras_comerciais_gerais.js` (margem, pagamento geral, ASA D+10, prazo, troca/devolução, programa/arte)
- [x] `db/seeds/002_catalogo.js` (6 produtos + custos por tamanho do REQUIREMENTS.md; preço de venda NULL)
- [x] `db/seeds/003_personalizacoes.js` (bordado padrão — venda definida, custo NULL de propósito)
- [x] PostgreSQL 16 via Postgres.app instalado sem privilégios de admin (`/Applications/Postgres.app`,
      cluster em `~/Library/Application Support/Postgres/var-16`)
- [x] Servidor rodando na porta 5432; role `vendedor_ia` + bancos `vendedor_ia_rei`/`_test` criados
- [x] `npm run migrate` executado com sucesso contra Postgres real (25 migrations, batch 1)
- [x] `npm run seed` executado com sucesso (3 arquivos de seed)
- [x] Inspeção do schema resultante (`\d produtos`, `\d custos_tamanho_produto`, `\d faixas_produto`)
      confirmada — reflete a correção §0.1 (custo-base independente de preço de venda)
- [x] Dados seedados conferidos linha a linha (6 produtos, 15 custos por tamanho, faixas de venda
      NULL, bordado com venda definida e custo NULL, 6 regras comerciais, grupo ASA)
- [x] Constraints testadas manualmente (preço negativo e enum inválido rejeitados pelo banco)

## Fase 2 — Motor Comercial
- [x] `src/motor-comercial/types.ts`
- [x] `src/motor-comercial/faixas.ts` (seleção de faixa + normalização preço zero/nulo)
- [x] `src/motor-comercial/custo-produto.ts` (substituição custo especial por tamanho)
- [x] `src/motor-comercial/regras.ts` (prioridade cliente > grupo > geral + conflito)
- [x] `src/motor-comercial/programa-arte.ts`
- [x] `src/motor-comercial/calcular.ts` (orquestração completa)
- [x] `src/motor-comercial/index.ts` (exports públicos)
- [x] `src/providers/ia-provider.ts` (interface, sem implementação)
- [x] `src/providers/whatsapp-provider.ts` (interface, sem implementação)
- [x] `src/providers/erp-provider.ts` (interface, sem implementação)
- [x] `npx tsc --noEmit` sem erros

## Fase 3 — Testes obrigatórios do motor
- [x] Teste #1 — produto sem personalização → bloqueado
- [x] Teste #2 — personalização sem custo → bloqueado (mesmo com venda definida)
- [x] Teste #3 — quantidade 30 → faixas corretas (produto e personalização)
- [x] Teste #4 — duas personalizações → soma custo e venda de ambas
- [x] Teste #5 — margem < 35% → PRECISA_ATENCAO
- [x] Teste #6 — cliente grupo ASA → pagamento integral D+10
- [x] Teste #7 — cliente normal → 50% entrada + 50% antes/na entrega
- [x] Teste #8 — cliente novo + personalizado + qtd < 10 → cobra R$50
- [x] Teste #9 — cliente recorrente → não cobra (+ variante qtd ≥ 10)
- [x] Teste #10 — preço zero/nulo → NÃO DEFINIDO, nunca enviado (produto e personalização)
- [x] Teste #11 — determinismo/pureza (pré-condição de snapshot imutável; fecho completo na Fase 4)
- [x] Extra — custo do produto NULL sem custo especial → bloqueado
- [x] Extra — custo especial substitui (não soma) custo-base
- [x] Extra — tamanho sem custo especial cadastrado usa custo-base
- [x] Extra — tamanho com custo especial NULL usa custo-base (não bloqueia)
- [x] Extra — política de personalização opcional não bloqueia por ausência
- [x] Extra — faixa de personalização sem cobertura → bloqueado
- [x] Extra — exceções conflitantes → PRECISA_ATENCAO (não escolhe arbitrariamente)
- [x] Extra — margem sem regra definida → PRECISA_ATENCAO
- [x] Extra — classificação cliente novo/recorrente indefinida → PRECISA_ATENCAO
- [x] Extra — determinismo com inputs equivalentes por valor
- [x] `npm test` — 23/23 testes passando

## Ambiente (resolvido)
- [x] Licença do Xcode Command Line Tools aceita (usuário) — `git`/`brew` desbloqueados
- [x] `git init` + primeiro commit (`3001ea8`)
- [x] PostgreSQL local via Postgres.app (sem Docker Desktop, sem privilégios de admin)

## Fase 4 — Persistência + integração
- [x] Migration 026 — preço de venda = 0 rejeitado pelo banco (`> 0` ou `NULL`, nunca 0 literal)
- [x] `src/db/connection.ts`
- [x] `src/repositorios/produtos.ts`
- [x] `src/repositorios/personalizacoes.ts`
- [x] `src/repositorios/clientes.ts`
- [x] `src/repositorios/regras.ts`
- [x] `src/orcamento/montarEntrada.ts`
- [x] `src/orcamento/persistirOrcamento.ts` (transação única; recusa persistir BLOQUEADO)
- [x] `npm run migrate:test` — migration 026 aplicada também no banco `vendedor_ia_rei_test`
- [x] Teste de integração — preço 0 rejeitado pelo banco
- [x] Teste de integração — preço NULL aceito e chega como `null` ao motor (nunca grátis)
- [x] Teste de integração — round-trip repositórios → Motor Comercial com números corretos
- [x] Teste de integração — Teste obrigatório #11 fechado (snapshot imutável contra Postgres real)
- [x] Teste de integração — BLOQUEADO nunca é persistido
- [x] Suíte completa: 28/28 testes passando (23 unitários + 5 integração), banco de teste limpo
      automaticamente no `afterAll`, banco de desenvolvimento confirmado intocado

## Fase 5 — Correção: cliente novo/recorrente (ver PLAN.md §5.0)
- [x] Confirmado com o usuário: derivação automática = "≥1 orçamento concluído" (não é threshold)
- [x] Migration 027 — renomeia `cliente_recorrente` → `cliente_recorrente_override` + colunas de override
- [x] Motor Comercial estendido: `ResultadoMotorComercial` ganha `margem`, `tabelaConsolidada` ganha
      `personalizacoes[]` (detalhe por item), `custoProdutoOrigem`, `margemRsUnitaria`
- [x] `src/repositorios/clientes.ts` — `resolverClienteRecorrente`, `definirOverrideRecorrencia`
- [x] Testes unitários do motor atualizados/estendidos para os novos campos (28 → ver contagem final)
- [x] docs/REQUIREMENTS.md, docs/ESPECIFICACAO_TECNICA_1.0.md (§0.2) atualizados sem apagar a decisão
      da Fase 1 — histórico preservado

## Fase 5 — Correção 5.0.1: critério de derivação automática ("enviado" não é venda)
- [x] Migration 031 — `orcamentos.venda_confirmada_em`/`venda_confirmada_por` (fato ortogonal ao
      `status` de cotação)
- [x] `resolverClienteRecorrente` corrigido: conta `venda_confirmada_em IS NOT NULL`, não mais
      `status IN (aprovado, enviado, proposta_bling_gerada)`
- [x] `POST /orcamentos/:id/confirmar-venda` — único jeito de marcar venda confirmada; auditado;
      falha com 409 se já confirmada
- [x] Testes: "enviado" isolado não torna recorrente; confirmar-venda torna recorrente
      automaticamente; confirmar duas vezes falha; auditoria da confirmação
- [x] docs/ESPECIFICACAO_TECNICA_1.0.md §0.2.1 e docs/REQUIREMENTS.md atualizados, preservando o
      histórico da versão anterior (§0.2, item 2, riscado e referenciado, não apagado)

## Fase 5 — API REST
- [x] Scaffolding: `src/api/app.ts` (Express, factory testável), `src/api/server.ts`
- [x] Middlewares: auth (JWT), erro central (sem stack trace ao cliente), validação (Zod)
- [x] Auth: `POST /auth/login`, `POST /auth/refresh`, bootstrap de admin via env vars (sem senha hardcoded)
- [x] CRUD categorias
- [x] CRUD produtos + faixas (sobreposição validada) + custos-tamanho
- [x] CRUD personalizações + faixas (sobreposição validada)
- [x] CRUD grupos
- [x] CRUD clientes + contatos + excecoes + override de recorrência (auditado)
- [x] CRUD regras comerciais + aplicações de posição (bônus, completa o contrato original)
- [x] `POST /simulador/calcular` (composição de preço completa, só chama o Motor Comercial)
- [x] Orçamentos: criar (via motor + persistirOrcamento) e consultar (com snapshot)
- [x] Atendimentos: máquina de estados, assumir/devolver (human takeover), modo IA por atendimento
- [x] Config modo IA (global, só admin altera)
- [x] Dashboard/resumo
- [x] Auditoria: endpoint de consulta
- [x] Migrations extras necessárias: 026 (preço>0 no banco), 027 (recorrência override), 028
      (status_anterior_assumir), 029 (configuracoes_sistema), 030 (auditoria.entidade_id → texto)
- [x] 62 testes de integração de API (8 arquivos) cobrindo a lista completa pedida — ver relatório
- [x] Type-check limpo (`tsc --noEmit`)
- [x] Suíte completa: 90/90 (23 motor + 5 persistência Fase 4 + 62 API) — 2 execuções seguidas iguais
- [x] Migrations confirmadas (dev: 31/31, teste: 31/31)
- [x] Banco de teste retorna a zero linhas em todas as tabelas transacionais após a suíte
- [x] Banco de desenvolvimento confirmado intocado (6 produtos do seed, 0 usuários/clientes/orçamentos)

## Fase 5 — Fechamento (revisão contra o prompt original, gaps encontrados e fechados)
- [x] Contatos: CRUD estava incompleto (só criar/listar) — adicionado `PUT`/`DELETE` (soft delete)
- [x] Validação de formato de UUID nos parâmetros de rota (`:id`, `:produtoId`, etc.) — antes um ID
      malformado virava erro cru de Postgres (500); agora retorna 400 claro. Implementado via
      `router.param()` em cada router (`app.param()` não se propaga para routers montados,
      comportamento documentado do Express — descoberto ao testar)
- [x] Teste de API para "faixa de produto não encontrada para a quantidade" (BLOQUEADO) — só existia
      no nível do Motor Comercial, faltava no nível de API
- [x] Teste de API para "conflito de regra comercial" (PRECISA_ATENCAO) — idem
- [x] Teste de ID malformado retornando 400
- [x] Suíte final: 90/90, banco de teste zerado, banco de desenvolvimento intocado
- [x] Commit final da Fase 5 (ver hash no relatório entregue ao usuário)

## Fase 6 — Painel web
- [x] Scaffold `web/` (Vite + React + TypeScript + Tailwind + TanStack Query + React Router)
- [x] Autenticação (login, refresh, contexto de sessão, rota protegida)
- [x] AppShell com a navegação exata pedida (Visão geral, Atendimentos, Clientes e grupos, Comercial
      [Produtos/Personalizações/Composição de preço/Regras], Histórico, Configurações, Simulador/Lab
      separado)
- [x] Dashboard "o que precisa da sua atenção hoje" com contadores clicáveis
- [x] Primeira versão de Produtos/Personalizações/Clientes/Regras/Atendimentos/Histórico/Configurações
      (cadastro básico) — **pausada por decisão do usuário, tratada como manutenção secundária**
- [x] **Correção de rumo**: Composição de Preço refeita como ferramenta de tela única (produto +
      quantidade + tamanho + N personalizações com posição + cliente, tudo simultâneo, recálculo
      automático via API real) — ver PLAN.md §6.0
- [x] Tabela "como fica em outras quantidades" (faixas descobertas do cadastro real, cada linha
      calculada por uma chamada real ao Motor Comercial)
- [x] Debounce no recálculo (350ms) para não disparar uma chamada por tecla digitada
- [x] Bug real corrigido: `POST /aplicacoes-posicao` (500, `created_by` ausente) — migration 032 +
      teste de integração novo; suíte de backend 92/92
- [x] Testado manualmente no navegador com o exemplo real do usuário (20 Polo Básica + bordado
      peito + bordado manga + DTF costas) — resultado correto ponta a ponta

## Fase 6 — CORREÇÃO GRAVE: dados comerciais inventados no banco de desenvolvimento
- [x] Identificado: teste de demonstração inseriu preço de venda fabricado em 2 produtos (Polo
      Básica R$65, Polo Premium Piquet R$50), custo de bordado fabricado (R$5/6/3) e uma
      personalização inteira inventada (DTF — Costas, custo/venda fabricados)
- [x] Revertido: `faixas_produto.preco_base_unitario` dos 2 produtos volta a NULL
- [x] Revertido: `faixas_personalizacao.custo_real_unitario` do bordado volta a NULL (venda 15/12/8
      mantida — é dado real do REQUIREMENTS.md)
- [x] Removida fisicamente a personalização "DTF — Costas" (entidade inteira inventada, nunca um
      cadastro real)
- [x] Mantidas as 3 `aplicacoes_posicao` (Peito/Manga/Costas) — só rótulos, sem valor comercial
- [x] Auditoria completa do banco de desenvolvimento — confirmado zero dado comercial inventado
      remanescente (clientes/atendimentos/orçamentos/exceções também conferidos: vazios, como
      devem estar)
- [x] Bug real corrigido na tabela de faixas: agrupava 1–10 numa linha só por desconhecer o limiar
      de quantidade do programa/arte (que não vem de faixa cadastrada). Corrigido sondando a API
      quantidade a quantidade e agrupando por resultado real — nunca supondo limiar de regra no
      frontend
- [x] Suíte de backend re-confirmada: 92/92
- [x] Composição de Preço testada de novo com dados reais (todos NULL) — mostra BLOQUEADO
      corretamente, "Como fica em outras quantidades" mostra "comportamento igual em todas as
      quantidades sondadas" (correto, já que bloqueia pelo mesmo motivo em qualquer quantidade)
- [ ] Aprovação do usuário sobre a Composição de Preço antes de continuar expandindo o restante do
      painel

## Fase 7 — Integrações reais Meta/OpenAI/Bling (não iniciada, fora do escopo desta rodada — inclui
webhook do WhatsApp com validação de assinatura, adiado porque não fazia parte da lista de rotas da
Fase 5 pedida pelo usuário e não há fluxo real de WhatsApp ainda)
