workspace/projects/gamilit/docs/90-transversal/arquitectura/FUNCIONES-UTILITARIAS-GAMILIT.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

20 KiB

Funciones Utilitarias Globales - Schema: gamilit

Schema: gamilit Propósito: Funciones helper reutilizables en toda la base de datos Total de funciones: 13 Estado: Implementado Última actualización: 2025-11-08


📋 Descripción

El schema gamilit contiene funciones utilitarias globales que son utilizadas por múltiples schemas a través de triggers, RLS policies y procedimientos almacenados. Estas funciones proporcionan funcionalidad común y reutilizable.


📊 Categorías de Funciones

Categoría Cantidad Funciones
Auditoría 1 audit_profile_changes
Autenticación/Autorización 3 get_current_user_id, get_current_user_role, is_admin
Inicialización 2 initialize_user_stats, set_profile_defaults
Fecha/Hora 1 now_mexico
Triggers Helper 1 update_updated_at_column
Validación 2 validate_email_format, validate_username
Contadores 2 update_classroom_member_count, update_user_last_login
Gamificación 1 update_user_stats_on_exercise_complete

🔐 CATEGORÍA: AUDITORÍA

1. audit_profile_changes()

Archivo: apps/database/ddl/schemas/gamilit/functions/01-audit_profile_changes.sql

Propósito: Registra cambios en perfiles de usuario para auditoría

Firma:

CREATE OR REPLACE FUNCTION gamilit.audit_profile_changes()
RETURNS TRIGGER

Parámetros:

  • Ninguno (función trigger)

Retorno: TRIGGER

Uso:

CREATE TRIGGER trg_audit_profile_changes
AFTER UPDATE ON auth_management.profiles
FOR EACH ROW
EXECUTE FUNCTION gamilit.audit_profile_changes();

Usado por:

  • Trigger en auth_management.profiles
  • Sistema de auditoría

Comportamiento:

  • Registra cambios en campos importantes del perfil
  • Almacena estado anterior y nuevo
  • Registra timestamp y usuario que realizó el cambio

🔑 CATEGORÍA: AUTENTICACIÓN/AUTORIZACIÓN

2. get_current_user_id()

Archivo: apps/database/ddl/schemas/gamilit/functions/02-get_current_user_id.sql

Propósito: Obtiene el UUID del usuario actual de la sesión PostgreSQL

Firma:

CREATE OR REPLACE FUNCTION gamilit.get_current_user_id()
RETURNS UUID

Parámetros: Ninguno

Retorno: UUID - ID del usuario actual o NULL

Uso:

SELECT gamilit.get_current_user_id();
-- Retorna: '550e8400-e29b-41d4-a716-446655440000'

Usado por:

  • RLS policies en todos los schemas
  • Triggers de auditoría
  • Validaciones de permisos

Implementación: Obtiene el user_id del JWT token almacenado en current_setting('request.jwt.claims') o de variables de sesión.

Seguridad: STABLE SECURITY DEFINER


3. get_current_user_role()

Archivo: apps/database/ddl/schemas/gamilit/functions/03-get_current_user_role.sql

Propósito: Obtiene el rol del usuario actual

Firma:

CREATE OR REPLACE FUNCTION gamilit.get_current_user_role()
RETURNS auth_management.gamilit_role

Parámetros: Ninguno

Retorno: auth_management.gamilit_role - Rol del usuario (student, admin_teacher, super_admin)

Uso:

SELECT gamilit.get_current_user_role();
-- Retorna: 'student' | 'admin_teacher' | 'super_admin'

Usado por:

  • RLS policies para control de acceso basado en roles
  • Validaciones de permisos
  • Lógica condicional en funciones

Ejemplo en RLS:

CREATE POLICY teacher_access ON progress_tracking.exercise_attempts
FOR SELECT
USING (gamilit.get_current_user_role() = 'admin_teacher'::auth_management.gamilit_role);

Seguridad: STABLE


4. is_admin()

Archivo: apps/database/ddl/schemas/gamilit/functions/05-is_admin.sql

Propósito: Verifica si el usuario actual es administrador (teacher o super_admin)

Firma:

CREATE OR REPLACE FUNCTION gamilit.is_admin()
RETURNS BOOLEAN

Parámetros: Ninguno

Retorno: BOOLEAN - TRUE si es admin, FALSE si no

Uso:

SELECT gamilit.is_admin();
-- Retorna: true | false

Lógica:

