# VALIDACIÓN DE PLAN: CORRECCIONES ACHIEVEMENTS PAGE **Fecha:** 2026-01-10 **Proyecto:** Gamilit **Componente:** /achievements (Student Portal) **Estado:** VALIDADO ✅ --- ## 1. RESUMEN DE VALIDACIÓN Se han verificado los archivos fuente contra el plan propuesto. Se confirman los siguientes hallazgos: ### ✅ Problemas Confirmados | ID | Problema | Estado | Archivo | Línea(s) | |----|----------|--------|---------|----------| | P1 | Función SQL usa columnas inexistentes | ✅ CONFIRMADO | `claim_achievement_reward.sql` | 35, 54, 70 | | P2 | Store usa `\|\|` en lugar de `??` | ✅ CONFIRMADO | `achievementsStore.ts` | 172-173 | | P3 | `completion_percentage` no se parsea | ⚠️ PARCIAL* | `achievementsStore.ts` | N/A | | P8 | Category mapping incompleto | ✅ CONFIRMADO | `achievementsAPI.ts` | 346-360 | *P3: La API (`achievementsAPI.ts`) SÍ hace el parseFloat correctamente (línea 162-163), pero el Store hace su propio mapeo y NO parsea. --- ## 2. VALIDACIÓN DETALLADA: BASE DE DATOS ### 2.1 Función claim_achievement_reward.sql **Columnas verificadas contra DDL:** | Columna en función | ¿Existe en DDL? | Columna correcta | |--------------------|-----------------|------------------| | `reward_claimed_at` | ❌ NO | `rewards_claimed` (BOOLEAN) | | `xp_reward` | ❌ NO | `rewards->>'xp'` o `points_value` | | `ml_coins_reward` | ✅ SÍ | `ml_coins_reward` (INTEGER) | **Errores línea por línea:** ```sql -- LÍNEA 35 (ERROR) v_already_claimed := v_user_achievement.reward_claimed_at IS NOT NULL; -- CORRECCIÓN: v_already_claimed := v_user_achievement.rewards_claimed = TRUE; -- LÍNEA 54 (ERROR) SET reward_claimed_at = NOW() -- CORRECCIÓN: SET rewards_claimed = TRUE -- LÍNEA 70 (ERROR) total_xp = total_xp + v_achievement.xp_reward, -- CORRECCIÓN: total_xp = total_xp + COALESCE((v_achievement.rewards->>'xp')::INTEGER, v_achievement.points_value, 0), ``` ### 2.2 Verificación de DDL **Tabla `user_achievements` (confirmado):** ```sql -- Columnas existentes (líneas 33-52): rewards_claimed boolean DEFAULT false -- Línea 44 ✅ completed_at timestamp with time zone -- Línea 41 ✅ -- NO existe: reward_claimed_at ``` **Tabla `achievements` (confirmado):** ```sql -- Columnas existentes (líneas 41-66): rewards jsonb DEFAULT '{"xp": 100, "badge": null, "ml_coins": 50}' -- Línea 51 ✅ points_value integer DEFAULT 0 -- Línea 56 ✅ ml_coins_reward integer DEFAULT 0 -- Línea 64 ✅ -- NO existe: xp_reward ``` --- ## 3. VALIDACIÓN DETALLADA: FRONTEND ### 3.1 achievementsStore.ts **Problema confirmado en líneas 172-173:** ```typescript // ACTUAL (línea 172-173): mlCoinsReward: ach.rewards?.ml_coins || ach.ml_coins_reward || 0, xpReward: ach.rewards?.xp || 0, // PROBLEMA: || convierte 0 en falsy y usa fallback incorrectamente // Si ml_coins = 0, usará ml_coins_reward en lugar de respetar el 0 ``` **Corrección requerida:** ```typescript mlCoinsReward: ach.rewards?.ml_coins ?? ach.ml_coins_reward ?? 0, xpReward: ach.rewards?.xp ?? ach.points_value ?? 0, ``` ### 3.2 achievementsAPI.ts **Ya corregido correctamente (líneas 383-385):** ```typescript // CORRECTO - Usa ?? mlCoinsReward: backendAchievement.rewards?.ml_coins ?? backendAchievement.ml_coins_reward ?? 0, xpReward: backendAchievement.rewards?.xp ?? backendAchievement.points_value ?? 0, ``` **Category mapping incompleto (líneas 346-360):** ```typescript // ACTUAL: const categoryMap: Record = { educational: 'progress', progress: 'progress', mastery: 'mastery', skill: 'mastery', social: 'social', hidden: 'hidden', special: 'hidden', collection: 'mastery', missions: 'progress', }; // FALTAN (del ENUM achievement_category): // - streak → debería mapear a 'progress' o nuevo tipo // - exploration → debería mapear a 'progress' o nuevo tipo // - completion → debería mapear a 'progress' o nuevo tipo ``` **Tipo de retorno limitado:** El tipo de retorno es `'progress' | 'mastery' | 'social' | 'hidden'` pero el ENUM tiene 9 valores: - `progress`, `streak`, `completion`, `social`, `special`, `mastery`, `exploration`, `collection`, `hidden` --- ## 4. DEPENDENCIAS VALIDADAS ### 4.1 Archivos que serán modificados | Archivo | Dependencias | Riesgo | |---------|--------------|--------| | `claim_achievement_reward.sql` | Ninguna directa, se llama desde backend | 🟡 Medio - afecta reclamar recompensas | | `achievementsStore.ts` | `achievementsAPI.ts`, componentes React | 🟡 Medio - afecta visualización | | `achievementsAPI.ts` | Solo mapeo interno | 🟢 Bajo - cambio aislado | ### 4.2 Archivos que NO necesitan cambios | Archivo | Razón | |---------|-------| | `AchievementsPage.tsx` | Usa gamificationApi, no achievementsStore directamente | | `achievements.service.ts` | Backend usa TypeORM, no función SQL | | DDL de tablas | Estructura correcta, no requiere cambios | --- ## 5. PLAN DE CORRECCIÓN VALIDADO ### FASE A: Base de Datos (CRÍTICO) **Archivo:** `claim_achievement_reward.sql` **Cambios específicos:** ```sql -- Línea 35: Cambiar verificación de reclamado -- DE: v_already_claimed := v_user_achievement.reward_claimed_at IS NOT NULL; -- A: v_already_claimed := v_user_achievement.rewards_claimed = TRUE; -- Línea 54: Cambiar actualización de estado -- DE: SET reward_claimed_at = NOW() -- A: SET rewards_claimed = TRUE -- Línea 70: Cambiar obtención de XP -- DE: total_xp = total_xp + v_achievement.xp_reward, -- A: total_xp = total_xp + COALESCE((v_achievement.rewards->>'xp')::INTEGER, v_achievement.points_value, 0), ``` ### FASE B: Frontend Store (CRÍTICO) **Archivo:** `achievementsStore.ts` **Cambios específicos:** ```typescript // Líneas 172-173: Usar nullish coalescing // DE: mlCoinsReward: ach.rewards?.ml_coins || ach.ml_coins_reward || 0, xpReward: ach.rewards?.xp || 0, // A: mlCoinsReward: ach.rewards?.ml_coins ?? ach.ml_coins_reward ?? 0, xpReward: ach.rewards?.xp ?? ach.points_value ?? 0, ``` ### FASE C: Frontend API (IMPORTANTE) **Archivo:** `achievementsAPI.ts` **Cambios específicos:** ```typescript // Líneas 346-360: Expandir mapeo de categorías const categoryMap: Record = { educational: 'progress', progress: 'progress', streak: 'progress', // AGREGAR completion: 'progress', // AGREGAR exploration: 'progress', // AGREGAR mastery: 'mastery', skill: 'mastery', collection: 'mastery', social: 'social', hidden: 'hidden', special: 'hidden', missions: 'progress', }; ``` --- ## 6. VERIFICACIÓN DE SEEDS ### Seeds de Achievements (04-achievements.sql) **Estructura de conditions verificada:** ```sql -- Ejemplo correcto de seed: conditions = '{"type": "exercise_completion", "requirements": {"exercises_completed": 1}}'::jsonb -- Estructura esperada por backend: ✅ CORRECTA ``` **Estructura de rewards verificada:** ```sql -- Ejemplo correcto de seed: rewards = '{"xp": 50, "ml_coins": 10, "badge": null}'::jsonb -- Estructura esperada: ✅ CORRECTA ``` ### Seeds de User Achievements (08-user_achievements.sql) **Campos verificados:** - `user_id` → FK válido ✅ - `achievement_id` → FK válido ✅ - `rewards_claimed` → BOOLEAN ✅ - `completed_at` → TIMESTAMPTZ ✅ --- ## 7. RIESGOS IDENTIFICADOS ### 7.1 Riesgo: Función SQL en producción **Escenario:** Si la función `claim_achievement_reward()` se está usando en producción, fallará con: ``` ERROR: column "reward_claimed_at" does not exist ``` **Mitigación:** - Verificar si hay llamadas activas a la función - Aplicar corrección en ventana de bajo tráfico ### 7.2 Riesgo: Datos legacy con completion_percentage string **Escenario:** Registros existentes pueden tener `completion_percentage` como string. **Mitigación:** - El Store NO parsea, pero la API SÍ lo hace - Si el Store se usa directamente sin pasar por API, podría fallar ### 7.3 Riesgo: Categorías nuevas en backend **Escenario:** Si el backend agrega nuevas categorías al ENUM, el frontend las mostrará como 'progress'. **Mitigación:** - Actualizar mapeo cuando se agreguen nuevas categorías - El default a 'progress' es seguro (fail-safe) --- ## 8. CONCLUSIÓN ### ✅ PLAN VALIDADO El plan de corrección es viable y los cambios propuestos son correctos. **Próximos pasos:** 1. ✅ Fase A: Corregir función SQL 2. ✅ Fase B: Corregir Store con nullish coalescing 3. ✅ Fase C: Expandir mapeo de categorías 4. ⏳ Fase D: Validar seeds (ya verificado - correcto) 5. ⏳ Fase E: Testing end-to-end --- **Validado por:** Claude (Arquitecto Técnico) **Fecha:** 2026-01-10 **Siguiente Fase:** Refinamiento del plan → Ejecución