# 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](../../02-especificaciones-tecnicas/02-gamificacion/ET-GAM-001-achievements.md)

### 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:**
1. `gamification_system.achievements` → `apps/database/ddl/schemas/gamification_system/tables/01-achievements.sql`
   - Columnas: `type achievement_type`, `category achievement_category`
2. `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 achievements
- `gamification_system.award_achievement_rewards()` → Otorga recompensas (XP, ML Coins)

🗄️ **Triggers:**
- `trg_achievement_unlocked` → Ejecuta al desbloquear achievement
- `trg_check_rank_promotion` → Verifica si XP alcanza siguiente rango

### Backend
💻 **Implementación:**
- **Enums:**
  - `apps/backend/src/modules/gamification/enums/achievement-type.enum.ts`
  - `apps/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.tsx`
  - `apps/frontend/src/components/gamification/AchievementCard.tsx`
  - `apps/frontend/src/components/gamification/AchievementUnlockedModal.tsx`
  - `apps/frontend/src/components/gamification/AchievementProgress.tsx`

### Mapeo Completo
📊 [Ver mapeo completo: Requerimientos → Implementación](../../03-desarrollo/base-de-datos/MAPEO-REQUERIMIENTOS-IMPLEMENTACION.md#21-sistema-de-logros-achievements)

---

## 📝 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:

```json
{
  "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 consecutivos
- `exercise_count`: Cantidad de ejercicios
- `completion_percentage`: Porcentaje de completitud
- `accuracy_threshold`: Precisión mínima
- `xp_reached`: XP alcanzado
- `social_connections`: Conexiones sociales
- `exploration_count`: Contenido explorado

---

### RF-GAM-001.5: Sistema de Recompensas

Al desbloquear achievement, el sistema **DEBE**:

1. **Registrar desbloqueo:**
```sql
INSERT INTO gamification_system.user_achievements (
    user_id, achievement_id, unlocked_at
) VALUES (
    user_id, achievement_id, NOW()
);
```

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

3. **Notificar usuario:**
```typescript
await notificationService.send({
  userId,
  type: 'achievement_unlocked',
  priority: 'medium',
  data: {
    achievementName: 'Racha de Fuego',
    badgeUrl: '/badges/fire-streak.png',
    xpEarned: 300,
    mlCoinsEarned: 100,
  }
});
```

4. **Disparar confetti/animación:**
- Frontend muestra modal de celebración
- Animación de confetti
- Sonido de logro

5. **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:**
1. Estudiante completa ejercicio final del módulo
2. Sistema actualiza `module_progress.status` → `'completed'`
3. **Trigger automático** verifica criterios de achievements:
   ```sql
   SELECT * FROM gamification_system.achievements
   WHERE criteria->>'type' = 'completion_percentage'
     AND criteria->>'module_id' = current_module_id
     AND criteria->>'percentage' = '100';
   ```
4. Sistema encuentra achievement "Módulo Maestro"
5. Sistema ejecuta `gamification_system.check_and_unlock_achievement(user_id, achievement_id)`
6. Sistema registra desbloqueo en `user_achievements`
7. Sistema otorga recompensas:
   - +100 XP → `user_stats.total_xp`
   - +50 ML Coins → `user_stats.ml_coins`
8. Sistema envía notificación `achievement_unlocked`
9. Frontend muestra modal de celebración con confetti
10. 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:**
1. Estudiante navega a "Mis Logros"
2. Sistema carga achievements:
   ```typescript
   const achievements = await achievementService.getGallery(userId);
   // Retorna: { unlocked: [], locked: [], progress: {} }
   ```
3. 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
4. Estudiante hace click en achievement desbloqueado
5. Sistema muestra detalle:
   - Nombre y descripción
   - Fecha de desbloqueo
   - Recompensas obtenidas
   - Rareza (% de usuarios que lo tienen)
6. 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:**
1. Estudiante completa al menos 1 ejercicio hoy (día 7)
2. **Cron job nocturno** ejecuta:
   ```typescript
   await streakService.updateStreaks(); // Actualiza todas las rachas
   ```
3. Sistema detecta que estudiante alcanzó 7 días consecutivos
4. Sistema verifica criterios del achievement "Semana Perfecta":
   ```json
   {
     "type": "streak_days",
     "required_days": 7,
     "consecutive": true
   }
   ```
5. Criterio cumplido → Sistema desbloquea achievement
6. Sistema otorga recompensas:
   - +50 XP
   - Badge "Semana Perfecta"
7. **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:**
```typescript
// 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:**
```sql
-- 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):
```typescript
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
- [x] ENUM `achievement_type` con 4 valores
- [x] ENUM `achievement_category` con 7 valores
- [x] Tabla `achievements` usa ambos ENUMs
- [x] Tabla `user_achievements` tracking desbloqueos

### AC-002: Criterios Verificables
- [x] Todos los achievements tienen criterios en JSONB
- [x] Sistema verifica criterios antes de desbloquear
- [x] Triggers automáticos verifican después de acciones clave

### AC-003: Recompensas Otorgadas
- [x] XP se suma correctamente a `user_stats`
- [x] ML Coins se suman correctamente
- [x] Badges aparecen en perfil del usuario
- [x] Rank promotions se disparan automáticamente

### AC-004: Notificaciones Enviadas
- [x] Notificación `achievement_unlocked` enviada
- [x] Modal de celebración muestra en frontend
- [x] Confetti/animación visible

### AC-005: Galería Funcional
- [x] Galería muestra achievements desbloqueados
- [x] Achievements bloqueados visibles (o secretos)
- [x] Progreso visible para achievements en curso
- [x] Detalle completo al hacer click

---

## 🧪 Testing

### Test Case 1: Desbloquear achievement al completar módulo
```typescript
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
```typescript
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
```typescript
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-GAM-002-comodines.md)
- 📄 [RF-NOT-001: Tipos de Notificaciones](../../06-notificaciones/RF-NOT-001-tipos-notificaciones.md) - `achievement_unlocked`
- 📄 [RF-PRG-001: Tracking de Progreso](../../04-progreso-seguimiento/RF-PRG-001-tracking-progreso.md) - Triggers de achievements

### Teoría de Gamificación
- [Octalysis Framework](https://yukaichou.com/gamification-examples/octalysis-complete-gamification-framework/) - Core drives
- [Bartle's Player Types](https://en.wikipedia.org/wiki/Bartle_taxonomy_of_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`
