# FASE 1: ANÁLISIS INICIAL DE DUPLICADOS - ACHIEVEMENTS SYSTEM **Fecha:** 2026-01-10 **Proyecto:** Gamilit **Componente:** Sistema de Achievements (Full Stack) **Estado:** EN ANÁLISIS --- ## 1. RESUMEN EJECUTIVO Se identificaron **múltiples duplicaciones y conflictos críticos** en el sistema de achievements a través de las tres capas (Database, Backend, Frontend): | Capa | Duplicados | Severidad | |------|------------|-----------| | **Database (SQL)** | 4 funciones con overlap | 🔴 CRÍTICO | | **Backend (NestJS)** | 3 servicios con lógica inconsistente | 🔴 CRÍTICO | | **Frontend (React)** | 6+ archivos duplicados | 🟡 IMPORTANTE | --- ## 2. HALLAZGOS POR CAPA ### 2.1 BASE DE DATOS - Funciones SQL Duplicadas #### CRÍTICO: Flujo de Recompensas Inconsistente | Función | Otorga XP | Otorga Coins | Registra Transacción | Usa Multiplicador | |---------|-----------|--------------|----------------------|-------------------| | `claim_achievement_reward` | ✅ | ✅ | ✅ | ❌ | | `check_and_award_achievements` | ✅ | ✅ | ✅ | ❌ | | `award_ml_coins` | ❌ | ✅ | ✅ | ✅ (rank) | | `process_exercise_completion` | ✅ | ✅ | ❌ | ❌ | | `promote_to_next_rank` | ❌ | ✅ | ✅ | ❌ | | `consume_comodin` | ✅ | ❌ | ❌ | ❌ | **Problema Principal:** `claim_achievement_reward` y `check_and_award_achievements` duplican funcionalidad: - `check_and_award_achievements`: Otorga recompensas AL DESBLOQUEAR - `claim_achievement_reward`: Otorga recompensas AL RECLAMAR **Riesgo:** Si ambas se ejecutan, el usuario recibe recompensas DUPLICADAS. #### Funciones SQL Relacionadas ``` /apps/database/ddl/schemas/gamification_system/functions/ ├── claim_achievement_reward.sql # MODIFICADA - Reclamar recompensas ├── check_and_award_achievements.sql # DUPLICACIÓN - Otorga al desbloquear ├── award_ml_coins.sql # HELPER - Con multiplicador de rank ├── process_exercise_completion.sql # PARCIAL - XP/coins sin transacción ├── promote_to_next_rank.sql # RANK - Bonus por promoción ├── update_user_rank.sql # RANK - Similar a promote ├── check_rank_promotion.sql # RANK - Orquestación ├── consume_comodin.sql # ITEMS - XP sin transacción └── apply_xp_boost.sql # HELPER - Solo lectura ``` --- ### 2.2 BACKEND - Servicios con Lógica Inconsistente #### CRÍTICO: claimRewards() NO Distribuye Recompensas **Archivo:** `achievements.service.ts` ```typescript // PROBLEMA: Solo marca como reclamado, NO distribuye XP/Coins async claimRewards(userId: string, achievementId: string): Promise { // ... validaciones ... userAchievement.rewards_claimed = true; return this.userAchievementRepo.save(userAchievement); // ❌ FALTA: Llamar a UserStatsService.addXp() // ❌ FALTA: Llamar a MLCoinsService.addCoins() } ``` **Contraste con otros servicios:** | Servicio | Método | Distribuye Rewards | |----------|--------|-------------------| | `achievements.service.ts` | `claimRewards()` | ❌ NO | | `exercise-rewards.service.ts` | `claimRewards()` | ✅ SÍ (XP + Coins) | | `mission-claim.service.ts` | `claimMission()` | ✅ SÍ (XP + Coins) | #### CRÍTICO: Backend NO Llama a claim_achievement_reward SQL **Hallazgo:** Ningún archivo del backend invoca la función SQL `claim_achievement_reward`. - La función SQL está corregida pero NO se usa - El backend usa TypeORM directamente para actualizar `rewards_claimed` - Las recompensas (XP/Coins) NO se distribuyen en absoluto #### Schema Mismatch en Admin **Archivo:** `admin-progress.service.ts` | Campo SQL Query | Campo en Entity | Problema | |-----------------|-----------------|----------| | `a.tier` | `a.rarity` / `a.difficulty_level` | ❌ No existe | | `ua.progress_current` | `ua.progress` | ❌ Nombre diferente | | `ua.progress_required` | `ua.max_progress` | ❌ Nombre diferente | | `ua.unlocked_at` | `ua.completed_at` | ❌ Nombre diferente | --- ### 2.3 FRONTEND - Archivos Duplicados #### APIs Duplicadas (3 archivos) | Archivo | Ubicación | Métodos Duplicados | |---------|-----------|-------------------| | `achievementsAPI.ts` | `/features/gamification/social/api/` | `claimAchievementRewards()` | | `gamification.api.ts` | `/lib/api/` | `claimAchievement()` | | `achievementsApi.ts` | `/services/api/admin/` | Admin-only (OK) | **Problema:** `claimAchievementRewards()` y `claimAchievement()` son implementaciones diferentes del mismo endpoint. #### Hooks Duplicados (4 archivos) | Hook | Ubicación | Líneas | Propósito | |------|-----------|--------|-----------| | `useAchievements.ts` | `/hooks/` | ~450 | Definiciones hardcodeadas + polling | | `useAchievements.ts` | `/features/gamification/social/hooks/` | ~80 | Wrapper del store | | `useAchievementsEnhanced.ts` | `/apps/student/hooks/` | ~300 | Filtrado avanzado | | `useAchievementsStats.ts` | `/apps/teacher/hooks/` | ~50 | Analytics (OK - diferente) | **Problema Principal:** `/hooks/useAchievements.ts` tiene 450+ líneas de definiciones de achievements hardcodeadas que deberían venir del backend. #### Transformers Inconsistentes | Archivo | Approach | |---------|----------| | `achievementsAPI.ts` | Mappers INLINE (`mapToFrontendAchievement`) | | `achievementTransformer.ts` | Transformer EXTERNO (`transformAchievements`) | | `gamification.api.ts` | Usa transformer externo | --- ## 3. MATRIZ DE DUPLICACIÓN ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ FLUJO DE CLAIM REWARDS │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ Frontend Backend Database │ │ ───────── ─────── ──────── │ │ │ │ achievementsAPI.ts ──► achievements.service.ts ──► [NO LLAMA SQL] │ │ claimAchievementRewards() claimRewards() claim_achievement_reward│ │ │ │ │ │ │ │ ❌ Solo marca flag │ │ │ │ ❌ NO distribuye rewards │ │ │ │ │ │ gamification.api.ts ───► [MISMO ENDPOINT] │ │ claimAchievement() │ │ │ │ ════════════════════════════════════════════════════════════════════════ │ │ PROBLEMA: Las recompensas NUNCA se distribuyen al reclamar achievements │ │ ════════════════════════════════════════════════════════════════════════ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` --- ## 4. ARCHIVOS IDENTIFICADOS PARA ANÁLISIS DETALLADO ### 4.1 Database (SQL) - 9 archivos ``` /apps/database/ddl/schemas/gamification_system/functions/ ├── claim_achievement_reward.sql # MODIFICADO ├── check_and_award_achievements.sql # DUPLICACIÓN POTENCIAL ├── award_ml_coins.sql # HELPER - Debería usarse ├── process_exercise_completion.sql # INCONSISTENTE ├── promote_to_next_rank.sql # OVERLAP CON update_user_rank ├── update_user_rank.sql # OVERLAP CON promote ├── check_rank_promotion.sql # ORQUESTACIÓN ├── consume_comodin.sql # INCONSISTENTE └── update_leaderboard_streaks.sql # MENOR ``` ### 4.2 Backend (NestJS) - 6 archivos ``` /apps/backend/src/modules/ ├── gamification/ │ ├── services/ │ │ ├── achievements.service.ts # CRÍTICO - NO distribuye rewards │ │ └── missions/mission-claim.service.ts # REFERENCIA - SÍ distribuye │ ├── controllers/achievements.controller.ts │ └── entities/ │ ├── achievement.entity.ts │ └── user-achievement.entity.ts ├── progress/services/ │ └── grading/exercise-rewards.service.ts # REFERENCIA - SÍ distribuye └── admin/services/ └── admin-progress.service.ts # SCHEMA MISMATCH ``` ### 4.3 Frontend (React) - 8 archivos ``` /apps/frontend/src/ ├── features/gamification/ │ ├── social/ │ │ ├── api/achievementsAPI.ts # MODIFICADO │ │ ├── store/achievementsStore.ts # MODIFICADO │ │ ├── hooks/useAchievements.ts # WRAPPER │ │ └── types/achievementsTypes.ts │ └── achievements/utils/ │ └── achievementTransformer.ts # NO USADO CONSISTENTEMENTE ├── hooks/useAchievements.ts # DUPLICADO - 450 líneas hardcoded ├── apps/student/hooks/useAchievementsEnhanced.ts └── lib/api/gamification.api.ts # DUPLICADO ``` --- ## 5. PROBLEMAS CRÍTICOS IDENTIFICADOS ### P-DUP-001: Recompensas NO Distribuidas al Reclamar - **Severidad:** 🔴 CRÍTICO - **Impacto:** Los usuarios reclaman achievements pero NO reciben XP/ML Coins - **Causa:** Backend no llama función SQL ni servicios de distribución - **Archivos:** `achievements.service.ts`, `claim_achievement_reward.sql` ### P-DUP-002: Doble Otorgamiento de Recompensas - **Severidad:** 🔴 CRÍTICO - **Impacto:** Si se usa `check_and_award_achievements` + `claim_achievement_reward`, rewards duplicados - **Causa:** Dos funciones con mismo propósito - **Archivos:** `check_and_award_achievements.sql`, `claim_achievement_reward.sql` ### P-DUP-003: APIs Frontend Duplicadas - **Severidad:** 🟡 IMPORTANTE - **Impacto:** Inconsistencia en manejo de errores y respuestas - **Causa:** Dos archivos con mismo endpoint - **Archivos:** `achievementsAPI.ts`, `gamification.api.ts` ### P-DUP-004: Hook con Definiciones Hardcodeadas - **Severidad:** 🟡 IMPORTANTE - **Impacto:** Achievements pueden no coincidir con backend - **Causa:** 450 líneas de definiciones en frontend - **Archivos:** `/hooks/useAchievements.ts` ### P-DUP-005: Schema Mismatch Admin - **Severidad:** 🟡 IMPORTANTE - **Impacto:** Dashboard admin muestra datos incorrectos o NULL - **Causa:** Campos SQL no coinciden con entities - **Archivos:** `admin-progress.service.ts` ### P-DUP-006: Transformers Inconsistentes - **Severidad:** 🟢 MENOR - **Impacto:** Código duplicado de transformación - **Causa:** Inline mappers vs external transformer - **Archivos:** `achievementsAPI.ts`, `achievementTransformer.ts` --- ## 6. SIGUIENTE FASE **FASE 2:** Análisis detallado de cada archivo identificado para: 1. Confirmar duplicaciones con comparación línea a línea 2. Identificar dependencias entre archivos 3. Determinar cuál versión mantener/consolidar 4. Mapear impacto de cambios --- ## 7. FASE 2: ANÁLISIS DETALLADO ### 7.1 Comparación SQL: claim_achievement_reward vs check_and_grant_achievements #### Función: `check_and_grant_achievements.sql` (Líneas 92-139) ```sql -- AL DESBLOQUEAR (is_completed = true): -- 1. Inserta en user_achievements INSERT INTO gamification_system.user_achievements ( user_id, achievement_id, is_completed, completed_at, progress, max_progress ) VALUES (p_user_id, v_achievement.id, true, NOW(), 100, 100); -- 2. Actualiza user_stats (XP + ML Coins) UPDATE gamification_system.user_stats SET total_xp = COALESCE(total_xp, 0) + v_xp_reward, ml_coins = v_new_balance, achievements_earned = COALESCE(achievements_earned, 0) + 1 WHERE user_id = p_user_id; -- 3. Registra transacción de coins INSERT INTO gamification_system.ml_coins_transactions (...) VALUES (...'earned_achievement'...'Logro desbloqueado: '...); ``` #### Función: `claim_achievement_reward.sql` (Líneas 54-95) ```sql -- AL RECLAMAR (rewards_claimed = true): -- 1. Actualiza user_achievements UPDATE gamification_system.user_achievements SET rewards_claimed = TRUE WHERE user_id = p_user_id AND achievement_id = p_achievement_id; -- 2. Actualiza user_stats (XP + ML Coins) UPDATE gamification_system.user_stats SET total_xp = total_xp + COALESCE((v_achievement.rewards->>'xp')::INTEGER, v_achievement.points_value, 0), ml_coins = v_new_balance WHERE user_id = p_user_id; -- 3. Registra transacción de coins INSERT INTO gamification_system.ml_coins_transactions (...) VALUES (...'earned_achievement'...'Recompensa reclamada: '...); ``` #### Tabla Comparativa | Aspecto | check_and_grant | claim_achievement_reward | |---------|-----------------|--------------------------| | **Cuándo se ejecuta** | Al desbloquear logro | Al reclamar recompensa | | **Otorga XP** | ✅ Sí | ✅ Sí | | **Otorga ML Coins** | ✅ Sí | ✅ Sí | | **Registra transacción** | ✅ Sí | ✅ Sí | | **Incrementa achievements_earned** | ✅ Sí | ❌ No | | **Mensaje transacción** | "Logro desbloqueado:" | "Recompensa reclamada:" | **⚠️ PROBLEMA CRÍTICO:** Si ambas funciones se ejecutan para el mismo achievement, el usuario recibe **DOBLE** recompensa de XP y ML Coins. --- ### 7.2 Backend: achievements.service.ts - claimRewards() (Líneas 745-759) ```typescript async claimRewards(userId: string, achievementId: string): Promise { const userAchievement = await this.checkProgress(userId, achievementId); if (!userAchievement.is_completed) { throw new BadRequestException(`Achievement ${achievementId} is not completed yet`); } if (userAchievement.rewards_claimed) { throw new BadRequestException(`Rewards already claimed for achievement ${achievementId}`); } // ⚠️ PROBLEMA: Solo marca el flag, NO distribuye recompensas userAchievement.rewards_claimed = true; return this.userAchievementRepo.save(userAchievement); // ❌ FALTA: No llama a claim_achievement_reward SQL // ❌ FALTA: No llama a UserStatsService.addXp() // ❌ FALTA: No llama a MLCoinsService.addCoins() // ❌ FALTA: No registra transacción de coins } ``` **Contraste con otros servicios que SÍ distribuyen:** | Servicio | Distribuye XP | Distribuye Coins | Registra Trans. | |----------|--------------|------------------|-----------------| | `achievements.service.claimRewards()` | ❌ NO | ❌ NO | ❌ NO | | `exercise-rewards.service.claimRewards()` | ✅ SÍ | ✅ SÍ | ✅ SÍ | | `mission-claim.service.claimMission()` | ✅ SÍ | ✅ SÍ | ✅ SÍ | --- ### 7.3 Frontend APIs: Comparación Detallada #### achievementsAPI.ts - claimAchievementRewards() (Líneas 303-334) ```typescript export const claimAchievementRewards = async ( userId: string, achievementId: string, ): Promise<{ success: boolean; achievement_id: string; rewards_claimed: boolean; ml_coins_awarded?: number; // ⚠️ Espera valores que backend NO envía xp_awarded?: number; // ⚠️ Espera valores que backend NO envía }> => { const { data } = await apiClient.post>( `/gamification/users/${userId}/achievements/${achievementId}/claim` ); return { success: true, achievement_id: achievementId, rewards_claimed: data.data.rewards_claimed, // ml_coins_awarded y xp_awarded NO vienen del backend }; }; ``` #### gamification.api.ts - claimAchievement() (Líneas 166-172) ```typescript claimAchievement: async (userId: string, achievementId: string): Promise => { const { data } = await apiClient.post( `/gamification/users/${userId}/achievements/${achievementId}/claim`, {}, ); return data; // Retorna datos sin transformar }; ``` #### Diferencias Clave | Aspecto | achievementsAPI.ts | gamification.api.ts | |---------|-------------------|---------------------| | **Ubicación** | /features/gamification/social/api/ | /lib/api/ | | **Tipo retorno** | Custom object | UserAchievement | | **Transformación** | Inline | Sin transformar | | **Usado por** | achievementsStore.ts | AchievementsPage.tsx | | **Manejo errores** | handleAPIError() | throw directo | --- ### 7.4 Frontend Hook: /hooks/useAchievements.ts - Definiciones Hardcodeadas **⚠️ PROBLEMA MAYOR:** Este hook tiene ~450 líneas de código con achievement definitions hardcodeadas: ```typescript // Líneas 71-250 - DEFINICIONES DUPLICADAS DEL BACKEND const ACHIEVEMENT_DEFINITIONS: AchievementDefinition[] = [ { id: 'first_steps', title: 'Primeros Pasos', description: 'Completa tu primer ejercicio', icon: '👣', rarity: 'common', xp_reward: 10, // ⚠️ Puede no coincidir con backend ml_coins_reward: 5, // ⚠️ Puede no coincidir con backend condition: { type: 'exercises_completed', value: 1, operator: '>=' }, }, // ... 20+ más achievements hardcodeados ]; ``` **Problemas:** 1. Las recompensas pueden NO coincidir con la base de datos 2. Nuevos achievements no aparecen hasta modificar código 3. Difícil mantener sincronizado con seeds/backend 4. Duplica lógica de detección que el backend ya tiene --- ### 7.5 Transformer: achievementTransformer.ts vs Inline Mappers #### achievementTransformer.ts (Externo - CORRECTO) ```typescript // Líneas 212-266 - Bien estructurado export const transformAchievement = (apiResponse: ApiAchievementResponse): Achievement => { const rewards = { xp: apiResponse.rewards?.xp ?? apiResponse.points_value ?? 0, mlCoins: apiResponse.rewards?.ml_coins ?? apiResponse.ml_coins_reward ?? 0, // ... }; return { id: apiResponse.id, name: apiResponse.name, // ... mapeo completo y consistente }; }; ``` #### achievementsAPI.ts (Inline - INCONSISTENTE) ```typescript // Líneas 382-411 - Duplica lógica del transformer export const mapToFrontendAchievement = ( backendAchievement: BackendAchievement, userProgress?: BackendUserAchievement, ): Achievement => { return { id: backendAchievement.id, title: backendAchievement.name, // ⚠️ 'title' vs 'name' en transformer // ... mapeo diferente mlCoinsReward: backendAchievement.rewards?.ml_coins ?? backendAchievement.ml_coins_reward ?? 0, xpReward: backendAchievement.rewards?.xp ?? backendAchievement.points_value ?? 0, // ... campos diferentes }; }; ``` --- ## 8. RESUMEN DE PROBLEMAS CONFIRMADOS ### CRÍTICOS (Requieren corrección inmediata) | ID | Problema | Impacto | Archivos | |----|----------|---------|----------| | P-DUP-001 | Backend NO distribuye recompensas al claim | Users no reciben XP/Coins | achievements.service.ts | | P-DUP-002 | SQL puede dar DOBLE recompensa | Inflación de economía | check_and_grant + claim_achievement | | P-DUP-003 | Hook tiene 450+ líneas hardcoded | Desincronización con backend | /hooks/useAchievements.ts | ### IMPORTANTES (Deben corregirse pronto) | ID | Problema | Impacto | Archivos | |----|----------|---------|----------| | P-DUP-004 | APIs duplicadas con diferente retorno | Confusión de desarrolladores | achievementsAPI.ts, gamification.api.ts | | P-DUP-005 | Transformers inconsistentes | Datos transformados diferente | achievementsAPI.ts inline mappers | ### MENORES (Mejoras de calidad) | ID | Problema | Impacto | Archivos | |----|----------|---------|----------| | P-DUP-006 | Schema mismatch en admin | Dashboard puede fallar | admin-progress.service.ts | --- **Analizado por:** Claude (Arquitecto Técnico) **Fecha:** 2026-01-10 **Estado:** FASE 2 COMPLETADA - Pendiente FASE 3 (Planeación)