📄 Card 55 [M10] — Certificações (Provas Formais Anti-Cola)
📚 Referência SULTS: https://www.sults.com.br/produtos/certificacoes Plataforma de avaliação e certificação empresarial com banco de questões próprio, sorteio aleatório anti-cola, correção automática e certificados nominais com validade. Diferente do Card 53 (cursos), este é foco em provas formais de competência.
🎯 Objetivo
Criar o módulo Certificações — provas formais para validar competências (onboarding, padrões de atendimento, uso do sistema, segurança):
- Banco de questões próprio por categoria (Geral, Operacional, Atendimento, Segurança) e nível (Básico→Alto)
- Sorteio aleatório: cadastra 50 questões, sistema sorteia 20 por participante
- Ordem aleatória anti-cola: perguntas e alternativas embaralhadas
- Cronômetro + tempo limite
- Correção automática + gabarito
- Certificados nominais com validade (data de expiração)
- Regras de tentativas/recertificação (com carência 24h ou desbloqueio manual)
- Área "Minhas Certificações" (histórico completo)
📚 Documentação de Referência
prisma/schema.prisma—Certificatemodel existe (Card 5), expandir- Padrão do Card 14 (provas de aluno) — referência de UI
🛠️ Especificação Técnica
Schema Prisma (novo + expandir Certificate)
model Certification {
id String @id @default(cuid())
name String // "Atendimento Padrão ANIK"
description String?
category String // "Geral", "Atendimento", "Operacional"
level CertLevel @default(BASIC) // BASIC, INTERMEDIATE, ADVANCED, EXPERT
questionsPerAttempt Int @default(20) // sorteia do banco
timeLimitMin Int @default(60)
passingScore Float @default(70)
attemptsPolicy AttemptsPolicy @default(UNLIMITED) // UNLIMITED, COOLDOWN_24H, MANUAL_UNLOCK
certificateValidityMonths Int? // null = sem expiração
questionBank CertQuestion[]
attempts CertAttempt[]
targetRoles String[]
isActive Boolean @default(true)
createdAt DateTime @default(now())
}
model CertQuestion {
id String @id @default(cuid())
certificationId String
certification Certification @relation(fields: [certificationId], references: [id])
text String @db.Text
type CertQType // SINGLE_CHOICE, MULTI_CHOICE, TRUE_FALSE
options Json // [{id, text, isCorrect}]
explanation String? // exibida no gabarito
category String?
difficulty CertLevel @default(BASIC)
imageUrl String?
createdAt DateTime @default(now())
}
model CertAttempt {
id String @id @default(cuid())
certificationId String
certification Certification @relation(fields: [certificationId], references: [id])
userId String
user User @relation(fields: [userId], references: [id])
status AttemptStatus @default(IN_PROGRESS) // IN_PROGRESS, COMPLETED, PASSED, FAILED
questionsSort String[] // IDs sorteados (ordem embaralhada)
answers Json // {questionId: selectedOptionIds[]}
score Float?
startedAt DateTime @default(now())
completedAt DateTime?
certificateIssuedId String? // se passou (Card 56)
}
enum CertLevel { BASIC INTERMEDIATE ADVANCED EXPERT }
enum AttemptsPolicy { UNLIMITED COOLDOWN_24H MANUAL_UNLOCK }
enum CertQType { SINGLE_CHOICE MULTI_CHOICE TRUE_FALSE }
enum AttemptStatus { IN_PROGRESS COMPLETED PASSED FAILED }
Rotas (page.tsx)
/ead/certificacoes— catálogo/ead/certificacoes/[id]/prova— execução (cronômetro + questões embaralhadas)/ead/certificacoes/[id]/resultado— gabarito + score + certificado (se passou)/ead/minhas-certificacoes— histórico (validade, recertificação)/master/certificacoes— admin (CRUD provas + banco questões + relatórios)
Procedures tRPC
master.certifications.*(CRUD)master.certifications.bank.*(CRUD questões)certifications.list(catálogo por role)certifications.startAttempt(sorteia questões + embaralha)certifications.submitAttempt(corrige + emite certificado se passou)certifications.myHistory
Anti-cola
- Sorteio aleatório de N questões do banco (algoritmo Fisher-Yates)
- Ordem das questões embaralhada por tentativa
- Ordem das alternativas embaralhada por tentativa
- Cronômetro no servidor (não só cliente) — bloqueia envio após timeLimitMin
- Tentativa em tela cheia (sem menu lateral) + warning ao sair da aba (
visibilitychange) - (Avançado/futuro) Captura de câmera para detectar rosto
✅ Critérios de Aceite
- MASTER cria certificação + banco de questões (4 tipos + imagens)
- Sorteio aleatório + ordem embaralhada por tentativa
- Cronômetro cliente + servidor (bloqueia após limite)
- Correção automática + gabarito com explicações
- Certificado nominal emitido se passingScore atingido (Card 56)
- Validade configurável + recertificação
- Políticas de tentativas (UNLIMITED, COOLDOWN_24H, MANUAL_UNLOCK)
- Histórico "Minhas Certificações"
- Build passa + lint OK
🔌 Handoff
- Job
cert-expiry-check(diário): certificados vencendo em 30 dias → notifica - Integração com Card 56 (Certificado PDF)
- Integração com Card 53 (Universidade Corp): curso concluído pode exigir certificação
🎯 Próximo
Card 56: Emissão de Certificados (PDF + Validação)
EXECUCAO — 2026-08-15
1. O que foi implementado
Certification/CertQuestion/CertAttempt: sorteio Fisher-Yates, alternativas embaralhadas SEM isCorrect ao cliente, cronômetro validado no server (startedAt+timeLimitMin), políticas COOLDOWN_24H/MANUAL_UNLOCK, página de prova com warning visibilitychange e auto-entrega. /ead/certificacoes + [id]/prova + /master/certificacoes (banco de questões).
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.