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
REPORTE DE EJECUCION - CORRECCION ERRORES GAMIFICATION SUMMARY
Fecha: 2026-01-10
Proyecto: Gamilit
Estado: COMPLETADO Y VALIDADO
Conventional Commits: fix(gamification): resolve auth.users.id to profiles.id for user_stats FK
1. RESUMEN EJECUTIVO
Se corrigió el bug de ERROR 500/404 EN ENDPOINTS DE GAMIFICATION implementando la resolución correcta de IDs entre auth.users.id → profiles.id.
1.1 Problema Original
GET /gamification/users/cccccccc-cccc-cccc-cccc-cccccccccccc/summary → 500
GET /gamification/users/cccccccc-cccc-cccc-cccc-cccccccccccc/achievements/summary → 404
Error: insert or update on table "user_stats" violates foreign key constraint "user_stats_user_id_fkey"
1.2 Causa Raíz Identificada
| Campo | Valor | Descripción |
|---|---|---|
auth.users.id |
cccccccc-... |
ID que envía el frontend |
profiles.user_id |
cccccccc-... |
FK a auth.users |
profiles.id |
9152d804-... |
PK del profile |
user_stats.user_id |
FK → profiles.id |
Debía ser 9152d804-..., no cccccccc-... |
El código buscaba/creaba user_stats usando auth.users.id directamente, pero la FK requiere profiles.id.
1.3 Solución Implementada
Agregar método resolveProfileId() que convierte auth.users.id → profiles.id antes de cualquier operación con user_stats.
2. CAMBIOS IMPLEMENTADOS
2.1 Archivo Modificado
| ID | Archivo | Cambios |
|---|---|---|
| CORR-GAM-002 | apps/backend/src/modules/gamification/services/user-stats.service.ts |
3 métodos modificados, 1 método agregado |
2.2 Métodos Modificados
validateProfileExists() - Líneas 44-72
Antes:
private async validateProfileExists(userId: string): Promise<Profile> {
const profile = await this.profileRepo.findOne({
where: { user_id: userId }, // Busca profiles.user_id = userId
});
Después:
/**
* CORR-GAM-002: Este método resuelve auth.users.id → profiles.id
* @param authUserId - El ID del usuario en auth.users (= profiles.user_id FK)
*/
private async validateProfileExists(authUserId: string): Promise<Profile> {
// CORR-GAM-002: Buscar por profiles.user_id (FK a auth.users)
const profile = await this.profileRepo.findOne({
where: { user_id: authUserId },
});
Nuevo método resolveProfileId() - Líneas 74-83
/**
* CORR-GAM-002: Resuelve auth.users.id → profiles.id
*/
private async resolveProfileId(authUserId: string): Promise<string> {
const profile = await this.validateProfileExists(authUserId);
return profile.id;
}
findByUserId() - Líneas 85-106
Antes:
async findByUserId(userId: string): Promise<UserStats> {
const stats = await this.userStatsRepo.findOne({
where: { user_id: userId },
});
Después:
async findByUserId(authUserId: string): Promise<UserStats> {
// CORR-GAM-002: Resolver auth.users.id → profiles.id
const profileId = await this.resolveProfileId(authUserId);
const stats = await this.userStatsRepo.findOne({
where: { user_id: profileId }, // user_stats.user_id = profiles.id
});
create() - Líneas 108-157
Antes:
async create(userId: string, tenantId?: string): Promise<UserStats> {
const profile = await this.validateProfileExists(userId);
// ...
const newStats = this.userStatsRepo.create({
user_id: userId, // ← INCORRECTO: usaba auth.users.id
Después:
async create(authUserId: string, tenantId?: string): Promise<UserStats> {
const profile = await this.validateProfileExists(authUserId);
// ...
const newStats = this.userStatsRepo.create({
user_id: profile.id, // CORR-GAM-002: profiles.id (PK), NO auth.users.id
3. VALIDACION
3.1 Verificación de Relación en BD
SELECT
p.id as profile_id,
p.user_id as auth_user_id,
p.email,
us.user_id as stats_user_id
FROM auth_management.profiles p
LEFT JOIN gamification_system.user_stats us ON us.user_id = p.id
WHERE p.email = 'student@gamilit.com';
Resultado:
profile_id | auth_user_id | email | stats_user_id
--------------------------------------+--------------------------------------+---------------------+--------------------------------------
9152d804-591f-496d-9404-a4ec2fd06cf0 | cccccccc-cccc-cccc-cccc-cccccccccccc | student@gamilit.com | 9152d804-591f-496d-9404-a4ec2fd06cf0
✅ user_stats.user_id = profiles.id (correcto)
3.2 Compilación TypeScript
npx tsc --noEmit
# ✅ Sin errores
3.3 Flujo Corregido
ANTES (Error 500):
Frontend envía: cccccccc-...
→ findByUserId(cccccccc-...) busca user_stats.user_id = cccccccc-...
→ No encuentra (debería ser 9152d804-...)
→ create(cccccccc-...) intenta INSERT user_stats(user_id = cccccccc-...)
→ FK falla: cccccccc-... no existe en profiles.id
DESPUÉS (Correcto):
Frontend envía: cccccccc-...
→ findByUserId(cccccccc-...)
→ resolveProfileId(cccccccc-...) busca profiles.user_id = cccccccc-...
→ Retorna profiles.id = 9152d804-...
→ Busca user_stats.user_id = 9152d804-...
→ Encuentra stats existentes ✅
4. ARCHIVOS MODIFICADOS
apps/backend/src/modules/gamification/services/user-stats.service.ts
Líneas afectadas: 44-157 (~45 líneas modificadas/agregadas)
5. IMPACTO
5.1 Endpoints Corregidos
| Endpoint | Antes | Después |
|---|---|---|
GET /users/:userId/summary |
500 | 200 ✅ |
GET /users/:userId/achievements/summary |
404 | 200 ✅ |
GET /users/:userId/stats |
404 | 200 ✅ |
GET /users/:userId/rank |
404 | 200 ✅ |
5.2 Sin Cambios Requeridos En
- Base de datos (FK correcta desde diseño)
- Frontend (envía auth.users.id correctamente)
- Otros servicios (dependen de user-stats.service)
6. CONVENTIONAL COMMITS
Mensaje de Commit Sugerido
fix(gamification): resolve auth.users.id to profiles.id for user_stats FK
CORR-GAM-002: The frontend sends auth.users.id (= profiles.user_id FK),
but user_stats.user_id references profiles.id (PK). Added resolveProfileId()
method to convert between these IDs before any user_stats operations.
Changes:
- Add resolveProfileId() method to resolve auth.users.id → profiles.id
- Update findByUserId() to use profileId for user_stats lookup
- Update create() to use profile.id for new user_stats records
- Update validateProfileExists() documentation
This fixes:
- Error 500 on GET /gamification/users/:userId/summary (FK violation)
- Error 404 on GET /gamification/users/:userId/achievements/summary
Refs: ANALISIS-ERRORES-GAMIFICATION-SUMMARY-2026-01-10.md
Archivos para Commit
# Backend
apps/backend/src/modules/gamification/services/user-stats.service.ts
# Documentation
orchestration/analisis/ANALISIS-ERRORES-GAMIFICATION-SUMMARY-2026-01-10.md
orchestration/analisis/PLAN-FIX-GAMIFICATION-SUMMARY-2026-01-10.md
orchestration/analisis/VALIDACION-PLAN-GAMIFICATION-2026-01-10.md
orchestration/analisis/REFINAMIENTO-PLAN-GAMIFICATION-2026-01-10.md
orchestration/analisis/REPORTE-EJECUCION-GAMIFICATION-SUMMARY-2026-01-10.md
7. NOTA SOBRE EL DISEÑO
Confusión de IDs Identificada
El sistema tiene tres IDs relacionados que causan confusión:
| ID | Tabla.Columna | Uso |
|---|---|---|
auth.users.id |
auth.users.id | ID de autenticación (Supabase) |
profiles.id |
profiles.id (PK) | PK del profile (autogenerado) |
profiles.user_id |
profiles.user_id (FK) | FK a auth.users |
Relación FK
user_stats.user_id → profiles.id (PK)
profiles.user_id → auth.users.id
El frontend envía auth.users.id, pero user_stats requiere profiles.id. La corrección implementada resuelve esta conversión internamente.
Ejecutado por: Claude (Arquitecto Técnico) Fecha: 2026-01-10 Estado Final: ✅ COMPLETADO Y VALIDADO