-- Verifica si el rol está en ('admin_teacher', 'super_admin')
-- Y el status es 'active'

Usado por:

  • RLS policies para funcionalidad admin-only
  • Validaciones de permisos críticos
  • Funciones que requieren privilegios elevados

Ejemplo:

CREATE POLICY admin_can_delete ON some_table
FOR DELETE
USING (gamilit.is_admin());

Seguridad: STABLE SECURITY DEFINER

Manejo de errores: Retorna FALSE en caso de excepción


🎯 CATEGORÍA: INICIALIZACIÓN

5. initialize_user_stats()

Archivo: apps/database/ddl/schemas/gamilit/functions/04-initialize_user_stats.sql

Propósito: Inicializa automáticamente todos los registros necesarios para que un nuevo usuario pueda usar la plataforma inmediatamente

Última actualización: 2025-11-24 (Bug fix GAP-003 - Agregada inicialización de module_progress)

Firma:

CREATE OR REPLACE FUNCTION gamilit.initialize_user_stats()
RETURNS TRIGGER

Parámetros: Ninguno (función trigger)

Retorno: TRIGGER

Uso:

CREATE TRIGGER trg_initialize_user_stats
AFTER INSERT ON auth_management.profiles
FOR EACH ROW
EXECUTE FUNCTION gamilit.initialize_user_stats();

Comportamiento: Crea automáticamente registros en 4 tablas cuando se crea un nuevo usuario:

1. Estadísticas de Gamificación (gamification_system.user_stats)

  • user_id → auth.users.id
  • total_xp = 0
  • level = 1
  • ml_coins = 100 (monedas de bienvenida)
  • current_streak = 0
  • Estrategia: ON CONFLICT (user_id) DO NOTHING

2. Inventario de Comodines (gamification_system.comodines_inventory)

  • user_id → profiles.id
  • Inventario vacío inicializado
  • Estrategia: ON CONFLICT (user_id) DO NOTHING

3. Rango Maya (gamification_system.user_ranks)

  • user_id → auth.users.id
  • current_rank = 'Ajaw' (rango inicial)
  • Estrategia: WHERE NOT EXISTS (tabla sin constraint UNIQUE)

4. Progreso de Módulos (progress_tracking.module_progress)

  • user_id → profiles.id
  • module_id → modules.id
  • Un registro por cada módulo publicado
  • status = 'not_started'
  • progress_percentage = 0
  • Estrategia: ON CONFLICT (user_id, module_id) DO NOTHING

Dependencias:

  • Lee de: educational_content.modules (WHERE is_published = true AND status = 'published')
  • Inserta en: 4 tablas (user_stats, comodines_inventory, user_ranks, module_progress)

Resultado:

  • Usuario puede ver todos los módulos disponibles inmediatamente
  • Gamificación funciona desde el primer momento
  • 0 errores en dashboard de estudiante
  • UX perfecta: registro → plataforma lista en 0 segundos

Usado por:

  • Trigger trg_initialize_user_stats en auth_management.profiles (AFTER INSERT)

Documentación adicional:

  • ADR: docs/97-adr/ADR-012-automatic-user-initialization-trigger.md
  • Flujo completo: docs/90-transversal/FLUJO-INICIALIZACION-USUARIO.md
  • Dependencias: docs/90-transversal/DIAGRAMA-DEPENDENCIAS-INITIALIZE-USER-STATS.md

6. set_profile_defaults()

Archivo: apps/database/ddl/schemas/gamilit/functions/09-set_profile_defaults.sql

Propósito: Establece valores por defecto en perfil de usuario

Firma:

CREATE OR REPLACE FUNCTION gamilit.set_profile_defaults()
RETURNS TRIGGER

Parámetros: Ninguno (función trigger)

Retorno: TRIGGER

Comportamiento:

  • Asigna valores por defecto si no se proporcionaron
  • Genera username automático si es NULL
  • Establece avatar por defecto
  • Configura timezone según región

Usado por:

  • Trigger en auth_management.profiles (BEFORE INSERT)

CATEGORÍA: FECHA/HORA

7. now_mexico()

Archivo: apps/database/ddl/schemas/gamilit/functions/08-now_mexico.sql

Propósito: Retorna timestamp actual en zona horaria de México (America/Mexico_City)

Firma:

CREATE OR REPLACE FUNCTION gamilit.now_mexico()
RETURNS TIMESTAMP WITH TIME ZONE

