workspace-v1/orchestration/analisis/ANALISIS-DUPLICADOS-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

20 KiB

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

// PROBLEMA: Solo marca como reclamado, NO distribuye XP/Coins
async claimRewards(userId: string, achievementId: string): Promise<UserAchievement> {
  // ... 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)

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

-- 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
Otorga ML Coins
Registra transacción
Incrementa achievements_earned 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)

async claimRewards(userId: string, achievementId: string): Promise<UserAchievement> {
  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()
mission-claim.service.claimMission()

7.3 Frontend APIs: Comparación Detallada

achievementsAPI.ts - claimAchievementRewards() (Líneas 303-334)

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<ApiResponse<{...}>>(
    `/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)

claimAchievement: async (userId: string, achievementId: string): Promise<UserAchievement> => {
  const { data } = await apiClient.post<UserAchievement>(
    `/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:

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

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

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