workspace/projects/gamilit/docs/90-transversal/arquitectura/FLUJO-INICIALIZACION-USUARIO.md
rckrdmrd ea1879f4ad feat: Initial workspace structure with multi-level Git configuration
- Configure workspace Git repository with comprehensive .gitignore
- Add Odoo as submodule for ERP reference code
- Include documentation: SETUP.md, GIT-STRUCTURE.md
- Add gitignore templates for projects (backend, frontend, database)
- Structure supports independent repos per project/subproject level

Workspace includes:
- core/ - Reusable patterns, modules, orchestration system
- projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.)
- knowledge-base/ - Reference code and patterns (includes Odoo submodule)
- devtools/ - Development tools and templates
- customers/ - Client implementations template

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 10:44:23 -06:00

648 lines
19 KiB
Markdown

# Flujo Completo: Inicialización de Usuario
**Última actualización:** 2025-11-24
**Relacionado con:** ADR-012, GAP-003 Bug Fix
**Propósito:** Documentar el flujo end-to-end desde registro de usuario hasta plataforma lista para usar
---
## 📋 Descripción General
Este documento describe el flujo completo de inicialización automática cuando un usuario se registra en GAMILIT. El sistema utiliza un **trigger de base de datos** para garantizar que cada nuevo usuario tenga todos los registros necesarios creados automáticamente en 0 segundos.
**Resultado final:**
- ✅ Usuario registrado puede usar la plataforma inmediatamente
- ✅ Ve 5 módulos disponibles sin esperar
- ✅ Gamificación funciona desde el primer momento
- ✅ 0 errores en dashboard
---
## 🔄 Flujo End-to-End
### 1. Usuario se Registra (Frontend)
**Componente:** `apps/frontend/src/apps/student/pages/RegisterPage.tsx`
**Acción del usuario:**
```typescript
// Usuario llena formulario de registro
{
email: "student@example.com",
password: "SecurePass123!",
role: "student",
displayName: "Juan Pérez"
}
```
**Request HTTP:**
```typescript
POST /api/v1/auth/register
Content-Type: application/json
{
"email": "student@example.com",
"password": "SecurePass123!",
"role": "student",
"displayName": "Juan Pérez"
}
```
---
### 2. Backend Procesa Registro
**Servicio:** `apps/backend/src/auth/auth.service.ts`
**Código simplificado:**
```typescript
async register(dto: RegisterDto) {
// Paso 1: Crear usuario en auth.users (Supabase Auth)
const { data: authUser, error } = await this.supabase.auth.signUp({
email: dto.email,
password: dto.password,
});
if (error) throw new UnauthorizedException(error.message);
// Paso 2: Crear perfil en auth_management.profiles
const { data: profile, error: profileError } = await this.supabase
.from('profiles')
.insert({
user_id: authUser.user.id, // FK a auth.users
email: dto.email,
role: dto.role,
display_name: dto.displayName,
})
.select()
.single();
// ⚡ En este punto, el TRIGGER se dispara automáticamente
// No se requiere código adicional para inicialización
return {
user: profile,
accessToken: authUser.session.access_token,
};
}
```
**Observaciones importantes:**
- ✅ El backend NO necesita código para inicializar gamificación
- ✅ El backend NO necesita código para crear module_progress
- ✅ Todo es manejado automáticamente por el trigger
- ✅ Código más simple y menos propenso a errores
---
### 3. Trigger de Base de Datos se Dispara
**Trigger:** `trg_initialize_user_stats`
**Ubicación:** `apps/database/ddl/schemas/auth_management/triggers/04-trg_initialize_user_stats.sql`
**Definición:**
```sql
CREATE TRIGGER trg_initialize_user_stats
AFTER INSERT ON auth_management.profiles
FOR EACH ROW
EXECUTE FUNCTION gamilit.initialize_user_stats();
```
**Momento de ejecución:**
- Inmediatamente DESPUÉS de INSERT en `profiles`
- Antes de que el backend retorne la respuesta
- Dentro de la misma transacción (atómico)
---
### 4. Función de Inicialización Ejecuta
**Función:** `gamilit.initialize_user_stats()`
**Ubicación:** `apps/database/ddl/schemas/gamilit/functions/04-initialize_user_stats.sql`
**Acciones realizadas (en orden):**
#### 4.1. Inicializar Estadísticas de Gamificación
```sql
INSERT INTO gamification_system.user_stats (
user_id,
total_xp,
level,
ml_coins,
current_streak,
created_at,
updated_at
)
VALUES (
NEW.user_id, -- auth.users.id
0, -- XP inicial
1, -- Nivel inicial
100, -- 100 ML Coins de bienvenida
0, -- Racha inicial
NOW(),
NOW()
)
ON CONFLICT (user_id) DO NOTHING;
```
**Resultado:**
- ✅ Usuario tiene 100 ML Coins para gastar
- ✅ Nivel 1 asignado
- ✅ Estadísticas listas para tracking
#### 4.2. Inicializar Inventario de Comodines
```sql
INSERT INTO gamification_system.comodines_inventory (
user_id,
total_comodines_earned,
total_comodines_used,
created_at,
updated_at
)
VALUES (
NEW.id, -- profiles.id
0,
0,
NOW(),
NOW()
)
ON CONFLICT (user_id) DO NOTHING;
```
**Resultado:**
- ✅ Inventario vacío creado
- ✅ Listo para acumular comodines
#### 4.3. Asignar Rango Maya Inicial
```sql
INSERT INTO gamification_system.user_ranks (
user_id,
current_rank,
rank_progress,
created_at,
updated_at
)
SELECT
NEW.user_id, -- auth.users.id
'Ajaw'::gamification_system.maya_rank,
0,
NOW(),
NOW()
WHERE NOT EXISTS (
SELECT 1 FROM gamification_system.user_ranks
WHERE user_id = NEW.user_id
);
```
**Resultado:**
- ✅ Rango inicial "Ajaw" asignado
- ✅ Sistema de progresión activado
#### 4.4. Crear Progreso para Todos los Módulos Publicados
```sql
INSERT INTO progress_tracking.module_progress (
user_id,
module_id,
status,
progress_percentage,
created_at,
updated_at
)
SELECT
NEW.id, -- profiles.id
m.id, -- modules.id
'not_started'::progress_tracking.progress_status,
0,
NOW(),
NOW()
FROM educational_content.modules m
WHERE m.is_published = true
AND m.status = 'published'
ON CONFLICT (user_id, module_id) DO NOTHING;
```
**Resultado:**
- ✅ Un registro por cada módulo publicado (típicamente 5 registros)
- ✅ Usuario ve módulos disponibles inmediatamente
- ✅ Sin errores de "no modules available"
---
### 5. Backend Retorna Respuesta
**Response HTTP:**
```json
{
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "student@example.com",
"role": "student",
"displayName": "Juan Pérez",
"createdAt": "2025-11-24T10:30:00.000Z"
},
"accessToken": "eyJhbGc..."
}
```
**Estado de base de datos en este momento:**
```sql
-- user_stats: 1 registro ✅
-- comodines_inventory: 1 registro ✅
-- user_ranks: 1 registro ✅
-- module_progress: 5 registros ✅
-- TOTAL: 8 registros creados automáticamente
```
---
### 6. Frontend Carga Dashboard
**Componente:** `apps/frontend/src/apps/student/pages/DashboardPage.tsx`
**Queries automáticas:**
```typescript
// 1. Cargar módulos disponibles
const { data: modules } = useQuery({
queryKey: ['modules'],
queryFn: () => api.get('/api/v1/modules'),
});
// Retorna: 5 módulos con status 'not_started'
// 2. Cargar estadísticas
const { data: stats } = useQuery({
queryKey: ['user-stats'],
queryFn: () => api.get('/api/v1/gamification/stats'),
});
// Retorna: { level: 1, ml_coins: 100, xp: 0, streak: 0 }
// 3. Cargar rango actual
const { data: rank } = useQuery({
queryKey: ['user-rank'],
queryFn: () => api.get('/api/v1/gamification/rank'),
});
// Retorna: { current_rank: 'Ajaw', progress: 0 }
```
**Resultado visual:**
```
┌─────────────────────────────────────┐
│ Dashboard - Bienvenido Juan Pérez │
├─────────────────────────────────────┤
│ Nivel: 1 │ Coins: 100 💰 │
│ Rango: Ajaw │ XP: 0/100 │
├─────────────────────────────────────┤
│ Módulos Disponibles: │
│ ✅ M1: María y su Huerto │
│ ✅ M2: Detective Numérico │
│ ✅ M3: La Gran Carrera │
│ ✅ M4: Mercado de las Fracciones │
│ ✅ M5: Aventura en el Museo │
└─────────────────────────────────────┘
```
**Usuario puede empezar a usar la plataforma inmediatamente**
---
## 📊 Diagrama de Secuencia Completo
```
┌────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐
│ Usuario│ │ Frontend │ │ Backend │ │ Database │ │ Trigger │
└───┬────┘ └────┬─────┘ └────┬────┘ └────┬─────┘ └────┬────┘
│ │ │ │ │
│ Registro │ │ │ │
│─────────────>│ │ │ │
│ │ │ │ │
│ │ POST /register │ │
│ │─────────────>│ │ │
│ │ │ │ │
│ │ │ INSERT │ │
│ │ │ auth.users │ │
│ │ │─────────────>│ │
│ │ │<─────────────│ │
│ │ │ User ID │ │
│ │ │ │ │
│ │ │ INSERT │ │
│ │ │ profiles │ │
│ │ │─────────────>│ │
│ │ │ │ │
│ │ │ │ ⚡ TRIGGER │
│ │ │ │──────────────>│
│ │ │ │ │
│ │ │ │ INSERT │
│ │ │ │ user_stats │
│ │ │ │<─────────────│
│ │ │ │ │
│ │ │ │ INSERT │
│ │ │ │ comodines │
│ │ │ │<─────────────│
│ │ │ │ │
│ │ │ │ INSERT │
│ │ │ │ user_ranks │
│ │ │ │<─────────────│
│ │ │ │ │
│ │ │ │ INSERT │
│ │ │ │ module_progress (x5)
│ │ │ │<─────────────│
│ │ │ │ │
│ │ │<─────────────│ │
│ │ │ Profile + Session │
│ │<─────────────│ │ │
│ │ User + Token │ │ │
│<─────────────│ │ │ │
│ │ │ │ │
│ Redirect │ │ │ │
│ Dashboard │ │ │ │
│─────────────>│ │ │ │
│ │ │ │ │
│ │ GET /modules │ │ │
│ │─────────────>│ │ │
│ │ │ SELECT │ │
│ │ │ module_progress │
│ │ │─────────────>│ │
│ │ │<─────────────│ │
│ │ │ 5 modules │ │
│ │<─────────────│ │ │
│ │ 5 modules │ │ │
│ │ │ │ │
│ 🎉 Plataforma│ │ │ │
│ Lista │ │ │ │
│<─────────────│ │ │ │
```
**Tiempo total:** ~300ms
- Registro: ~100ms
- Trigger: ~50ms (4 INSERTs en paralelo)
- Primera carga: ~150ms
---
## 🔍 Validación del Flujo
### Query de Validación Post-Registro
```sql
-- Verificar que nuevo usuario tiene todo inicializado
WITH new_user AS (
SELECT id, user_id, email
FROM auth_management.profiles
WHERE email = 'student@example.com'
)
SELECT
'auth_management.profiles' as tabla,
1 as esperado,
COUNT(*) as real,
CASE WHEN COUNT(*) = 1 THEN '✅' ELSE '❌' END as status
FROM new_user
UNION ALL
SELECT
'gamification_system.user_stats',
1,
COUNT(*),
CASE WHEN COUNT(*) = 1 THEN '✅' ELSE '❌' END
FROM new_user nu
JOIN gamification_system.user_stats us ON us.user_id = nu.user_id
UNION ALL
SELECT
'gamification_system.comodines_inventory',
1,
COUNT(*),
CASE WHEN COUNT(*) = 1 THEN '✅' ELSE '❌' END
FROM new_user nu
JOIN gamification_system.comodines_inventory ci ON ci.user_id = nu.id
UNION ALL
SELECT
'gamification_system.user_ranks',
1,
COUNT(*),
CASE WHEN COUNT(*) = 1 THEN '✅' ELSE '❌' END
FROM new_user nu
JOIN gamification_system.user_ranks ur ON ur.user_id = nu.user_id
UNION ALL
SELECT
'progress_tracking.module_progress',
5, -- Asumiendo 5 módulos publicados
COUNT(*),
CASE WHEN COUNT(*) = 5 THEN '✅' ELSE '❌' END
FROM new_user nu
JOIN progress_tracking.module_progress mp ON mp.user_id = nu.id;
```
**Resultado esperado:**
```
tabla | esperado | real | status
-------------------------------------------+----------+------+--------
auth_management.profiles | 1 | 1 | ✅
gamification_system.user_stats | 1 | 1 | ✅
gamification_system.comodines_inventory | 1 | 1 | ✅
gamification_system.user_ranks | 1 | 1 | ✅
progress_tracking.module_progress | 5 | 5 | ✅
```
---
## 🎯 Casos de Uso Comunes
### Caso 1: Usuario se Registra por Primera Vez
**Escenario:**
- Usuario nuevo
- Email no existe en sistema
- Primer registro
**Flujo:**
1. POST /api/v1/auth/register
2. Trigger crea 8 registros (1+1+1+5)
3. Usuario ve dashboard completo
4. ✅ Resultado: Usuario listo para usar plataforma
---
### Caso 2: Re-registro del Mismo Usuario (Idempotencia)
**Escenario:**
- Usuario intenta registrarse 2 veces con mismo email
- Sistema debe manejar gracefully
**Flujo:**
1. POST /api/v1/auth/register (segunda vez)
2. Trigger intenta crear registros
3. `ON CONFLICT DO NOTHING` previene duplicados
4. ✅ Resultado: Sin errores, registros existentes no se alteran
**SQL:**
```sql
-- Todas las inserciones usan ON CONFLICT
ON CONFLICT (user_id) DO NOTHING;
ON CONFLICT (user_id, module_id) DO NOTHING;
```
---
### Caso 3: Se Publica un Nuevo Módulo (M6)
**Escenario:**
- Admin publica módulo M6
- Usuarios existentes deben poder acceder
**Opción 1: Migration Manual**
```sql
-- Crear module_progress para usuarios existentes
INSERT INTO progress_tracking.module_progress (
user_id, module_id, status, progress_percentage
)
SELECT
p.id,
'm6-new-module'::uuid,
'not_started',
0
FROM auth_management.profiles p
WHERE p.role IN ('student', 'admin_teacher', 'super_admin')
AND NOT EXISTS (
SELECT 1 FROM progress_tracking.module_progress mp
WHERE mp.user_id = p.id AND mp.module_id = 'm6-new-module'::uuid
);
```
**Opción 2: Lazy Loading en Backend**
- Crear module_progress al primer acceso
- Más simple pero menos consistente
**Recomendación:** Opción 1 (migration) para consistencia
---
## 🚨 Troubleshooting
### Problema 1: Usuario Sin Módulos
**Síntoma:**
```
Dashboard muestra: "No modules available"
```
**Diagnóstico:**
```sql
SELECT COUNT(*) FROM progress_tracking.module_progress
WHERE user_id = 'USER_PROFILE_ID';
-- Resultado esperado: 5
-- Si retorna 0: Trigger no se ejecutó
```
**Causas posibles:**
1. ❌ Trigger deshabilitado
2. ❌ No hay módulos publicados
3. ❌ FK incorrecta (user_id → auth.users.id en vez de profiles.id)
**Solución:**
```sql
-- Verificar trigger existe
SELECT * FROM pg_trigger WHERE tgname = 'trg_initialize_user_stats';
-- Verificar módulos publicados
SELECT COUNT(*) FROM educational_content.modules
WHERE is_published = true AND status = 'published';
-- Re-inicializar manualmente
SELECT gamilit.initialize_user_stats() FROM auth_management.profiles
WHERE id = 'USER_PROFILE_ID';
```
---
### Problema 2: Usuario Sin Stats
**Síntoma:**
```
Dashboard muestra: "Error loading stats"
```
**Diagnóstico:**
```sql
SELECT * FROM gamification_system.user_stats
WHERE user_id = 'USER_AUTH_ID';
-- Resultado esperado: 1 fila
```
**Solución:**
```sql
-- Insertar stats manualmente
INSERT INTO gamification_system.user_stats (
user_id, total_xp, level, ml_coins, current_streak
)
VALUES ('USER_AUTH_ID', 0, 1, 100, 0)
ON CONFLICT (user_id) DO NOTHING;
```
---
### Problema 3: Duplicados en module_progress
**Síntoma:**
```
ERROR: duplicate key value violates unique constraint "module_progress_pkey"
```
**Diagnóstico:**
```sql
SELECT user_id, module_id, COUNT(*)
FROM progress_tracking.module_progress
GROUP BY user_id, module_id
HAVING COUNT(*) > 1;
```
**Solución:**
```sql
-- Eliminar duplicados, mantener el más antiguo
WITH duplicates AS (
SELECT id, ROW_NUMBER() OVER (
PARTITION BY user_id, module_id ORDER BY created_at
) as rn
FROM progress_tracking.module_progress
)
DELETE FROM progress_tracking.module_progress
WHERE id IN (
SELECT id FROM duplicates WHERE rn > 1
);
```
---
## 📚 Referencias
**Documentación relacionada:**
- ADR: `docs/97-adr/ADR-012-automatic-user-initialization-trigger.md`
- Función: `docs/90-transversal/FUNCIONES-UTILITARIAS-GAMILIT.md`
- Dependencias: `docs/90-transversal/DIAGRAMA-DEPENDENCIAS-INITIALIZE-USER-STATS.md`
**Código fuente:**
- Trigger: `apps/database/ddl/schemas/auth_management/triggers/04-trg_initialize_user_stats.sql`
- Función: `apps/database/ddl/schemas/gamilit/functions/04-initialize_user_stats.sql`
- Backend: `apps/backend/src/auth/auth.service.ts`
- Frontend: `apps/frontend/src/apps/student/pages/DashboardPage.tsx`
**Inventarios:**
- `docs/90-transversal/inventarios/DATABASE_INVENTORY.yml`
- `orchestration/inventarios/MASTER_INVENTORY.yml`
---
**FIN DEL DOCUMENTO**
**Última actualización:** 2025-11-24
**Mantenedores:** Architecture-Analyst, Database-Agent
**Revisión necesaria:** Sí, al agregar nuevos módulos o cambiar proceso de registro