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
255 lines
7.9 KiB
Markdown
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
|