Entrar como:FuncionalidadesDoc
Pular para o conteúdo
← Voltar para a visão geral
✅ ConcluídoCard #7b · M1

Motor de Gamificação (Ranking, Medalhas, XP)

Marco M1: Portal do Aluno (STUDENT)

📅27 de julho de 2026
👤Wellington Santiago (via ZCode)
🔧Commits: b0094284d5e99a

📄 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

  1. Schema Prisma (5 modelos novos + 3 enums): StudentXP, XPHistory, Achievement, StudentAchievement, Leaderboard + enums Patent, XPReason, LeaderboardPeriod. Relationados a User (gamificação é por usuário) e Class (ranking por turma).

  2. 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.

  3. awardXP service (src/server/gamification/award.ts): função central idempotente via metadata.idempotencyKey. Transação atômica (StudentXP update + XPHistory insert). Após creditar, dispara checkAchievements() fire-and-forget. Também updateStreakOnLogin (idempotente por dia) e awardHighGradeXP (helper para o Card 13).

  4. 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.

  5. 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)
    • awardHighGradeXP pronto para Card 13 (notas ≥ 9.0 → 200 XP)
  6. 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.
  7. tRPC procedures (student.gamification.{profile, achievements, leaderboard}):

    • profile: XP, nível, patente, streak, progresso de nível, top 5 medalhas, histórico recente. Chama updateStreakOnLogin no 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.
  8. 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.
  9. Página /aluno/perfil: layout com XPBar + grid (Leaderboard + feed de atividade recente) + MedalWall. Carregamento via Suspense + ErrorBoundary + Skeleton.

  10. i18n: seção student.gamification (PT/EN/ES) + nav.profile renomeado para "Progresso"/"Progress"/"Progreso".

Bug corrigido durante o card

  • StudentShell: o item profile do top nav apontava para /aluno/tarefas (placeholder errado desde o Card 4). Corrigido para apontar para /aluno/perfil + adicionado item tasks separado.

✅ 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, lastStreakBonus
  • xp_history: id, studentId, amount, reason (enum), metadata (Json), createdAt. Index em [studentId, createdAt]
  • achievements: id, key (unique), title, description, iconKey, xpReward, criteria (Json), isActive, timestamps
  • student_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, LENDA
  • XPReason: LESSON_COMPLETED, TASK_SUBMITTED, HIGH_GRADE, COURSE_COMPLETE, STREAK_BONUS, PERFECT_ATTENDANCE, ACHIEVEMENT, MANUAL
  • LeaderboardPeriod: WEEKLY, MONTHLY, ALL_TIME

⚠️ Migração Oracle → Hostinger (M7): o schema foi aplicado via prisma db push em DEV. No M7 (Card 35), criar migration formal via prisma migrate dev para 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.leaderboard para mostrar ranking completo da turma (top 10 + todos).
  • Card 8 (Home Família): pais veem XP/medalhas dos filhos — reusar profile com studentUserId do 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


🚀 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 push aplicou 5 tabelas novas + 3 enums
  • Seed: node scripts/seed_card7b_demo.js populou:
    • 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 awardHighGradeXP de @/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.leaderboard retorna top 10 + posição do aluno autenticado
  • Para painel do LOCAL/MASTER, criar local.gamification.classLeaderboard que 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.profile com studentUserId = 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

  1. Idempotência é rei: triggers de XP podem disparar múltiplas vezes (re-upsert, retry, race condition). metadata.idempotencyKey + unique constraint em StudentAchievement resolvem isso limpo.
  2. Prisma 7 Json typing: cast as unknown as MeuTipo para sair de JsonValue; as never para campos Json? em create/update.
  3. 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.
  4. Pure functions no rules engine: getPatentForXP, getLevelForXP etc. não tocam DB — facilita testes e uso em SSR/client sem round-trip.
  5. Seed em Node puro não importa .ts: inline do catálogo no script JS (mirror). Para seed maior, usar tsx ou compilar para .js.

🎯 8. Próximo card

Card 8 [M2] Home da Família (Mobile)

  • Spec: docs/execucao/08-m2-home-familia.md
  • Reuso: StudentShell adaptado 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
← Voltar para a visão geral