- 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>
783 lines
20 KiB
Markdown
783 lines
20 KiB
Markdown
# 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:**
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION gamilit.audit_profile_changes()
|
|
RETURNS TRIGGER
|
|
```
|
|
|
|
**Parámetros:**
|
|
- Ninguno (función trigger)
|
|
|
|
**Retorno:** `TRIGGER`
|
|
|
|
**Uso:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION gamilit.get_current_user_id()
|
|
RETURNS UUID
|
|
```
|
|
|
|
**Parámetros:** Ninguno
|
|
|
|
**Retorno:** `UUID` - ID del usuario actual o NULL
|
|
|
|
**Uso:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION gamilit.is_admin()
|
|
RETURNS BOOLEAN
|
|
```
|
|
|
|
**Parámetros:** Ninguno
|
|
|
|
**Retorno:** `BOOLEAN` - TRUE si es admin, FALSE si no
|
|
|
|
**Uso:**
|
|
```sql
|
|
SELECT gamilit.is_admin();
|
|
-- Retorna: true | false
|
|
```
|
|
|
|
**Lógica:**
|
|
```sql
|
|
-- 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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION gamilit.initialize_user_stats()
|
|
RETURNS TRIGGER
|
|
```
|
|
|
|
**Parámetros:** Ninguno (función trigger)
|
|
|
|
**Retorno:** `TRIGGER`
|
|
|
|
**Uso:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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():**
|
|
```sql
|
|
-- 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:**
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION gamilit.update_updated_at_column()
|
|
RETURNS TRIGGER
|
|
```
|
|
|
|
**Parámetros:** Ninguno (función trigger)
|
|
|
|
**Retorno:** `TRIGGER`
|
|
|
|
**Comportamiento:**
|
|
```sql
|
|
-- En cada UPDATE, automáticamente:
|
|
NEW.updated_at = gamilit.now_mexico();
|
|
RETURN NEW;
|
|
```
|
|
|
|
**Uso:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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:**
|
|
```regex
|
|
^[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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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:**
|
|
```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:**
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION gamilit.update_classroom_member_count()
|
|
RETURNS TRIGGER
|
|
```
|
|
|
|
**Parámetros:** Ninguno (función trigger)
|
|
|
|
**Retorno:** `TRIGGER`
|
|
|
|
**Comportamiento:**
|
|
```sql
|
|
-- 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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
-- Llamada desde backend al hacer login exitoso:
|
|
SELECT gamilit.update_user_last_login('550e8400-e29b-41d4-a716-446655440000');
|
|
```
|
|
|
|
**Comportamiento:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION gamilit.update_user_stats_on_exercise_complete()
|
|
RETURNS TRIGGER
|
|
```
|
|
|
|
**Parámetros:** Ninguno (función trigger)
|
|
|
|
**Retorno:** `TRIGGER`
|
|
|
|
**Comportamiento:**
|
|
```sql
|
|
-- 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:**
|
|
```sql
|
|
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:**
|
|
```sql
|
|
-- 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:**
|
|
```sql
|
|
-- ✅ 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:**
|
|
```sql
|
|
-- ✅ 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:**
|
|
```sql
|
|
-- ✅ 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
|