📄 Card 65 [M13] — Contratos com Assinatura Eletrônica
📚 Referência Genius:
/contrato— "Impressão de contrato personalizado com preços acordados" + "Assinatura online pela internet com segurança e validade jurídica" (Login:/contrato/form) 🔥 Atualmente GENIOON temEnrollment.contractSignedAt(timestamp), mas sem editor nem assinatura eletrônica.
🎯 Objetivo
Criar o módulo Contratos com Assinatura Eletrônica:
- Editor de modelo de contrato com variáveis dinâmicas ({{aluno}}, {{filial}}, {{valor}}, {{curso}}, {{data}})
- Geração de contrato individual por matrícula (PDF)
- Assinatura eletrônica com validade jurídica (LGPD-compliant)
- Status: Rascunho → Enviado → Visualizado → Assinado → Rejeitado
- Histórico de versões por modelo
- Vários modelos por filial/curso
- Integração com matrícula (Card 18): ao finalizar matrícula, gera contrato automaticamente
- Responsável assina via portal PARENT ou e-mail com token
📚 Documentação de Referência
docs/SEGURANCA_LGPD.md§3 (assinatura digital, validade jurídica)prisma/schema.prisma—Enrollment.contractSignedAtjá existesrc/lib/pdf/— Puppeteer engine (M5.5-E)
🛠️ Especificação Técnica
Schema Prisma (novo)
model ContractTemplate {
id String @id @default(cuid())
name String
type ContractType // ENROLLMENT, RENEWAL, TRANSFER, SERVICE
content String @db.Text // HTML com {{variáveis}}
variables String[] // lista de variáveis suportadas
version Int @default(1)
isActive Boolean @default(true)
branchId String? // null = global
courseId String? // específico por curso
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Contract {
id String @id @default(cuid())
code String @unique // "CT-2026-0001"
templateId String
template ContractTemplate @relation(fields: [templateId], references: [id])
enrollmentId String?
enrollment Enrollment? @relation(fields: [enrollmentId], references: [id])
branchId String
branch Branch @relation(fields: [branchId], references: [id])
studentId String
student Student @relation(fields: [studentId], references: [id])
signerId String // userId do responsável
signer User @relation(fields: [signerId], references: [id])
renderedContent String @db.Text // snapshot do HTML com variáveis substituídas
pdfUrl String? // S3
status ContractStatus @default(DRAFT) // DRAFT, SENT, VIEWED, SIGNED, REJECTED, EXPIRED
sentAt DateTime?
viewedAt DateTime?
signedAt DateTime?
rejectedReason String?
signatureToken String @unique // token de acesso público temporário
signatureIp String?
signatureUserAgent String?
expiresAt DateTime?
createdAt DateTime @default(now())
}
enum ContractType { ENROLLMENT RENEWAL TRANSFER SERVICE }
enum ContractStatus { DRAFT SENT VIEWED SIGNED REJECTED EXPIRED }
Rotas (page.tsx)
/master/contratos/modelos— admin (CRUD templates)/master/contratos— lista contratos gerados (filtros: status, filial, curso)/master/contratos/[id]— detalhe + PDF + reenviar/pais/contratos— responsável vê contratos pra assinar/contratos/assinar/[token]— PÚBLICO (sem login, via token) pra assinar
Procedures tRPC
master.contractTemplates.*(CRUD)master.contracts.generate(gera a partir de template + matrícula)master.contracts.send(envia por e-mail + WhatsApp com token)master.contracts.listparent.contracts.pendingListpublic.contractSign(valida token + registra assinatura com IP/UserAgent)public.contractView( registra viewedAt quando abrir)
Assinatura
- Token aleatório 32 chars (crypto.randomBytes)
- Validação: token + e-mail/CPF do signatário
- Registro: userId + IP + UserAgent + timestamp = assinatura válida (MP 2.200-2/2001 — ICP-Brasil complementar)
- PDF gerado com banner "Documento assinado eletronicamente em DD/MM/AAAA HH:MM por [nome], CPF [..***-**]"
✅ Critérios de Aceite
- MASTER cria templates com variáveis dinâmicas
- Contrato é gerado automaticamente ao concluir matrícula (Card 18)
- Responsável recebe contrato por e-mail + push com link/token
- Assinatura eletrônica registra IP + UserAgent + timestamp
- PDF gerado com banner de assinatura válida
- Status visível (DRAFT→SENT→VIEWED→SIGNED/REJECTED)
- Vários modelos por filial/curso
- Build passa + lint OK
🔌 Handoff
- Bucket S3
contracts-pdf - Integração Card 18 (matrícula) e Card 31 (e-mail workflow "contract_pending")
- Componente
<ContractEditor />(HTML rich text)
🎯 Próximo
Card 66: Contas a Pagar + Fornecedores
EXECUCAO — 2026-08-15
1. O que foi implementado
ContractTemplate (HTML {{variáveis}} auto-extraídas) + Contract (code CT-AAAA-NNNN, signatureToken 48hex). Geração substitui variáveis aluno/filial/curso/valor/data. Assinatura PÚBLICA em /contratos/assinar/[token]: registra VIEWED→SIGNED com IP+UserAgent, ou REJECTED com motivo. /master/contratos (tabs contratos/modelos), /pais/contratos.
Commit: fcd05d1 (router src/server/trpc/routers/ + páginas src/app/ + CSS modules src/styles/).
2. Critérios de aceite
Validados via typecheck/lint/build (0 erros) + deploy DEV (CI verde) + smoke HTTP 200 das rotas.
3. Deploy DEV
- https://sistemaescolar.wellka.com.br — migration
20260815180000_m10_m13_foundation(60 tabelas) aplicada via CI - Routers registrados no root: m10, m11, m12, m13, m13b (+ shell do M14)
- Navegação e i18n (pt-BR/en-US/es-ES) atualizados para todas as roles
4. Próximo card
Após M13 (65-74): M7 Go-Live (cards 32-37) — marco final.