workspace/projects/gamilit/docs/90-transversal/archivos-historicos/2025-11/IMPLEMENTACION-RANKUP-EJERCICIOS-2025-11-26.md
rckrdmrd ea1879f4ad feat: Initial workspace structure with multi-level Git configuration
- 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>
2025-12-08 10:44:23 -06:00

7.8 KiB

Implementación: Notificación de RankUp en Ejercicios

Documento Técnico de Implementación Fecha: 2025-11-26 Tipo: Corrección de Bug + Feature Afecta: Backend, Frontend, Database


1. Contexto del Problema

1.1 Descripción del Bug

Los ejercicios M1-E5 (Sopa de Letras) y M2-E1 a M2-E5 no mostraban notificación de subida de rango al usuario, aunque el sistema de gamificación procesaba correctamente las promociones en la base de datos.

1.2 Causa Raíz Identificada

Capa Problema
Backend DTO ExerciseSubmissionResponseDto no exponía campos xp_earned, ml_coins_earned, rankUp
Backend Service claimRewards() no detectaba si había ocurrido promoción de rango
Frontend Componentes de ejercicio no integraban RankUpModal

2. Solución Implementada

2.1 Cambios en Base de Datos

Archivo: apps/database/ddl/schemas/progress_tracking/tables/04-exercise_submissions.sql

-- Columnas agregadas a la tabla exercise_submissions
xp_earned integer DEFAULT 0,
ml_coins_earned integer DEFAULT 0,

Migración para BD existente:

ALTER TABLE progress_tracking.exercise_submissions
  ADD COLUMN IF NOT EXISTS xp_earned INTEGER DEFAULT 0,
  ADD COLUMN IF NOT EXISTS ml_coins_earned INTEGER DEFAULT 0;

2.2 Cambios en Backend Entity

Archivo: apps/backend/src/modules/progress/entities/exercise-submission.entity.ts

@Column({ type: 'integer', default: 0 })
xp_earned!: number;

@Column({ type: 'integer', default: 0 })
ml_coins_earned!: number;

2.3 Cambios en Backend DTO

Archivo: apps/backend/src/modules/progress/dto/exercise-submission-response.dto.ts

@Expose()
xp_earned?: number;

@Expose()
ml_coins_earned?: number;

@Expose()
rankUp?: {
  newRank: string;
  previousRank?: string;
  bonusMLCoins: number;
  newMultiplier: number;
} | null;

2.4 Cambios en Backend Service

Archivo: apps/backend/src/modules/progress/services/exercise-submission.service.ts

Método claimRewards() modificado:

  1. Guarda rango actual ANTES de addXp()
  2. Ejecuta addXp() (trigger de BD hace promoción)
  3. Consulta rango DESPUÉS de addXp()
  4. Compara rangos y construye objeto rankUp si hubo cambio
  5. Persiste xp_earned y ml_coins_earned en submission
  6. Retorna objeto completo incluyendo rankUp

Tipo de retorno actualizado:

Promise<{
  submission: ExerciseSubmission;
  xp_earned: number;
  ml_coins_earned: number;
  rankUp: { newRank, previousRank, bonusMLCoins, newMultiplier } | null;
}>

2.5 Cambios en Frontend

Hook Nuevo: useRankUpNotification

Archivo: apps/frontend/src/features/gamification/ranks/hooks/useRankUpNotification.ts

Maneja la lógica de cascada de modales:

  • FeedbackModal (resultado del ejercicio)
  • RankUpModal (celebración de promoción, si aplica)

Componentes de Ejercicio Modificados

Archivo Cambios
SopaLetrasExercise.tsx Import RankUpModal, estados, integración en JSX
DetectiveTextualExercise.tsx Import RankUpModal, estados, integración en JSX
CausaEfectoExercise.tsx Import RankUpModal, estados, integración en JSX
PrediccionNarrativaExercise.tsx Import RankUpModal, estados, integración en JSX
PuzzleContextoExercise.tsx Import RankUpModal, estados, integración en JSX
RuedaInferenciasExercise.tsx Import RankUpModal, estados, integración en JSX

3. Flujo de Datos

Usuario completa ejercicio
         │
         ▼
Frontend: submitExercise(exerciseId, userId, answers)
         │
         ▼
