Files
eden/docs/progress.md
Matheus (Handix) 18838a9031 feat(api): Quotes, Client Registration e esqueleto de Contratos
Fecha o núcleo comercial da Fase 2/3: motor de precificação de Ofertas
(feito diretamente, dado o risco de erro de arredondamento/regra de
negócio), Cadastro de Cliente e esqueleto de Contratos (via 2 subagentes
em paralelo, salvos manualmente após ambos baterem no limite de gasto
mensal da conta — trabalho não commitado nos worktrees foi revisado
arquivo a arquivo e copiado para main, com as peças faltantes
completadas diretamente).

- Quotes: pricing.ts porta as fórmulas do legado (eden.md §7/§8) —
  needsApproval, faixas de condição de pagamento, efeito legal da
  fidelidade condicionado a approval_status==='approved'. Fluxo
  criar→decidir aprovação→fechar negócio (vinculado a um cadastro de
  cliente, com guarda contra fechamento duplo).
- Client Registration: cadastro direto (eden.md §3), MVP autenticado-only
  (sem token público de autoatendimento, upload ou lookup CNPJ/CEP nesta
  fase — documentado em comentário).
- Contracts: máquina de estados do ADR-0006 imposta via SELECT...FOR
  UPDATE + tabela de transições, com signed_at/activated_at e histórico
  de status; 4 das 11 tabelas do ADR-0006 (as demais adiadas).

2 bugs próprios corrigidos antes do commit: UpdateQuoteInput usava
COALESCE para campos financeiros (undefined do JS apagaria valor em
update parcial) — campos tornados obrigatórios; User.reseller_id/
can_approve_discount faltavam em types.ts apesar das colunas já
existirem no banco desde migrations anteriores.

Verificado: pnpm turbo run typecheck build (28/28), pnpm test (15/15),
e todo o fluxo testado ao vivo via curl contra a stack Docker real,
incluindo cenários de erro (transição de status inválida, FK de produto
inexistente, fechamento duplo, 401 sem token) e trilha de auditoria
conferida no Postgres.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 12:25:49 -03:00

