workspace-v1/orchestration/analisis/VALIDACION-PLAN-ACHIEVEMENTS-2026-01-10.md
rckrdmrd e56e927a4d [MAINT-001] docs(orchestration): Actualizacion directivas SIMCO, perfiles y documentacion
Cambios incluidos:
- INDICE-DIRECTIVAS-WORKSPACE.yml actualizado
- Perfiles de agentes: PERFIL-ML.md, PERFIL-SECURITY.md
- Directivas SIMCO actualizadas:
  - SIMCO-ASIGNACION-PERFILES.md
  - SIMCO-CCA-SUBAGENTE.md
  - SIMCO-CONTEXT-ENGINEERING.md
  - SIMCO-CONTEXT-RESOLUTION.md
  - SIMCO-DELEGACION-PARALELA.md
- Inventarios actualizados: DEVENV-MASTER, DEVENV-PORTS
- Documentos de analisis agregados:
  - Analisis y planes de fix student portal
  - Analisis scripts BD
  - Analisis achievements, duplicados, gamification
  - Auditoria documentacion gamilit
  - Backlog discrepancias NEXUS
  - Planes maestros de resolucion
- Reportes de ejecucion agregados
- Knowledge base gamilit README actualizado
- Referencia submodulo gamilit actualizada (commit beb94f7)

Validaciones:
- Plan validado contra directivas SIMCO-GIT
- Dependencias verificadas
- Build gamilit: EXITOSO
2026-01-10 04:51:28 -06:00

8.5 KiB

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 ml_coins_reward (INTEGER)

Errores línea por línea:

-- 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):

-- 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):

-- 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:

// 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:

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):

// 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):

// ACTUAL:
const categoryMap: Record<string, 'progress' | 'mastery' | 'social' | 'hidden'> = {
  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:

-- 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:

// 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:

// Líneas 346-360: Expandir mapeo de categorías
const categoryMap: Record<string, Achievement['category']> = {
  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:

-- Ejemplo correcto de seed:
conditions = '{"type": "exercise_completion", "requirements": {"exercises_completed": 1}}'::jsonb

-- Estructura esperada por backend: ✅ CORRECTA

Estructura de rewards verificada:

-- 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