workspace-v1/orchestration/analisis/ANALISIS-ERRORES-GAMIFICATION-SUMMARY-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

255 lines
7.9 KiB
Markdown

# 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<Profile> {
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<Profile> {
const profile = await this.profileRepo.findOne({
where: { user_id: userId },
});
```
**A:**
```typescript
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
```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