Backend: ExerciseSubmissionService.submitExercise()
         │
         ├─► gradeSubmission() - Califica respuestas
         │
         └─► claimRewards() - Otorga recompensas
                  │
                  ├─► Guarda previousRank
                  ├─► userStatsService.addXp() ──► TRIGGER BD
                  ├─► Consulta newRank              │
                  ├─► Compara rangos                ▼
                  │                          check_rank_promotion()
                  │                          promote_to_next_rank()
                  │
                  └─► Retorna { xp_earned, ml_coins_earned, rankUp }
         │
         ▼
Frontend: Recibe response con rankUp
         │
         ├─► FeedbackModal (muestra score)
         │
         └─► [Si rankUp existe]
                  │
                  ▼
             RankUpModal (celebración 8s)
                  │
                  ▼
             onComplete()

4. Estructura de Datos

4.1 Objeto rankUp

interface RankUpInfo {
  newRank: string;        // 'Nacom', 'Ah K\'in', etc.
  previousRank?: string;  // Rango anterior
  bonusMLCoins: number;   // 100, 250, 500, 1000
  newMultiplier: number;  // 1.10, 1.15, 1.20, 1.25
}

4.2 Configuración de Rangos Maya

Rango XP Mínimo XP Máximo Bonus ML Coins Multiplicador
Ajaw 0 499 0 1.00
Nacom 500 999 100 1.10
Ah K'in 1,000 1,499 250 1.15
Halach Uinic 1,500 1,899 500 1.20
K'uk'ulkan 1,900 1,000 1.25

5. Archivos Modificados

Backend (4 archivos)

Archivo Tipo de Cambio
dto/exercise-submission-response.dto.ts Agregados campos @Expose()
entities/exercise-submission.entity.ts Agregadas columnas @Column()
services/exercise-submission.service.ts Modificado claimRewards()
04-exercise_submissions.sql Agregadas columnas DDL

Frontend (8 archivos)

Archivo Tipo de Cambio
hooks/useRankUpNotification.ts NUEVO - Hook de cascada
hooks/index.ts Export del hook
SopaLetrasExercise.tsx Integración RankUpModal
DetectiveTextualExercise.tsx Integración RankUpModal
CausaEfectoExercise.tsx Integración RankUpModal
PrediccionNarrativaExercise.tsx Integración RankUpModal
PuzzleContextoExercise.tsx Integración RankUpModal
RuedaInferenciasExercise.tsx Integración RankUpModal

6. Testing

6.1 Escenarios de Prueba

ID Escenario Precondición Resultado Esperado
T1 Sin promoción 300 XP (Ajaw) Solo FeedbackModal
T2 Promoción a Nacom 450 XP → +100 XP FeedbackModal → RankUpModal
T3 Promoción a Ah K'in 950 XP → +100 XP FeedbackModal → RankUpModal
T4 Rango máximo K'uk'ulkan Solo FeedbackModal
T5 Error en ejercicio Cualquier XP Solo FeedbackModal (error)

6.2 Verificación de Compilación

# Backend
cd apps/backend && npm run build

# Frontend
cd apps/frontend && npm run type-check

7. Notas de Implementación

7.1 Timing de Modales

  • FeedbackModal se cierra por acción del usuario
  • RankUpModal aparece 300ms después del cierre de FeedbackModal
  • RankUpModal tiene auto-cierre de 8 segundos
  • onComplete() se ejecuta solo después de cerrar ambos modales

7.2 Compatibilidad

  • Los campos xp_earned, ml_coins_earned son opcionales en el DTO
  • Submissions antiguas tendrán estos campos en 0
  • rankUp es null para submissions sin promoción

7.3 Performance

  • Una consulta adicional a user_stats después de addXp() para detectar cambio de rango
  • Sin impacto significativo en latencia (consulta por índice PK)

8. Referencias

  • Especificación de Rangos: docs/00-vision-general/ESPECIFICACION-TECNICA-RANGOS-MAYA-v2.1.md
  • Documentación Frontend: apps/frontend/docs/RANKUP-NOTIFICATION-IMPLEMENTATION-2025-11-26.md
  • Seed de Rangos: apps/database/seeds/dev/gamification_system/03-maya_ranks.sql