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

21 KiB
Raw Blame History

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.