# ANALISIS DETALLADO - ERRORES EN GAMIFICATION SUMMARY ENDPOINTS **Fecha:** 2026-01-10 **Proyecto:** Gamilit **Estado:** EN ANALISIS **Conventional Commits:** `fix(gamification): correct profile lookup in user stats validation` --- ## 1. RESUMEN DE ERRORES | # | Endpoint | Codigo | Error | Archivo Origen | |---|----------|--------|-------|----------------| | ERR-GAM-001 | `GET /gamification/users/:userId/summary` | 500 | `user_stats_user_id_fkey` FK violation | `user-stats.service.ts:329` | | ERR-GAM-002 | `GET /gamification/users/:userId/achievements/summary` | 404 | Resource not found | `achievements.service.ts:803` | **Usuario afectado:** `cccccccc-cccc-cccc-cccc-cccccccccccc` (Carlos Herrera - admin_teacher) --- ## 2. ANALISIS DETALLADO - ERR-GAM-001 ### 2.1 Traza del Error ``` 1. Frontend → GET /api/v1/gamification/users/cccccccc-.../summary 2. user-stats.controller.ts:158 → getUserGamificationSummary(userId) 3. user-stats.service.ts:320 → getUserGamificationSummary(userId) 4. user-stats.service.ts:325 → findByUserId(userId) → NotFoundException 5. user-stats.service.ts:329 → create(userId) ← ERROR AQUI 6. user-stats.service.ts:86 → validateProfileExists(userId) 7. user-stats.service.ts:51-53 → profileRepo.findOne({ where: { user_id: userId } }) ↑ INCORRECTO ``` ### 2.2 Causa Raiz **El método `validateProfileExists` busca por columna incorrecta.** ```typescript // CODIGO ACTUAL (INCORRECTO) - user-stats.service.ts:51-53 private async validateProfileExists(userId: string): Promise { const profile = await this.profileRepo.findOne({ where: { user_id: userId }, // ← BUSCA profiles.user_id = userId }); ``` **Problema:** - La FK en `user_stats` es: `REFERENCES auth_management.profiles(id)` - Esto significa que `user_stats.user_id` debe contener un `profiles.id` (PK) - El método busca `profiles.user_id = userId`, pero `profiles.user_id` es una FK a `auth.users` - La búsqueda no encuentra el profile porque el ID es un `profiles.id`, no un `profiles.user_id` ### 2.3 Estructura de Datos Relevante ```sql -- auth_management.profiles id uuid PRIMARY KEY, -- PK del profile (ej: cccccccc-cccc-...) user_id uuid, -- FK → auth.users (puede ser null) -- gamification_system.user_stats user_id uuid NOT NULL, -- FK → profiles.id (NO profiles.user_id) CONSTRAINT user_stats_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth_management.profiles(id) ON DELETE CASCADE ``` ### 2.4 Flujo Correcto vs Incorrecto ``` FLUJO INCORRECTO (actual): userId = 'cccccccc-...' (profiles.id) → SELECT * FROM profiles WHERE user_id = 'cccccccc-...' → No encuentra (porque user_id es FK a auth.users, no el PK) → Lanza NotFoundException (pero debería retornar profile) → Si pasara, intentaría INSERT INTO user_stats(user_id) VALUES('cccccccc-...') → FK falla porque busca profiles.id = 'cccccccc-...' y no lo encuentra por la query incorrecta FLUJO CORRECTO (esperado): userId = 'cccccccc-...' (profiles.id) → SELECT * FROM profiles WHERE id = 'cccccccc-...' → Encuentra el profile → INSERT INTO user_stats(user_id) VALUES('cccccccc-...') → FK valida correctamente profiles.id = 'cccccccc-...' ``` --- ## 3. ANALISIS DETALLADO - ERR-GAM-002 ### 3.1 Traza del Error ``` 1. Frontend → GET /api/v1/gamification/users/cccccccc-.../achievements/summary 2. achievements.controller.ts:343 → getAchievementSummary(userId) 3. achievements.service.ts:793 → getUserAchievementStats(userId) 4. achievements.service.ts:799-801 → userStatsRepo.findOne({ where: { user_id: userId } }) 5. achievements.service.ts:803-805 → throw NotFoundException (user stats not found) ``` ### 3.2 Causa Raiz **Dependencia transitiva del ERR-GAM-001.** El método `getUserAchievementStats` depende de que exista un registro en `user_stats`: ```typescript // achievements.service.ts:799-805 const userStats = await this.userStatsRepo.findOne({ where: { user_id: userId }, }); if (!userStats) { throw new NotFoundException(`User stats not found for ${userId}`); } ``` Como el ERR-GAM-001 impide crear registros en `user_stats`, este endpoint también falla. ### 3.3 Por qué retorna 404 en lugar de mensaje personalizado El `NotFoundException` en NestJS retorna: ```json { "statusCode": 404, "message": "User stats not found for cccccccc-..." } ``` Pero el frontend muestra "Resource not found" que es el handler genérico del apiClient. --- ## 4. DEPENDENCIAS IDENTIFICADAS ### 4.1 Archivos que usan validateProfileExists | Archivo | Método | Línea | Impacto | |---------|--------|-------|---------| | `user-stats.service.ts` | `create()` | 86 | Directo - causa ERR-GAM-001 | ### 4.2 Archivos que dependen de user_stats existente | Archivo | Método | Línea | Impacto | |---------|--------|-------|---------| | `achievements.service.ts` | `getUserAchievementStats()` | 799 | Transitivo - causa ERR-GAM-002 | | `achievements.service.ts` | `detectAndGrantEarned()` | 310 | Transitivo | | `user-stats.service.ts` | `findByUserId()` | 68 | Transitivo | | `user-stats.controller.ts` | `getUserStats()` | 91 | Transitivo | | `user-stats.controller.ts` | `getUserRank()` | 206 | Transitivo | ### 4.3 Frontend afectado | Archivo | Hook/Función | Endpoint | |---------|--------------|----------| | `gamificationAPI.ts:116` | `getUserGamificationSummary()` | `/summary` | | `gamification.api.ts:154` | `getAchievementSummary()` | `/achievements/summary` | | `useUserGamification.ts:55` | `queryFn` | `/summary` | | `AchievementsPage.tsx:98` | `loadUserData()` | `/achievements/summary` | --- ## 5. FK VERIFICATION ### 5.1 DDL de user_stats (líneas 164-165) ```sql -- apps/database/ddl/schemas/gamification_system/tables/01-user_stats.sql CONSTRAINT user_stats_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth_management.profiles(id) ON DELETE CASCADE ``` ### 5.2 DDL de profiles (líneas 48, 52) ```sql -- apps/database/ddl/schemas/auth_management/tables/03-profiles.sql CONSTRAINT profiles_pkey PRIMARY KEY (id), CONSTRAINT profiles_user_id_key UNIQUE (user_id), -- FK a auth.users ``` ### 5.3 Confirmación de la FK La FK `user_stats_user_id_fkey` referencia `profiles(id)` que es el PK, NO `profiles(user_id)` que es FK a auth.users. --- ## 6. SOLUCION PROPUESTA ### 6.1 Corrección en user-stats.service.ts **Cambiar de:** ```typescript private async validateProfileExists(userId: string): Promise { const profile = await this.profileRepo.findOne({ where: { user_id: userId }, }); ``` **A:** ```typescript private async validateProfileExists(userId: string): Promise { const profile = await this.profileRepo.findOne({ where: { id: userId }, // Buscar por PK (id) en lugar de FK (user_id) }); ``` ### 6.2 Impacto de la Corrección | Archivo | Cambio Requerido | |---------|------------------| | `user-stats.service.ts` | Línea 52: `{ user_id: userId }` → `{ id: userId }` | **No se requieren cambios en:** - Base de datos (DDL correcto) - Frontend (usa el profile.id correcto) - Otros servicios (dependen de user_stats que se creará correctamente) --- ## 7. VALIDACION PRE-CORRECCION ### 7.1 Verificar que el profile existe ```sql SELECT id, user_id, email, role FROM auth_management.profiles WHERE id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'; ``` **Resultado esperado:** 1 fila con Carlos Herrera ### 7.2 Verificar user_stats no existe ```sql SELECT * FROM gamification_system.user_stats WHERE user_id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'; ``` **Resultado esperado:** 0 filas (por eso intenta crear) --- ## 8. RIESGOS | Riesgo | Probabilidad | Mitigación | |--------|--------------|------------| | Otros métodos usan user_id incorrectamente | Baja | Grep exhaustivo realizado | | Tests fallan después del cambio | Media | Actualizar mocks si es necesario | | Nomenclatura confusa userId vs profile.id | Alta | Agregar comentario aclaratorio | --- **Elaborado por:** Claude (Arquitecto Técnico) **Fecha:** 2026-01-10 **Próximo paso:** FASE 3 - Planeación