📄 Card 56 [M10] — Emissão de Certificados (PDF + Validação)
📚 Referência SULTS: Certificados automáticos nominais (Universidade Corp + Certificações). Geração na hora ao atingir pontuação. 🔥 Cliente confirmou (decisoes.md §EAD: "SIM — emitir certificado por módulo"). Schema
Certificatejá existe (Card 5) — agora com UI + geração.
🎯 Objetivo
Implementar a emissão e validação de certificados para:
- Alunos (conclusão de módulo/curso — Card 5)
- Colaboradores (Universidade Corp Card 53 + Certificações Card 55)
Inclui:
- Templates personalizáveis (Brand center da rede ANIK)
- Geração automática de PDF (Puppeteer — M5.5-E já existe)
- Código de validação único (qualquer um pode verificar autenticidade pública)
- Página pública
/certificados/validar/[codigo] - Validade + revogação
- Histórico por usuário
📚 Documentação de Referência
prisma/schema.prisma—Certificatemodel existe (expandir)src/lib/pdf/— Puppeteer engine (M5.5-E)docs/DESIGN_SYSTEM.md— branding (#3B82F6)
🛠️ Especificação Técnica
Schema Prisma (expandir Certificate)
model Certificate {
id String @id @default(cuid())
code String @unique // código validação (8 chars)
type CertOrigin // STUDENT_COURSE, CORP_COURSE, CERTIFICATION
title String // "Curso de Desenho - Módulo 1"
recipientId String // userId (aluno/colaborador)
recipient User @relation(fields: [recipientId], references: [id])
recipientName String // snapshot do nome (imutável)
issuerName String // "ANIK FRANCHISING"
issuedAt DateTime @default(now())
expiresAt DateTime? // null = perpétuo
score Float? // nota (se aplicável)
workloadHours Int? // carga horária
templateId String? // layout visual
template CertificateTemplate? @relation(fields: [templateId], references: [id])
pdfUrl String? // S3 do PDF gerado
sourceId String? // courseId / certificationId
isRevoked Boolean @default(false)
revokedAt DateTime?
revokedReason String?
createdAt DateTime @default(now())
}
model CertificateTemplate {
id String @id @default(cuid())
name String
backgroundUrl String? // S3 (fundo do certificado)
logoUrl String?
primaryColor String @default("#3B82F6")
signatureUrl String? // assinatura digitalizada
signerName String?
signerRole String?
layout Json // positions, fonts
}
enum CertOrigin { STUDENT_COURSE CORP_COURSE CERTIFICATION }
Rotas (page.tsx)
/certificados/validar/[code]— PÚBLICO (qualquer um valida sem login)/ead/minhas-certificacoes— lista do usuário/aluno/certificados— certificados do aluno/master/certificados— admin (templates + emitir manual + revogar)/master/certificados/templates— editor de templates
Procedures tRPC
master.certTemplates.*(CRUD templates)master.certificates.issue(emite manualmente)master.certificates.revokecertificates.myList(por usuário)public.validateCertificate(procedimento público — sem auth)
Geração de PDF
- Reusar Puppeteer engine (M5.5-E)
- Template HTML + dados → render → S3
- Job Bull
certificate-issue(background): ao concluir curso/certificação → gera PDF + upload S3 +pdfUrl - Código de validação: 8 chars alfanuméricos (ex:
AN1K-X8Y9)
Validação pública
- Página
/certificados/validar/[code]:- Busca por
code - Se válido: mostra nome, título, data, emitente, validade
- Se revogado: aviso vermelho
- Se não existe: 404
- Busca por
✅ Critérios de Aceite
- Ao concluir curso (aluno ou corp) com nota ≥ passingScore → certificado emitido
- PDF gerado com template branded ANIK (logo + cores + assinatura)
- Código de validação único (8 chars)
- Página pública valida sem login
- MASTER configura templates (cores, logo, assinatura)
- Revogação com motivo
- Validade configurável por template/curso
- Histórico por usuário
- Build passa + lint OK
🔌 Handoff
- Job
certificate-issueno bootstrap (background após conclusão) - Bucket S3
certificates-pdf - Integração com Cards 5, 53, 55 (triggers de emissão)
🎯 Próximo
Card 57: Brand Center (Marketing) — M11
EXECUCAO — 2026-08-15
1. O que foi implementado
IssuedCertificate unificado (code AN1K-XXXX/CERT-AAAA-NNNN, revogação, validade, templates CertificateTemplate). Emissão automática na conclusão de curso corp e aprovação em certificação. Página pública /certificados/validar/[code] sem login. PDF: pendência externa (pdfUrl null; print via browser).
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.