# FASE 3: PLAN DE CORRECCIÓN - DUPLICADOS ACHIEVEMENTS SYSTEM **Fecha:** 2026-01-10 **Proyecto:** Gamilit **Basado en:** ANALISIS-DUPLICADOS-ACHIEVEMENTS-2026-01-10.md **Estado:** EN PLANEACIÓN --- ## 1. DECISIONES ARQUITECTÓNICAS ### 1.1 Modelo de Distribución de Recompensas **DECISIÓN:** Implementar modelo de **Claim-to-Earn** (reclamar para ganar) | Acción | Qué sucede | Quién distribuye | |--------|-----------|------------------| | Achievement desbloqueado | Solo marca `is_completed = true` | Backend service | | Achievement reclamado | Distribuye XP + ML Coins | SQL function | **Justificación:** - Permite UX de "reclamar recompensa" (más satisfactorio) - Evita dar recompensas automáticas si usuario no las ve - Consistent con misiones y ejercicios que también usan "claim" ### 1.2 API Unificada **DECISIÓN:** Mantener `gamification.api.ts` como API principal, deprecar duplicados en `achievementsAPI.ts` **Justificación:** - `gamification.api.ts` ya usa transformers externos (mejor mantenibilidad) - Consolidar en `/lib/api/` para acceso global - `achievementsAPI.ts` tiene mappers inline que duplican transformer ### 1.3 Hook de Definiciones **DECISIÓN:** Deprecar `/hooks/useAchievements.ts` (450 líneas hardcodeadas) **Justificación:** - Las definiciones deben venir del backend - El hook `/features/gamification/social/hooks/useAchievements.ts` usa el store correctamente - La detección automática la debe hacer el backend (ya implementada en `detectAndGrantEarned`) --- ## 2. PLAN DE CORRECCIÓN POR PROBLEMA ### 2.1 P-DUP-001: Backend NO Distribuye Recompensas **Severidad:** 🔴 CRÍTICO **Archivo a modificar:** `/apps/backend/src/modules/gamification/services/achievements.service.ts` **Cambio propuesto:** Llamar a función SQL `claim_achievement_reward` en lugar de solo marcar flag ```typescript // ANTES (líneas 745-759): async claimRewards(userId: string, achievementId: string): Promise { const userAchievement = await this.checkProgress(userId, achievementId); // validaciones... userAchievement.rewards_claimed = true; return this.userAchievementRepo.save(userAchievement); } // DESPUÉS: async claimRewards(userId: string, achievementId: string): Promise<{ userAchievement: UserAchievement; xp_granted: number; coins_granted: number; }> { // Llamar función SQL que distribuye recompensas const result = await this.dataSource.query( `SELECT * FROM gamification_system.claim_achievement_reward($1, $2)`, [userId, achievementId] ); if (!result[0].success) { throw new BadRequestException(result[0].message); } // Obtener userAchievement actualizado const userAchievement = await this.checkProgress(userId, achievementId); return { userAchievement, xp_granted: result[0].xp_granted, coins_granted: result[0].coins_granted, }; } ``` **Dependencias:** - Función SQL `claim_achievement_reward` debe estar actualizada (✅ Ya corregida) - Controller debe actualizarse para manejar nuevo retorno --- ### 2.2 P-DUP-002: SQL Puede Dar DOBLE Recompensa **Severidad:** 🔴 CRÍTICO **Archivos a modificar:** 1. `/apps/database/ddl/schemas/gamification_system/functions/check_and_award_achievements.sql` **Cambio propuesto:** Remover distribución de recompensas de `check_and_grant_achievements` - solo debe marcar `is_completed`, no dar XP/Coins ```sql -- ANTES (líneas 111-118): 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, updated_at = NOW() WHERE user_id = p_user_id; -- Registrar transaccion de coins INSERT INTO gamification_system.ml_coins_transactions (...) -- DESPUÉS: UPDATE gamification_system.user_stats SET achievements_earned = COALESCE(achievements_earned, 0) + 1, updated_at = NOW() WHERE user_id = p_user_id; -- NO registrar transacción - se hará en claim_achievement_reward ``` **Impacto:** - El desbloqueo automático ya no dará recompensas - Las recompensas solo se dan al reclamar --- ### 2.3 P-DUP-003: Hook con Definiciones Hardcodeadas **Severidad:** 🔴 CRÍTICO **Archivo a deprecar:** `/apps/frontend/src/hooks/useAchievements.ts` **Acción:** Agregar comentario de deprecación y redireccionar a hook correcto ```typescript /** * @deprecated Use useAchievements from '@/features/gamification/social/hooks/useAchievements' * Este hook contiene definiciones hardcodeadas que están desactualizadas. * La detección de achievements se hace en el backend (achievements.service.detectAndGrantEarned) */ ``` **Alternativa:** Si se necesita auto-detection en frontend, llamar endpoint: ```typescript // En lugar de polling local, usar endpoint del backend const result = await apiClient.post(`/gamification/users/${userId}/achievements/detect`); ``` --- ### 2.4 P-DUP-004: APIs Duplicadas **Severidad:** 🟡 IMPORTANTE **Archivo a modificar:** `/apps/frontend/src/features/gamification/social/api/achievementsAPI.ts` **Cambio propuesto:** Deprecar métodos duplicados y re-exportar desde gamification.api.ts ```typescript // ANTES: export const claimAchievementRewards = async (...) => { ... }; // DESPUÉS: /** * @deprecated Use gamificationApi.claimAchievement from '@/lib/api/gamification.api' */ export const claimAchievementRewards = gamificationApi.claimAchievement; ``` **Archivos que consumen achievementsAPI.ts:** - `achievementsStore.ts` - Actualizar imports --- ### 2.5 P-DUP-005: Transformers Inconsistentes **Severidad:** 🟡 IMPORTANTE **Archivo a modificar:** `/apps/frontend/src/features/gamification/social/api/achievementsAPI.ts` **Cambio propuesto:** Remover mappers inline y usar transformer externo ```typescript // ANTES (líneas 382-411): export const mapToFrontendAchievement = (...) => { ... }; // DESPUÉS: import { transformAchievement, transformUserAchievement } from '@/features/gamification/achievements/utils/achievementTransformer'; // Re-export para compatibilidad export { transformAchievement as mapToFrontendAchievement }; ``` --- ### 2.6 P-DUP-006: Schema Mismatch Admin **Severidad:** 🟢 MENOR **Archivo a modificar:** `/apps/backend/src/modules/admin/services/admin-progress.service.ts` **Cambio propuesto:** Actualizar query SQL para usar campos correctos ```sql -- ANTES: SELECT ua.id, ua.achievement_id, a.name, a.description, a.category, a.tier, a.xp_reward, a.ml_coins_reward, a.icon_url, ua.unlocked_at, ua.progress_current, ua.progress_required -- DESPUÉS: SELECT ua.id, ua.achievement_id, a.name, a.description, a.category, a.difficulty_level as tier, -- Mapear difficulty_level a tier COALESCE((a.rewards->>'xp')::INTEGER, a.points_value, 0) as xp_reward, a.ml_coins_reward, a.icon as icon_url, ua.completed_at as unlocked_at, -- Usar completed_at ua.progress as progress_current, -- Usar progress ua.max_progress as progress_required -- Usar max_progress ``` --- ## 3. ORDEN DE EJECUCIÓN ### Fase A: Correcciones SQL (Base de datos) | Orden | Archivo | Cambio | Dependencias | |-------|---------|--------|--------------| | A.1 | `check_and_award_achievements.sql` | Remover distribución de rewards | Ninguna | ### Fase B: Correcciones Backend (NestJS) | Orden | Archivo | Cambio | Dependencias | |-------|---------|--------|--------------| | B.1 | `achievements.service.ts` | Llamar SQL function en claimRewards | A.1 completado | | B.2 | `achievements.controller.ts` | Actualizar response type | B.1 completado | | B.3 | `admin-progress.service.ts` | Fix schema mismatch | Ninguna | ### Fase C: Correcciones Frontend (React) | Orden | Archivo | Cambio | Dependencias | |-------|---------|--------|--------------| | C.1 | `achievementsAPI.ts` | Deprecar y usar transformer | Ninguna | | C.2 | `achievementsStore.ts` | Actualizar imports | C.1 completado | | C.3 | `/hooks/useAchievements.ts` | Agregar deprecation notice | Ninguna | --- ## 4. VALIDACIONES REQUERIDAS ### 4.1 Antes de Ejecutar - [ ] Backup de base de datos - [ ] Verificar que no hay usuarios en proceso de claim - [ ] Tests existentes pasan ### 4.2 Durante Ejecución - [ ] Cada cambio SQL aplicado con `\df` para verificar - [ ] Build de backend exitoso después de cada cambio - [ ] Build de frontend exitoso después de cada cambio ### 4.3 Después de Ejecutar - [ ] Test E2E: Usuario desbloquea achievement → NO recibe rewards automático - [ ] Test E2E: Usuario reclama achievement → SÍ recibe rewards - [ ] Test: Intentar reclamar dos veces → Error apropiado - [ ] Test: Dashboard admin muestra datos correctos --- ## 5. ROLLBACK PLAN ### Si falla Fase A (SQL): ```sql -- Restaurar check_and_award_achievements.sql desde git git checkout HEAD~1 -- apps/database/ddl/schemas/gamification_system/functions/check_and_award_achievements.sql -- Re-ejecutar script de creación de función ``` ### Si falla Fase B (Backend): ```bash # Restaurar archivos modificados git checkout HEAD~1 -- apps/backend/src/modules/gamification/services/achievements.service.ts git checkout HEAD~1 -- apps/backend/src/modules/gamification/controllers/achievements.controller.ts npm run build ``` ### Si falla Fase C (Frontend): ```bash # Restaurar archivos modificados git checkout HEAD~1 -- apps/frontend/src/features/gamification/social/ npm run build ``` --- ## 6. ESTIMACIÓN DE IMPACTO | Métrica | Antes | Después | |---------|-------|---------| | **Archivos modificados** | - | 7 | | **Líneas cambiadas** | - | ~150 | | **Funciones deprecadas** | - | 3 | | **Tests requeridos** | - | 4 E2E | --- ## 7. DOCUMENTOS RELACIONADOS | Documento | Estado | |-----------|--------| | ANALISIS-DUPLICADOS-ACHIEVEMENTS-2026-01-10.md | ✅ Completado | | PLAN-DUPLICADOS-ACHIEVEMENTS-2026-01-10.md | ✅ Actual | | VALIDACION-PLAN-DUPLICADOS-2026-01-10.md | ⏳ Pendiente | --- **Planeado por:** Claude (Arquitecto Técnico) **Fecha:** 2026-01-10 **Estado:** FASE 3 COMPLETADA - Pendiente FASE 4 (Validación)