Files
gerador-contratos/docs/contracts/P00-discovery-audit.md
T
2026-10-03 00:36:29 -03:00

186 lines
13 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.
# 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.