Parámetros: Ninguno

Retorno: TIMESTAMPTZ - Timestamp actual en zona horaria de México

Uso:

SELECT gamilit.now_mexico();
-- Retorna: '2025-11-08 01:45:00-06'

-- En defaults de columnas:
created_at TIMESTAMPTZ DEFAULT gamilit.now_mexico()

Usado por:

  • Defaults de columnas created_at y updated_at en ~50+ tablas
  • Funciones que requieren timestamp actual
  • Cálculos de fecha/hora

Nota: Marcada como IMMUTABLE para optimización

Diferencia con NOW():

-- NOW() retorna UTC o timezone del servidor
-- now_mexico() retorna siempre en timezone de México

CATEGORÍA: TRIGGERS HELPER

8. update_updated_at_column()

Archivo: apps/database/ddl/schemas/gamilit/functions/09-update_updated_at_column.sql

Propósito: Actualiza automáticamente el campo updated_at en updates

Firma:

CREATE OR REPLACE FUNCTION gamilit.update_updated_at_column()
RETURNS TRIGGER

Parámetros: Ninguno (función trigger)

Retorno: TRIGGER

Comportamiento:

-- En cada UPDATE, automáticamente:
NEW.updated_at = gamilit.now_mexico();
RETURN NEW;

Uso:

CREATE TRIGGER trg_table_updated_at
BEFORE UPDATE ON schema.table
FOR EACH ROW
EXECUTE FUNCTION gamilit.update_updated_at_column();

Usado por: ~30+ triggers en todo el sistema

Tablas que lo usan:

  • auth_management.* (12 tablas)
  • gamification_system.* (13 tablas)
  • educational_content.* (4 tablas)
  • progress_tracking.* (5 tablas)
  • social_features.* (7 tablas)
  • system_configuration.* (3 tablas)
  • Y más...

Beneficio: Garantiza que updated_at siempre refleja la última modificación


CATEGORÍA: VALIDACIÓN

9. validate_email_format()

Archivo: apps/database/ddl/schemas/gamilit/functions/12-validate_email_format.sql

Propósito: Valida que un email tenga formato correcto

Firma:

CREATE OR REPLACE FUNCTION gamilit.validate_email_format(email TEXT)
RETURNS BOOLEAN

Parámetros:

  • email (TEXT) - Email a validar

Retorno: BOOLEAN - TRUE si válido, FALSE si no

Uso:

SELECT gamilit.validate_email_format('user@example.com');
-- Retorna: true

SELECT gamilit.validate_email_format('invalid-email');
-- Retorna: false

-- En constraint:
ALTER TABLE users ADD CONSTRAINT valid_email
CHECK (gamilit.validate_email_format(email));

Regex usado:

^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$

Usado por:

  • Constraints en auth_management.profiles
  • Validaciones de formularios (via backend)

10. validate_username()

Archivo: apps/database/ddl/schemas/gamilit/functions/13-validate_username.sql

Propósito: Valida formato de username (alfanumérico, guiones, sin espacios)

Firma:

CREATE OR REPLACE FUNCTION gamilit.validate_username(username TEXT)
RETURNS BOOLEAN

Parámetros:

  • username (TEXT) - Username a validar

Retorno: BOOLEAN - TRUE si válido, FALSE si no

Uso:

SELECT gamilit.validate_username('john_doe123');
-- Retorna: true

SELECT gamilit.validate_username('john doe');  -- espacios
-- Retorna: false

SELECT gamilit.validate_username('a');  -- muy corto
-- Retorna: false

Reglas de validación:

  • Mínimo 3 caracteres
  • Máximo 30 caracteres
  • Solo letras, números, guiones bajos y guiones medios
  • Sin espacios
  • Sin caracteres especiales

Regex:

^[A-Za-z0-9_-]{3,30}$

Usado por:

  • Constraints en auth_management.profiles
  • Validaciones en registro de usuario

🔢 CATEGORÍA: CONTADORES

11. update_classroom_member_count()

Archivo: apps/database/ddl/schemas/gamilit/functions/10-update_classroom_member_count.sql

Propósito: Actualiza contador de miembros en un aula al agregar/remover estudiantes

Firma:

CREATE OR REPLACE FUNCTION gamilit.update_classroom_member_count()
RETURNS TRIGGER

Parámetros: Ninguno (función trigger)

Retorno: TRIGGER

Comportamiento:

