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

19 KiB

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:

// Usuario llena formulario de registro
{
  email: "student@example.com",
  password: "SecurePass123!",
  role: "student",
  displayName: "Juan Pérez"
}

Request HTTP:

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:

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:

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

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

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

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

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:

{
  "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:

-- 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:

// 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

-- 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:

-- 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

-- 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:

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:

-- 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:

SELECT * FROM gamification_system.user_stats
WHERE user_id = 'USER_AUTH_ID';
-- Resultado esperado: 1 fila

Solución:

-- 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:

SELECT user_id, module_id, COUNT(*)
FROM progress_tracking.module_progress
GROUP BY user_id, module_id
HAVING COUNT(*) > 1;

Solución:

-- 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