- 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>
1793 lines
51 KiB
Markdown
1793 lines
51 KiB
Markdown
# Guía de Desarrollo - Portal Student
|
|
|
|
**Fecha de creación:** 2025-11-29
|
|
**Versión:** 1.0.0
|
|
**Estado:** VIGENTE
|
|
**Aplica a:** apps/frontend/src/apps/student/ + apps/backend/src/modules/[progress, gamification, educational]
|
|
|
|
---
|
|
|
|
## 1. Visión General
|
|
|
|
### 1.1 Propósito
|
|
|
|
El Portal Student es la interfaz principal para estudiantes en GAMILIT. Es una plataforma educativa gamificada con temática de detective e inspiración Maya que proporciona:
|
|
|
|
- **Aprendizaje Interactivo:** Módulos educativos con ejercicios de comprensión lectora
|
|
- **Gamificación Completa:** Sistema de rangos Maya, achievements, misiones y ML Coins
|
|
- **Progreso Personalizado:** Dashboard con métricas de desempeño y actividades recientes
|
|
- **Economía Virtual:** Tienda de power-ups, comodines y items cosméticos
|
|
- **Social:** Leaderboards, guilds, friends y competencia sana
|
|
- **Notificaciones:** Sistema de alertas y celebraciones por logros
|
|
|
|
### 1.2 Usuarios Objetivo
|
|
|
|
| Rol | Acceso | Funcionalidades |
|
|
|-----|--------|-----------------|
|
|
| Student | Completo | Todas las funcionalidades del portal |
|
|
| Teacher | Supervisión | Vista de progreso de estudiantes |
|
|
| Admin | Monitoreo | Vista de todas las métricas y configuración |
|
|
|
|
### 1.3 Temática y Narrativa
|
|
|
|
**Tema Principal:** Detective educativo con elementos de cultura Maya
|
|
|
|
- **Rangos:** Jerarquía Maya (Ajaw → Nacom → Ah K'in → Halach Uinic → K'uk'ulkan)
|
|
- **Moneda:** ML Coins (Marie Learning Coins)
|
|
- **Narrativa:** Estudiante como "Detective" que resuelve "casos" (ejercicios)
|
|
- **Iconografía:** Lupa, expediente, evidencias, rango Maya
|
|
|
|
---
|
|
|
|
## 2. Arquitectura del Portal
|
|
|
|
### 2.1 Estructura de Carpetas
|
|
|
|
#### Frontend (apps/frontend/src/apps/student/)
|
|
|
|
```
|
|
student/
|
|
├── pages/ # 27 páginas principales
|
|
│ ├── DashboardComplete.tsx # ⭐ Dashboard principal
|
|
│ ├── ExercisePage.tsx # ⭐ Ejercicios interactivos (mecánicas)
|
|
│ ├── GamificationPage.tsx # ⭐ Hub de gamificación
|
|
│ ├── ModuleDetailPage.tsx # Detalle de módulo educativo
|
|
│ ├── AchievementsPage.tsx # Logros desbloqueados
|
|
│ ├── ShopPage.tsx # Tienda ML Coins
|
|
│ ├── LeaderboardPage.tsx # Rankings globales
|
|
│ ├── MissionsPage.tsx # Misiones activas
|
|
│ ├── InventoryPage.tsx # Inventario de comodines
|
|
│ ├── FriendsPage.tsx # Sistema de amigos
|
|
│ ├── GuildsPage.tsx # Guilds/clanes
|
|
│ ├── EnhancedProfilePage.tsx # Perfil de usuario
|
|
│ ├── SettingsPage.tsx # Configuración
|
|
│ ├── NotificationsPage.tsx # Centro de notificaciones
|
|
│ ├── AssignmentsPage.tsx # Tareas del teacher
|
|
│ ├── LoginPage.tsx # Autenticación
|
|
│ ├── RegisterPage.tsx # Registro
|
|
│ ├── PasswordRecoveryPage.tsx # Recuperación contraseña
|
|
│ └── ...
|
|
├── components/ # Componentes organizados por dominio
|
|
│ ├── dashboard/ # 17 componentes del dashboard
|
|
│ │ ├── BottomNavigation.tsx # ⭐ Navegación móvil (6 tabs)
|
|
│ │ ├── EnhancedStatsGrid.tsx # Estadísticas detective
|
|
│ │ ├── RankProgressWidget.tsx # Widget de rango Maya
|
|
│ │ ├── MLCoinsWidget.tsx # Balance de ML Coins
|
|
│ │ ├── MissionsPanel.tsx # Misiones activas
|
|
│ │ ├── ModulesSection.tsx # Grid de módulos
|
|
│ │ ├── RecentActivityPanel.tsx # Actividades recientes
|
|
│ │ ├── QuickActionsWidget.tsx # Acciones rápidas
|
|
│ │ ├── AchievementMilestones.tsx # Hitos de logros
|
|
│ │ └── ...
|
|
│ ├── exercise/ # Componentes de ejercicios
|
|
│ │ ├── ExerciseHeader.tsx # Header con timer y score
|
|
│ │ ├── CompletionModal.tsx # Modal de finalización
|
|
│ │ ├── HintModal.tsx # Sistema de pistas
|
|
│ │ ├── PowerUpEffects.tsx # Efectos de power-ups
|
|
│ │ └── ExerciseSidebar.tsx # Sidebar con progreso
|
|
│ ├── gamification/ # Componentes de gamificación
|
|
│ │ ├── GamificationHero.tsx # Hero section
|
|
│ │ ├── RanksSection.tsx # Sección de rangos
|
|
│ │ ├── MLCoinsSection.tsx # Sección de economía
|
|
│ │ ├── AchievementsPreview.tsx # Preview de logros
|
|
│ │ ├── LeaderboardPreview.tsx # Preview de ranking
|
|
│ │ └── StreaksMissionsSection.tsx # Rachas y misiones
|
|
│ ├── achievements/ # Sistema de logros
|
|
│ │ ├── AchievementGrid.tsx # Grid de achievements
|
|
│ │ ├── AchievementDetailModal.tsx # Modal de detalle
|
|
│ │ ├── AchievementFilters.tsx # Filtros (rarity, category)
|
|
│ │ └── AchievementStatistics.tsx # Estadísticas
|
|
│ ├── notifications/ # Notificaciones y celebraciones
|
|
│ │ ├── AchievementToast.tsx # Toast de logro desbloqueado
|
|
│ │ └── CelebrationModal.tsx # Modal de celebración
|
|
│ ├── interactions/ # Interacciones gestuales
|
|
│ │ └── SwipeableContainer.tsx # Swipe para móvil
|
|
│ └── PowerUpBar.tsx # Barra de power-ups activos
|
|
├── hooks/ # 14 custom hooks
|
|
│ ├── useDashboardData.ts # ⭐ Dashboard data + React Query
|
|
│ ├── useUserModules.ts # Módulos del usuario
|
|
│ ├── useRecentActivities.ts # Actividades recientes
|
|
│ ├── useGamificationData.ts # Datos de gamificación
|
|
│ ├── useAchievementsEnhanced.ts # Achievements con filtros
|
|
│ ├── useExerciseState.ts # Estado de ejercicio
|
|
│ ├── useExerciseAutoSave.ts # Auto-save de progreso
|
|
│ ├── useExercisePowerUps.ts # Power-ups en ejercicios
|
|
│ ├── useUserClassroom.ts # Classroom del usuario
|
|
│ ├── useSwipeGesture.ts # Gestos táctiles
|
|
│ ├── useResponsiveLayout.ts # Responsive breakpoints
|
|
│ └── index.ts # Barrel export
|
|
└── types/
|
|
└── index.ts # 40+ interfaces/types
|
|
```
|
|
|
|
#### Backend - Módulos Principales
|
|
|
|
```
|
|
apps/backend/src/modules/
|
|
├── progress/ # Sistema de progreso
|
|
│ ├── controllers/
|
|
│ │ ├── exercise-submission.controller.ts
|
|
│ │ ├── exercise-attempt.controller.ts
|
|
│ │ ├── module-progress.controller.ts
|
|
│ │ └── learning-session.controller.ts
|
|
│ ├── services/
|
|
│ │ ├── exercise-submission.service.ts
|
|
│ │ ├── module-progress.service.ts
|
|
│ │ └── learning-session.service.ts
|
|
│ ├── entities/
|
|
│ │ ├── exercise-submission.entity.ts
|
|
│ │ ├── module-progress.entity.ts
|
|
│ │ └── learning-session.entity.ts
|
|
│ └── dto/
|
|
│ ├── create-exercise-submission.dto.ts
|
|
│ └── exercise-submission-response.dto.ts
|
|
├── gamification/ # Sistema de gamificación
|
|
│ ├── controllers/
|
|
│ │ ├── achievements.controller.ts
|
|
│ │ ├── missions.controller.ts
|
|
│ │ ├── ml-coins.controller.ts
|
|
│ │ ├── ranks.controller.ts
|
|
│ │ ├── leaderboard.controller.ts
|
|
│ │ └── comodines.controller.ts
|
|
│ ├── services/
|
|
│ │ ├── achievements.service.ts
|
|
│ │ ├── missions.service.ts
|
|
│ │ ├── ml-coins.service.ts
|
|
│ │ ├── ranks.service.ts
|
|
│ │ └── user-stats.service.ts
|
|
│ └── entities/
|
|
│ ├── achievement.entity.ts
|
|
│ ├── user-achievement.entity.ts
|
|
│ ├── mission.entity.ts
|
|
│ ├── user-rank.entity.ts
|
|
│ ├── ml-coins-transaction.entity.ts
|
|
│ └── user-stats.entity.ts
|
|
├── educational/ # Contenido educativo
|
|
│ ├── controllers/
|
|
│ │ ├── modules.controller.ts
|
|
│ │ ├── exercises.controller.ts
|
|
│ │ └── media.controller.ts
|
|
│ ├── services/
|
|
│ │ ├── modules.service.ts
|
|
│ │ └── exercises.service.ts
|
|
│ └── entities/
|
|
│ ├── module.entity.ts
|
|
│ └── exercise.entity.ts
|
|
└── social/ # Características sociales
|
|
├── controllers/
|
|
│ ├── friendships.controller.ts
|
|
│ ├── teams.controller.ts
|
|
│ └── peer-challenges.controller.ts
|
|
└── services/
|
|
├── friendships.service.ts
|
|
└── teams.service.ts
|
|
```
|
|
|
|
### 2.2 Diagrama de Flujo de Datos
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ STUDENT PORTAL │
|
|
├─────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
│ │ Dashboard │───►│ Modules │───►│ Exercise │ │
|
|
│ │ Complete │ │ (Learning) │ │ Page │ │
|
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
│ │ │ │ │
|
|
│ │ │ │ │
|
|
│ ▼ ▼ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ Custom Hooks Layer (React Query) │ │
|
|
│ │ - useDashboardData() - useUserModules() │ │
|
|
│ │ - useExerciseState() - useGamificationData() │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
│ │ │ │ │
|
|
│ │ │ │ │
|
|
│ ▼ ▼ ▼ │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ API Services Layer (Axios) │ │
|
|
│ │ - progressAPI - gamificationAPI │ │
|
|
│ │ - educationalAPI - socialAPI │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
│ │ │
|
|
└──────────────────────────────┼───────────────────────────────────┘
|
|
│
|
|
HTTP/REST
|
|
│
|
|
┌──────────────────────────────▼───────────────────────────────────┐
|
|
│ BACKEND API │
|
|
├──────────────────────────────────────────────────────────────────┤
|
|
│ Controllers ──► Services ──► Repositories ──► Database │
|
|
│ │ │ │
|
|
│ │ ├──► Gamification Module │
|
|
│ │ ├──► Progress Module │
|
|
│ │ ├──► Educational Module │
|
|
│ │ └──► Social Module │
|
|
└──────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Módulos Principales
|
|
|
|
### 3.1 Dashboard (DashboardComplete)
|
|
|
|
**Ruta:** `/`
|
|
|
|
**Propósito:** Centro de control del estudiante con visión general de progreso, misiones, módulos y actividades recientes.
|
|
|
|
**Componentes Clave:**
|
|
|
|
1. **GamifiedHeader**
|
|
- Usuario autenticado
|
|
- Balance de ML Coins
|
|
- Rank actual con icono Maya
|
|
- Botón de logout
|
|
|
|
2. **QuickActionsWidget**
|
|
- Continuar último ejercicio
|
|
- Ver misiones activas
|
|
- Ir a tienda
|
|
- Ver perfil
|
|
|
|
3. **RankProgressWidget** (4 columnas)
|
|
- Rank actual con icono Maya
|
|
- XP actual / XP requerido
|
|
- Barra de progreso
|
|
- Multiplicador activo
|
|
|
|
4. **ModulesSection** (8 columnas)
|
|
- Grid de 4 módulos (2 col c/u)
|
|
- Estado: available, in_progress, locked
|
|
- Progreso porcentual
|
|
- Dificultad (fácil, medio, difícil)
|
|
|
|
5. **EnhancedStatsGrid** (4 columnas)
|
|
- Casos resueltos (ejercicios completados)
|
|
- Racha actual de días
|
|
- Tiempo total invertido
|
|
- XP total acumulado
|
|
|
|
6. **MissionsPanel** (4 columnas)
|
|
- Top 3 misiones activas
|
|
- Progreso de cada misión
|
|
- Recompensas (XP, ML Coins)
|
|
- Tiempo límite
|
|
|
|
7. **RecentActivityPanel** (4 columnas)
|
|
- Últimas 5 actividades
|
|
- Timestamps relativos
|
|
- Iconos por tipo de actividad
|
|
|
|
**Hooks Utilizados:**
|
|
|
|
```typescript
|
|
// Dashboard data (React Query)
|
|
const { rank, progress, loading, error, refresh } = useDashboardData();
|
|
|
|
// Missions from backend
|
|
const { allMissions, activeMissions } = useMissions();
|
|
|
|
// Modules filtered by classroom
|
|
const { modules, loading: modulesLoading } = useUserModules({
|
|
classroomId: userClassroomId,
|
|
});
|
|
|
|
// Recent activities
|
|
const { activities, loading: activitiesLoading } = useRecentActivities(5);
|
|
|
|
// Gamification data (mock until backend ready)
|
|
const { gamificationData } = useUserGamification(user?.id);
|
|
```
|
|
|
|
**APIs:**
|
|
|
|
```typescript
|
|
// Dashboard aggregated data
|
|
GET /api/v1/gamification/users/:userId/ml-coins
|
|
GET /api/v1/gamification/ranks/current
|
|
GET /api/v1/gamification/ranks/users/:userId/rank-progress
|
|
GET /api/v1/gamification/users/:userId/achievements
|
|
GET /api/v1/progress/users/:userId
|
|
|
|
// Missions
|
|
GET /api/v1/gamification/missions/active
|
|
|
|
// Modules
|
|
GET /api/v1/educational/modules
|
|
GET /api/v1/educational/modules/:classroomId/assigned
|
|
|
|
// Activities
|
|
GET /api/v1/progress/users/:userId/recent-activities
|
|
```
|
|
|
|
### 3.2 Módulos Educativos
|
|
|
|
**Ruta:** `/modules/:moduleId`
|
|
|
|
**Propósito:** Vista detallada de un módulo con sus ejercicios, progreso y recomendaciones.
|
|
|
|
**Estructura de Módulo:**
|
|
|
|
- **Módulo 1:** Comprensión Literal
|
|
- **Módulo 2:** Comprensión Inferencial
|
|
- **Módulo 3:** Comprensión Crítica
|
|
- **Módulo 4:** Textos Digitales y Multimediales
|
|
|
|
**Componentes:**
|
|
|
|
- **ModuleHeader:** Título, descripción, progreso general
|
|
- **ExercisesList:** Lista de ejercicios con estado (locked, available, in_progress, completed)
|
|
- **ProgressChart:** Gráfico de progreso por competencia
|
|
- **RecommendedExercises:** Ejercicios sugeridos según desempeño
|
|
|
|
**APIs:**
|
|
|
|
```typescript
|
|
GET /api/v1/educational/modules/:moduleId
|
|
GET /api/v1/educational/modules/:moduleId/exercises
|
|
GET /api/v1/progress/modules/:moduleId/progress
|
|
```
|
|
|
|
### 3.3 Ejercicios Interactivos (ExercisePage)
|
|
|
|
**Ruta:** `/exercises/:exerciseId`
|
|
|
|
**Propósito:** Interfaz para completar ejercicios con mecánicas interactivas variadas.
|
|
|
|
**Mecánicas Implementadas (por módulo):**
|
|
|
|
#### Módulo 1 - Comprensión Literal
|
|
- `crucigrama` - Crucigrama científico
|
|
- `timeline` / `linea_tiempo` - Línea de tiempo
|
|
- `sopa_letras` - Sopa de letras
|
|
- `mapa_conceptual` - Mapa conceptual
|
|
- `emparejamiento` - Emparejamiento
|
|
- `verdadero_falso` - Verdadero/Falso
|
|
- `completar_espacios` - Completar espacios en blanco
|
|
|
|
#### Módulo 2 - Comprensión Inferencial
|
|
- `detective_textual` - Detective textual
|
|
- `lectura_inferencial` - Lectura inferencial
|
|
- `construccion_hipotesis` - Construcción de hipótesis
|
|
- `prediccion_narrativa` - Predicción narrativa
|
|
- `puzzle_contexto` - Puzzle de contexto
|
|
- `rueda_inferencias` - Rueda de inferencias
|
|
|
|
#### Módulo 3 - Comprensión Crítica
|
|
- `analisis_fuentes` - Análisis de fuentes
|
|
- `debate_digital` - Debate digital
|
|
- `matriz_perspectivas` - Matriz de perspectivas
|
|
- `podcast_argumentativo` - Podcast argumentativo
|
|
- `tribunal_opiniones` - Tribunal de opiniones
|
|
|
|
#### Módulo 4 - Textos Digitales
|
|
- `verificador_fakenews` - Verificador de fake news
|
|
- `quiz_tiktok` - Quiz TikTok
|
|
- `navegacion_hipertextual` - Navegación hipertextual
|
|
- `analisis_memes` - Análisis de memes
|
|
- `infografia_interactiva` - Infografía interactiva
|
|
- `email_formal` - Email formal
|
|
- `chat_literario` - Chat literario
|
|
- `ensayo_argumentativo` - Ensayo argumentativo
|
|
- `resena_critica` - Reseña crítica
|
|
|
|
**Componentes de Ejercicio:**
|
|
|
|
```typescript
|
|
// Layout principal
|
|
<div className="exercise-page">
|
|
<ExerciseHeader
|
|
title={exercise.title}
|
|
difficulty={exercise.difficulty}
|
|
points={exercise.points}
|
|
/>
|
|
|
|
<div className="exercise-container">
|
|
<ExerciseSidebar>
|
|
<TimerWidget />
|
|
<ScoreDisplay score={progress.score} />
|
|
<ProgressTracker current={step} total={totalSteps} />
|
|
<HintSystem hints={hints} onUseHint={handleHint} />
|
|
</ExerciseSidebar>
|
|
|
|
<Suspense fallback={<Loader />}>
|
|
<DynamicMechanic
|
|
mechanicType={exercise.type}
|
|
data={exercise.mechanicData}
|
|
onProgressUpdate={handleProgress}
|
|
onSubmit={handleSubmit}
|
|
/>
|
|
</Suspense>
|
|
</div>
|
|
|
|
<PowerUpBar powerUps={activePowerUps} />
|
|
|
|
<FeedbackModal
|
|
isOpen={showFeedback}
|
|
feedback={feedbackData}
|
|
onClose={handleCloseFeedback}
|
|
/>
|
|
</div>
|
|
```
|
|
|
|
**Flujo de Ejercicio:**
|
|
|
|
1. **Carga:** GET `/api/v1/educational/exercises/:exerciseId`
|
|
2. **Auto-save:** POST `/api/v1/progress/exercises/:exerciseId/save` (cada 30s)
|
|
3. **Uso de Hint:** POST `/api/v1/progress/exercises/:exerciseId/use-hint`
|
|
4. **Uso de Power-up:** POST `/api/v1/gamification/comodines/use`
|
|
5. **Envío:** POST `/api/v1/progress/exercises/:exerciseId/submit`
|
|
6. **Feedback:** Recibe calificación, XP ganado, ML Coins, achievements desbloqueados
|
|
|
|
**Hooks:**
|
|
|
|
```typescript
|
|
// Exercise state management
|
|
const { exerciseState, updateProgress } = useExerciseState(exerciseId);
|
|
|
|
// Auto-save every 30s
|
|
useExerciseAutoSave(exerciseId, exerciseState, {
|
|
interval: 30000,
|
|
enabled: !exerciseState.completed,
|
|
});
|
|
|
|
// Power-ups activation
|
|
const { activePowerUps, usePowerUp } = useExercisePowerUps();
|
|
```
|
|
|
|
**APIs:**
|
|
|
|
```typescript
|
|
GET /api/v1/educational/exercises/:exerciseId
|
|
GET /api/v1/educational/exercises/:exerciseId/hints
|
|
POST /api/v1/progress/exercises/:exerciseId/save
|
|
POST /api/v1/progress/exercises/:exerciseId/submit
|
|
POST /api/v1/gamification/comodines/use
|
|
```
|
|
|
|
### 3.4 Sistema de Gamificación
|
|
|
|
**Ruta:** `/gamification`
|
|
|
|
**Propósito:** Hub central de gamificación con ranks, achievements, economy y stats.
|
|
|
|
**Secciones:**
|
|
|
|
#### 3.4.1 Ranks Maya System
|
|
|
|
**Jerarquía de Rangos:**
|
|
|
|
| Rango | Icono | XP Requerido | Multiplicador |
|
|
|-------|-------|--------------|---------------|
|
|
| Ajaw | 🏹 | 0 | 1.0x |
|
|
| Nacom | 🔍 | 1,000 | 1.2x |
|
|
| Ah K'in | 🗡️ | 5,000 | 1.5x |
|
|
| Halach Uinic | ⚔️ | 15,000 | 2.0x |
|
|
| K'uk'ulkan | 👑 | 50,000 | 3.0x |
|
|
|
|
**Componentes:**
|
|
|
|
```typescript
|
|
<RankBadgeAdvanced
|
|
rank={userProgress.currentRank}
|
|
prestigeLevel={userProgress.prestigeLevel}
|
|
showGlow={true}
|
|
animated={true}
|
|
/>
|
|
|
|
<RankProgressBar
|
|
currentXP={userProgress.xp}
|
|
requiredXP={userProgress.nextRankXP}
|
|
currentRank={userProgress.currentRank}
|
|
nextRank={userProgress.nextRank}
|
|
/>
|
|
|
|
<MultiplierWidget
|
|
multiplier={multiplierBreakdown.total}
|
|
breakdown={multiplierBreakdown}
|
|
/>
|
|
|
|
<ProgressTimeline
|
|
progressionHistory={progressionHistory}
|
|
/>
|
|
|
|
<PrestigeSystem
|
|
prestigeProgress={prestigeProgress}
|
|
onPrestige={handlePrestige}
|
|
/>
|
|
```
|
|
|
|
**APIs:**
|
|
|
|
```typescript
|
|
GET /api/v1/gamification/ranks/current
|
|
GET /api/v1/gamification/ranks/users/:userId/rank-progress
|
|
GET /api/v1/gamification/ranks/users/:userId/history
|
|
POST /api/v1/gamification/ranks/users/:userId/prestige
|
|
```
|
|
|
|
#### 3.4.2 ML Coins Economy
|
|
|
|
**Fuentes de Ingresos:**
|
|
- Completar ejercicios: 10-100 ML Coins (según dificultad y score)
|
|
- Completar misiones: 50-500 ML Coins
|
|
- Desbloquear achievements: 20-200 ML Coins
|
|
- Bonificaciones del teacher: Variable
|
|
- Daily login bonus: 10 ML Coins
|
|
|
|
**Gastos:**
|
|
- Power-ups (comodines): 50-200 ML Coins
|
|
- Items cosméticos: 100-1,000 ML Coins
|
|
- Profile customizations: 50-500 ML Coins
|
|
|
|
**Componentes:**
|
|
|
|
```typescript
|
|
<CoinBalanceWidget
|
|
balance={balance.current}
|
|
todayEarned={balance.earnedToday}
|
|
todaySpent={balance.spentToday}
|
|
/>
|
|
|
|
<TransactionHistory
|
|
transactions={transactions}
|
|
limit={10}
|
|
/>
|
|
|
|
<EarningSourcesBreakdown
|
|
sources={earningSources}
|
|
/>
|
|
|
|
<SpendingAnalytics
|
|
data={spendingData}
|
|
period="week"
|
|
/>
|
|
```
|
|
|
|
**APIs:**
|
|
|
|
```typescript
|
|
GET /api/v1/gamification/users/:userId/ml-coins
|
|
GET /api/v1/gamification/users/:userId/ml-coins/transactions
|
|
POST /api/v1/gamification/users/:userId/ml-coins/add
|
|
POST /api/v1/gamification/users/:userId/ml-coins/deduct
|
|
```
|
|
|
|
#### 3.4.3 Achievements System
|
|
|
|
**Categorías:**
|
|
|
|
- **Progress:** Completar módulos, ejercicios
|
|
- **Mastery:** Puntuaciones perfectas, rachas
|
|
- **Social:** Amigos, guild, colaboración
|
|
- **Explorer:** Descubrir contenido, probar mecánicas
|
|
- **Economy:** Gastar ML Coins, comprar items
|
|
- **Special:** Eventos, logros únicos
|
|
|
|
**Rarities:**
|
|
|
|
- Common (Común) - Gris
|
|
- Rare (Raro) - Azul
|
|
- Epic (Épico) - Morado
|
|
- Legendary (Legendario) - Dorado
|
|
|
|
**Componentes:**
|
|
|
|
```typescript
|
|
<AchievementGrid
|
|
achievements={achievements}
|
|
filters={filters}
|
|
sort={sortBy}
|
|
/>
|
|
|
|
<AchievementDetailModal
|
|
achievement={selectedAchievement}
|
|
isOpen={showModal}
|
|
onClose={closeModal}
|
|
/>
|
|
|
|
<AchievementStatistics
|
|
stats={achievementStats}
|
|
/>
|
|
|
|
// Toast cuando se desbloquea
|
|
<AchievementToast
|
|
achievement={unlockedAchievement}
|
|
onClose={handleCloseToast}
|
|
/>
|
|
```
|
|
|
|
**APIs:**
|
|
|
|
```typescript
|
|
GET /api/v1/gamification/achievements
|
|
GET /api/v1/gamification/users/:userId/achievements
|
|
GET /api/v1/gamification/achievements/:achievementId
|
|
POST /api/v1/gamification/users/:userId/achievements/:achievementId/claim
|
|
```
|
|
|
|
#### 3.4.4 Missions System
|
|
|
|
**Tipos de Misiones:**
|
|
|
|
- **Daily:** Reseteables cada día (ej: "Completa 3 ejercicios")
|
|
- **Weekly:** Reseteables cada semana (ej: "Completa 1 módulo")
|
|
- **Seasonal:** Eventos especiales (ej: "Participa en Halloween Challenge")
|
|
- **Progressive:** Una sola vez (ej: "Alcanza Rank Nacom")
|
|
|
|
**Dificultades:**
|
|
|
|
- Easy: 50 XP, 20 ML Coins
|
|
- Medium: 100 XP, 50 ML Coins
|
|
- Hard: 200 XP, 100 ML Coins
|
|
|
|
**Componentes:**
|
|
|
|
```typescript
|
|
<MissionCard
|
|
mission={mission}
|
|
progress={mission.currentProgress}
|
|
target={mission.targetProgress}
|
|
onClaim={handleClaim}
|
|
/>
|
|
|
|
<MissionProgress
|
|
current={progress}
|
|
required={target}
|
|
percentage={percentage}
|
|
/>
|
|
|
|
<MissionRewards
|
|
xp={mission.xpReward}
|
|
mlCoins={mission.mlCoinsReward}
|
|
/>
|
|
```
|
|
|
|
**APIs:**
|
|
|
|
```typescript
|
|
GET /api/v1/gamification/missions/active
|
|
GET /api/v1/gamification/missions/completed
|
|
POST /api/v1/gamification/missions/:missionId/claim
|
|
```
|
|
|
|
### 3.5 Tienda (ShopPage)
|
|
|
|
**Ruta:** `/shop`
|
|
|
|
**Propósito:** Compra de power-ups, comodines y items cosméticos con ML Coins.
|
|
|
|
**Categorías:**
|
|
|
|
1. **Power-ups** (Premium) - ✅ IMPLEMENTADO
|
|
- Hint Revealer (50 ML Coins) - Revela una pista gratis
|
|
- Time Freeze (100 ML Coins) - Pausa el timer 60s
|
|
- XP Boost (150 ML Coins) - +50% XP por 1 hora
|
|
- Score Multiplier (200 ML Coins) - +2x score en próximo ejercicio
|
|
|
|
2. **Cosmetics** - ❌ NO IMPLEMENTADO
|
|
- Avatares
|
|
- Marcos de perfil
|
|
- Badges decorativos
|
|
|
|
3. **Profile** - ❌ NO IMPLEMENTADO
|
|
- Títulos
|
|
- Efectos de partículas
|
|
- Temas de color
|
|
|
|
**Componentes:**
|
|
|
|
```typescript
|
|
<ShopGrid
|
|
items={shopItems}
|
|
category={selectedCategory}
|
|
onPurchase={handlePurchase}
|
|
/>
|
|
|
|
<ShopItem
|
|
item={item}
|
|
balance={userBalance}
|
|
isOwned={item.isOwned}
|
|
onBuy={handleBuy}
|
|
/>
|
|
|
|
<PurchaseConfirmationModal
|
|
item={selectedItem}
|
|
balance={userBalance}
|
|
onConfirm={confirmPurchase}
|
|
onCancel={cancelPurchase}
|
|
/>
|
|
```
|
|
|
|
**APIs:**
|
|
|
|
```typescript
|
|
GET /api/v1/gamification/shop/items
|
|
GET /api/v1/gamification/shop/power-ups
|
|
POST /api/v1/gamification/shop/purchase
|
|
GET /api/v1/gamification/users/:userId/inventory
|
|
```
|
|
|
|
### 3.6 Leaderboard
|
|
|
|
**Ruta:** `/leaderboard`
|
|
|
|
**Propósito:** Rankings globales y por classroom para competencia sana.
|
|
|
|
**Tipos de Rankings:**
|
|
|
|
1. **Global XP:** Top 100 usuarios por XP total
|
|
2. **Weekly XP:** Top 50 usuarios por XP de esta semana
|
|
3. **Classroom:** Top estudiantes del aula
|
|
4. **Streak Leaders:** Top rachas activas
|
|
5. **ML Coins:** Top por balance de ML Coins
|
|
|
|
**Componentes:**
|
|
|
|
```typescript
|
|
<LeaderboardTabs
|
|
tabs={['Global', 'Weekly', 'Classroom', 'Streaks']}
|
|
activeTab={activeTab}
|
|
onChange={setActiveTab}
|
|
/>
|
|
|
|
<LeaderboardTable
|
|
entries={leaderboardEntries}
|
|
currentUserId={user.id}
|
|
highlightCurrentUser={true}
|
|
/>
|
|
|
|
<UserRankCard
|
|
rank={userRank}
|
|
xp={userXP}
|
|
position={userPosition}
|
|
/>
|
|
```
|
|
|
|
**APIs:**
|
|
|
|
```typescript
|
|
GET /api/v1/gamification/leaderboard/global
|
|
GET /api/v1/gamification/leaderboard/weekly
|
|
GET /api/v1/gamification/leaderboard/classroom/:classroomId
|
|
GET /api/v1/gamification/leaderboard/streaks
|
|
GET /api/v1/gamification/leaderboard/user-position/:userId
|
|
```
|
|
|
|
### 3.7 Perfil de Usuario
|
|
|
|
**Ruta:** `/profile`
|
|
|
|
**Propósito:** Visualizar y editar perfil personal, estadísticas y configuración.
|
|
|
|
**Secciones:**
|
|
|
|
1. **Profile Header**
|
|
- Avatar
|
|
- Username
|
|
- Rank actual con icono
|
|
- Titles (si tiene)
|
|
|
|
2. **Stats Overview**
|
|
- Total XP
|
|
- ML Coins balance
|
|
- Achievements desbloqueados
|
|
- Módulos completados
|
|
- Racha actual
|
|
|
|
3. **Recent Achievements**
|
|
- Últimos 5 logros desbloqueados
|
|
|
|
4. **Activity Graph**
|
|
- Actividad de los últimos 30 días
|
|
|
|
5. **Edit Profile**
|
|
- Cambiar avatar
|
|
- Cambiar username
|
|
- Bio
|
|
|
|
**APIs:**
|
|
|
|
```typescript
|
|
GET /api/v1/auth/users/:userId/profile
|
|
PATCH /api/v1/auth/users/:userId/profile
|
|
GET /api/v1/progress/users/:userId/stats
|
|
GET /api/v1/gamification/users/:userId/activity-graph
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Navegación
|
|
|
|
### 4.1 BottomNavigation (Móvil)
|
|
|
|
**Componente:** `BottomNavigation.tsx`
|
|
|
|
**Ubicación:** Fixed bottom en mobile (< 768px)
|
|
|
|
**Tabs:**
|
|
|
|
| ID | Label | Icon | Path | Descripción |
|
|
|----|-------|------|------|-------------|
|
|
| home | Home | Home | `/` | Dashboard principal |
|
|
| modules | Modules | BookOpen | `/modules` | Módulos educativos |
|
|
| gamification | Gamification | Trophy | `/gamification` | Hub de gamificación |
|
|
| notifications | Alerts | Bell | `/notifications` | Centro de notificaciones |
|
|
| profile | Profile | User | `/profile` | Perfil de usuario |
|
|
| settings | Settings | Settings | `/settings` | Configuración |
|
|
|
|
**Features:**
|
|
|
|
- **Active indicator:** Tab activo resaltado con color detective-orange
|
|
- **Notification badge:** Badge rojo en Bell si hay notificaciones sin leer
|
|
- **Smooth animations:** Framer Motion para transiciones
|
|
- **Accessibility:** ARIA labels y roles correctos
|
|
|
|
```typescript
|
|
<BottomNavigation />
|
|
|
|
// Detecta pathname y resalta tab activo
|
|
const isActive = (path: string) => {
|
|
if (path === '/') return location.pathname === '/';
|
|
return location.pathname.startsWith(path);
|
|
};
|
|
|
|
// Muestra badge de notificaciones
|
|
{item.id === 'notifications' && unreadCount > 0 && (
|
|
<Badge count={unreadCount} />
|
|
)}
|
|
```
|
|
|
|
### 4.2 Desktop Navigation
|
|
|
|
**Componente:** `GamifiedHeader`
|
|
|
|
**Features:**
|
|
|
|
- Logo GAMILIT
|
|
- ML Coins balance widget
|
|
- Rank badge
|
|
- Notifications dropdown
|
|
- User menu (perfil, settings, logout)
|
|
|
|
---
|
|
|
|
## 5. Hooks Principales
|
|
|
|
### 5.1 useDashboardData
|
|
|
|
**Propósito:** Fetches aggregated dashboard data con React Query.
|
|
|
|
**Endpoints llamados (en paralelo):**
|
|
|
|
```typescript
|
|
GET /api/v1/gamification/users/:userId/ml-coins
|
|
GET /api/v1/gamification/ranks/current
|
|
GET /api/v1/gamification/ranks/users/:userId/rank-progress
|
|
GET /api/v1/gamification/users/:userId/achievements
|
|
GET /api/v1/progress/users/:userId
|
|
```
|
|
|
|
**Return value:**
|
|
|
|
```typescript
|
|
{
|
|
coins: MLCoinsData | null,
|
|
rank: RankData | null,
|
|
achievements: AchievementData[],
|
|
progress: ProgressData | null,
|
|
recentAchievements: AchievementData[],
|
|
loading: boolean,
|
|
error: string | null,
|
|
isRefreshing: boolean,
|
|
refresh: () => Promise<void>
|
|
}
|
|
```
|
|
|
|
**Configuración React Query:**
|
|
|
|
- **staleTime:** 5 minutos
|
|
- **gcTime:** 10 minutos
|
|
- **refetchOnWindowFocus:** true
|
|
- **retry:** 2 veces con backoff exponencial
|
|
|
|
### 5.2 useUserModules
|
|
|
|
**Propósito:** Fetches módulos educativos del usuario, opcionalmente filtrados por classroom.
|
|
|
|
**API:**
|
|
|
|
```typescript
|
|
GET /api/v1/educational/modules
|
|
GET /api/v1/educational/modules/:classroomId/assigned
|
|
```
|
|
|
|
**Return value:**
|
|
|
|
```typescript
|
|
{
|
|
modules: Module[],
|
|
loading: boolean,
|
|
error: string | null
|
|
}
|
|
```
|
|
|
|
### 5.3 useExerciseState
|
|
|
|
**Propósito:** Gestiona estado local de ejercicio en progreso.
|
|
|
|
**State:**
|
|
|
|
```typescript
|
|
{
|
|
currentStep: number,
|
|
totalSteps: number,
|
|
score: number,
|
|
answers: Record<string, any>,
|
|
hintsUsed: number,
|
|
timeSpent: number,
|
|
powerupsUsed: string[],
|
|
completed: boolean
|
|
}
|
|
```
|
|
|
|
**Métodos:**
|
|
|
|
- `updateProgress(progress: Partial<ExerciseProgress>)`
|
|
- `submitExercise()`
|
|
- `resetExercise()`
|
|
|
|
### 5.4 useExerciseAutoSave
|
|
|
|
**Propósito:** Auto-save de progreso cada 30s mientras ejercicio no completado.
|
|
|
|
**API:**
|
|
|
|
```typescript
|
|
POST /api/v1/progress/exercises/:exerciseId/save
|
|
{
|
|
progress: ExerciseProgress,
|
|
answers: any
|
|
}
|
|
```
|
|
|
|
**Configuración:**
|
|
|
|
```typescript
|
|
useExerciseAutoSave(exerciseId, exerciseState, {
|
|
interval: 30000, // 30 segundos
|
|
enabled: !exerciseState.completed,
|
|
});
|
|
```
|
|
|
|
### 5.5 useExercisePowerUps
|
|
|
|
**Propósito:** Gestión de power-ups activos durante ejercicio.
|
|
|
|
**API:**
|
|
|
|
```typescript
|
|
POST /api/v1/gamification/comodines/use
|
|
{
|
|
comodinId: string,
|
|
exerciseId: string
|
|
}
|
|
```
|
|
|
|
**Métodos:**
|
|
|
|
- `usePowerUp(powerUpId: string)`
|
|
- `getActivePowerUps()`
|
|
- `checkPowerUpActive(type: string)`
|
|
|
|
---
|
|
|
|
## 6. APIs del Portal Student
|
|
|
|
### 6.1 Endpoints Principales
|
|
|
|
| Módulo | Método | Endpoint | Descripción | Guard |
|
|
|--------|--------|----------|-------------|-------|
|
|
| **Progress** |
|
|
| | GET | `/progress/users/:userId` | Progreso general del usuario | JwtAuth |
|
|
| | GET | `/progress/users/:userId/recent-activities` | Últimas actividades | JwtAuth |
|
|
| | GET | `/progress/modules/:moduleId/progress` | Progreso en módulo | JwtAuth |
|
|
| | POST | `/progress/exercises/:exerciseId/save` | Auto-save de ejercicio | JwtAuth |
|
|
| | POST | `/progress/exercises/:exerciseId/submit` | Enviar ejercicio completo | JwtAuth |
|
|
| **Gamification** |
|
|
| | GET | `/gamification/users/:userId/ml-coins` | Balance de ML Coins | JwtAuth |
|
|
| | GET | `/gamification/users/:userId/ml-coins/transactions` | Historial de transacciones | JwtAuth |
|
|
| | GET | `/gamification/ranks/current` | Ranks disponibles | JwtAuth |
|
|
| | GET | `/gamification/ranks/users/:userId/rank-progress` | Progreso de rank | JwtAuth |
|
|
| | GET | `/gamification/achievements` | Todos los achievements | JwtAuth |
|
|
| | GET | `/gamification/users/:userId/achievements` | Achievements del usuario | JwtAuth |
|
|
| | GET | `/gamification/missions/active` | Misiones activas | JwtAuth |
|
|
| | POST | `/gamification/missions/:missionId/claim` | Reclamar recompensa | JwtAuth |
|
|
| | GET | `/gamification/leaderboard/global` | Ranking global | JwtAuth |
|
|
| | GET | `/gamification/leaderboard/classroom/:id` | Ranking del aula | JwtAuth |
|
|
| | GET | `/gamification/shop/items` | Items de la tienda | JwtAuth |
|
|
| | POST | `/gamification/shop/purchase` | Comprar item | JwtAuth |
|
|
| | POST | `/gamification/comodines/use` | Usar power-up | JwtAuth |
|
|
| **Educational** |
|
|
| | GET | `/educational/modules` | Lista de módulos | JwtAuth |
|
|
| | GET | `/educational/modules/:id` | Detalle de módulo | JwtAuth |
|
|
| | GET | `/educational/modules/:id/exercises` | Ejercicios del módulo | JwtAuth |
|
|
| | GET | `/educational/exercises/:id` | Detalle de ejercicio | JwtAuth |
|
|
| | GET | `/educational/exercises/:id/hints` | Pistas disponibles | JwtAuth |
|
|
| **Social** |
|
|
| | GET | `/social/friendships` | Lista de amigos | JwtAuth |
|
|
| | POST | `/social/friendships` | Enviar solicitud de amistad | JwtAuth |
|
|
| | GET | `/social/teams/:id` | Detalle de guild | JwtAuth |
|
|
|
|
### 6.2 Frontend API Services
|
|
|
|
```
|
|
services/api/
|
|
├── educationalAPI.ts # Módulos y ejercicios
|
|
├── progressAPI.ts # Progreso y submissions
|
|
├── gamificationAPI.ts # Gamificación general
|
|
├── ranksAPI.ts # Sistema de ranks
|
|
├── achievementsAPI.ts # Achievements
|
|
├── missionsAPI.ts # Misiones
|
|
├── economyAPI.ts # ML Coins y shop
|
|
├── socialAPI.ts # Amigos, guilds, leaderboard
|
|
└── apiClient.ts # Axios instance configurado
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Estado y Stores (Zustand)
|
|
|
|
### 7.1 Stores Principales
|
|
|
|
```typescript
|
|
// Auth Store
|
|
authStore:
|
|
- user: User | null
|
|
- isAuthenticated: boolean
|
|
- login()
|
|
- logout()
|
|
- register()
|
|
|
|
// Ranks Store
|
|
ranksStore:
|
|
- userProgress: UserRankProgress
|
|
- multiplierBreakdown: MultiplierData
|
|
- prestigeProgress: PrestigeData
|
|
- progressionHistory: ProgressEvent[]
|
|
- fetchUserProgress()
|
|
- showRankUpModal: boolean
|
|
- closeRankUpModal()
|
|
|
|
// Economy Store
|
|
economyStore:
|
|
- balance: MLCoinsBalance
|
|
- transactions: Transaction[]
|
|
- fetchBalance()
|
|
- addTransaction()
|
|
|
|
// Achievements Store
|
|
achievementsStore:
|
|
- achievements: Achievement[]
|
|
- userAchievements: UserAchievement[]
|
|
- stats: AchievementStats
|
|
- fetchAchievements()
|
|
- claimAchievement()
|
|
|
|
// Notifications Store
|
|
notificationsStore:
|
|
- notifications: Notification[]
|
|
- unreadCount: number
|
|
- fetchNotifications()
|
|
- markAsRead()
|
|
- fetchUnreadCount()
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Flujos Principales
|
|
|
|
### 8.1 Flujo: Completar Ejercicio
|
|
|
|
```
|
|
1. Student navega a /exercises/:exerciseId
|
|
↓
|
|
2. ExercisePage carga ejercicio: GET /educational/exercises/:exerciseId
|
|
↓
|
|
3. useExerciseState inicializa estado local
|
|
↓
|
|
4. useExerciseAutoSave inicia (cada 30s):
|
|
POST /progress/exercises/:exerciseId/save
|
|
↓
|
|
5. Student completa ejercicio y presiona "Submit"
|
|
↓
|
|
6. POST /progress/exercises/:exerciseId/submit
|
|
{
|
|
answers: {...},
|
|
timeSpent: 180,
|
|
hintsUsed: 1,
|
|
powerupsUsed: ['hint_revealer']
|
|
}
|
|
↓
|
|
7. Backend calcula score, XP ganado, ML Coins
|
|
↓
|
|
8. Response:
|
|
{
|
|
score: 85,
|
|
maxScore: 100,
|
|
xpEarned: 120,
|
|
mlCoinsEarned: 50,
|
|
achievements: ['first_completion'],
|
|
feedback: {...}
|
|
}
|
|
↓
|
|
9. FeedbackModal muestra resultados
|
|
↓
|
|
10. Si achievement desbloqueado → AchievementToast
|
|
↓
|
|
11. Si rank up → RankUpModal
|
|
↓
|
|
12. Redirect a /modules o /dashboard
|
|
```
|
|
|
|
### 8.2 Flujo: Ganar XP y Subir de Rango
|
|
|
|
```
|
|
1. Student completa ejercicio → Gana XP (ej: 120 XP)
|
|
↓
|
|
2. Backend actualiza user_stats.total_xp
|
|
↓
|
|
3. Backend verifica si XP >= siguiente rank:
|
|
SELECT * FROM gamification.maya_ranks
|
|
WHERE xp_required <= :totalXP
|
|
ORDER BY xp_required DESC LIMIT 1
|
|
↓
|
|
4. Si cambió rank:
|
|
- Actualizar user_rank.current_rank
|
|
- Crear entry en rank_progression_history
|
|
- Crear achievement 'rank_up_{rankName}'
|
|
↓
|
|
5. Response incluye:
|
|
{
|
|
rankUp: true,
|
|
newRank: 'Nacom',
|
|
newMultiplier: 1.2,
|
|
prestigePoints: 0
|
|
}
|
|
↓
|
|
6. Frontend muestra RankUpModal con celebración
|
|
↓
|
|
7. ranksStore.fetchUserProgress() actualiza estado
|
|
```
|
|
|
|
### 8.3 Flujo: Comprar Item en Shop
|
|
|
|
```
|
|
1. Student navega a /shop
|
|
↓
|
|
2. GET /gamification/shop/items → Lista items disponibles
|
|
↓
|
|
3. Student selecciona item → Modal de confirmación
|
|
↓
|
|
4. Verificar balance suficiente
|
|
↓
|
|
5. POST /gamification/shop/purchase
|
|
{
|
|
itemId: 'hint_revealer',
|
|
quantity: 1
|
|
}
|
|
↓
|
|
6. Backend:
|
|
- Verifica balance >= item.price
|
|
- Deducir ML Coins
|
|
- Agregar item a inventory
|
|
- Crear transaction log
|
|
↓
|
|
7. Response:
|
|
{
|
|
success: true,
|
|
newBalance: 450,
|
|
item: {...}
|
|
}
|
|
↓
|
|
8. Frontend:
|
|
- economyStore.fetchBalance() (actualiza balance)
|
|
- Toast: "Item comprado exitosamente"
|
|
- Modal cierra
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Sistema de Gamificación Detallado
|
|
|
|
### 9.1 Cálculo de XP
|
|
|
|
**Fórmula Base:**
|
|
|
|
```typescript
|
|
baseXP = exercise.points; // Ej: 100 XP
|
|
|
|
// Multiplicadores
|
|
scoreMultiplier = (score / maxScore); // Ej: 85/100 = 0.85
|
|
rankMultiplier = userRank.multiplier; // Ej: 1.2x (Nacom)
|
|
streakBonus = min(currentStreak * 0.05, 0.5); // Max +50%
|
|
|
|
totalXP = baseXP * scoreMultiplier * rankMultiplier * (1 + streakBonus);
|
|
|
|
// Ejemplo:
|
|
// 100 XP * 0.85 * 1.2 * 1.15 = 117.3 XP → 117 XP
|
|
```
|
|
|
|
**Bonuses Adicionales:**
|
|
|
|
- **Perfect Score:** +20% XP si score = 100%
|
|
- **Speed Bonus:** +10% XP si completa en < 50% tiempo estimado
|
|
- **No Hints Used:** +15% XP si no usó pistas
|
|
- **First Try:** +25% XP si completa en primer intento
|
|
|
|
### 9.2 Cálculo de ML Coins
|
|
|
|
**Fórmula Base:**
|
|
|
|
```typescript
|
|
baseCoins = Math.floor(exercise.points / 10); // Ej: 100 XP → 10 ML Coins
|
|
|
|
// Multiplicadores
|
|
scoreMultiplier = (score / maxScore);
|
|
difficultyBonus = {
|
|
easy: 1.0,
|
|
medium: 1.5,
|
|
hard: 2.0
|
|
}[exercise.difficulty];
|
|
|
|
totalCoins = baseCoins * scoreMultiplier * difficultyBonus;
|
|
|
|
// Ejemplo (hard, 85% score):
|
|
// 10 * 0.85 * 2.0 = 17 ML Coins
|
|
```
|
|
|
|
### 9.3 Sistema de Streaks
|
|
|
|
**Cómo funciona:**
|
|
|
|
- **Racha (Streak):** Días consecutivos con al menos 1 ejercicio completado
|
|
- **Zona horaria:** UTC-5 (Colombia)
|
|
- **Reset:** Si pasa 1 día completo sin actividad → streak = 0
|
|
- **Bonus XP:** +5% por cada día de racha (max +50% a 10 días)
|
|
|
|
**Guardado:**
|
|
|
|
```sql
|
|
-- En user_stats
|
|
current_streak: integer DEFAULT 0
|
|
longest_streak: integer DEFAULT 0
|
|
last_activity_date: date
|
|
```
|
|
|
|
**Lógica:**
|
|
|
|
```typescript
|
|
const today = new Date().toISOString().split('T')[0];
|
|
const lastActivity = user.lastActivityDate;
|
|
|
|
if (lastActivity === today) {
|
|
// Mismo día, no cambiar streak
|
|
} else {
|
|
const daysSince = daysBetween(lastActivity, today);
|
|
|
|
if (daysSince === 1) {
|
|
// Día consecutivo
|
|
user.currentStreak++;
|
|
user.longestStreak = Math.max(user.longestStreak, user.currentStreak);
|
|
} else {
|
|
// Se rompió la racha
|
|
user.currentStreak = 1;
|
|
}
|
|
|
|
user.lastActivityDate = today;
|
|
}
|
|
```
|
|
|
|
### 9.4 Achievements: Triggers y Lógica
|
|
|
|
**Ejemplos de Achievements:**
|
|
|
|
| ID | Nombre | Trigger | Condición | Recompensa |
|
|
|----|--------|---------|-----------|------------|
|
|
| first_steps | Primeros Pasos | exercise_completed | count === 1 | 20 XP, 10 ML Coins |
|
|
| speed_demon | Demonio de Velocidad | exercise_completed | timeSpent < estimatedTime * 0.5 | 50 XP, 25 ML Coins |
|
|
| perfectionist | Perfeccionista | exercise_completed | score === 100 && attempts === 1 | 100 XP, 50 ML Coins |
|
|
| rank_nacom | Ascenso a Nacom | rank_up | newRank === 'Nacom' | 200 XP, 100 ML Coins |
|
|
| module_master | Maestro de Módulo | module_completed | completionRate === 100% | 500 XP, 200 ML Coins |
|
|
| streak_warrior | Guerrero Constante | daily_activity | currentStreak === 7 | 150 XP, 75 ML Coins |
|
|
| coin_collector | Coleccionista | ml_coins_earned | totalCoinsEarned >= 1000 | 100 XP, 50 ML Coins |
|
|
|
|
**Backend Logic:**
|
|
|
|
```typescript
|
|
// En exercise-submission.service.ts
|
|
async checkAchievements(userId: string, context: AchievementContext) {
|
|
const triggers = await this.achievementsRepo.find({
|
|
where: { trigger: context.trigger }
|
|
});
|
|
|
|
for (const achievement of triggers) {
|
|
const meetsCondition = await this.evaluateCondition(
|
|
achievement.condition,
|
|
userId,
|
|
context
|
|
);
|
|
|
|
if (meetsCondition) {
|
|
await this.unlockAchievement(userId, achievement.id);
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Responsive Design
|
|
|
|
### 10.1 Breakpoints
|
|
|
|
```typescript
|
|
// tailwind.config.js
|
|
screens: {
|
|
'sm': '640px', // Mobile landscape
|
|
'md': '768px', // Tablet
|
|
'lg': '1024px', // Desktop
|
|
'xl': '1280px', // Large desktop
|
|
'2xl': '1536px' // Extra large
|
|
}
|
|
```
|
|
|
|
### 10.2 Layout Adaptations
|
|
|
|
**Mobile (< 768px):**
|
|
- BottomNavigation visible
|
|
- Single column layouts
|
|
- Swipeable carousels
|
|
- Collapsible sections
|
|
- Touch-optimized controls (min 44x44px)
|
|
|
|
**Tablet (768px - 1024px):**
|
|
- BottomNavigation hidden
|
|
- GamifiedHeader expanded
|
|
- 2-column grids
|
|
- Sidebars overlay
|
|
|
|
**Desktop (> 1024px):**
|
|
- Full navigation
|
|
- 3-4 column grids
|
|
- Persistent sidebars
|
|
- Hover states
|
|
|
|
**Hook:**
|
|
|
|
```typescript
|
|
const { isMobile, isTablet, isDesktop } = useResponsiveLayout();
|
|
|
|
// Usage
|
|
{isMobile && <MobileView />}
|
|
{isDesktop && <DesktopSidebar />}
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Buenas Prácticas
|
|
|
|
### 11.1 Frontend
|
|
|
|
#### 11.1.1 React Query
|
|
|
|
```typescript
|
|
// DO: Query keys descriptivas y jerárquicas
|
|
queryKey: ['dashboard', userId, 'coins']
|
|
queryKey: ['modules', moduleId, 'exercises']
|
|
|
|
// DO: Usar staleTime para reducir re-fetches
|
|
staleTime: 5 * 60 * 1000, // 5 min
|
|
|
|
// DO: Invalidar queries relacionadas después de mutations
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ['dashboard'] });
|
|
queryClient.invalidateQueries({ queryKey: ['gamification', 'coins'] });
|
|
}
|
|
|
|
// DON'T: Queries sin enabled cuando dependen de parámetros
|
|
enabled: !!userId, // SIEMPRE verificar
|
|
```
|
|
|
|
#### 11.1.2 State Management
|
|
|
|
```typescript
|
|
// DO: Zustand para global state, React Query para server state
|
|
// Global UI state → Zustand
|
|
// Server data → React Query
|
|
|
|
// DO: Hooks para lógica reutilizable
|
|
export function useDashboardData() { ... }
|
|
|
|
// DON'T: Props drilling más de 2 niveles
|
|
// Usar context o store
|
|
```
|
|
|
|
#### 11.1.3 Performance
|
|
|
|
```typescript
|
|
// DO: Lazy load de mecánicas
|
|
const DynamicMechanic = React.lazy(() =>
|
|
import(`@/features/mechanics/${mechanicType}`)
|
|
);
|
|
|
|
// DO: Memoizar cálculos pesados
|
|
const expNeeded = useMemo(() =>
|
|
calculateNextRankXP(currentRank),
|
|
[currentRank]
|
|
);
|
|
|
|
// DO: Debounce en búsquedas
|
|
const debouncedSearch = useDebounce(searchQuery, 300);
|
|
```
|
|
|
|
### 11.2 Backend
|
|
|
|
#### 11.2.1 Controllers
|
|
|
|
```typescript
|
|
// DO: DTOs con validación completa
|
|
@Post('submit')
|
|
@ApiOperation({ summary: 'Submit exercise completion' })
|
|
@ApiOkResponse({ type: SubmissionResultDto })
|
|
async submitExercise(
|
|
@CurrentUser() user: User,
|
|
@Param('exerciseId') exerciseId: string,
|
|
@Body() dto: SubmitExerciseDto,
|
|
): Promise<SubmissionResultDto> {
|
|
return this.submissionService.submitExercise(user.id, exerciseId, dto);
|
|
}
|
|
```
|
|
|
|
#### 11.2.2 Services
|
|
|
|
```typescript
|
|
// DO: Transactions para operaciones atómicas
|
|
async submitExercise(userId: string, dto: SubmitExerciseDto) {
|
|
return this.dataSource.transaction(async (manager) => {
|
|
// 1. Crear submission
|
|
const submission = await manager.save(ExerciseSubmission, {...});
|
|
|
|
// 2. Actualizar progreso
|
|
await manager.update(ModuleProgress, {...});
|
|
|
|
// 3. Otorgar XP y ML Coins
|
|
await this.grantRewards(userId, submission, manager);
|
|
|
|
// 4. Check achievements
|
|
await this.checkAchievements(userId, submission, manager);
|
|
|
|
return submission;
|
|
});
|
|
}
|
|
```
|
|
|
|
#### 11.2.3 Optimización
|
|
|
|
```typescript
|
|
// DO: Eager loading para evitar N+1 queries
|
|
const exercises = await this.exercisesRepo.find({
|
|
where: { module_id: moduleId },
|
|
relations: ['module', 'submissions'],
|
|
});
|
|
|
|
// DO: Cache para datos que cambian poco
|
|
@Cacheable('ranks', 3600) // 1 hora
|
|
async getRanks(): Promise<Rank[]> {
|
|
return this.ranksRepo.find();
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Testing
|
|
|
|
### 12.1 Tests Unitarios Frontend
|
|
|
|
```typescript
|
|
// useDashboardData.test.ts
|
|
describe('useDashboardData', () => {
|
|
it('should fetch dashboard data successfully', async () => {
|
|
const { result } = renderHook(() => useDashboardData(), {
|
|
wrapper: createWrapper(),
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.rank).toBeDefined();
|
|
expect(result.current.coins).toBeDefined();
|
|
});
|
|
});
|
|
```
|
|
|
|
### 12.2 Tests de Integración
|
|
|
|
```typescript
|
|
// ExerciseSubmission.integration.test.ts
|
|
describe('Exercise Submission Flow', () => {
|
|
it('should complete exercise and grant rewards', async () => {
|
|
const user = await createTestUser();
|
|
const exercise = await createTestExercise();
|
|
|
|
const response = await request(app.getHttpServer())
|
|
.post(`/progress/exercises/${exercise.id}/submit`)
|
|
.set('Authorization', `Bearer ${user.token}`)
|
|
.send({
|
|
answers: { q1: 'correct' },
|
|
timeSpent: 120,
|
|
hintsUsed: 0,
|
|
})
|
|
.expect(201);
|
|
|
|
expect(response.body.xpEarned).toBeGreaterThan(0);
|
|
expect(response.body.mlCoinsEarned).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 13. Checklist de Desarrollo
|
|
|
|
### 13.1 Nueva Funcionalidad
|
|
|
|
- [ ] Definir types en `student/types/` o `@shared/types`
|
|
- [ ] Crear/actualizar DTOs en backend
|
|
- [ ] Implementar service en backend
|
|
- [ ] Crear/modificar controller con guards
|
|
- [ ] Agregar validaciones (class-validator)
|
|
- [ ] Crear API service en frontend
|
|
- [ ] Implementar custom hook si necesario
|
|
- [ ] Crear componentes UI necesarios
|
|
- [ ] Integrar en página correspondiente
|
|
- [ ] Agregar tests unitarios
|
|
- [ ] Probar responsive (mobile, tablet, desktop)
|
|
- [ ] Documentar en Swagger (decoradores)
|
|
- [ ] Actualizar esta guía si aplica
|
|
|
|
### 13.2 Code Review
|
|
|
|
- [ ] Types alineados frontend/backend
|
|
- [ ] Guards aplicados correctamente (JwtAuth mínimo)
|
|
- [ ] Validación de DTOs completa
|
|
- [ ] Error handling implementado
|
|
- [ ] React Query keys descriptivas
|
|
- [ ] Invalidación de cache correcta después de mutations
|
|
- [ ] Loading y error states manejados
|
|
- [ ] Responsive design verificado
|
|
- [ ] Accessibility (ARIA labels, keyboard navigation)
|
|
- [ ] Performance optimizado (memo, lazy load)
|
|
|
|
---
|
|
|
|
## 14. Troubleshooting
|
|
|
|
### 14.1 Problemas Comunes
|
|
|
|
| Problema | Causa Probable | Solución |
|
|
|----------|----------------|----------|
|
|
| 401 Unauthorized | Token JWT expirado/inválido | Renovar token con refresh endpoint |
|
|
| Data desactualizada | Cache no invalidado | `queryClient.invalidateQueries()` |
|
|
| Types mismatch | Desync FE/BE | Regenerar types desde DTOs backend |
|
|
| Exercise no se guarda | Auto-save disabled | Verificar `enabled` en `useExerciseAutoSave` |
|
|
| XP no se actualiza | Estado local no sincroniza | Llamar `refresh()` de `useDashboardData` |
|
|
| Achievements no se desbloquean | Condiciones no se cumplen | Verificar lógica en backend service |
|
|
| Power-up no aplica | Item no en inventory | Verificar inventario antes de usar |
|
|
|
|
### 14.2 Debugging
|
|
|
|
```typescript
|
|
// Habilitar logs de React Query en dev
|
|
if (import.meta.env.DEV) {
|
|
import('@tanstack/react-query-devtools').then(({ ReactQueryDevtools }) => {
|
|
// Montar devtools
|
|
});
|
|
}
|
|
|
|
// Log de API calls
|
|
apiClient.interceptors.request.use((config) => {
|
|
console.log(`[API] ${config.method?.toUpperCase()} ${config.url}`);
|
|
return config;
|
|
});
|
|
|
|
// Log de state changes (Zustand)
|
|
devtools(storeImpl, { name: 'RanksStore' })
|
|
```
|
|
|
|
---
|
|
|
|
## 15. Performance y Optimización
|
|
|
|
### 15.1 Frontend Optimization
|
|
|
|
**Lazy Loading de Rutas:**
|
|
|
|
```typescript
|
|
const DashboardComplete = lazy(() => import('./pages/DashboardComplete'));
|
|
const ExercisePage = lazy(() => import('./pages/ExercisePage'));
|
|
const GamificationPage = lazy(() => import('./pages/GamificationPage'));
|
|
|
|
<Suspense fallback={<PageLoader />}>
|
|
<Routes>
|
|
<Route path="/" element={<DashboardComplete />} />
|
|
<Route path="/exercises/:id" element={<ExercisePage />} />
|
|
<Route path="/gamification" element={<GamificationPage />} />
|
|
</Routes>
|
|
</Suspense>
|
|
```
|
|
|
|
**Code Splitting:**
|
|
|
|
```typescript
|
|
// Dynamic imports for mechanics
|
|
const loadMechanic = (type: string) => {
|
|
return import(`@/features/mechanics/${type}/${type}Exercise`);
|
|
};
|
|
```
|
|
|
|
**Image Optimization:**
|
|
|
|
- Usar WebP para imágenes modernas
|
|
- Lazy load de imágenes con `loading="lazy"`
|
|
- Sprites para iconos pequeños
|
|
|
|
### 15.2 Backend Optimization
|
|
|
|
**Database Indexing:**
|
|
|
|
```sql
|
|
-- Indices críticos
|
|
CREATE INDEX idx_exercise_submissions_user ON progress.exercise_submissions(user_id);
|
|
CREATE INDEX idx_user_stats_total_xp ON gamification.user_stats(total_xp DESC);
|
|
CREATE INDEX idx_achievements_trigger ON gamification.achievements(trigger);
|
|
```
|
|
|
|
**Query Optimization:**
|
|
|
|
```typescript
|
|
// Usar select específico en vez de SELECT *
|
|
const stats = await this.userStatsRepo.findOne({
|
|
where: { user_id: userId },
|
|
select: ['total_xp', 'total_ml_coins', 'current_streak'],
|
|
});
|
|
```
|
|
|
|
**Caching:**
|
|
|
|
- Redis para leaderboards (TTL 5 min)
|
|
- Cache de ranks/achievements (TTL 1 hora)
|
|
- Invalidación al actualizar datos
|
|
|
|
---
|
|
|
|
## 16. Seguridad
|
|
|
|
### 16.1 Autenticación y Autorización
|
|
|
|
**Guards:**
|
|
|
|
- **JwtAuthGuard:** TODOS los endpoints (excepto public)
|
|
- **RolesGuard:** Si endpoint específico para rol
|
|
|
|
**Validación de Ownership:**
|
|
|
|
```typescript
|
|
// Verificar que submission pertenece al usuario
|
|
const submission = await this.submissionRepo.findOne({
|
|
where: {
|
|
id: submissionId,
|
|
user_id: userId // CRÍTICO
|
|
}
|
|
});
|
|
|
|
if (!submission) {
|
|
throw new ForbiddenException('Not your submission');
|
|
}
|
|
```
|
|
|
|
### 16.2 Validación de Datos
|
|
|
|
```typescript
|
|
// DTOs con class-validator
|
|
export class SubmitExerciseDto {
|
|
@IsObject()
|
|
@ValidateNested()
|
|
answers!: Record<string, any>;
|
|
|
|
@IsNumber()
|
|
@Min(0)
|
|
@Max(7200) // Max 2 horas
|
|
timeSpent!: number;
|
|
|
|
@IsNumber()
|
|
@Min(0)
|
|
@Max(10)
|
|
hintsUsed!: number;
|
|
}
|
|
```
|
|
|
|
### 16.3 Rate Limiting
|
|
|
|
```typescript
|
|
// Exercise submission: max 100/día por usuario
|
|
@ThrottlerGuard({ limit: 100, ttl: 86400 })
|
|
@Post('submit')
|
|
async submitExercise(...) { ... }
|
|
```
|
|
|
|
---
|
|
|
|
## 17. Referencias
|
|
|
|
### Documentos Complementarios del Portal Student
|
|
|
|
| Documento | Descripción |
|
|
|-----------|-------------|
|
|
| [EXERCISE-MECHANICS-REFERENCE.md](./EXERCISE-MECHANICS-REFERENCE.md) | Referencia completa de 30+ mecánicas implementadas |
|
|
| [GAMIFICATION-SYSTEM-GUIDE.md](./GAMIFICATION-SYSTEM-GUIDE.md) | Sistema de gamificación en profundidad |
|
|
| [STUDENT-API-REFERENCE.md](./STUDENT-API-REFERENCE.md) | Referencia de 60+ APIs con ejemplos |
|
|
|
|
### Guías Generales
|
|
|
|
- [COMPONENT-PATTERNS.md](./frontend/COMPONENT-PATTERNS.md) - Patrones de componentes
|
|
- [HOOK-PATTERNS.md](./frontend/HOOK-PATTERNS.md) - Patrones de hooks
|
|
- [REACT-QUERY-GUIDE.md](./frontend/REACT-QUERY-GUIDE.md) - Uso de React Query
|
|
- [ZUSTAND-PATTERNS.md](./frontend/ZUSTAND-PATTERNS.md) - Patrones de Zustand
|
|
- [TYPES-CONVENTIONS.md](./frontend/TYPES-CONVENTIONS.md) - Convenciones de types
|
|
- [DTO-CONVENTIONS.md](./backend/DTO-CONVENTIONS.md) - Convenciones de DTOs
|
|
- [ESTRUCTURA-MODULOS.md](./backend/ESTRUCTURA-MODULOS.md) - Estructura de módulos
|
|
- [ESTANDARES-API-ROUTES.md](../../orchestration/directivas/ESTANDARES-API-ROUTES.md) - Rutas API
|
|
|
|
### Documentación de Arquitectura
|
|
|
|
- [ADR-001: Sistema de Ranks Maya](../../97-adr/ADR-001-sistema-ranks-maya.md)
|
|
- [ADR-002: ML Coins Economy](../../97-adr/ADR-002-ml-coins-economy.md)
|
|
- [ADR-003: Exercise Mechanics](../../97-adr/ADR-003-exercise-mechanics.md)
|
|
|
|
---
|
|
|
|
## Changelog
|
|
|
|
| Versión | Fecha | Cambios |
|
|
|---------|-------|---------|
|
|
| 1.0.0 | 2025-11-29 | Creación inicial - Documentación completa del Portal Student |
|
|
|
|
---
|
|
|
|
**Mantenido por:** Tech Lead - GAMILIT Project
|
|
**Última revisión:** 2025-11-29
|