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

296 lines
8.5 KiB
Markdown

# 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<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:**
```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<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:**
```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