- Configure workspace Git repository with comprehensive .gitignore - Add Odoo as submodule for ERP reference code - Include documentation: SETUP.md, GIT-STRUCTURE.md - Add gitignore templates for projects (backend, frontend, database) - Structure supports independent repos per project/subproject level Workspace includes: - core/ - Reusable patterns, modules, orchestration system - projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.) - knowledge-base/ - Reference code and patterns (includes Odoo submodule) - devtools/ - Development tools and templates - customers/ - Client implementations template 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
18 KiB
RF-GAM-001: Sistema de Logros (Achievements)
📋 Metadata
| Campo | Valor |
|---|---|
| ID | RF-GAM-001 |
| Módulo | Gamificación |
| Prioridad | Alta |
| Estado | ✅ Implementado |
| Versión | 1.0 |
| Fecha creación | 2025-11-07 |
| Última actualización | 2025-11-07 |
🔗 Referencias
Especificación Técnica
📐 ET-GAM-001: Sistema de Achievements
Implementación DDL
🗄️ ENUMs Canónicos:
achievement_type:
- Ubicación:
apps/database/ddl/00-prerequisites.sql:51-54 - Tipo:
gamification_system.achievement_type - Valores:
badge,milestone,special,rank_promotion
achievement_category:
- Ubicación:
apps/database/ddl/00-prerequisites.sql:47-50 - Tipo:
gamification_system.achievement_category - Valores:
progress,streak,completion,social,special,mastery,exploration
🗄️ Tablas:
gamification_system.achievements→apps/database/ddl/schemas/gamification_system/tables/01-achievements.sql- Columnas:
type achievement_type,category achievement_category
- Columnas:
gamification_system.user_achievements→apps/database/ddl/schemas/gamification_system/tables/02-user_achievements.sql- Tracking de achievements desbloqueados por usuario
🗄️ Funciones:
gamification_system.check_and_unlock_achievement()→ Verifica y desbloquea achievementsgamification_system.award_achievement_rewards()→ Otorga recompensas (XP, ML Coins)
🗄️ Triggers:
trg_achievement_unlocked→ Ejecuta al desbloquear achievementtrg_check_rank_promotion→ Verifica si XP alcanza siguiente rango
Backend
💻 Implementación:
- Enums:
apps/backend/src/modules/gamification/enums/achievement-type.enum.tsapps/backend/src/modules/gamification/enums/achievement-category.enum.ts
- Service:
apps/backend/src/modules/gamification/services/achievement.service.ts - Listeners:
apps/backend/src/modules/gamification/listeners/achievement.listener.ts - DTOs:
apps/backend/src/modules/gamification/dto/unlock-achievement.dto.ts
Frontend
🎨 Componentes:
- Types:
apps/frontend/src/types/gamification.types.ts - Componentes:
apps/frontend/src/components/gamification/AchievementGallery.tsxapps/frontend/src/components/gamification/AchievementCard.tsxapps/frontend/src/components/gamification/AchievementUnlockedModal.tsxapps/frontend/src/components/gamification/AchievementProgress.tsx
Mapeo Completo
📊 Ver mapeo completo: Requerimientos → Implementación
📝 Descripción del Requerimiento
Contexto
El sistema educativo Gamilit necesita motivar y mantener comprometidos a los estudiantes mediante mecánicas de gamificación. Los achievements (logros) son un elemento fundamental para:
- Reconocer el progreso del estudiante
- Motivar la continuidad en el aprendizaje
- Celebrar hitos importantes
- Fomentar comportamientos positivos (rachas, exploración, dominio)
- Proporcionar feedback visual del desarrollo
Necesidad del Negocio
Problema: Sin un sistema de achievements:
- Estudiantes pierden motivación tras completar ejercicios
- No hay reconocimiento visible del progreso
- Difícil fomentar hábitos de estudio consistentes
- Falta de incentivos para explorar contenido nuevo
Solución: Implementar un sistema de achievements clasificados por tipo y categoría que recompense diversos comportamientos y logros, proporcionando feedback constante y motivación intrínseca.
🎯 Requerimiento Funcional
RF-GAM-001.1: Tipos de Achievements
El sistema DEBE soportar 4 tipos de achievements:
1. Badge (Insignia) 🏅
Descripción: Insignias coleccionables por completar acciones específicas
Características:
- Coleccionables (el estudiante puede tener múltiples)
- Visuales llamativas con iconografía única
- Permanentes una vez desbloqueados
- Mostrados en perfil público del estudiante
Ejemplos:
- "Primer Paso" - Completar primer ejercicio
- "Lector Ávido" - Leer 10 textos completos
- "Explorador" - Probar 5 mecánicas diferentes
Recompensas Típicas:
- 10-50 XP
- Badge visible en perfil
- Entrada en galería de achievements
2. Milestone (Hito) 📍
Descripción: Hitos que marcan progreso significativo en el aprendizaje
Características:
- Marca progreso cuantificable
- Desbloqueables en secuencia
- Asociados a métricas específicas
Ejemplos:
- "Módulo Maestro" - Completar módulo al 100%
- "50 Ejercicios" - Resolver 50 ejercicios correctamente
- "Nivel 10" - Alcanzar nivel de usuario 10
Recompensas Típicas:
- 50-200 XP
- 20-100 ML Coins
- Badge + título especial
3. Special (Especial) ⭐
Descripción: Achievements especiales para eventos, temporadas, o logros únicos
Características:
- Tiempo limitado (eventos)
- Únicos o muy difíciles de obtener
- Mayor valor social (prestigio)
Ejemplos:
- "Racha de Fuego" - 30 días consecutivos practicando
- "Perfeccionista" - Completar módulo con 100% accuracy en primer intento
- "Pionero" - Ser de los primeros 100 usuarios en probar nueva feature
Recompensas Típicas:
- 200-500 XP
- 100-300 ML Coins
- Badge dorado/animado
- Título exclusivo
4. Rank Promotion (Promoción de Rango) 👑
Descripción: Achievement otorgado al ascender de rango Maya
Características:
- Automático al alcanzar XP requerido
- Jerárquico (solo puedes tener un rank activo)
- Visible en toda la UI del estudiante
Ejemplos:
- "Nacom" - Alcanzar 1000 XP (Rango 2)
- "Ah K'in" - Alcanzar 5000 XP (Rango 3)
- "Halach Uinic" - Alcanzar 20000 XP (Rango 4)
Recompensas:
- Nuevo título de rango
- Desbloqueo de features exclusivas del rango
- Badge de rango + animación especial
- Notificación destacada a amigos
RF-GAM-001.2: Categorías de Achievements
El sistema DEBE clasificar achievements en 7 categorías:
1. Progress (Progreso) 📈
Relacionados con avance general en el sistema
- Ejemplos: "Nivel 5", "50% del curso", "Primera semana completada"
2. Streak (Racha) 🔥
Relacionados con consistencia y hábitos
- Ejemplos: "3 días consecutivos", "Racha de 30 días", "Sin faltar en un mes"
3. Completion (Completitud) ✅
Relacionados con finalizar contenido
- Ejemplos: "Módulo completado", "Todos los ejercicios del tema", "Curso finalizado"
4. Social (Social) 👥
Relacionados con interacción social
- Ejemplos: "Primer amigo", "Equipo de 5", "Ayudó a 10 compañeros"
5. Special (Especiales) 🌟
Eventos, temporadas, logros únicos
- Ejemplos: "Evento de Halloween", "Beta Tester", "Aniversario 1 año"
6. Mastery (Dominio) 🎓
Relacionados con dominar contenido
- Ejemplos: "100% accuracy en 10 ejercicios", "Maestro de gramática", "Sin errores en módulo"
7. Exploration (Exploración) 🗺️
Relacionados con descubrir contenido nuevo
- Ejemplos: "Probó todas las mecánicas", "Exploró 3 módulos", "Primera simulación"
RF-GAM-001.3: Matriz de Achievements
El sistema DEBE permitir combinar tipos y categorías:
| Tipo | Category | Ejemplo | Criterio | Recompensa |
|---|---|---|---|---|
| badge | progress | "Principiante" | Completar tutorial | 10 XP |
| badge | streak | "Constante" | 7 días consecutivos | 50 XP |
| badge | social | "Amigable" | Agregar 5 amigos | 30 XP |
| milestone | completion | "Módulo 1 Completo" | 100% Módulo 1 | 100 XP + 50 ML Coins |
| milestone | mastery | "Maestro del Análisis" | 10 ejercicios >90% | 150 XP |
| special | streak | "Racha Épica" | 30 días | 300 XP + Badge oro |
| special | exploration | "Aventurero" | Probar 20 mecánicas | 200 XP |
| rank_promotion | progress | "Nacom" | 1000 XP alcanzados | Rango 2 + features |
RF-GAM-001.4: Criterios de Desbloqueo
Cada achievement DEBE tener criterios explícitos almacenados en JSONB:
{
"achievement_id": "ach_001",
"name": "Racha de Fuego",
"type": "special",
"category": "streak",
"criteria": {
"type": "streak_days",
"required_days": 30,
"consecutive": true,
"min_activities_per_day": 1
},
"rewards": {
"xp": 300,
"ml_coins": 100,
"badge_url": "/badges/fire-streak.png",
"title": "Imparable"
}
}
Tipos de Criterios:
streak_days: Días consecutivosexercise_count: Cantidad de ejercicioscompletion_percentage: Porcentaje de completitudaccuracy_threshold: Precisión mínimaxp_reached: XP alcanzadosocial_connections: Conexiones socialesexploration_count: Contenido explorado
RF-GAM-001.5: Sistema de Recompensas
Al desbloquear achievement, el sistema DEBE:
- Registrar desbloqueo:
INSERT INTO gamification_system.user_achievements (
user_id, achievement_id, unlocked_at
) VALUES (
user_id, achievement_id, NOW()
);
- Otorgar recompensas:
- Sumar XP a
user_stats.total_xp - Sumar ML Coins a
user_stats.ml_coins - Verificar si XP alcanza nuevo rango → disparar
rank_promotion
- Notificar usuario:
await notificationService.send({
userId,
type: 'achievement_unlocked',
priority: 'medium',
data: {
achievementName: 'Racha de Fuego',
badgeUrl: '/badges/fire-streak.png',
xpEarned: 300,
mlCoinsEarned: 100,
}
});
- Disparar confetti/animación:
- Frontend muestra modal de celebración
- Animación de confetti
- Sonido de logro
- Actualizar galería:
- Achievement aparece como "desbloqueado" en galería
- Badge visible en perfil público
📊 Casos de Uso
UC-GAM-001: Estudiante desbloquea achievement por completar módulo
Actor: Estudiante Precondiciones: Estudiante completó último ejercicio del módulo
Flujo:
- Estudiante completa ejercicio final del módulo
- Sistema actualiza
module_progress.status→'completed' - Trigger automático verifica criterios de achievements:
SELECT * FROM gamification_system.achievements WHERE criteria->>'type' = 'completion_percentage' AND criteria->>'module_id' = current_module_id AND criteria->>'percentage' = '100'; - Sistema encuentra achievement "Módulo Maestro"
- Sistema ejecuta
gamification_system.check_and_unlock_achievement(user_id, achievement_id) - Sistema registra desbloqueo en
user_achievements - Sistema otorga recompensas:
- +100 XP →
user_stats.total_xp - +50 ML Coins →
user_stats.ml_coins
- +100 XP →
- Sistema envía notificación
achievement_unlocked - Frontend muestra modal de celebración con confetti
- Achievement aparece en galería del estudiante
Resultado: Achievement desbloqueado, recompensas otorgadas, estudiante motivado
UC-GAM-002: Estudiante ve galería de achievements
Actor: Estudiante Precondiciones: Usuario autenticado
Flujo:
- Estudiante navega a "Mis Logros"
- Sistema carga achievements:
const achievements = await achievementService.getGallery(userId); // Retorna: { unlocked: [], locked: [], progress: {} } - Frontend renderiza galería en grid:
- Achievements desbloqueados: Full color, con fecha
- Achievements bloqueados: Silueta con "???" (si es secreto)
- Achievements en progreso: Barra de progreso visible
- Estudiante hace click en achievement desbloqueado
- Sistema muestra detalle:
- Nombre y descripción
- Fecha de desbloqueo
- Recompensas obtenidas
- Rareza (% de usuarios que lo tienen)
- Estudiante puede compartir achievement en redes
Resultado: Estudiante visualiza su colección y progreso
UC-GAM-003: Achievement de racha de 7 días consecutivos
Actor: Sistema (automático) Precondiciones: Estudiante tiene 6 días consecutivos registrados
Flujo:
- Estudiante completa al menos 1 ejercicio hoy (día 7)
- Cron job nocturno ejecuta:
await streakService.updateStreaks(); // Actualiza todas las rachas - Sistema detecta que estudiante alcanzó 7 días consecutivos
- Sistema verifica criterios del achievement "Semana Perfecta":
{ "type": "streak_days", "required_days": 7, "consecutive": true } - Criterio cumplido → Sistema desbloquea achievement
- Sistema otorga recompensas:
- +50 XP
- Badge "Semana Perfecta"
- Al día siguiente: Usuario recibe notificación matutina:
"🔥 ¡7 días consecutivos! Desbloqueaste 'Semana Perfecta'"
Resultado: Racha reconocida, estudiante motivado a continuar
🔐 Consideraciones de Seguridad
1. Prevención de Manipulación
Problema: Usuario podría intentar manipular criterios de achievements
Soluciones:
// Backend valida criterios antes de desbloquear
async function unlockAchievement(userId: string, achievementId: string) {
// 1. Verificar que criterios se cumplan REALMENTE
const criteriaMetFlagg = await verifyCriteria(userId, achievementId);
if (!criteriaMetFlagg) {
throw new ForbiddenException('Criteria not met');
}
// 2. Verificar que no esté ya desbloqueado
const alreadyUnlocked = await checkIfUnlocked(userId, achievementId);
if (alreadyUnlocked) {
throw new ConflictException('Already unlocked');
}
// 3. Rate limiting (max 5 achievements por minuto)
await rateLimiter.check(`unlock:${userId}`, 5, 60);
// 4. Desbloquear
await unlockAndReward(userId, achievementId);
}
2. Integridad de Recompensas
Problema: Recompensas podrían duplicarse por race conditions
Solución:
-- Transaction con locks
BEGIN;
-- Lock row del user
SELECT * FROM gamification_system.user_stats
WHERE user_id = $1
FOR UPDATE;
-- Insertar achievement (UNIQUE constraint previene duplicados)
INSERT INTO gamification_system.user_achievements (user_id, achievement_id)
VALUES ($1, $2)
ON CONFLICT (user_id, achievement_id) DO NOTHING;
-- Otorgar recompensas solo si se insertó
GET DIAGNOSTICS rows_inserted = ROW_COUNT;
IF rows_inserted > 0 THEN
UPDATE gamification_system.user_stats
SET total_xp = total_xp + $3,
ml_coins = ml_coins + $4
WHERE user_id = $1;
END IF;
COMMIT;
3. Secretos vs Públicos
Algunos achievements pueden ser secretos (sorpresas):
interface Achievement {
id: string;
name: string;
description: string;
isSecret: boolean; // Si true, no mostrar en galería hasta desbloquear
hints?: string[]; // Pistas opcionales
}
✅ Criterios de Aceptación
AC-001: ENUMs Implementados
- ENUM
achievement_typecon 4 valores - ENUM
achievement_categorycon 7 valores - Tabla
achievementsusa ambos ENUMs - Tabla
user_achievementstracking desbloqueos
AC-002: Criterios Verificables
- Todos los achievements tienen criterios en JSONB
- Sistema verifica criterios antes de desbloquear
- Triggers automáticos verifican después de acciones clave
AC-003: Recompensas Otorgadas
- XP se suma correctamente a
user_stats - ML Coins se suman correctamente
- Badges aparecen en perfil del usuario
- Rank promotions se disparan automáticamente
AC-004: Notificaciones Enviadas
- Notificación
achievement_unlockedenviada - Modal de celebración muestra en frontend
- Confetti/animación visible
AC-005: Galería Funcional
- Galería muestra achievements desbloqueados
- Achievements bloqueados visibles (o secretos)
- Progreso visible para achievements en curso
- Detalle completo al hacer click
🧪 Testing
Test Case 1: Desbloquear achievement al completar módulo
test('Achievement unlocks when module completed', async () => {
const user = await createUser();
const module = await createModule();
// Completar todos los ejercicios del módulo
await completeAllExercises(user.id, module.id);
// Verificar achievement desbloqueado
const achievements = await getUnlockedAchievements(user.id);
expect(achievements).toContainEqual(
expect.objectContaining({
name: 'Módulo Maestro',
type: 'milestone',
category: 'completion',
})
);
// Verificar recompensas
const userStats = await getUserStats(user.id);
expect(userStats.total_xp).toBeGreaterThanOrEqual(100);
expect(userStats.ml_coins).toBeGreaterThanOrEqual(50);
});
Test Case 2: No duplicar achievement
test('Cannot unlock same achievement twice', async () => {
const user = await createUser();
const achievement = await createAchievement();
// Desbloquear primera vez
await unlockAchievement(user.id, achievement.id);
// Intentar desbloquear segunda vez
await expect(
unlockAchievement(user.id, achievement.id)
).rejects.toThrow(ConflictException);
// Verificar que solo hay 1 registro
const count = await countUserAchievements(user.id, achievement.id);
expect(count).toBe(1);
});
Test Case 3: Galería muestra correctamente
test('Gallery shows unlocked and locked achievements', async () => {
const user = await createUser();
// Crear 3 achievements
const ach1 = await createAchievement({ name: 'First' });
const ach2 = await createAchievement({ name: 'Second' });
const ach3 = await createAchievement({ name: 'Third' });
// Desbloquear solo el primero
await unlockAchievement(user.id, ach1.id);
// Obtener galería
const gallery = await getAchievementGallery(user.id);
expect(gallery.unlocked).toHaveLength(1);
expect(gallery.unlocked[0].name).toBe('First');
expect(gallery.locked).toHaveLength(2);
});
📚 Referencias Adicionales
Documentos Relacionados
- 📄 RF-GAM-002: Sistema de Comodines
- 📄 RF-NOT-001: Tipos de Notificaciones -
achievement_unlocked - 📄 RF-PRG-001: Tracking de Progreso - Triggers de achievements
Teoría de Gamificación
- Octalysis Framework - Core drives
- Bartle's Player Types - Achievers, Explorers, Socializers, Killers
📅 Historial de Cambios
| Versión | Fecha | Autor | Cambios |
|---|---|---|---|
| 1.0 | 2025-11-07 | Database Team | Creación inicial del requerimiento |
Documento: docs/01-requerimientos/02-gamificacion/RF-GAM-001-achievements.md
Ruta relativa desde docs/: 01-requerimientos/02-gamificacion/RF-GAM-001-achievements.md