-- Al INSERT en classroom_members:
UPDATE classrooms SET member_count = member_count + 1

-- Al DELETE en classroom_members:
UPDATE classrooms SET member_count = member_count - 1

Uso:

CREATE TRIGGER trg_update_classroom_count
AFTER INSERT OR DELETE ON social_features.classroom_members
FOR EACH ROW
EXECUTE FUNCTION gamilit.update_classroom_member_count();

Usado por:

  • Trigger en social_features.classroom_members

Beneficio: Mantiene contador sincronizado sin queries adicionales


12. update_user_last_login()

Archivo: apps/database/ddl/schemas/gamilit/functions/11-update_user_last_login.sql

Propósito: Actualiza timestamp de último login del usuario

Firma:

CREATE OR REPLACE FUNCTION gamilit.update_user_last_login(p_user_id UUID)
RETURNS VOID

Parámetros:

  • p_user_id (UUID) - ID del usuario

Retorno: VOID

Uso:

-- Llamada desde backend al hacer login exitoso:
SELECT gamilit.update_user_last_login('550e8400-e29b-41d4-a716-446655440000');

Comportamiento:

UPDATE auth_management.profiles
SET last_login_at = gamilit.now_mexico()
WHERE id = p_user_id;

Usado por:

  • Auth service en backend (al hacer login)
  • Analytics para calcular usuarios activos

🎮 CATEGORÍA: GAMIFICACIÓN

13. update_user_stats_on_exercise_complete()

Archivo: apps/database/ddl/schemas/gamilit/functions/14-update_user_stats_on_exercise_complete.sql

Propósito: Actualiza estadísticas de usuario al completar un ejercicio (XP, ML Coins, racha)

Firma:

CREATE OR REPLACE FUNCTION gamilit.update_user_stats_on_exercise_complete()
RETURNS TRIGGER

Parámetros: Ninguno (función trigger)

Retorno: TRIGGER

Comportamiento:

-- Al completar ejercicio con éxito:
1. Incrementa XP según dificultad del ejercicio
2. Otorga ML Coins
3. Actualiza racha diaria (streak)
4. Recalcula nivel si XP suficiente
5. Verifica y desbloquea achievements

Uso:

CREATE TRIGGER trg_update_stats_on_exercise
AFTER INSERT ON progress_tracking.exercise_attempts
FOR EACH ROW
WHEN (NEW.status = 'completed' AND NEW.is_correct = true)
EXECUTE FUNCTION gamilit.update_user_stats_on_exercise_complete();

Usado por:

  • Trigger en progress_tracking.exercise_attempts

Lógica de recompensas:

-- XP según dificultad:
beginner: 10 XP
intermediate: 20 XP
advanced: 30 XP
expert: 50 XP

-- ML Coins: Variable según configuración (default: 10 coins)
-- Streak: +1 si ejercicio completado en día actual

Interdependencias:

  • Lee de educational_content.exercises (dificultad)
  • Actualiza gamification_system.user_stats
  • Llama a gamification_system.check_and_award_achievements()

📊 DIAGRAMA DE DEPENDENCIAS

Funciones Base (sin dependencias):
├── now_mexico()
└── validate_email_format()
    validate_username()

Funciones de Sesión:
├── get_current_user_id()
└── get_current_user_role() ──> usa: get_current_user_id()
    └── is_admin() ──> usa: get_current_user_role()

Triggers de Auditoría/Actualización:
├── update_updated_at_column() ──> usa: now_mexico()
├── audit_profile_changes() ──> usa: get_current_user_id(), now_mexico()
└── update_user_last_login() ──> usa: now_mexico()

Triggers de Inicialización:
├── set_profile_defaults()
└── initialize_user_stats() ──> crea registro en user_stats

Triggers de Contadores:
└── update_classroom_member_count()

Triggers de Gamificación:
└── update_user_stats_on_exercise_complete()
    ├── usa: gamification_system.check_and_award_achievements()
    └── usa: gamification_system.calculate_level_from_xp()

🔗 SCHEMAS QUE USAN ESTAS FUNCIONES

