- 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>
1452 lines
40 KiB
Markdown
1452 lines
40 KiB
Markdown
# Funciones Utilitarias del Schema `public`
|
|
|
|
**Schema:** `public`
|
|
**Propósito:** Funciones utilitarias compartidas del sistema (mantenimiento, feature flags, logging, validaciones)
|
|
**Cantidad:** 7 funciones
|
|
**Estado:** ✅ Documentadas
|
|
**Última actualización:** 2025-11-08
|
|
|
|
---
|
|
|
|
## 📋 Índice
|
|
|
|
1. [Resumen Ejecutivo](#resumen-ejecutivo)
|
|
2. [Funciones por Categoría](#funciones-por-categoría)
|
|
3. [Documentación Detallada](#documentación-detallada)
|
|
4. [Diagrama de Dependencias](#diagrama-de-dependencias)
|
|
5. [Mejores Prácticas](#mejores-prácticas)
|
|
6. [Consideraciones de Seguridad](#consideraciones-de-seguridad)
|
|
7. [Performance y Optimización](#performance-y-optimización)
|
|
|
|
---
|
|
|
|
## 📊 Resumen Ejecutivo
|
|
|
|
El schema `public` contiene **7 funciones utilitarias** críticas para el sistema, organizadas en 4 categorías funcionales:
|
|
|
|
| Categoría | Funciones | Uso Principal |
|
|
|-----------|-----------|---------------|
|
|
| **Mantenimiento** | 2 | Limpieza automática de logs antiguos |
|
|
| **Feature Management** | 2 | Sistema de feature flags y A/B testing |
|
|
| **Logging/Notifications** | 2 | Registro de eventos y notificaciones a usuarios |
|
|
| **Validación** | 1 | Validación de rangos de fechas |
|
|
|
|
**Todas las funciones tienen:**
|
|
- ✅ `SECURITY DEFINER` para control de permisos
|
|
- ✅ `SET search_path` para seguridad
|
|
- ✅ Manejo de excepciones robusto
|
|
- ✅ Documentación inline con COMMENT ON
|
|
- ✅ Ejemplos de uso
|
|
|
|
---
|
|
|
|
## 🗂️ Funciones por Categoría
|
|
|
|
### 1. Mantenimiento y Limpieza (2 funciones)
|
|
|
|
#### 1.1 `cleanup_old_system_logs()`
|
|
**Archivo:** `apps/database/ddl/schemas/public/functions/01-cleanup_old_system_logs.sql`
|
|
**Propósito:** Elimina logs de sistema antiguos para optimización de storage
|
|
|
|
**Firma:**
|
|
```sql
|
|
CREATE FUNCTION public.cleanup_old_system_logs(
|
|
p_retention_days INTEGER DEFAULT 90
|
|
)
|
|
RETURNS TABLE(
|
|
deleted_count INTEGER,
|
|
status_message TEXT
|
|
)
|
|
```
|
|
|
|
**Características:**
|
|
- Retención configurable (default: 90 días)
|
|
- VACUUM ANALYZE automático después de limpieza
|
|
- Logs de auditoría de la operación
|
|
- Retorna conteo de registros eliminados
|
|
|
|
---
|
|
|
|
#### 1.2 `cleanup_old_user_activity()`
|
|
**Archivo:** `apps/database/ddl/schemas/public/functions/02-cleanup_old_user_activity.sql`
|
|
**Propósito:** Elimina registros de actividad de usuario antiguos
|
|
|
|
**Firma:**
|
|
```sql
|
|
CREATE FUNCTION public.cleanup_old_user_activity(
|
|
p_retention_days INTEGER DEFAULT 180
|
|
)
|
|
RETURNS TABLE(
|
|
deleted_count INTEGER,
|
|
status_message TEXT
|
|
)
|
|
```
|
|
|
|
**Características:**
|
|
- Retención más larga (default: 180 días vs 90 para system logs)
|
|
- Optimización automática de tabla (VACUUM ANALYZE)
|
|
- Manejo robusto de errores
|
|
- Útil para compliance (GDPR, retención de datos)
|
|
|
|
---
|
|
|
|
### 2. Feature Management (2 funciones)
|
|
|
|
#### 2.1 `is_feature_enabled()`
|
|
**Archivo:** `apps/database/ddl/schemas/public/functions/03-is_feature_enabled.sql`
|
|
**Propósito:** Verifica si un feature flag está habilitado (con soporte para targeting y A/B testing)
|
|
|
|
**Firma:**
|
|
```sql
|
|
CREATE FUNCTION public.is_feature_enabled(
|
|
p_feature_key TEXT,
|
|
p_user_id UUID DEFAULT NULL
|
|
)
|
|
RETURNS BOOLEAN
|
|
```
|
|
|
|
**Características avanzadas:**
|
|
- ✅ Habilitación global simple
|
|
- ✅ **Whitelist de usuarios** (target_users array)
|
|
- ✅ **Targeting por roles** (target_roles array)
|
|
- ✅ **Gradual rollout** (rollout_percentage 0-100)
|
|
- ✅ **Time windows** (starts_at, ends_at)
|
|
- ✅ **Hashing determinístico** para consistencia de usuario
|
|
|
|
**Algoritmo de evaluación:**
|
|
```
|
|
1. ¿Feature existe y está enabled globalmente? → Si NO: return FALSE
|
|
2. ¿Está fuera del time window? → Si SÍ: return FALSE
|
|
3. ¿user_id es NULL? → Si SÍ: return TRUE (global enabled)
|
|
4. ¿User está en whitelist (target_users)? → Si SÍ: return TRUE
|
|
5. ¿target_roles especificado?
|
|
- Si SÍ: ¿User tiene rol permitido? → return TRUE/FALSE según match
|
|
- Si NO: continuar
|
|
6. ¿rollout_percentage < 100?
|
|
- Si SÍ: hash(user_id) % 100 < rollout_percentage → return TRUE/FALSE
|
|
- Si NO: return TRUE
|
|
```
|
|
|
|
---
|
|
|
|
#### 2.2 `update_feature_flag()`
|
|
**Archivo:** `apps/database/ddl/schemas/public/functions/06-update_feature_flag.sql`
|
|
**Propósito:** Actualiza o crea feature flags con configuración de rollout
|
|
|
|
**Firma:**
|
|
```sql
|
|
CREATE FUNCTION public.update_feature_flag(
|
|
p_feature_key TEXT,
|
|
p_enabled BOOLEAN,
|
|
p_rollout_percentage INTEGER DEFAULT 100,
|
|
p_description TEXT DEFAULT NULL
|
|
)
|
|
RETURNS TABLE(
|
|
feature_id UUID,
|
|
key TEXT,
|
|
enabled BOOLEAN,
|
|
rollout_percentage INTEGER,
|
|
status_message TEXT
|
|
)
|
|
```
|
|
|
|
**Características:**
|
|
- **Upsert automático** (UPDATE o INSERT según exista)
|
|
- Validación de rollout_percentage (0-100)
|
|
- Logging automático del cambio
|
|
- Retorna estado completo del feature flag
|
|
|
|
---
|
|
|
|
### 3. Logging y Notificaciones (2 funciones)
|
|
|
|
#### 3.1 `log_system_event()`
|
|
**Archivo:** `apps/database/ddl/schemas/public/functions/04-log_system_event.sql`
|
|
**Propósito:** Registra eventos del sistema para auditoría y monitoreo
|
|
|
|
**Firma:**
|
|
```sql
|
|
CREATE FUNCTION public.log_system_event(
|
|
p_event_type TEXT,
|
|
p_event_source TEXT,
|
|
p_event_data JSONB DEFAULT NULL,
|
|
p_severity TEXT DEFAULT 'INFO'
|
|
)
|
|
RETURNS UUID
|
|
```
|
|
|
|
**Niveles de severidad:**
|
|
- `DEBUG` - Información de debugging detallada
|
|
- `INFO` - Eventos informativos normales
|
|
- `WARNING` - Advertencias (no críticas)
|
|
- `ERROR` - Errores que requieren atención
|
|
- `CRITICAL` - Errores críticos del sistema
|
|
|
|
**Características:**
|
|
- Almacenamiento en `audit_logging.system_logs`
|
|
- Metadata flexible (JSONB)
|
|
- Validación de severity level
|
|
- Retorna UUID del evento creado
|
|
|
|
---
|
|
|
|
#### 3.2 `send_notification()`
|
|
**Archivo:** `apps/database/ddl/schemas/public/functions/05-send_notification.sql`
|
|
**Propósito:** Envía notificaciones a usuarios a través de múltiples canales
|
|
|
|
**Firma:**
|
|
```sql
|
|
CREATE FUNCTION public.send_notification(
|
|
p_user_id UUID,
|
|
p_title TEXT,
|
|
p_message TEXT,
|
|
p_notification_type TEXT,
|
|
p_delivery_channels TEXT[] DEFAULT ARRAY['IN_APP'],
|
|
p_metadata JSONB DEFAULT NULL
|
|
)
|
|
RETURNS UUID
|
|
```
|
|
|
|
**Tipos de notificación:**
|
|
- `ASSIGNMENT` - Nueva asignación de maestro
|
|
- `ACHIEVEMENT` - Logro desbloqueado
|
|
- `SYSTEM` - Notificación del sistema
|
|
- `ALERT` - Alerta urgente
|
|
- `MESSAGE` - Mensaje directo
|
|
|
|
**Canales de entrega:**
|
|
- `IN_APP` - Notificación in-app (default)
|
|
- `EMAIL` - Email
|
|
- `SMS` - SMS (futuro)
|
|
- `PUSH` - Push notification (futuro)
|
|
|
|
**Características:**
|
|
- Almacena en `gamification_system.notifications`
|
|
- Crea entradas en `notification_delivery_queue` por cada canal
|
|
- Logging automático del envío
|
|
- Metadata JSONB para contexto adicional
|
|
|
|
---
|
|
|
|
### 4. Validación (1 función)
|
|
|
|
#### 4.1 `validate_date_range()`
|
|
**Archivo:** `apps/database/ddl/schemas/public/functions/07-validate_date_range.sql`
|
|
**Propósito:** Valida rangos de fechas para lógica y restricciones de duración
|
|
|
|
**Firma:**
|
|
```sql
|
|
CREATE FUNCTION public.validate_date_range(
|
|
p_start_date TIMESTAMP WITHOUT TIME ZONE,
|
|
p_end_date TIMESTAMP WITHOUT TIME ZONE,
|
|
p_max_range_days INTEGER DEFAULT 365
|
|
)
|
|
RETURNS TABLE(
|
|
is_valid BOOLEAN,
|
|
validation_message TEXT,
|
|
days_in_range INTEGER
|
|
)
|
|
```
|
|
|
|
**Validaciones aplicadas:**
|
|
1. ✅ Ninguna fecha puede ser NULL
|
|
2. ✅ start_date debe ser <= end_date
|
|
3. ✅ Rango no puede exceder `p_max_range_days`
|
|
4. ✅ Permite rangos en el futuro (para scheduling)
|
|
|
|
**Casos de uso:**
|
|
- Validar rangos de assignments (due_date)
|
|
- Validar rangos de reportes
|
|
- Validar time windows de feature flags
|
|
- Validar períodos de campañas/eventos
|
|
|
|
---
|
|
|
|
## 📋 Documentación Detallada
|
|
|
|
### Función: `cleanup_old_system_logs()`
|
|
|
|
**Descripción completa:**
|
|
Función de mantenimiento que elimina logs de sistema antiguos del schema `audit_logging` para optimizar almacenamiento y performance. Ejecuta VACUUM ANALYZE automáticamente después de la eliminación para recuperar espacio.
|
|
|
|
**Parámetros:**
|
|
| Parámetro | Tipo | Default | Descripción |
|
|
|-----------|------|---------|-------------|
|
|
| `p_retention_days` | INTEGER | 90 | Días de retención de logs |
|
|
|
|
**Retorno:**
|
|
| Campo | Tipo | Descripción |
|
|
|-------|------|-------------|
|
|
| `deleted_count` | INTEGER | Número de registros eliminados |
|
|
| `status_message` | TEXT | Mensaje de estado de la operación |
|
|
|
|
**Uso:**
|
|
```sql
|
|
-- Limpiar logs con retención default (90 días)
|
|
SELECT * FROM public.cleanup_old_system_logs();
|
|
|
|
-- Limpiar logs con retención custom (30 días)
|
|
SELECT * FROM public.cleanup_old_system_logs(30);
|
|
|
|
-- Retención extendida para auditoría (365 días)
|
|
SELECT * FROM public.cleanup_old_system_logs(365);
|
|
```
|
|
|
|
**Resultado esperado:**
|
|
```
|
|
deleted_count | status_message
|
|
--------------+---------------------------------------------------------------
|
|
15247 | Successfully deleted 15247 log entries older than 90 days
|
|
```
|
|
|
|
**Uso en cron jobs:**
|
|
```sql
|
|
-- Ejecutar semanalmente (Domingo 3am)
|
|
-- pg_cron example:
|
|
SELECT cron.schedule(
|
|
'cleanup-system-logs-weekly',
|
|
'0 3 * * 0', -- Domingo 3am
|
|
$$SELECT public.cleanup_old_system_logs(90)$$
|
|
);
|
|
```
|
|
|
|
**Schemas afectados:**
|
|
- `audit_logging.system_logs` (DELETE + VACUUM)
|
|
|
|
---
|
|
|
|
### Función: `cleanup_old_user_activity()`
|
|
|
|
**Descripción completa:**
|
|
Función de mantenimiento para eliminar registros antiguos de actividad de usuario. Útil para cumplir con regulaciones de retención de datos (GDPR) y optimizar performance de queries.
|
|
|
|
**Parámetros:**
|
|
| Parámetro | Tipo | Default | Descripción |
|
|
|-----------|------|---------|-------------|
|
|
| `p_retention_days` | INTEGER | 180 | Días de retención de activity logs |
|
|
|
|
**Retorno:**
|
|
| Campo | Tipo | Descripción |
|
|
|-------|------|-------------|
|
|
| `deleted_count` | INTEGER | Número de registros eliminados |
|
|
| `status_message` | TEXT | Mensaje de estado de la operación |
|
|
|
|
**Uso:**
|
|
```sql
|
|
-- Limpiar activity con retención default (180 días = 6 meses)
|
|
SELECT * FROM public.cleanup_old_user_activity();
|
|
|
|
-- Retención mínima legal (90 días)
|
|
SELECT * FROM public.cleanup_old_user_activity(90);
|
|
|
|
-- Retención extendida (2 años)
|
|
SELECT * FROM public.cleanup_old_user_activity(730);
|
|
```
|
|
|
|
**Consideraciones GDPR:**
|
|
- Default de 180 días cumple con mayoría de regulaciones
|
|
- Para compliance estricto, usar 90 días
|
|
- Documentar política de retención en términos de servicio
|
|
|
|
**Uso en cron jobs:**
|
|
```sql
|
|
-- Ejecutar mensualmente (1er día del mes, 4am)
|
|
SELECT cron.schedule(
|
|
'cleanup-user-activity-monthly',
|
|
'0 4 1 * *', -- 1er día de mes, 4am
|
|
$$SELECT public.cleanup_old_user_activity(180)$$
|
|
);
|
|
```
|
|
|
|
**Schemas afectados:**
|
|
- `audit_logging.user_activity_logs` (DELETE + VACUUM)
|
|
|
|
---
|
|
|
|
### Función: `is_feature_enabled()`
|
|
|
|
**Descripción completa:**
|
|
Sistema avanzado de feature flags con soporte para:
|
|
- Habilitación/deshabilitación global
|
|
- Targeting por usuarios específicos (whitelist)
|
|
- Targeting por roles
|
|
- Gradual rollout (A/B testing)
|
|
- Time windows (scheduling)
|
|
|
|
**Parámetros:**
|
|
| Parámetro | Tipo | Default | Descripción |
|
|
|-----------|------|---------|-------------|
|
|
| `p_feature_key` | TEXT | - | Clave única del feature flag |
|
|
| `p_user_id` | UUID | NULL | Usuario para verificación (opcional) |
|
|
|
|
**Retorno:** BOOLEAN
|
|
|
|
**Casos de uso:**
|
|
|
|
#### Caso 1: Feature global simple
|
|
```sql
|
|
-- Habilitar nuevo dashboard para todos
|
|
INSERT INTO system_configuration.feature_flags (feature_key, is_enabled)
|
|
VALUES ('new_dashboard', TRUE);
|
|
|
|
-- Verificar si está habilitado globalmente
|
|
SELECT is_feature_enabled('new_dashboard');
|
|
-- Returns: TRUE
|
|
```
|
|
|
|
#### Caso 2: Beta testing con usuarios específicos
|
|
```sql
|
|
-- Habilitar feature solo para beta testers
|
|
INSERT INTO system_configuration.feature_flags (
|
|
feature_key,
|
|
is_enabled,
|
|
target_users
|
|
) VALUES (
|
|
'beta_analytics',
|
|
TRUE,
|
|
ARRAY['uuid-beta-user-1'::UUID, 'uuid-beta-user-2'::UUID]
|
|
);
|
|
|
|
-- Verificar para beta user
|
|
SELECT is_feature_enabled('beta_analytics', 'uuid-beta-user-1'::UUID);
|
|
-- Returns: TRUE
|
|
|
|
-- Verificar para usuario normal
|
|
SELECT is_feature_enabled('beta_analytics', 'uuid-normal-user'::UUID);
|
|
-- Returns: FALSE
|
|
```
|
|
|
|
#### Caso 3: Gradual rollout (10% → 50% → 100%)
|
|
```sql
|
|
-- Fase 1: Rollout al 10% de usuarios
|
|
UPDATE system_configuration.feature_flags
|
|
SET rollout_percentage = 10
|
|
WHERE feature_key = 'new_exercise_ui';
|
|
|
|
-- Fase 2: Incrementar a 50% (después de 1 semana)
|
|
UPDATE system_configuration.feature_flags
|
|
SET rollout_percentage = 50
|
|
WHERE feature_key = 'new_exercise_ui';
|
|
|
|
-- Fase 3: Full rollout (100%)
|
|
UPDATE system_configuration.feature_flags
|
|
SET rollout_percentage = 100
|
|
WHERE feature_key = 'new_exercise_ui';
|
|
|
|
-- Verificación (consistente para el mismo usuario)
|
|
SELECT is_feature_enabled('new_exercise_ui', user_id);
|
|
-- User hash determina si ve el feature (consistente entre llamadas)
|
|
```
|
|
|
|
#### Caso 4: Feature solo para maestros
|
|
```sql
|
|
-- Feature exclusivo para teachers y admin_teachers
|
|
INSERT INTO system_configuration.feature_flags (
|
|
feature_key,
|
|
is_enabled,
|
|
target_roles
|
|
) VALUES (
|
|
'advanced_grading_tools',
|
|
TRUE,
|
|
ARRAY['teacher', 'admin_teacher']::auth_management.gamilit_role[]
|
|
);
|
|
|
|
-- Verificar para teacher
|
|
SELECT is_feature_enabled('advanced_grading_tools', teacher_user_id);
|
|
-- Returns: TRUE
|
|
|
|
-- Verificar para student
|
|
SELECT is_feature_enabled('advanced_grading_tools', student_user_id);
|
|
-- Returns: FALSE
|
|
```
|
|
|
|
#### Caso 5: Time-limited feature (evento/campaña)
|
|
```sql
|
|
-- Feature activo solo durante Diciembre (campaña navideña)
|
|
INSERT INTO system_configuration.feature_flags (
|
|
feature_key,
|
|
is_enabled,
|
|
starts_at,
|
|
ends_at
|
|
) VALUES (
|
|
'winter_event_2025',
|
|
TRUE,
|
|
'2025-12-01 00:00:00',
|
|
'2025-12-31 23:59:59'
|
|
);
|
|
|
|
-- Verificación automática de time window
|
|
SELECT is_feature_enabled('winter_event_2025');
|
|
-- Returns: TRUE solo entre dic 1-31, 2025
|
|
```
|
|
|
|
**Uso en backend:**
|
|
```typescript
|
|
// apps/backend/src/modules/feature-flags/feature-flags.service.ts
|
|
|
|
async isFeatureEnabled(featureKey: string, userId?: string): Promise<boolean> {
|
|
const result = await this.db.query(
|
|
`SELECT public.is_feature_enabled($1, $2)`,
|
|
[featureKey, userId]
|
|
);
|
|
return result.rows[0].is_feature_enabled;
|
|
}
|
|
|
|
// Ejemplo de uso
|
|
if (await this.featureFlags.isFeatureEnabled('new_dashboard', user.id)) {
|
|
// Mostrar nuevo dashboard
|
|
return this.renderNewDashboard();
|
|
} else {
|
|
// Mostrar dashboard legacy
|
|
return this.renderLegacyDashboard();
|
|
}
|
|
```
|
|
|
|
**Schemas utilizados:**
|
|
- `system_configuration.feature_flags` (READ)
|
|
- `auth_management.user_roles` (READ)
|
|
|
|
---
|
|
|
|
### Función: `update_feature_flag()`
|
|
|
|
**Descripción completa:**
|
|
Gestión de feature flags con upsert automático (crea si no existe, actualiza si existe). Incluye logging automático de cambios para auditoría.
|
|
|
|
**Parámetros:**
|
|
| Parámetro | Tipo | Default | Descripción |
|
|
|-----------|------|---------|-------------|
|
|
| `p_feature_key` | TEXT | - | Clave única del feature |
|
|
| `p_enabled` | BOOLEAN | - | Estado enabled/disabled |
|
|
| `p_rollout_percentage` | INTEGER | 100 | Porcentaje de rollout (0-100) |
|
|
| `p_description` | TEXT | NULL | Descripción del feature |
|
|
|
|
**Retorno:**
|
|
| Campo | Tipo | Descripción |
|
|
|-------|------|-------------|
|
|
| `feature_id` | UUID | ID del feature flag |
|
|
| `key` | TEXT | Clave del feature |
|
|
| `enabled` | BOOLEAN | Estado actual |
|
|
| `rollout_percentage` | INTEGER | Porcentaje actual |
|
|
| `status_message` | TEXT | Mensaje de resultado |
|
|
|
|
**Uso:**
|
|
|
|
#### Crear nuevo feature flag
|
|
```sql
|
|
SELECT * FROM public.update_feature_flag(
|
|
'dark_mode',
|
|
TRUE,
|
|
100,
|
|
'Dark mode theme for UI'
|
|
);
|
|
|
|
-- Resultado:
|
|
-- feature_id | key | enabled | rollout_percentage | status_message
|
|
-- -----------+-----------+---------+--------------------+--------------------------------
|
|
-- uuid-xxx | dark_mode | TRUE | 100 | Feature flag 'dark_mode' created
|
|
```
|
|
|
|
#### Actualizar feature existente
|
|
```sql
|
|
-- Reducir rollout a 50% (gradual rollout)
|
|
SELECT * FROM public.update_feature_flag(
|
|
'dark_mode',
|
|
TRUE,
|
|
50,
|
|
NULL -- Mantiene descripción existente
|
|
);
|
|
```
|
|
|
|
#### Desactivar feature urgentemente
|
|
```sql
|
|
-- Kill switch: desactivar feature con problemas
|
|
SELECT * FROM public.update_feature_flag(
|
|
'problematic_feature',
|
|
FALSE,
|
|
0,
|
|
'Disabled due to production bug'
|
|
);
|
|
```
|
|
|
|
**Uso en admin dashboard:**
|
|
```typescript
|
|
// apps/backend/src/modules/admin/feature-flags.controller.ts
|
|
|
|
@Patch('/feature-flags/:key')
|
|
async updateFeatureFlag(
|
|
@Param('key') key: string,
|
|
@Body() dto: UpdateFeatureFlagDto
|
|
) {
|
|
return await this.db.query(
|
|
`SELECT * FROM public.update_feature_flag($1, $2, $3, $4)`,
|
|
[key, dto.enabled, dto.rolloutPercentage, dto.description]
|
|
);
|
|
}
|
|
```
|
|
|
|
**Schemas afectados:**
|
|
- `system_configuration.feature_flags` (INSERT/UPDATE)
|
|
- `audit_logging.system_logs` (INSERT - logging automático)
|
|
|
|
---
|
|
|
|
### Función: `log_system_event()`
|
|
|
|
**Descripción completa:**
|
|
Sistema centralizado de logging para eventos del sistema. Almacena eventos en `audit_logging.system_logs` con metadata JSONB flexible.
|
|
|
|
**Parámetros:**
|
|
| Parámetro | Tipo | Default | Descripción |
|
|
|-----------|------|---------|-------------|
|
|
| `p_event_type` | TEXT | - | Tipo de evento |
|
|
| `p_event_source` | TEXT | - | Sistema/componente origen |
|
|
| `p_event_data` | JSONB | NULL | Metadata del evento |
|
|
| `p_severity` | TEXT | 'INFO' | Nivel de severidad |
|
|
|
|
**Retorno:** UUID (ID del evento creado)
|
|
|
|
**Uso por severidad:**
|
|
|
|
#### DEBUG - Información detallada
|
|
```sql
|
|
SELECT log_system_event(
|
|
'CACHE_MISS',
|
|
'cache_service',
|
|
jsonb_build_object(
|
|
'cache_key', 'user:123:profile',
|
|
'ttl_remaining', 0
|
|
),
|
|
'DEBUG'
|
|
);
|
|
```
|
|
|
|
#### INFO - Eventos normales
|
|
```sql
|
|
SELECT log_system_event(
|
|
'USER_LOGIN_SUCCESS',
|
|
'auth_service',
|
|
jsonb_build_object(
|
|
'user_id', '550e8400-e29b-41d4-a716-446655440000',
|
|
'ip_address', '192.168.1.100',
|
|
'user_agent', 'Mozilla/5.0...'
|
|
),
|
|
'INFO'
|
|
);
|
|
```
|
|
|
|
#### WARNING - Advertencias
|
|
```sql
|
|
SELECT log_system_event(
|
|
'API_RATE_LIMIT_APPROACHING',
|
|
'api_gateway',
|
|
jsonb_build_object(
|
|
'user_id', '550e8400-e29b-41d4-a716-446655440000',
|
|
'requests_count', 95,
|
|
'limit', 100,
|
|
'window', '1 minute'
|
|
),
|
|
'WARNING'
|
|
);
|
|
```
|
|
|
|
#### ERROR - Errores
|
|
```sql
|
|
SELECT log_system_event(
|
|
'DATABASE_CONNECTION_FAILED',
|
|
'connection_pool',
|
|
jsonb_build_object(
|
|
'error', 'Connection timeout',
|
|
'pool_size', 20,
|
|
'available_connections', 0
|
|
),
|
|
'ERROR'
|
|
);
|
|
```
|
|
|
|
#### CRITICAL - Errores críticos
|
|
```sql
|
|
SELECT log_system_event(
|
|
'PAYMENT_GATEWAY_DOWN',
|
|
'payment_service',
|
|
jsonb_build_object(
|
|
'gateway', 'stripe',
|
|
'last_successful_ping', '2025-11-08 10:30:00',
|
|
'consecutive_failures', 5
|
|
),
|
|
'CRITICAL'
|
|
);
|
|
```
|
|
|
|
**Uso en application code:**
|
|
```typescript
|
|
// apps/backend/src/common/logging/system-logger.service.ts
|
|
|
|
async logEvent(
|
|
eventType: string,
|
|
source: string,
|
|
data?: object,
|
|
severity: 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL' = 'INFO'
|
|
) {
|
|
return await this.db.query(
|
|
`SELECT public.log_system_event($1, $2, $3, $4)`,
|
|
[eventType, source, data ? JSON.stringify(data) : null, severity]
|
|
);
|
|
}
|
|
```
|
|
|
|
**Queries útiles para monitoring:**
|
|
```sql
|
|
-- Últimos 100 eventos críticos
|
|
SELECT event_type, event_source, event_data, created_at
|
|
FROM audit_logging.system_logs
|
|
WHERE severity = 'CRITICAL'
|
|
ORDER BY created_at DESC
|
|
LIMIT 100;
|
|
|
|
-- Conteo de errores por source en última hora
|
|
SELECT event_source, COUNT(*) as error_count
|
|
FROM audit_logging.system_logs
|
|
WHERE severity IN ('ERROR', 'CRITICAL')
|
|
AND created_at > NOW() - INTERVAL '1 hour'
|
|
GROUP BY event_source
|
|
ORDER BY error_count DESC;
|
|
|
|
-- Timeline de eventos de un usuario
|
|
SELECT event_type, severity, created_at
|
|
FROM audit_logging.system_logs
|
|
WHERE event_data->>'user_id' = '550e8400-e29b-41d4-a716-446655440000'
|
|
ORDER BY created_at DESC;
|
|
```
|
|
|
|
**Schemas afectados:**
|
|
- `audit_logging.system_logs` (INSERT)
|
|
|
|
---
|
|
|
|
### Función: `send_notification()`
|
|
|
|
**Descripción completa:**
|
|
Sistema multi-canal de notificaciones a usuarios. Crea notificación en BD y la encola para entrega por canales configurados (IN_APP, EMAIL, SMS, PUSH).
|
|
|
|
**Parámetros:**
|
|
| Parámetro | Tipo | Default | Descripción |
|
|
|-----------|------|---------|-------------|
|
|
| `p_user_id` | UUID | - | Usuario destinatario |
|
|
| `p_title` | TEXT | - | Título de notificación |
|
|
| `p_message` | TEXT | - | Mensaje/cuerpo |
|
|
| `p_notification_type` | TEXT | - | Tipo de notificación |
|
|
| `p_delivery_channels` | TEXT[] | ['IN_APP'] | Canales de entrega |
|
|
| `p_metadata` | JSONB | NULL | Metadata adicional |
|
|
|
|
**Retorno:** UUID (ID de la notificación creada)
|
|
|
|
**Uso por tipo:**
|
|
|
|
#### ASSIGNMENT - Nueva tarea asignada
|
|
```sql
|
|
SELECT send_notification(
|
|
student_id,
|
|
'Nueva Tarea de Matemáticas',
|
|
'Tu maestro ha asignado una nueva tarea de fracciones. Fecha límite: Viernes',
|
|
'ASSIGNMENT',
|
|
ARRAY['IN_APP', 'EMAIL'],
|
|
jsonb_build_object(
|
|
'assignment_id', assignment_id,
|
|
'teacher_name', 'Prof. García',
|
|
'subject', 'Matemáticas',
|
|
'due_date', '2025-11-15'
|
|
)
|
|
);
|
|
```
|
|
|
|
#### ACHIEVEMENT - Logro desbloqueado
|
|
```sql
|
|
SELECT send_notification(
|
|
student_id,
|
|
'🏆 ¡Logro Desbloqueado!',
|
|
'Has ganado la insignia "Maestro de la Lectura" por completar 50 ejercicios',
|
|
'ACHIEVEMENT',
|
|
ARRAY['IN_APP', 'PUSH'],
|
|
jsonb_build_object(
|
|
'achievement_id', achievement_id,
|
|
'badge_name', 'Maestro de la Lectura',
|
|
'xp_earned', 500,
|
|
'ml_coins_earned', 100
|
|
)
|
|
);
|
|
```
|
|
|
|
#### SYSTEM - Mantenimiento programado
|
|
```sql
|
|
SELECT send_notification(
|
|
user_id,
|
|
'Mantenimiento Programado',
|
|
'El sistema estará en mantenimiento el Domingo 10am-12pm. Guarda tu progreso.',
|
|
'SYSTEM',
|
|
ARRAY['IN_APP', 'EMAIL'],
|
|
jsonb_build_object(
|
|
'maintenance_start', '2025-11-10 10:00:00',
|
|
'maintenance_end', '2025-11-10 12:00:00',
|
|
'affected_services', ['ejercicios', 'dashboard']
|
|
)
|
|
);
|
|
```
|
|
|
|
#### ALERT - Advertencia importante
|
|
```sql
|
|
SELECT send_notification(
|
|
parent_id,
|
|
'⚠️ Actividad Inusual Detectada',
|
|
'Se detectó un intento de inicio de sesión desde ubicación no reconocida',
|
|
'ALERT',
|
|
ARRAY['IN_APP', 'EMAIL', 'SMS'],
|
|
jsonb_build_object(
|
|
'ip_address', '123.45.67.89',
|
|
'location', 'Ciudad desconocida',
|
|
'timestamp', NOW()
|
|
)
|
|
);
|
|
```
|
|
|
|
#### MESSAGE - Mensaje directo
|
|
```sql
|
|
SELECT send_notification(
|
|
student_id,
|
|
'Mensaje de tu Maestro',
|
|
'Excelente trabajo en la última tarea. Sigue así!',
|
|
'MESSAGE',
|
|
ARRAY['IN_APP'],
|
|
jsonb_build_object(
|
|
'from_user_id', teacher_id,
|
|
'from_name', 'Prof. García',
|
|
'message_id', message_id
|
|
)
|
|
);
|
|
```
|
|
|
|
**Notificación masiva (todos los estudiantes de un classroom):**
|
|
```sql
|
|
-- Notificar a todos los estudiantes de un classroom
|
|
DO $$
|
|
DECLARE
|
|
r RECORD;
|
|
BEGIN
|
|
FOR r IN
|
|
SELECT student_id
|
|
FROM social_features.classroom_members
|
|
WHERE classroom_id = 'classroom-uuid'
|
|
LOOP
|
|
PERFORM send_notification(
|
|
r.student_id,
|
|
'Nuevo Anuncio del Classroom',
|
|
'El maestro ha publicado un nuevo anuncio',
|
|
'SYSTEM',
|
|
ARRAY['IN_APP', 'EMAIL']
|
|
);
|
|
END LOOP;
|
|
END $$;
|
|
```
|
|
|
|
**Uso en backend:**
|
|
```typescript
|
|
// apps/backend/src/modules/notifications/notifications.service.ts
|
|
|
|
async notifyAssignmentCreated(
|
|
studentId: string,
|
|
assignment: Assignment,
|
|
teacher: User
|
|
) {
|
|
return await this.db.query(
|
|
`SELECT public.send_notification($1, $2, $3, $4, $5, $6)`,
|
|
[
|
|
studentId,
|
|
'Nueva Tarea Asignada',
|
|
`${teacher.name} te ha asignado: ${assignment.title}`,
|
|
'ASSIGNMENT',
|
|
['IN_APP', 'EMAIL'],
|
|
JSON.stringify({
|
|
assignment_id: assignment.id,
|
|
teacher_name: teacher.name,
|
|
due_date: assignment.dueDate
|
|
})
|
|
]
|
|
);
|
|
}
|
|
```
|
|
|
|
**Procesamiento de cola de entrega:**
|
|
```sql
|
|
-- Worker process que envía emails/SMS/push basado en queue
|
|
SELECT
|
|
ndq.id,
|
|
ndq.notification_id,
|
|
ndq.delivery_channel,
|
|
n.title,
|
|
n.message,
|
|
u.email,
|
|
u.phone
|
|
FROM gamification_system.notification_delivery_queue ndq
|
|
JOIN gamification_system.notifications n ON ndq.notification_id = n.id
|
|
JOIN auth.users u ON n.user_id = u.id
|
|
WHERE ndq.status = 'PENDING'
|
|
AND ndq.delivery_channel = 'EMAIL'
|
|
LIMIT 100;
|
|
|
|
-- Marcar como sent después de envío exitoso
|
|
UPDATE gamification_system.notification_delivery_queue
|
|
SET status = 'SENT', sent_at = NOW()
|
|
WHERE id = queue_item_id;
|
|
```
|
|
|
|
**Schemas afectados:**
|
|
- `gamification_system.notifications` (INSERT)
|
|
- `gamification_system.notification_delivery_queue` (INSERT)
|
|
- `audit_logging.system_logs` (INSERT - logging automático)
|
|
|
|
---
|
|
|
|
### Función: `validate_date_range()`
|
|
|
|
**Descripción completa:**
|
|
Validación robusta de rangos de fechas con múltiples reglas de negocio. Útil para prevenir errores de input antes de INSERT/UPDATE.
|
|
|
|
**Parámetros:**
|
|
| Parámetro | Tipo | Default | Descripción |
|
|
|-----------|------|---------|-------------|
|
|
| `p_start_date` | TIMESTAMP | - | Fecha de inicio |
|
|
| `p_end_date` | TIMESTAMP | - | Fecha de fin |
|
|
| `p_max_range_days` | INTEGER | 365 | Duración máxima permitida |
|
|
|
|
**Retorno:**
|
|
| Campo | Tipo | Descripción |
|
|
|-------|------|-------------|
|
|
| `is_valid` | BOOLEAN | TRUE si válido |
|
|
| `validation_message` | TEXT | Mensaje descriptivo |
|
|
| `days_in_range` | INTEGER | Días en el rango |
|
|
|
|
**Casos de validación:**
|
|
|
|
#### Caso 1: Rango válido
|
|
```sql
|
|
SELECT * FROM public.validate_date_range(
|
|
'2025-11-01 00:00:00',
|
|
'2025-11-30 23:59:59',
|
|
365
|
|
);
|
|
|
|
-- Resultado:
|
|
-- is_valid | validation_message | days_in_range
|
|
-- ---------+-------------------------+---------------
|
|
-- TRUE | Date range is valid | 29
|
|
```
|
|
|
|
#### Caso 2: Start date después de end date
|
|
```sql
|
|
SELECT * FROM public.validate_date_range(
|
|
'2025-12-31 00:00:00',
|
|
'2025-01-01 00:00:00',
|
|
365
|
|
);
|
|
|
|
-- Resultado:
|
|
-- is_valid | validation_message | days_in_range
|
|
-- ---------+------------------------------------------------------+---------------
|
|
-- FALSE | Start date cannot be after end date | 0
|
|
```
|
|
|
|
#### Caso 3: Rango excede duración máxima
|
|
```sql
|
|
SELECT * FROM public.validate_date_range(
|
|
'2025-01-01 00:00:00',
|
|
'2026-12-31 00:00:00',
|
|
365 -- Máximo 1 año
|
|
);
|
|
|
|
-- Resultado:
|
|
-- is_valid | validation_message | days_in_range
|
|
-- ---------+-----------------------------------------------+---------------
|
|
-- FALSE | Date range exceeds maximum allowed duration | 729
|
|
```
|
|
|
|
#### Caso 4: Rango en el futuro (válido para scheduling)
|
|
```sql
|
|
SELECT * FROM public.validate_date_range(
|
|
NOW() + INTERVAL '1 day',
|
|
NOW() + INTERVAL '7 days',
|
|
30
|
|
);
|
|
|
|
-- Resultado:
|
|
-- is_valid | validation_message | days_in_range
|
|
-- ---------+----------------------------------------------+---------------
|
|
-- TRUE | Date range is in the future (valid for...) | 6
|
|
```
|
|
|
|
**Uso en triggers:**
|
|
```sql
|
|
-- Trigger para validar assignments antes de INSERT
|
|
CREATE OR REPLACE FUNCTION validate_assignment_dates()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
v_validation RECORD;
|
|
BEGIN
|
|
IF NEW.due_date IS NOT NULL THEN
|
|
SELECT * INTO v_validation
|
|
FROM public.validate_date_range(
|
|
NEW.created_at,
|
|
NEW.due_date,
|
|
180 -- Máximo 6 meses para un assignment
|
|
);
|
|
|
|
IF NOT v_validation.is_valid THEN
|
|
RAISE EXCEPTION 'Invalid assignment dates: %', v_validation.validation_message;
|
|
END IF;
|
|
END IF;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER validate_assignment_dates_trigger
|
|
BEFORE INSERT OR UPDATE ON public.assignments
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_assignment_dates();
|
|
```
|
|
|
|
**Uso en backend:**
|
|
```typescript
|
|
// apps/backend/src/common/validators/date-range.validator.ts
|
|
|
|
async validateDateRange(
|
|
startDate: Date,
|
|
endDate: Date,
|
|
maxDays: number = 365
|
|
): Promise<{ isValid: boolean; message: string; days: number }> {
|
|
const result = await this.db.query(
|
|
`SELECT * FROM public.validate_date_range($1, $2, $3)`,
|
|
[startDate, endDate, maxDays]
|
|
);
|
|
|
|
const row = result.rows[0];
|
|
return {
|
|
isValid: row.is_valid,
|
|
message: row.validation_message,
|
|
days: row.days_in_range
|
|
};
|
|
}
|
|
|
|
// Uso en service
|
|
async createAssignment(dto: CreateAssignmentDto) {
|
|
const validation = await this.dateValidator.validateDateRange(
|
|
new Date(),
|
|
dto.dueDate,
|
|
180 // 6 meses máximo
|
|
);
|
|
|
|
if (!validation.isValid) {
|
|
throw new BadRequestException(validation.message);
|
|
}
|
|
|
|
// Continuar con creación...
|
|
}
|
|
```
|
|
|
|
**Schemas utilizados:**
|
|
- Ninguno (función pura de validación)
|
|
|
|
---
|
|
|
|
## 🔗 Diagrama de Dependencias
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Funciones Utilitarias Public │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
|
|
┌──────────────────────────┐
|
|
│ MANTENIMIENTO (2) │
|
|
├──────────────────────────┤
|
|
│ cleanup_old_system_logs │────► audit_logging.system_logs
|
|
│ │ (DELETE + VACUUM)
|
|
│ cleanup_old_user_activity│────► audit_logging.user_activity_logs
|
|
└──────────────────────────┘ (DELETE + VACUUM)
|
|
|
|
|
|
┌──────────────────────────┐
|
|
│ FEATURE MANAGEMENT (2) │
|
|
├──────────────────────────┤
|
|
│ is_feature_enabled │────► system_configuration.feature_flags (READ)
|
|
│ │────► auth_management.user_roles (READ)
|
|
│ │
|
|
│ update_feature_flag │────► system_configuration.feature_flags (UPSERT)
|
|
│ │────► log_system_event() (CALL)
|
|
└──────────────────────────┘
|
|
|
|
|
|
┌──────────────────────────┐
|
|
│ LOGGING/NOTIF (2) │
|
|
├──────────────────────────┤
|
|
│ log_system_event │────► audit_logging.system_logs (INSERT)
|
|
│ │
|
|
│ send_notification │────► gamification_system.notifications (INSERT)
|
|
│ │────► gamification_system.notification_delivery_queue (INSERT)
|
|
│ │────► log_system_event() (CALL)
|
|
└──────────────────────────┘
|
|
|
|
|
|
┌──────────────────────────┐
|
|
│ VALIDACIÓN (1) │
|
|
├──────────────────────────┤
|
|
│ validate_date_range │ (Función pura, sin dependencias)
|
|
└──────────────────────────┘
|
|
|
|
|
|
DEPENDENCIAS ENTRE FUNCIONES:
|
|
- update_feature_flag() → log_system_event()
|
|
- send_notification() → log_system_event()
|
|
```
|
|
|
|
---
|
|
|
|
## 🎯 Mejores Prácticas
|
|
|
|
### 1. Mantenimiento y Limpieza
|
|
|
|
**✅ DO:**
|
|
- Ejecutar cleanup functions en horarios de bajo tráfico (madrugada)
|
|
- Usar cron jobs/pg_cron para automatización
|
|
- Monitorear espacio recuperado después de VACUUM
|
|
- Documentar políticas de retención
|
|
|
|
**❌ DON'T:**
|
|
- Ejecutar durante horario pico (afecta performance)
|
|
- Usar retención muy corta (< 30 días) sin justificación legal
|
|
- Ignorar errores de cleanup (pueden indicar problemas mayores)
|
|
|
|
**Ejemplo de política de retención:**
|
|
```yaml
|
|
audit_logging.system_logs:
|
|
retention_days: 90
|
|
cleanup_frequency: weekly
|
|
cleanup_schedule: "Sunday 3:00 AM"
|
|
|
|
audit_logging.user_activity_logs:
|
|
retention_days: 180
|
|
cleanup_frequency: monthly
|
|
cleanup_schedule: "1st day of month 4:00 AM"
|
|
legal_minimum: 90 # GDPR compliance
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Feature Flags
|
|
|
|
**✅ DO:**
|
|
- Usar naming convention consistente: `feature_name` (snake_case)
|
|
- Documentar propósito en campo `description`
|
|
- Gradual rollout: 10% → 50% → 100% con monitoring entre fases
|
|
- Mantener feature flags temporales (remover después de full rollout)
|
|
- Logging de cambios de feature flags
|
|
|
|
**❌ DON'T:**
|
|
- Crear feature flags permanentes (usar configuración normal)
|
|
- Cambiar rollout_percentage drásticamente (10% → 100%)
|
|
- Olvidar remover flags después de rollout completo
|
|
- Usar para configuración de sistema (usar system_settings)
|
|
|
|
**Lifecycle de feature flag:**
|
|
```
|
|
1. CREATE → rollout_percentage: 0, is_enabled: FALSE
|
|
2. BETA → rollout_percentage: 0, target_users: [beta_testers]
|
|
3. GRADUAL_10 → rollout_percentage: 10, is_enabled: TRUE
|
|
4. GRADUAL_50 → rollout_percentage: 50 (1 semana después)
|
|
5. FULL_ROLLOUT → rollout_percentage: 100 (1 semana después)
|
|
6. DEPRECATE → Remover flag del código
|
|
7. DELETE → Eliminar de BD (1 mes después)
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Logging y Notificaciones
|
|
|
|
**✅ DO:**
|
|
- Usar severidad apropiada (no todo es ERROR)
|
|
- Incluir context útil en JSONB metadata
|
|
- Estructurar metadata consistentemente
|
|
- Rate limiting para notificaciones (evitar spam)
|
|
- Respetar preferencias de usuario (canales opt-out)
|
|
|
|
**❌ DON'T:**
|
|
- Log de información sensible (passwords, tokens)
|
|
- Notificaciones sin opción de desactivar
|
|
- Metadata excesivamente grande (> 5KB)
|
|
- Logging síncrono que bloquea requests
|
|
|
|
**Estructura recomendada de metadata:**
|
|
```json
|
|
{
|
|
"user_id": "uuid",
|
|
"request_id": "uuid",
|
|
"ip_address": "xxx.xxx.xxx.xxx",
|
|
"user_agent": "string",
|
|
"action": "specific_action",
|
|
"resource_type": "assignment|exercise|user",
|
|
"resource_id": "uuid",
|
|
"timestamp": "ISO8601",
|
|
"duration_ms": 123
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Validación de Fechas
|
|
|
|
**✅ DO:**
|
|
- Validar ANTES de INSERT/UPDATE (prevención)
|
|
- Usar en triggers para validación automática
|
|
- Configurar max_range_days según contexto
|
|
- Proporcionar mensajes de error claros al usuario
|
|
|
|
**❌ DON'T:**
|
|
- Validar solo en frontend (inseguro)
|
|
- Asumir timezone sin normalizar
|
|
- Ignorar edge cases (DST, leap years)
|
|
|
|
**Max range por contexto:**
|
|
```yaml
|
|
assignments:
|
|
max_range_days: 180 # 6 meses máximo
|
|
|
|
feature_flags_time_window:
|
|
max_range_days: 365 # 1 año para campañas
|
|
|
|
reports:
|
|
max_range_days: 730 # 2 años para análisis histórico
|
|
|
|
maintenance_windows:
|
|
max_range_days: 7 # 1 semana máximo
|
|
```
|
|
|
|
---
|
|
|
|
## 🔒 Consideraciones de Seguridad
|
|
|
|
### SECURITY DEFINER
|
|
|
|
**Todas las funciones usan `SECURITY DEFINER`:**
|
|
- Ejecutan con permisos del creador (generalmente superuser)
|
|
- **Riesgo:** Privilege escalation si no se validan inputs
|
|
- **Mitigación:** Validación estricta + `SET search_path`
|
|
|
|
**Validaciones implementadas:**
|
|
- ✅ `is_feature_enabled`: Validación de feature_key (text)
|
|
- ✅ `log_system_event`: Validación de severity (enum check)
|
|
- ✅ `send_notification`: Validación de notification_type (enum check)
|
|
- ✅ `validate_date_range`: Validación de rangos
|
|
- ✅ `update_feature_flag`: Validación de rollout_percentage (0-100)
|
|
|
|
---
|
|
|
|
### SET search_path
|
|
|
|
**Todas las funciones especifican `search_path` explícito:**
|
|
```sql
|
|
SECURITY DEFINER SET search_path = public, audit_logging
|
|
```
|
|
|
|
**Propósito:**
|
|
- Prevenir SQL injection via search_path manipulation
|
|
- Garantizar resolución correcta de schemas
|
|
- Evitar uso de schemas maliciosos
|
|
|
|
---
|
|
|
|
### Manejo de Errores
|
|
|
|
**Todas las funciones tienen bloque EXCEPTION:**
|
|
```sql
|
|
EXCEPTION WHEN OTHERS THEN
|
|
RAISE NOTICE 'Error: %', SQLERRM;
|
|
RETURN FALSE/NULL/0;
|
|
END;
|
|
```
|
|
|
|
**Beneficios:**
|
|
- Fail-safe (no crash, retorna valor seguro)
|
|
- Logging de errores
|
|
- Prevención de information leakage
|
|
|
|
---
|
|
|
|
## ⚡ Performance y Optimización
|
|
|
|
### Cleanup Functions
|
|
|
|
**Impacto:**
|
|
- DELETE masivo puede bloquear tabla
|
|
- VACUUM reconstruye índices
|
|
|
|
**Optimizaciones:**
|
|
- ✅ Ejecutar fuera de horario pico
|
|
- ✅ VACUUM ANALYZE automático (recupera espacio)
|
|
- ✅ WHERE con índice en created_at
|
|
- 🔄 Considerar: DELETE en batches para tablas muy grandes
|
|
|
|
**Ejemplo de batch delete:**
|
|
```sql
|
|
-- Versión optimizada para tablas gigantes (> 10M registros)
|
|
CREATE OR REPLACE FUNCTION cleanup_old_system_logs_batched(
|
|
p_retention_days INTEGER DEFAULT 90,
|
|
p_batch_size INTEGER DEFAULT 10000
|
|
)
|
|
RETURNS TABLE(deleted_count INTEGER, status_message TEXT) AS $$
|
|
DECLARE
|
|
v_total_deleted INTEGER := 0;
|
|
v_batch_deleted INTEGER;
|
|
v_cutoff_date TIMESTAMP;
|
|
BEGIN
|
|
v_cutoff_date := NOW() - (p_retention_days || ' days')::INTERVAL;
|
|
|
|
LOOP
|
|
DELETE FROM audit_logging.system_logs
|
|
WHERE id IN (
|
|
SELECT id FROM audit_logging.system_logs
|
|
WHERE created_at < v_cutoff_date
|
|
LIMIT p_batch_size
|
|
);
|
|
|
|
GET DIAGNOSTICS v_batch_deleted = ROW_COUNT;
|
|
v_total_deleted := v_total_deleted + v_batch_deleted;
|
|
|
|
EXIT WHEN v_batch_deleted = 0;
|
|
|
|
-- Breathe between batches
|
|
PERFORM pg_sleep(0.1);
|
|
END LOOP;
|
|
|
|
VACUUM ANALYZE audit_logging.system_logs;
|
|
|
|
RETURN QUERY SELECT v_total_deleted, FORMAT('Deleted %L records in batches', v_total_deleted);
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
```
|
|
|
|
---
|
|
|
|
### Feature Flags
|
|
|
|
**Optimizaciones:**
|
|
- ✅ Índice en `feature_key` (PK)
|
|
- ✅ Índice en `is_enabled` (filtering)
|
|
- ✅ `STABLE` function (cacheable dentro de transaction)
|
|
- 🔄 Considerar: Cache en Redis para high-traffic features
|
|
|
|
**Cache pattern:**
|
|
```typescript
|
|
// Cache feature flags en Redis (TTL: 5 minutos)
|
|
async isFeatureEnabled(key: string, userId?: string): Promise<boolean> {
|
|
const cacheKey = userId ? `ff:${key}:${userId}` : `ff:${key}`;
|
|
|
|
// Check cache
|
|
const cached = await this.redis.get(cacheKey);
|
|
if (cached !== null) {
|
|
return cached === '1';
|
|
}
|
|
|
|
// Query DB
|
|
const result = await this.db.query(
|
|
`SELECT public.is_feature_enabled($1, $2)`,
|
|
[key, userId]
|
|
);
|
|
|
|
const enabled = result.rows[0].is_feature_enabled;
|
|
|
|
// Cache result (TTL: 5 min)
|
|
await this.redis.setex(cacheKey, 300, enabled ? '1' : '0');
|
|
|
|
return enabled;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Logging Functions
|
|
|
|
**Consideraciones:**
|
|
- INSERT síncrono puede ser lento bajo carga
|
|
- Metadata JSONB flexible pero puede crecer
|
|
|
|
**Optimizaciones:**
|
|
- 🔄 Considerar: Queue asíncrona (RabbitMQ/Redis)
|
|
- 🔄 Considerar: Partitioning de system_logs por fecha
|
|
- ✅ Índice GIN en event_data JSONB
|
|
- ✅ Limitar tamaño de metadata (< 5KB)
|
|
|
|
---
|
|
|
|
## 📝 Resumen de Uso
|
|
|
|
| Función | Frecuencia de Uso | Performance | Uso Recomendado |
|
|
|---------|-------------------|-------------|-----------------|
|
|
| `cleanup_old_system_logs` | Semanal (cron) | Media | Automatizado |
|
|
| `cleanup_old_user_activity` | Mensual (cron) | Media | Automatizado |
|
|
| `is_feature_enabled` | Alta (cada request) | Alta | Con cache |
|
|
| `update_feature_flag` | Baja (admin) | Alta | Manual/Dashboard |
|
|
| `log_system_event` | Muy Alta | Media | Async queue |
|
|
| `send_notification` | Alta | Media | Async queue |
|
|
| `validate_date_range` | Media | Muy Alta | Triggers/Validación |
|
|
|
|
---
|
|
|
|
## 🎯 Próximos Pasos Sugeridos
|
|
|
|
### Corto Plazo
|
|
1. ✅ Implementar cron jobs para cleanup functions
|
|
2. ✅ Crear dashboard admin para feature flags
|
|
3. ✅ Implementar cache Redis para feature flags hot paths
|
|
4. ✅ Configurar alerting para eventos CRITICAL
|
|
|
|
### Mediano Plazo
|
|
1. 📋 Migrar logging a queue asíncrona (performance)
|
|
2. 📋 Implementar partitioning en system_logs por fecha
|
|
3. 📋 Crear índices adicionales en notification_delivery_queue
|
|
4. 📋 Implementar retry logic para failed notifications
|
|
|
|
### Largo Plazo
|
|
1. 🔮 Considerar sistema de observability externo (Datadog, New Relic)
|
|
2. 🔮 Implementar real-time feature flag updates (WebSockets)
|
|
3. 🔮 A/B testing framework completo con métricas
|
|
4. 🔮 Machine learning para rollout percentage optimization
|
|
|
|
---
|
|
|
|
## 🔗 Referencias
|
|
|
|
**Archivos de implementación:**
|
|
- `apps/database/ddl/schemas/public/functions/01-cleanup_old_system_logs.sql`
|
|
- `apps/database/ddl/schemas/public/functions/02-cleanup_old_user_activity.sql`
|
|
- `apps/database/ddl/schemas/public/functions/03-is_feature_enabled.sql`
|
|
- `apps/database/ddl/schemas/public/functions/04-log_system_event.sql`
|
|
- `apps/database/ddl/schemas/public/functions/05-send_notification.sql`
|
|
- `apps/database/ddl/schemas/public/functions/06-update_feature_flag.sql`
|
|
- `apps/database/ddl/schemas/public/functions/07-validate_date_range.sql`
|
|
|
|
**Schemas relacionados:**
|
|
- `audit_logging` (system_logs, user_activity_logs)
|
|
- `system_configuration` (feature_flags)
|
|
- `gamification_system` (notifications, notification_delivery_queue)
|
|
- `auth_management` (user_roles)
|
|
|
|
**Documentación relacionada:**
|
|
- RF-SYS-001: Sistema de Configuración Global
|
|
- RF-SYS-002: Sistema de Feature Flags
|
|
- RF-AUD-001: Sistema de Auditoría
|
|
|
|
---
|
|
|
|
**Creado:** 2025-11-08
|
|
**Tipo:** Documentación retroactiva
|
|
**Estado:** ✅ Documentación completa de 7 funciones utilitarias
|
|
**Issue:** ISSUE-007 (Resuelto)
|