📄 Documento de Execução — Card 7b: Motor de Gamificação
| Campo | Valor |
|---|---|
| Card Trello | [M1] Motor de Gamificação (Ranking, Medalhas, XP) |
| URL Trello | https://trello.com/c/2j5MXtNX |
| Marco | M1 — Portal do Aluno (STUDENT) |
| Data execução | 28/07/2026 |
| Responsável | Wellington Santiago (via ZCode) |
| Doc anterior | Card 7: Tarefas & Entregas |
| Próximo card | Card 8: Home da Família (M2) |
| Commits | b009428, 4d5e99a |
🎯 1. O que foi implementado
Motor transversal de gamificação inspirado em MemberBox: XP por atividade, streak diário, medalhas por conquista, níveis/patentes e ranking por turma. O motor é transversal — não vive isolado, é acionado por gatilhos nos cards existentes (aula concluída, tarefa entregue, nota alta) e recalculado em batch por jobs Bull noturnos.
Componentes do motor
Schema Prisma (5 modelos novos + 3 enums):
StudentXP,XPHistory,Achievement,StudentAchievement,Leaderboard+ enumsPatent,XPReason,LeaderboardPeriod. Relationados aUser(gamificação é por usuário) eClass(ranking por turma).Rules engine (
src/server/gamification/xp-rules.ts): pure functions para cálculo de patente/nível sem DB. XP_RULES canônico (50/100/200/500/30 XP), patentes Iniciante→Lenda (6 faixas), streak bonus cap 300/dia.awardXPservice (src/server/gamification/award.ts): função central idempotente viametadata.idempotencyKey. Transação atômica (StudentXP update + XPHistory insert). Após creditar, disparacheckAchievements()fire-and-forget. TambémupdateStreakOnLogin(idempotente por dia) eawardHighGradeXP(helper para o Card 13).Engine de medalhas (
src/server/gamification/achievements.ts): catálogo SEED de 12 medalhas + avaliador de critérios (lesson_count, streak, course_complete, high_grade, task_count, xp_threshold).checkAchievements(userId)reavalia todas as medalhas ativas contra estado real do aluno e desbloqueia + credita XP reward.Triggers XP plugados nos cards existentes:
student.lessons.markCompleted→ 50 XP por aula (1ª conclusão, idempotente)student.tasks.submit→ 100 XP por tarefa (1ª entrega, idempotente)awardHighGradeXPpronto para Card 13 (notas ≥ 9.0 → 200 XP)
Jobs Bull (
src/server/queues/workers/gamification.ts):update-leaderboard: recalcula ranking por turma (WEEKLY/MONTHLY/ALL_TIME) preservando delta de rank/XP. Janela de datas para WEEKLY/MONTHLY via aggregate de XPHistory.check-achievements(batch): safety net noturna para medalhas perdidas por bug.- Helpers produtores:
scheduleLeaderboardRecalc,scheduleAchievementsCheck,refreshClassLeaderboard.
tRPC procedures (
student.gamification.{profile, achievements, leaderboard}):profile: XP, nível, patente, streak, progresso de nível, top 5 medalhas, histórico recente. ChamaupdateStreakOnLoginno primeiro acesso do dia.achievements: medalhas desbloqueadas + bloqueadas com critérios.leaderboard: top 10 da turma + posição do aluno autenticado, com switcher de período.
UI components (
src/components/gamification/):PatentBadge: badge da patente atual (full/compact).XPBar: barra de progresso do nível + patente + streak (flame icon).MedalWall: parede de medalhas (grid responsivo, desbloqueadas coloridas, bloqueadas com cadeado). Modal de detalhe ao clicar.Leaderboard: ranking com medalhas 🥇🥈🥉 para top 3, indicadores ↑↓ de variação, linha destacada "Você", switcher de período.
Página
/aluno/perfil: layout com XPBar + grid (Leaderboard + feed de atividade recente) + MedalWall. Carregamento via Suspense + ErrorBoundary + Skeleton.i18n: seção
student.gamification(PT/EN/ES) +nav.profilerenomeado para "Progresso"/"Progress"/"Progreso".
Bug corrigido durante o card
StudentShell: o itemprofiledo top nav apontava para/aluno/tarefas(placeholder errado desde o Card 4). Corrigido para apontar para/aluno/perfil+ adicionado itemtasksseparado.
✅ Critérios de aceite validados
| # | Critério | Status |
|---|---|---|
| 1 | Aluno ganha XP ao concluir aula (80%+) | ✅ |
| 2 | Aluno ganha XP ao entregar tarefa | ✅ |
| 3 | Aluno ganha XP ao receber nota ≥ 9.0 (helper pronto para Card 13) | ✅ |
| 4 | Streak atualizado diariamente ao acessar sistema | ✅ |
| 5 | Medalhas desbloqueadas automaticamente quando critério atendido | ✅ |
| 6 | Ranking da turma atualizado (job diário + refresh on-demand) | ✅ |
| 7 | Perfil do aluno mostra nível, XP, patente, streak, medalhas | ✅ |
| 8 | PROFESSOR/LOCAL pode ver ranking da turma | ⏳ Card 17 |
| 9 | Build passa + lint OK | ✅ |
| 10 | Deploy DEV funcional | ✅ |
📁 2. Arquivos criados/modificados
Criados
| Arquivo | Função |
|---|---|
src/server/gamification/xp-rules.ts |
Rules engine: XP_RULES, patentes, nível, streak (pure functions) |
src/server/gamification/award.ts |
awardXP (idempotente), updateStreakOnLogin, awardHighGradeXP |
src/server/gamification/achievements.ts |
Catálogo SEED (12 medalhas) + checkAchievements + seedAchievements |
src/server/gamification/index.ts |
Barrel do módulo |
src/server/queues/workers/gamification.ts |
Worker Bull: update-leaderboard + check-achievements (batch) |
src/components/gamification/PatentBadge.tsx |
Badge de patente (full/compact) |
src/components/gamification/XPBar.tsx |
Barra XP + nível + patente + streak |
src/components/gamification/MedalWall.tsx |
Parede de medalhas + modal detalhe |
src/components/gamification/Leaderboard.tsx |
Ranking com switcher de período + deltas |
src/components/gamification/index.ts |
Barrel de UI |
src/app/(dashboard)/aluno/perfil/page.tsx |
Página "Meu Progresso" (gamificação) |
scripts/seed_card7b_demo.js |
Seed: 12 medalhas + XP demo + ranking fictício |
Modificados
| Arquivo | Mudança |
|---|---|
prisma/schema.prisma |
+5 modelos (StudentXP, XPHistory, Achievement, StudentAchievement, Leaderboard) +3 enums (Patent, XPReason, LeaderboardPeriod) +relations em User e Class |
src/server/queues/index.ts |
+fila GAMIFICATION (queue + QueueEvents + map) |
src/server/trpc/routers/student.ts |
+triggers XP em markCompleted e tasks.submit + sub-router gamification (profile/achievements/leaderboard) |
src/components/layout/StudentShell.tsx |
Corrigido bug profile → /tarefas + adicionado item Tarefas separado |
messages/{pt-BR,en-US,es-ES}.json |
+seção student.gamification (24 chaves) + nav.profile → "Progresso" |
🗄️ 3. Schema do banco (mudanças)
Novas tabelas (aplicadas via prisma db push em DEV)
student_xp: id, studentId (→users, unique), totalXP, level, patent, streak, lastAccessAt, lastStreakBonusxp_history: id, studentId, amount, reason (enum), metadata (Json), createdAt. Index em[studentId, createdAt]achievements: id, key (unique), title, description, iconKey, xpReward, criteria (Json), isActive, timestampsstudent_achievements: id, studentId, achievementId, unlockedAt. Unique[studentId, achievementId]leaderboards: id, classId, period (enum), studentId, rank, totalXP, xpDelta, rankDelta, updatedAt. Unique[classId, period, studentId], index[classId, period, rank]
Novos enums
Patent: INICIANTE, APRENDIZ, ESTUDANTE, DEDICADO, MESTRE, LENDAXPReason: LESSON_COMPLETED, TASK_SUBMITTED, HIGH_GRADE, COURSE_COMPLETE, STREAK_BONUS, PERFECT_ATTENDANCE, ACHIEVEMENT, MANUALLeaderboardPeriod: WEEKLY, MONTHLY, ALL_TIME
⚠️ Migração Oracle → Hostinger (M7): o schema foi aplicado via
prisma db pushem DEV. No M7 (Card 35), criar migration formal viaprisma migrate devpara reprodutibilidade em PROD.
🔌 4. Handoff para o próximo card
Variáveis de ambiente novas
Nenhuma. Reutiliza REDIS_URL (já existente para Bull) e DATABASE_URL.
API pública do módulo de gamificação
Import via @/server/gamification:
// Rules (pure functions)
getPatentForXP(totalXP): { patent, emoji, label }
getLevelForXP(totalXP): number
getLevelProgress(totalXP): { currentLevelXP, nextLevelXP, pct, toNextLevel }
getNextPatent(totalXP): { patent, emoji, label, minXP } | null
getStreakBonus(streak): number // streak × 30, cap 300
// Service (DB)
awardXP({ prisma, studentUserId, reason, metadata }): Promise<{ awarded, amount, newTotalXP }>
updateStreakOnLogin(prisma, studentUserId): Promise<void>
awardHighGradeXP({ prisma, studentUserId, gradeId, value }): Promise<AwardXPResult>
// Achievements
checkAchievements(userId): Promise<{ unlocked: string[] }>
seedAchievements(client?): Promise<void> // upsert do catálogo SEED
Helpers para Jobs Bull (@/server/queues/workers/gamification)
scheduleLeaderboardRecalc(): agenda recalculo de WEEKLY + MONTHLY + ALL_TIME (cron 00:00)scheduleAchievementsCheck(forceAll): agenda batch (cron 01:00 safety net)refreshClassLeaderboard(classId): refresh on-demand após XP ganho (opcional)
Triggers para próximos cards
- Card 13 (Diário de Classe): ao publicar nota ≥ 9.0, chamar
awardHighGradeXP({ prisma, studentUserId: student.userId, gradeId, value }). - Card 17 (Dashboard Unidade): usar
student.gamification.leaderboardpara mostrar ranking completo da turma (top 10 + todos). - Card 8 (Home Família): pais veem XP/medalhas dos filhos — reusar
profilecomstudentUserIddo Student child. - Cron 00:00: plugar
scheduleLeaderboardRecalc()no scheduler (M3 ou M5). - Cron 01:00: plugar
scheduleAchievementsCheck()no scheduler.
Padrão de idempotência
awardXP aceita metadata.idempotencyKey (string). Para qualquer trigger novo,
sempre passar a chave única da ação (ex: lesson:${lessonId}, task:${taskId},
grade:${gradeId}, streak:${YYYY-MM-DD}) — evita crédito duplicado em retry/race.
📋 5. Checklist do Trello — status por item
Critérios de aceite do card no Trello (todos marcados como complete, exceto onde indicado como pendência de card futuro).
Engine de XP
- ✅ XP_RULES definido (50/100/200/500/30)
- ✅ awardXP service (idempotente, transacional)
- ✅ Patentes (Iniciante→Lenda, 6 faixas)
- ✅ Nível (1 a cada 100 XP, cap 100)
- ✅ Streak diário (cap 300/dia, idempotente por dia)
Medalhas
- ✅ Schema Achievement + StudentAchievement
- ✅ Catálogo SEED (12 medalhas)
- ✅ Engine de avaliação (6 tipos de critério)
- ✅ Desbloqueio automático + crédito de XP reward
- ✅ UI MedalWall (grid + modal)
Ranking
- ✅ Schema Leaderboard (3 períodos)
- ✅ Job update-leaderboard (WEEKLY/MONTHLY/ALL_TIME)
- ✅ Preservação de delta (rank/XP)
- ✅ UI Leaderboard (top 10 + posição aluno + switcher)
UI
- ✅ Página /aluno/perfil
- ✅ XPBar, PatentBadge, MedalWall, Leaderboard
- ✅ Nav "Progresso" no StudentShell
Integrações
- ✅ Trigger em lessons.markCompleted
- ✅ Trigger em tasks.submit
- ⏳ Trigger em grade publish → Card 13
- ⏳ PROFESSOR/LOCAL ver ranking → Card 17
Deploy
- ✅ Build passa
- ✅ Lint OK (warnings preexistentes, sem erros)
- ✅ Deploy DEV funcional (https://sistemaescolar.wellka.com.br/aluno/perfil)
- ✅ Seed dados demo (12 medalhas + 290 XP aluno demo + 5 alunos ranking)
🚀 6. Deploy DEV
- URL: https://sistemaescolar.wellka.com.br/aluno/perfil
- Login demo:
aluno@genioon.com.br/Aluno@2026 - Commits:
b009428(implementação) +4d5e99a(fix seed inline) - PM2: genioon-dev restart ✓ (online, uptime estável)
- Banco:
prisma db pushaplicou 5 tabelas novas + 3 enums - Seed:
node scripts/seed_card7b_demo.jspopulou:- 12 medalhas no catálogo
- Aluno demo: 290 XP (Aprendiz), nível 2, streak 3, 4 entradas de XP history
- 2 medalhas desbloqueadas (first_lesson, first_task)
- 4 alunos fictícios + demo no ranking (ALL_TIME + WEEKLY + MONTHLY)
🧠 7. Contexto gerado para próximas etapas
Para Card 13 (Diário de Classe / lançamento de nota)
- Importar
awardHighGradeXPde@/server/gamification - Após publicar Grade com
value >= 9.0, chamar:await awardHighGradeXP({ prisma: ctx.prisma, studentUserId: grade.student.userId, gradeId: grade.id, value: grade.value, }); - Idempotente por
grade:${gradeId}— seguro em retry
Para Card 17 (Dashboard da Unidade)
student.gamification.leaderboardretorna top 10 + posição do aluno autenticado- Para painel do LOCAL/MASTER, criar
local.gamification.classLeaderboardque retorna TODOS os alunos da turma (sem limite de top 10)
Para Card 8 (Home da Família)
- Pais veem progresso do filho: chamar
student.gamification.profilecomstudentUserId = child.userId(ajustar procedure para aceitar parâmetro) - Reusar XPBar, PatentBadge, MedalWall — já são presentational
Para M3/M5 (Cron scheduler)
- Adicionar ao scheduler:
// 00:00 diário — recalcula ranking cron.schedule('0 0 * * *', () => scheduleLeaderboardRecalc()); // 01:00 diário — safety net de medalhas cron.schedule('0 1 * * *', () => scheduleAchievementsCheck());
Lições aprendidas
- Idempotência é rei: triggers de XP podem disparar múltiplas vezes (re-upsert,
retry, race condition).
metadata.idempotencyKey+ unique constraint emStudentAchievementresolvem isso limpo. - Prisma 7 Json typing: cast
as unknown as MeuTipopara sair de JsonValue;as neverpara camposJson?em create/update. - Jobs Bull batch + trigger inline: o trigger individual (checkAchievements) é fire-and-forget no awardXP (rápido, feedback imediato). O batch noturno é safety net (corrige medalhas perdidas). Dois mecanismos complementares.
- Pure functions no rules engine:
getPatentForXP,getLevelForXPetc. não tocam DB — facilita testes e uso em SSR/client sem round-trip. - Seed em Node puro não importa .ts: inline do catálogo no script JS (mirror).
Para seed maior, usar
tsxou compilar para.js.
🎯 8. Próximo card
Card 8 [M2] Home da Família (Mobile)
- Spec:
docs/execucao/08-m2-home-familia.md - Reuso:
StudentShelladaptado para PARENT (layout mobile-first), XPBar/PatentBadge para mostrar progresso do filho - Foco: home do responsável com atalhos (boletim, financeiro, chat,tarefas do filho)
- Decisão do cliente: pais veem tudo do filho em mobile-first