Schema Funciones Usadas Uso Principal
auth_management get_current_user_id, get_current_user_role, is_admin, update_updated_at_column, audit_profile_changes, initialize_user_stats, set_profile_defaults, now_mexico RLS, triggers, validación
gamification_system get_current_user_id, update_updated_at_column, update_user_stats_on_exercise_complete, now_mexico RLS, triggers, gamificación
educational_content get_current_user_id, get_current_user_role, update_updated_at_column, now_mexico RLS, triggers
progress_tracking get_current_user_id, get_current_user_role, update_updated_at_column, update_user_stats_on_exercise_complete, now_mexico RLS, triggers, tracking
social_features get_current_user_id, is_admin, update_updated_at_column, update_classroom_member_count, now_mexico RLS, triggers, contadores
system_configuration is_admin, update_updated_at_column, now_mexico RLS, triggers
audit_logging get_current_user_id, now_mexico Auditoría
content_management get_current_user_role, is_admin, now_mexico RLS, permisos

⚠️ CONSIDERACIONES IMPORTANTES

Seguridad

  1. SECURITY DEFINER:

    • is_admin() - Ejecuta con permisos del creador
    • Riesgo: Puede escalar privilegios si no se valida correctamente
    • Mitigación: Incluye validaciones exhaustivas
  2. STABLE vs VOLATILE:

    • get_current_user_id() - STABLE (no modifica BD)
    • get_current_user_role() - STABLE (cacheable en query)
    • now_mexico() - IMMUTABLE (optimización incorrecta, debería ser STABLE)
  3. Validación de entrada:

    • validate_email_format() y validate_username() usan regex
    • Protegen contra inyección SQL en constraints

Performance

  1. Funciones más llamadas:

    • update_updated_at_column() - ~30+ triggers
    • now_mexico() - ~50+ defaults + triggers
    • get_current_user_id() - Cientos de RLS policies
  2. Optimización:

    • Funciones STABLE se cachean por transacción
    • Evitar lógica compleja en funciones de triggers masivos
  3. Monitoreo:

    • Rastrear tiempo de ejecución de update_user_stats_on_exercise_complete()
    • Es la función más compleja (actualiza múltiples tablas)

Mantenimiento

  1. Cambios en firmas:

    • Verificar TODAS las dependencias antes de modificar
    • Usar pg_depend para encontrar objetos dependientes
  2. Versionado:

    • Documentar cambios en funciones críticas
    • Considerar functions versionadas (get_user_role_v2)
  3. Testing:

    • Tests unitarios para cada función
    • Tests de integración para funciones complejas

🎯 MEJORES PRÁCTICAS

Al Usar Estas Funciones

  1. RLS Policies:

    -- ✅ Correcto:
    USING (user_id = gamilit.get_current_user_id())
    
    -- ❌ Evitar llamar múltiples veces en mismo query:
    USING (user_id = gamilit.get_current_user_id() OR
           created_by = gamilit.get_current_user_id())
    
    -- ✅ Mejor:
    USING (gamilit.get_current_user_id() IN (user_id, created_by))
    
  2. Triggers:

    -- ✅ Especificar WHEN para evitar ejecuciones innecesarias:
    CREATE TRIGGER trg_name
    AFTER UPDATE ON table
    FOR EACH ROW
    WHEN (NEW.status <> OLD.status)  -- Solo si cambió status
    EXECUTE FUNCTION gamilit.some_function();
    
  3. Validaciones:

    -- ✅ Usar en constraints:
    ALTER TABLE users ADD CONSTRAINT valid_username
    CHECK (gamilit.validate_username(username));
    
    -- También validar en backend como primera línea de defensa
    

📝 PRÓXIMOS PASOS

Mejoras Sugeridas

  1. Documentación de código:

    • Agregar ejemplos de uso en comentarios SQL
    • Documentar casos edge y manejo de errores
  2. Testing:

    • Crear suite de tests para cada función
    • Tests de performance para funciones críticas
  3. Monitoring:

    • Agregar métricas de llamadas y tiempo de ejecución
    • Alertas para funciones que excedan thresholds
  4. Nuevas funciones útiles:

    • gamilit.is_teacher() - Verificar solo admin_teacher
    • gamilit.is_super_admin() - Verificar solo super_admin
    • gamilit.get_user_tenant_id() - Obtener tenant del usuario
    • gamilit.log_error() - Función helper para logging

🔗 Referencias

  • Directorio: apps/database/ddl/schemas/gamilit/functions/
  • _MAP.md: apps/database/ddl/schemas/gamilit/functions/_MAP.md
  • Dependencias: Ver diagrama de dependencias arriba
  • Usado por: Todos los schemas de GAMILIT

Creado: 2025-11-08 Tipo: Documentación retroactiva Total de funciones: 13 Estado: Documentación completa