156 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# EDEN — Progresso
## Fase 0 — Descoberta e Arquitetura: CONCLUÍDA
Data: 2026-09-03.
### Entregáveis
- `docs/project-inventory.md`, `docs/glossary.md`, `docs/architecture.md`, `docs/assumptions.md`, `docs/security/threat-model.md`, `docs/implementation-plan.md`, `docs/progress.md` (este arquivo).
- 12 ADRs em `docs/adr/` (0001–0012), cobrindo: modular monolith, topologia de containers (Postgres isolado + eden-core/parceiros/assinante em containers/portas distintas — decisão explícita do operador), auth/sessões, modelo de permissões, criptografia/segredos, contrato first-class, stock ledger, separação billing/finance/fiscal, outbox transacional, adapter SaperX, adapter Focus NFe, três apps com identidade compartilhada.
- 14 subagentes em `.claude/agents/`: eden-architect, eden-database, eden-security, eden-commercial, eden-finance, eden-fiscal, eden-inventory, eden-telecom, eden-support, eden-hr-timeclock, eden-frontend, eden-api-integrations, eden-qa, eden-code-reviewer.
- 7 skills em `.claude/skills/`: eden-domain, eden-security, eden-finance, eden-fiscal, eden-inventory, eden-telecom, eden-timeclock — cada uma com `SKILL.md` + `references/`.
- Hooks em `.claude/settings.json` + `.claude/hooks/*`: `PreToolUse` (guarda de comandos Bash destrutivos/externos e de acesso a arquivo fora do projeto/`.ssh`/`/etc`/segredo), `PostToolUse` (lint/typecheck incremental, fail-open enquanto pnpm não existir), `Stop` (checagem advisória de segredo/TODO crítico no diff, silenciosa até existir repositório git). Testados via pipe com payloads sintéticos — todos os cenários (allow/ask/deny/fail-open) se comportaram como esperado.
### Revisão cruzada (self-review contra os critérios dos agentes eden-architect / eden-security / eden-database, criados nesta mesma fase)
**eden-architect**: bounded contexts (`docs/architecture.md` §2) têm direção de dependência consistente (nenhum ciclo); HR/Timeclock corretamente isolado; candidatos a extração futura (Billing, Fiscal) identificados sem extração prematura. Nenhuma feature de negócio foi implementada antes deste gate — consistente com o Master Prompt §18.
**eden-security**: threat model cobre as superfícies do legado (rotas públicas, auth, autorização, integrações, upload, PDF, auditoria, infraestrutura) mais os invariantes de teste obrigatórios (§15.3). Hooks `PreToolUse` já impõem, a nível de ferramenta, várias regras de §2.1/§2.2 (fora do projeto, `.ssh`/`/etc`, impressão de `.env`, SSH/SCP externo, remoção ampla de volume/prune). Pendência real registrada (não bloqueante): política de retenção/eliminação LGPD ainda não definida pela Handix (`docs/assumptions.md` #1).
**eden-database**: princípios de modelagem (§11) já refletidos nas ADRs de contrato/estoque/billing (nunca saldo editável, nunca CASCADE indiscriminado, JSONB só para snapshot/metadata). Nenhum schema físico foi criado ainda nesta fase — corretamente adiado para a Fase 1, quando o Postgres 18 realmente subir.
**Nota**: esta revisão foi feita pela própria sessão orquestradora aplicando os critérios definidos nos arquivos de agente recém-criados. Os subagentes dedicados (`eden-architect`, `eden-security`, `eden-database`) ficam disponíveis para revisões reais em sessões futuras assim que o Claude Code os carregar como subagent types.
### Pendências não bloqueantes (ver `docs/assumptions.md`)
1. Política de retenção/eliminação de dados pessoais (LGPD) — Handix ainda não definiu.
2. Docker/Node/pnpm precisam ser provisionados no ambiente antes da Fase 1.
3. Portas dos containers definidas via `.env` (não fixas) — ver ADR-0002.
4. Git ainda não iniciado no projeto — planejado para o início da Fase 1.
5. Inventário fino do `tema_do_Eden.zip` (extração de componentes) adiado para o início de `eden-frontend`.
## Gate de saída da Fase 0: VERDE
Todos os itens do checklist da seção 22 do Master Prompt (“Primeira Execução”) estão completos.
---
## Fase 1 — Plataforma: EM ANDAMENTO
Data de início: 2026-09-03.
### Ambiente provisionado
- Docker CE 29.7.2 + Compose v5.5.0 (repositório oficial Docker, não o `docker.io` do Debian) instalados via apt.
- Node.js 22.23.2 LTS (NodeSource) + pnpm 11.25.0 (via corepack).
- Git 2.47.3. Repositório inicializado (`main`), remoto `origin` = `https://git.falehandix.com.br/Matheus/eden.git` (nenhum push feito ainda).
### Monorepo
- pnpm workspaces + Turborepo. 5 apps (`api`, `worker`, `core-web`, `reseller-web`, `subscriber-web`) + 9 packages, todos com `package.json`/`tsconfig.json` válidos.
- `pnpm install`, `pnpm turbo run typecheck` e `pnpm turbo run build` passam 100% (15/15 e 14/14 tarefas, respectivamente) em todo o monorepo.
- `apps/api`: NestJS mínimo com `/health/live` e `/health/ready` (este último checando Postgres de verdade via `@eden/database`).
- `apps/worker`: placeholder que confirma conectividade com o banco; sem BullMQ/Redis ainda (conforme ADR-0002 — só entra quando houver job real).
- `apps/core-web`, `apps/reseller-web`, `apps/subscriber-web`: Vite + React + TS + Tailwind, cada um com favicon oficial do EDEN.
- `packages/database`: migrations via `node-pg-migrate` (formato `.cjs`), client de query (`pg` Pool).
### Banco de dados
- Migration baseline aplicada com sucesso contra Postgres 18.6 real (container `eden-postgres`): `roles`, `role_permissions`, `applications`, `users`, `user_applications`, `sessions`, `audit_log` — cobrindo Identity & Access (ADR-0003/ADR-0004) e um audit log append-only com hash-chain (testado: `UPDATE`/`DELETE` são efetivamente bloqueados pelo trigger, só liberados via `SET LOCAL eden.allow_audit_mutation`).
- Seeds: 4 papéis do sistema (pesos idênticos ao legado) + as 3 aplicações (`core`/`reseller`/`subscriber`).
- Organization (empresas/entidades legais), Commercial e demais bounded contexts **ainda não têm migration** — ficam para quando cada fase começar, por design (ver ADR-0001).
### Docker Compose — topologia ADR-0002 validada de ponta a ponta
`docker compose --env-file .env up -d` sobe os 6 serviços com sucesso:
| Serviço | Status observado |
|---|---|
| `eden-postgres` | healthy — porta dev 55432, nunca 5432 direto |
| `eden-api` | healthy — único serviço com `DATABASE_URL`; `/health/ready` confirma Postgres via rede interna |
| `eden-worker` | up — conecta ao Postgres, sem porta exposta |
| `eden-core` (:3001) | healthy — HTTP 200 |
| `eden-parceiros` (:3002) | healthy — HTTP 200 |
| `eden-assinante` (:3003) | healthy — HTTP 200 |
Bugs de bootstrap corrigidos nesta sessão (documentados aqui para não se repetirem):
1. Imagem oficial `postgres:18-alpine` mudou o layout do volume — precisa montar `/var/lib/postgresql` (não `.../data`).
2. `apps/api/package.json` tinha `"type": "module"` incompatível com o `tsconfig` (saída CommonJS) — Node falhava com `exports is not defined`.
3. `node-pg-migrate --envPath` não carregava o `.env` corretamente nesta versão — trocado por `bash -c 'source .env; ...'` nos scripts `migrate:up`/`migrate:down`.
4. Healthcheck dos 3 frontends usava `wget http://localhost/...`, que resolve para `::1` (IPv6) dentro do container — nginx só escuta IPv4; trocado para `127.0.0.1`.
### Auth, permissões, Companies e OpenAPI — implementados e testados ao vivo
- **Auth** (`apps/api/src/auth/`): Argon2id (parâmetros versionados, ADR-0005), JWT de acesso de 15 min, refresh token opaco (256 bits) com rotação e **detecção de replay** (reuso de token já rotacionado revoga toda a cadeia de sessões do usuário). Endpoints `POST /api/v1/auth/{login,refresh,logout}` e `GET /api/v1/auth/me`.
- **Permissões** (`apps/api/src/auth/permissions.ts`, `permission.guard.ts`): resource+action+scope (ADR-0004), `super_admin` com bypass hardcoded (nunca consulta `role_permissions`, espelhando a trava também existente no banco). Guard resolve permissões do banco a cada request (nunca cacheado no JWT) — mudança de papel tem efeito imediato, mesma filosofia do legado.
- **Companies** (`apps/api/src/companies/` + migration `1788437478189_companies`): CRUD com leitura aberta a qualquer autenticado e escrita restrita a `companies:manage`; validação de CNPJ (dígito verificador, `packages/domain-shared`) testada com CNPJ válido/inválido conhecidos.
- **Seed automático**: `SeedService` cria o primeiro `super_admin` a partir de `EDEN_SUPERADMIN_EMAIL`/`PASSWORD` só quando `users` está vazia — testado (log `super_admin inicial criado`).
- **OpenAPI/Swagger**: `/api/docs` (UI) e `/api/docs-json`, cobrindo todos os endpoints atuais.
- **Validação/segurança de request**: `ValidationPipe` global com `whitelist`+`forbidNonWhitelisted` (proteção contra mass-assignment) — testado (`id`/`isActive` extras num POST de company são rejeitados com 400).
Testes manuais realizados contra a stack real (não só typecheck): login com senha errada → 401 genérico; refresh rotaciona e o token antigo some; reuso do token antigo → 401 + toda a cadeia revogada; logout revoga e é idempotente; audit log com a sequência completa de eventos (`auth.login`, `auth.refresh`, `auth.refresh_replay_detected`, `company.created`).
### CRUD de Usuários/Papéis, Products, Resellers, adapters S3/SMTP — implementados via 4 subagentes especializados em paralelo (worktrees isolados) + trabalho direto
Estratégia: 4 subagentes (`general-purpose`, atuando com a persona `eden-commercial`/`eden-api-integrations` definida em `.claude/agents/`, já que os subagent types customizados ainda não carregaram nesta sessão) rodaram em paralelo, cada um em `git worktree` isolado, enquanto o CRUD de Usuários/Papéis foi feito diretamente (por tocar peso hierárquico e segurança). Cada agente foi instruído a não editar `app.module.ts` nem `packages/database/src/index.ts` — a integração final (merge das branches, wiring, resolução de conflitos, rebuild, teste ao vivo) foi feita manualmente.
- **Users/Roles** (`apps/api/src/users/`, `apps/api/src/roles/`): convite com senha temporária, troca de senha self-service, reset administrativo, grant/revoke de aplicação — tudo com checagem de peso hierárquico (`assertCanAssignRole`/`assertCanActOnUser`, eden.md §1.6). CRUD de papéis e `role_permissions` exclusivo `super_admin` (`SuperAdminGuard`), deliberadamente não generalizado para o modelo de permissão (raiz do próprio sistema de autorização).
- **Products** (`apps/api/src/products/` + migration `products`): catálogo com as 5 faixas de preço fixas, `product_code` sequencial, flags de rastreamento (estoque/serial/patrimônio/MAC). Leitura aberta, escrita via `products:edit`.
- **Resellers** (`apps/api/src/resellers/` + migration `resellers`): CRUD operacional + vínculo usuário↔revenda (`users.reseller_id`). Leitura aberta, escrita via `resellers:edit` — desvio deliberado do legado (que travava em `requireAdmin`), documentado em comentário.
- **Adapters S3/SMTP** (`packages/integrations/`): portados fielmente do legado — S3 falha alto (erro claro se não configurado), SMTP falha silencioso (nunca quebra um fluxo de negócio por causa de e-mail).
- **CRM** (`apps/api/src/leads/`, `apps/api/src/opportunities/` + migration `crm`): leads e oportunidades com conversão transacional (`convertLeadToOpportunity`, `FOR UPDATE` + `withTransaction`). Diferente de Companies/Products, leitura também exige permissão (`leads:view`/`opportunities:view`) por conter PII de prospect.
**4 bugs reais encontrados em teste manual e corrigidos antes do commit final:**
1. `assertCanAssignRole`/`assertCanActOnUser` pulavam a validação de existência do papel quando o ator era `super_admin` (o bypass de peso pulava a checagem inteira) — convidar usuário com `role_key` inexistente estourava a constraint FK do banco como 500 não tratado em vez de 400 claro.
2. `PATCH /v1/resellers/:id/assign-user` aceitava um `resellerId` diferente no corpo, que na prática era o valor usado — o `:id` da URL virava só uma checagem de existência, não o alvo real da atribuição. Corrigido para o `:id` da URL ser a única fonte de verdade; desatribuição virou endpoint próprio (`POST /v1/resellers/unassign-user`).
3. Os controllers de Leads/Opportunities liam `req.user?.id` em vez de usar `@CurrentUser()`/`request.edenUser` (o padrão real do `JwtAuthGuard`) — todo evento de auditoria de lead/oportunidade estaria gravando `actorId: null` sempre. O agente que os escreveu não tinha o módulo de auth disponível no worktree dele para testar isso de verdade (só stubou e reverteu antes de commitar).
4. As chamadas `appendAuditEvent()` de leads/opportunities omitiam o campo obrigatório `actorType` (6 ocorrências).
Também precisou renomear o timestamp da migration de CRM, que o agente gerou com valor anterior ao da migration de `resellers` (já aplicada por outro agente/merge) — `node-pg-migrate` recusa migration fora de ordem.
Todos os módulos testados ao vivo contra a stack real (não só typecheck): convite de usuário, papel inexistente (400 após o fix), CRUD de produto com preços NUMERIC corretos, CRUD de revenda, atribuição/desatribuição de usuário à revenda, lead→oportunidade transacional.
### Suíte de testes de integração (agente eden-qa)
Vitest + supertest + `@nestjs/testing` (bootstrap real do Nest, sem mock) contra o Postgres real — 5 arquivos, 15 testes, todos verdes: `auth.spec.ts` (anti-enumeração, rotação de refresh, **detecção de replay fim-a-fim**, logout idempotente), `role-weight.spec.ts` (guarda a regressão do bug #1), `companies.spec.ts` (CNPJ), `mass-assignment.spec.ts` (whitelist), `reseller-assignment.spec.ts` (guarda a regressão do bug #2). Achado técnico do agente: o `esbuild` (transform padrão do Vitest) não implementa `emitDecoratorMetadata`, quebrando toda injeção de dependência do Nest silenciosamente sob teste — corrigido trocando para SWC (`unplugin-swc`), abordagem padrão documentada pelo próprio NestJS. Rodar com `pnpm --filter @eden/api test`.
### Resumo do lote de agentes paralelos
6 subagentes no total (4 + 1 QA + Users/Roles feito diretamente), rodando em `git worktree` isolados, revisados e mesclados manualmente um a um (nunca merge automático sem verificação). **4 bugs reais** encontrados só porque cada módulo foi testado contra a stack viva antes do commit — nenhum deles teria aparecido só com typecheck/build, que todos os agentes já reportavam verde. Padrão validado: delegar implementação para agentes especializados funciona bem para módulos CRUD bem especificados, mas a integração final (merge, wiring, teste ao vivo, revisão) precisa ficar com quem tem visão do sistema inteiro.
### Pendências da Fase 1 (não iniciadas)
- MFA/TOTP (schema já existe — `users.mfa_*` — endpoint ainda não implementado).
- Extração do Design System a partir do tema DreamsERP (`packages/ui` ainda é placeholder).
- Wiring dos adapters S3/SMTP em algum módulo consumidor real (hoje só existem como biblioteca em `packages/integrations`, nada os chama ainda).
- Nota de dado de teste: a empresa "Handix Telecom LTDA" e a revenda "Handix (revenda própria)" criadas durante os testes usam dados de exemplo, não os dados reais da Handix — substituir/remover antes de qualquer ambiente que não seja dev local.
---
## Fase 2/3 — Ofertas (Quotes), Cadastro de Cliente (Customer 360) e esqueleto de Contratos
Data: 2026-09-03.
### Ofertas / Quotes (`apps/api/src/quotes/` + migration `quotes`) — implementado diretamente
Núcleo comercial mais sensível a erro de arredondamento/regra de negócio (eden.md §7/§8), por isso feito sem delegar a subagente:
- `pricing.ts`: funções puras porteadas literalmente do legado — `monthlyTotal = Σitem.total`; `totalImpl = implFromProducts + implGeral`; `hasSpecialPrice/isDiscount/isMarkup` por comparação com `proposedMonthlyTotal`; `contractTotal = effectiveMonthly * (contractPeriod||1) + totalImpl`; `needsApproval = isDiscount || hasReducedFidelity`; fidelidade só tem efeito legal com `approval_status === 'approved'`. Faixas de condição de pagamento (`À vista` ≤1500; `+1+1` ≤3000; `+1+2` acima) replicadas do legado.
- Preço sempre derivado server-side do produto vivo no banco (nunca confia em preço vindo do cliente).
- Fluxo completo: criar oferta → decidir aprovação (`PATCH :id/approval`) → fechar negócio vinculando a um cadastro de cliente (`POST :id/close`, bloqueado por `client_registration_id IS NULL` — não fecha duas vezes).
### Customer 360 / Cadastro de Cliente (`apps/api/src/client-registrations/` + migration `client-registrations`) e Contratos (`apps/api/src/contracts/` + migration `contracts`) — subagentes + salvamento manual
Lançados 2 subagentes em paralelo (worktrees isolados) para Cadastro de Cliente e esqueleto de Contratos. Ambos falharam duas vezes: primeiro com rate limit de sessão (retomado após o usuário habilitar cobrança de excedente), depois com **limite de gasto mensal da conta** — um bloqueio de nível de conta que não se resolve tentando de novo. Em vez de insistir, os dois worktrees foram inspecionados (`git status`, diff arquivo a arquivo); o trabalho não commitado encontrado em ambos era substancial e de boa qualidade — foi copiado diretamente para `main` (sem passar por branch/merge, já que nada tinha sido commitado) e o que faltava foi completado manualmente:
- **Cadastro de Cliente**: repositórios e camada de API salvos do agente praticamente inalterados; só faltava o `client-registrations.module.ts`, escrito manualmente. MVP com escopo explícito (eden.md §3): só fluxo autenticado, sem token de autoatendimento público, sem upload de anexo, sem integração CNPJ/CEP — tudo documentado em comentário no código, não descartado silenciosamente.
- **Contratos**: repositório salvo do agente (com um import não usado removido); toda a camada de API (DTOs, service, controller, module) escrita manualmente, pois o agente não tinha chegado lá. Máquina de estados (ADR-0006) `draft→{pending_signature,cancelled}`, `pending_signature→{active,cancelled}`, `active→{suspended,cancelled,terminated,expired}`, `suspended→{active,cancelled,terminated}` — imposta via `SELECT...FOR UPDATE` + tabela de transições, com `signed_at`/`activated_at` carimbados na transição correta e histórico completo em `contract_status_history`. Escopo: 4 das 11 tabelas do ADR-0006 (contracts, contract_items, contract_parties, contract_status_history); as outras 7 explicitamente adiadas.
Wiring final feito manualmente: barrel (`packages/database/src/index.ts`) e `app.module.ts` atualizados; 3 migrations aplicadas ao Postgres real; `pnpm turbo run typecheck build` (28/28 tasks) e `pnpm --filter @eden/api test` (15/15 testes) verdes; container `eden-api` reconstruído e todo o fluxo testado ao vivo via curl contra a stack real:
- Oferta sem preço especial (sem aprovação) e oferta com desconto + fidelidade reduzida (`approval_status: pending`) — valores conferidos manualmente contra a fórmula.
- Aprovação da oferta, fechamento vinculando a um cadastro de cliente criado via `POST /client-registrations/direct`, e guarda de fechamento duplo (409 na segunda tentativa).
- Contrato criado a partir da oferta fechada, transições de status válidas e inválidas (draft→active direto rejeitado com 409; draft→pending_signature→active aceito, `signed_at`/`activated_at` carimbados corretamente), adição de item/parte, FK de produto inexistente rejeitada com 400.
- 401 sem token; trilha de auditoria (`audit_log`) conferida no Postgres com a sequência completa de eventos do lote (`quote.created`, `quote.approval_approved`, `quote.closed`, `client_registration.created`, `contract.created`, `contract.status_changed` ×2, `contract.party_added`, `client_registration.updated`).
**2 bugs reais encontrados e corrigidos antes do commit (nenhum via subagente — achados na própria implementação/revisão):**
1. Primeiro rascunho do `updateQuote` (escrito por mim) usava `COALESCE` para os campos financeiros (`fidelity_period`, `proposed_monthly_total`, `approved_by_id`) — como `undefined` do JS também vira `NULL` no SQL, qualquer update parcial que omitisse esses campos os apagaria silenciosamente. Corrigido tornando esses campos **obrigatórios** em `UpdateQuoteInput` (não mais opcionais), forçando a camada de serviço a sempre fornecer um valor deliberado.
2. `packages/database/src/repositories/types.ts` — a interface `User` estava sem `reseller_id`/`can_approve_discount`, mesmo essas colunas já existindo no banco desde as migrations de Resellers/Quotes (ninguém tinha atualizado o tipo compartilhado). Descoberto ao ligar o uso desses campos em Quotes; corrigido no `types.ts` e propagado para `EdenAuthenticatedUser` em `jwt-auth.guard.ts`.
### Pendências deste lote
- FKs cruzadas entre as 3 tabelas ainda não existem (ficaram como UUID nullable simples, documentado em comentário nas migrations): `quotes.client_registration_id → client_registrations(id)`, `client_registrations.quote_id → quotes(id)`, `contracts.quote_id → quotes(id)`, `contracts.client_registration_id → client_registrations(id)`.
- Fluxo de autoatendimento público (token) de Cadastro de Cliente, upload de anexo, integração CNPJ/CEP — todos fora do escopo desta MVP.
- 7 das 11 tabelas do ADR-0006 de Contratos (aditivos, faturas, documentos/e-signature, etc.) ainda não criadas.