Implantação MVP
This commit is contained in:
commit
aa1d925642
20 files changed
+1162
No files matched your search
@@ -0,0 +1,185 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user