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
7.9 KiB
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.
// CODIGO ACTUAL (INCORRECTO) - user-stats.service.ts:51-53
private async validateProfileExists(userId: string): Promise<Profile> {
const profile = await this.profileRepo.findOne({
where: { user_id: userId }, // ← BUSCA profiles.user_id = userId
});
Problema:
- La FK en
user_statses:REFERENCES auth_management.profiles(id) - Esto significa que
user_stats.user_iddebe contener unprofiles.id(PK) - El método busca
profiles.user_id = userId, peroprofiles.user_ides una FK aauth.users - La búsqueda no encuentra el profile porque el ID es un
profiles.id, no unprofiles.user_id
2.3 Estructura de Datos Relevante
-- 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:
// 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:
{
"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)
-- 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)
-- 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:
private async validateProfileExists(userId: string): Promise<Profile> {
const profile = await this.profileRepo.findOne({
where: { user_id: userId },
});
A:
private async validateProfileExists(userId: string): Promise<Profile> {
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
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
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