- 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>
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.idtotal_xp = 0level = 1ml_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.idcurrent_rank = 'Ajaw'(rango inicial)- Estrategia:
WHERE NOT EXISTS(tabla sin constraint UNIQUE)
4. Progreso de Módulos (progress_tracking.module_progress)
user_id→ profiles.idmodule_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_statsenauth_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_atyupdated_aten ~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
-
SECURITY DEFINER:
is_admin()- Ejecuta con permisos del creador- Riesgo: Puede escalar privilegios si no se valida correctamente
- Mitigación: Incluye validaciones exhaustivas
-
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)
-
Validación de entrada:
validate_email_format()yvalidate_username()usan regex- Protegen contra inyección SQL en constraints
Performance
-
Funciones más llamadas:
update_updated_at_column()- ~30+ triggersnow_mexico()- ~50+ defaults + triggersget_current_user_id()- Cientos de RLS policies
-
Optimización:
- Funciones STABLE se cachean por transacción
- Evitar lógica compleja en funciones de triggers masivos
-
Monitoreo:
- Rastrear tiempo de ejecución de
update_user_stats_on_exercise_complete() - Es la función más compleja (actualiza múltiples tablas)
- Rastrear tiempo de ejecución de
Mantenimiento
-
Cambios en firmas:
- Verificar TODAS las dependencias antes de modificar
- Usar
pg_dependpara encontrar objetos dependientes
-
Versionado:
- Documentar cambios en funciones críticas
- Considerar functions versionadas (
get_user_role_v2)
-
Testing:
- Tests unitarios para cada función
- Tests de integración para funciones complejas
🎯 MEJORES PRÁCTICAS
Al Usar Estas Funciones
-
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)) -
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(); -
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
-
Documentación de código:
- Agregar ejemplos de uso en comentarios SQL
- Documentar casos edge y manejo de errores
-
Testing:
- Crear suite de tests para cada función
- Tests de performance para funciones críticas
-
Monitoring:
- Agregar métricas de llamadas y tiempo de ejecución
- Alertas para funciones que excedan thresholds
-
Nuevas funciones útiles:
gamilit.is_teacher()- Verificar solo admin_teachergamilit.is_super_admin()- Verificar solo super_admingamilit.get_user_tenant_id()- Obtener tenant del usuariogamilit.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