# P00 — Discovery técnico e auditoria do módulo existente Data: 2026-10-02 Escopo auditado: `c:\Users\Usuário\Documents\crm` (package `crm-2.0`, Next.js 16.0.10). Workspace desta sessão: `gerador-contratos` (repositório Gitea vazio, sem código de produto). Cópia irmã `Documents\CRM-2.0` existe e contém o mesmo módulo antigo de contratos, sem as migrations mais novas da calculadora. A fonte usada aqui é `crm`. Nenhum código de produto, banco ou UI foi alterado. ## Achado de localização O Gerador de Contratos não pode nascer neste repositório. `gerador-contratos` só tem git e remote `https://gitea.fatorxtech.com/cferraz/gerador-contratos.git`. Auth, RLS, feature flag, AppShell, Sentry, Redis e Design System estão em `crm`. A spec e a regra do módulo proíbem arquitetura paralela. **DT-CTR-007 foi aprovado:** implementar dentro de `crm`, preservando `/dashboard/contracts`. ## Arquitetura atual ``` Sidebar (lib/nav.ts, flag contracts) | v app/dashboard/contracts/layout.tsx requireFeature('contracts') --> fail-closed, redirect /dashboard | +-- page.tsx lista +-- new/page.tsx ContractWizard (passos fixos) +-- [id]/page.tsx ContractEditor +-- actions.ts Server Actions (CRUD, PDF, deal) | v Supabase Auth/SSR + getCurrentWorkspaceId() cookie active_workspace_id validado por membership | v Postgres contracts + contract_commercial + contract_sla_items + contract_modules + contract_clauses + contract_sla_defaults + contract_clause_defaults RLS: is_workspace_member (qualquer papel) | v PDF @react-pdf/renderer (utils/contractPdf.tsx) persistido no bucket privado Storage `contracts` caminho {workspace_id}/{contract_id}.pdf pdf_url gravado na linha API externa paralela (Bearer API key, service role): POST/GET /api/v1/contracts /api/v1/contracts/defaults /api/v1/contracts/:id POST /api/v1/contracts/:id/generate-pdf ``` Fora deste fluxo, e **não é o gerador**: - `calculator_contracts` (migration 156) confirma uma versão imutável de proposta da calculadora. Dono, time e hash. Não usar essas tabelas para templates. ## Inventário | Caminho | Papel | Classe | |---|---|---| | `lib/nav.ts` | Item Contratos, flag `contracts` | REUTILIZAR | | `utils/featureFlags.ts` | `isFeatureEnabled` / `requireFeature`, sem linha = desligado | REUTILIZAR | | `app/dashboard/contracts/layout.tsx` | Guard da flag | REUTILIZAR | | `utils/supabase/server.ts`, `utils/supabase/user.ts` | Auth SSR | REUTILIZAR | | `utils/workspace.ts` | `getCurrentWorkspaceId`, `isWorkspaceAdmin`, `isWorkspaceManager`, `getManagedTeamIds` | REUTILIZAR | | `utils/workspaceAdmin.ts` | Decisões puras de papel | REUTILIZAR | | `migrations/038_contracts.sql` | Schema do contrato comercial Fly Apex | MANTER como legado; não é o gerador | | `app/dashboard/contracts/actions.ts` | CRUD do contrato fixo | ADAPTAR só depois do DT-CTR-007; hoje qualquer membro edita | | `components/Contracts/ContractWizard.tsx` | Wizard de campos fixos + hex antigo | ADAPTAR ou conviver; não é wizard de template | | `components/Contracts/ContractEditor.tsx` | Editor de cláusulas/SLA/módulos | idem | | `components/Contracts/DealContractsTab.tsx` | Contratos no card do deal | REUTILIZAR o gancho de deal; domínio novo é outro | | `components/Contracts/ContractStatusBadge.tsx` | Badge de status comercial | REUTILIZAR se o status legado permanecer | | `utils/contractPdf.tsx` | PDF do schema fixo + persistência | ADAPTAR o renderer; o modelo de documento é CRIAR | | `app/api/v1/contracts/**` | API externa do schema antigo | MANTER para o legado; API nova só se DT-CTR-006 exigir | | `utils/sanitize.ts` | `stripHtml` / `sanitizeText` (remove tags) | ADAPTAR; não é sanitizador HTML com allowlist | | `utils/auditLog.ts` | Auditoria de plataforma (workspace/user/org) | ADAPTAR o padrão; eventos de contrato são CRIAR | | `utils/rateLimit.ts`, `utils/cache.ts` | Redis/ioredis, rate limit fail-open | REUTILIZAR | | `sentry.*.config.ts`, `instrumentation-client.ts` | Sentry, `sendDefaultPii: false`, replay em erro | ADAPTAR (falta scrub de conteúdo contratual) | | `app/globals.css` tokens `--bc-*` | Traxion DS implementado no app | REUTILIZAR; ver conflito visual abaixo | | `scripts/db/migrate.ts` + `migrations/*.sql` | Migrations numeradas, `npm run migrate` | REUTILIZAR | | `.github/workflows/ci.yml` | npm ci, Node 20, tsc, build | REUTILIZAR | | Famílias/versões de template, placeholders, drafts 24 h, Contract Document Model | inexistentes | CRIAR | Não há parser DOCX, upload de `.docx`/`.txt` de modelo, autosave de rascunho de formulário, nem cota de 10 modelos. ## Schema encontrado Migration `038_contracts.sql`: - `contracts`: workspace, deal opcional, título, cliente, status `rascunho|enviado|assinado|ativo|expirado|cancelado`, `pdf_url`. - `contract_commercial`: limites, setup, mensalidade, vencimento, pagamento. - `contract_sla_items`, `contract_modules`, `contract_clauses`. - `contract_sla_defaults` e `contract_clause_defaults`: seed global (`workspace_id` nulo) ou por workspace. Escrita de global exige `is_superadmin()`. - Flag `contracts` inserida com `enabled_globally = false`. - RLS de membro em todas as tabelas de contrato. Sem divisão owner/admin vs gestor/agent. - Nenhuma migration posterior altera `public.contracts`. `calculator_contracts` é outro bounded context (proposta da calculadora). ## Feature flag e guards - Flag `contracts`, default global desligado. Override por workspace vence o global (`utils/featureFlags.ts`). - Ausência da linha retorna `false` (fail-closed). - Layout chama `requireFeature('contracts')` e redireciona para `/dashboard`. - Server Actions repetem `guardFeature()`. - API v1 repete a checagem e devolve 403. - Sidebar só mostra o item com a flag ligada. - **Não há guard de papel** nas actions de contrato. `isWorkspaceAdmin()` não é usado em `app/dashboard/contracts`. Tenant: `getCurrentWorkspaceId()` lê o cookie `active_workspace_id` e confirma membership. As queries filtram `workspace_id` no servidor. A API v1 usa o workspace da API key, não um id solto do body. ## Gaps contra o Gerador de Contratos | Requisito | Estado | |---|---| | Famílias e versões; mesmo nome cria versão; sem overwrite | Inexistente. Defaults globais são seed fixo. | | Upload `.docx`/`.txt`, 5 MB, 50 `{{chave}}` | Inexistente. | | Tipos `data_*` date, `valor_*`/`preco_*` number, resto text | Inexistente. Campos comerciais são colunas fixas. | | Todos os campos obrigatórios | Wizard só exige título. | | Máx. 10 modelos-base ativos; versões não contam | Inexistente. | | Draft autosave ~15 s, TTL 24 h | Inexistente. Status `rascunho` é o contrato persistido. | | Draft ativo bloqueia exclusão da versão | Delete bloqueia só status `assinado` e `ativo`. | | Preview e PDF no mesmo Contract Document Model | PDF lê o schema relacional direto. Não há preview compartilhado. | | PDF não é repositório permanente no MVP | O fluxo atual grava o PDF no Storage e `pdf_url`. | | owner/admin gerenciam templates; gestor/agent operam | Qualquer membro do workspace faz CRUD. | | Superadmin não edita documento privado no fluxo comum | Superadmin entra em `isWorkspaceAdmin` (plataforma). Defaults globais usam `is_superadmin()`. Não misturar com o fluxo operacional novo. | | Sentry sem conteúdo contratual | `sendDefaultPii: false`. Sem `beforeSend` que raspe corpo de contrato, formulário, token ou cookie. Replay de erro está ligado no client (`replaysOnErrorSampleRate: 1`). | | UI no Design System, sem tokens do protocolo antigo | Wizard/editor usam hex `#001A3B`, `#00478F`, `#00AEEF`. O app vivo usa `--bc-*` (Traxion). | Conflitos que não devem ser resolvidos em silêncio: 1. O módulo em produção é um contrato comercial de plataforma (SLA, módulos, cláusulas markdown), ligado a deal e funil (`proposal_created` / `proposal_sent`). O gerador novo é template com placeholders. Substituir o schema atual quebra deal, API v1 e PDF armazenado. 2. A spec chama o visual de EloPro Design System V2.1. No `crm` o handoff implementado é Traxion DS, tokens `--bc-*` em `app/globals.css` e `docs/design/redesign-traxion-ds1.md`. Não há artefato nomeado V2.1 neste repo. Os hex do wizard são o visual antigo e não devem ser copiados. 3. `audit_log` da plataforma não tem ações de contrato e o `target_type` não inclui contrato. A calculadora tem `calculator_audit_log` próprio. O gerador precisa de trilha nova, no mesmo estilo, sem enfiar conteúdo do documento no `meta`. ## Estratégia recomendada Não apagar o módulo atual nesta fase. 1. Implementar o gerador **dentro de `crm`**, na flag `contracts` já existente. 2. Conviver com o contrato comercial legado até uma decisão de produto (aba, rota irmã ou substituição). Não reutilizar `contracts` / `contract_clauses` como tabela de template. 3. Criar tabelas novas de família, versão, campo, draft e auditoria do gerador, com RLS por `workspace_id` e checagem de papel no servidor (`isWorkspaceAdmin` para gestão). 4. Reusar Auth SSR, `getCurrentWorkspaceId`, feature flag, nav, Redis, `@react-pdf/renderer`, runner de migration e CI. 5. PDF novo: gerar sob demanda com o Contract Document Model e não gravar como acervo. O bucket `contracts` continua válido para o PDF legado. 6. UI nova com tokens `--bc-*` já presentes, até alguém apontar o arquivo V2.1 se ele for diferente do Traxion. ## Decisões aprovadas em 2026-10-02 (encerram o P00) 1. Implementar dentro de `Documents\crm`, ao lado do módulo antigo. Não substituir, migrar nem alterar o contrato comercial ligado ao deal. 2. Parser DOCX: Mammoth. Sanitização HTML: `sanitize-html` no servidor, obrigatória antes de persistir ou renderizar, preservando `{{chave}}`. Sem troca de biblioteca; Mammoth fica no Node (não no client e não no Edge). Cabeçalho, rodapé e caixa de texto seguem fora até a matriz DT-CTR-003. 3. Sem endpoint público `/api/v1` para o gerador. A API antiga permanece só no domínio legado. Os dez achados deste relatório continuam vinculantes. `workspace_id` não vem do browser. Tabela do módulo antigo não é reaproveitada por semelhança de nome. ## Decision gates | Gate | Status | Evidência | |---|---|---| | DT-CTR-001 parser DOCX | RESOLVIDO | Mammoth 1.13 no `crm`. Conversão do upload fica no P03. Roda só no Node. | | DT-CTR-002 sanitização HTML | RESOLVIDO | `sanitize-html` no servidor (`lib/gerador-contratos/sanitize.ts`), antes de persistir ou renderizar. `{{chave}}` é preservado fora de tag removida. | | DT-CTR-003 matriz DOCX | PENDENTE | Cabeçalho, rodapé, caixa de texto e estilo não estão prometidos. | | DT-CTR-004 Storage | RESOLVIDO POR EVIDÊNCIA | Supabase Storage. Bucket privado `contracts`, service role, URL assinada. Templates devem usar esse mecanismo; o path/bucket exato do arquivo-fonte fica para a implementação, sem trocar de produto. | | DT-CTR-005 deploy/CI/package | RESOLVIDO POR EVIDÊNCIA | npm (`package-lock.json`), Node 20, `npm run migrate` via `postgres`+`tsx`, CI em `.github/workflows/ci.yml` (tsc + build; lint não bloqueia). | | DT-CTR-006 API `/api/v1` | RESOLVIDO | Sem API pública nova no gerador. `/api/v1/contracts` permanece só no legado. | | DT-CTR-007 migração de `/dashboard/contracts` | RESOLVIDO | Convive. Gerador em `/dashboard/gerador-contratos`. Contrato comercial do deal intocado. | ## Plano P01–P12 (só depois do DT-CTR-007) Trabalho no repo `crm`, branch dedicada. Este repo `gerador-contratos` não recebe o app. 1. **P01** Fundação: tipos de domínio, guards de flag e de admin, esqueleto de telas (lista, novo, modelos admin, histórico) sem parser nem PDF novo. 2. **P02** Migrations novas (próximo número após a última em `crm/migrations`), RLS por workspace e papel, índices. Não alterar o significado das tabelas da 038. 3. **P03** Upload e parser com Mammoth e `sanitizeContractHtml`. Validação 5 MB, 50 chaves, tipos por prefixo, cota de 10 bases, versão por nome. A matriz DT-CTR-003 continua em aberto. 4. **P04** Wizard operacional: escolha, revisão, formulário dinâmico, tudo obrigatório. 5. **P05** Draft autosave ~15 s, TTL 24 h. Redis só como apoio; Postgres é a fonte se o draft precisar sobreviver. Bloqueio de exclusão com draft ativo. 6. **P06** Contract Document Model único para preview e PDF. `@react-pdf/renderer`. Sem persistir o PDF novo. 7. **P07** Admin de templates, versões, exclusão protegida, auditoria sem conteúdo sensível no log. 8. **P08** Trocar hex do fluxo novo pelos tokens `--bc-*`. Não criar design system. 9. **P09** Hardening: papel no servidor, scrub Sentry (corpo, formulário, token, cookie, replay), upload, XSS, rate limit reusando `utils/rateLimit.ts`. 10. **P10** Vitest + critérios de aceite. E2E no Playwright já existente se o fluxo estiver estável. 11. **P11** Performance, WCAG AA, responsivo. 12. **P12** Rollout pela flag `contracts` (continua fail-closed, default desligada). P13 e P14 ficam para revisão e correção, fora deste plano de construção.