📘 Laboratorio 1: Programación en el Servidor
📘 Laboratorio 1: Programación en el Servidor
🔍 Paso 1: Vistas (Views) en Supabase
1.1 Vista Simple para Usuarios Activos
-- Crear vista para usuarios activos
CREATE VIEW vista_usuarios_activos AS
SELECT
id,
username,
email,
pais,
ciudad,
creado_en
FROM usuarios
WHERE activo = TRUE;
-- Consultar la vista
SELECT * FROM vista_usuarios_activos;
1.2 Vista con JOIN para Resumen de Actividad
-- Vista que combina usuarios con estadísticas de sesiones y mensajes
CREATE VIEW vista_resumen_actividad AS
SELECT
u.id,
u.username,
u.email,
u.pais,
COUNT(DISTINCT s.id) AS total_sesiones,
COUNT(DISTINCT m.id) AS total_mensajes,
MIN(s.iniciada_en) AS primera_sesion,
MAX(s.iniciada_en) AS ultima_sesion,
COUNT(DISTINCT m.modalidad) AS modalidades_usadas
FROM usuarios u
LEFT JOIN sesiones s ON u.id = s.usuario_id
LEFT JOIN mensajes m ON u.id = m.remitente_id
WHERE u.activo = TRUE
GROUP BY u.id, u.username, u.email, u.pais;
-- Consultar la vista
SELECT * FROM vista_resumen_actividad ORDER BY total_mensajes DESC;
1.3 Vista con Datos Sensibles Protegidos
-- Vista que oculta información sensible según rol
-- NOTA: En Supabase, se usa Row Level Security (RLS) en lugar de vistas para seguridad
-- Pero podemos crear una vista con CASE para simular
CREATE VIEW vista_usuarios_segura AS
SELECT
id,
username,
email,
-- Ocultar email parcialmente para usuarios no autenticados
CASE
WHEN current_user = 'authenticated' THEN email
ELSE '***@' || SPLIT_PART(email, '@', 2)
END AS email_parcial,
pais,
ciudad,
creado_en
FROM usuarios;
-- Consultar como usuario anónimo (simulado)
SELECT * FROM vista_usuarios_segura;
1.4 Vista para Análisis de Rendimiento por Módulo
-- Vista que muestra métricas de rendimiento de módulos IA
CREATE VIEW vista_rendimiento_modulos AS
SELECT
l.modulo,
COUNT(*) AS total_ejecuciones,
SUM(CASE WHEN l.exito THEN 1 ELSE 0 END) AS exitos,
ROUND(100.0 * SUM(CASE WHEN l.exito THEN 1 ELSE 0 END) / COUNT(*), 2) AS tasa_exito,
ROUND(AVG(l.tiempo_ejecucion_ms), 2) AS tiempo_promedio_ms,
ROUND(MIN(l.tiempo_ejecucion_ms), 2) AS tiempo_minimo_ms,
ROUND(MAX(l.tiempo_ejecucion_ms), 2) AS tiempo_maximo_ms,
COUNT(DISTINCT l.mensaje_id) AS mensajes_procesados
FROM logs_ejecucion l
GROUP BY l.modulo
ORDER BY tasa_exito DESC;
-- Consultar la vista
SELECT * FROM vista_rendimiento_modulos;
1.5 Vista Materializada para Reportes
-- Vista materializada para reportes diarios (almacena físicamente)
CREATE MATERIALIZED VIEW mv_resumen_diario AS
SELECT
DATE(enviado_en) AS fecha,
modalidad,
COUNT(*) AS total_mensajes,
COUNT(DISTINCT remitente_id) AS usuarios_activos
FROM mensajes
WHERE enviado_en >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE(enviado_en), modalidad
ORDER BY fecha DESC, modalidad;
-- Consultar vista materializada
SELECT * FROM mv_resumen_diario;
-- Actualizar vista materializada (ejecutar periódicamente)
REFRESH MATERIALIZED VIEW mv_resumen_diario;
-- Verificar última actualización
SELECT
matviewname,
pg_size_pretty(pg_total_relation_size('mv_resumen_diario'::regclass)) AS tamaño
FROM pg_matviews
WHERE matviewname = 'mv_resumen_diario';
⚙️ Paso 2: Funciones en PostgreSQL
2.1 Función Escalar - Calcular Nivel de Actividad
-- Función que calcula nivel de actividad de un usuario
CREATE OR REPLACE FUNCTION calcular_nivel_actividad(p_usuario_id INTEGER)
RETURNS TEXT
LANGUAGE plpgsql
AS $$
DECLARE
v_total_mensajes INTEGER;
v_total_sesiones INTEGER;
v_nivel TEXT;
BEGIN
-- Contar mensajes del usuario
SELECT COUNT(*) INTO v_total_mensajes
FROM mensajes
WHERE remitente_id = p_usuario_id;
-- Contar sesiones del usuario
SELECT COUNT(*) INTO v_total_sesiones
FROM sesiones
WHERE usuario_id = p_usuario_id;
-- Determinar nivel
IF v_total_mensajes > 10 AND v_total_sesiones > 3 THEN
v_nivel := 'Alto';
ELSIF v_total_mensajes > 5 OR v_total_sesiones > 1 THEN
v_nivel := 'Medio';
ELSE
v_nivel := 'Bajo';
END IF;
RETURN v_nivel;
END;
$$;
-- Probar función
SELECT
username,
calcular_nivel_actividad(id) AS nivel_actividad
FROM usuarios;
2.2 Función Escalar - Tasa de Éxito por Módulo
-- Función que calcula tasa de éxito de un módulo
CREATE OR REPLACE FUNCTION tasa_exito_modulo(p_modulo TEXT)
RETURNS NUMERIC
LANGUAGE plpgsql
AS $$
DECLARE
v_total INTEGER;
v_exitos INTEGER;
v_tasa NUMERIC;
BEGIN
SELECT
COUNT(*),
COUNT(*) FILTER (WHERE exito = TRUE)
INTO v_total, v_exitos
FROM logs_ejecucion
WHERE modulo = p_modulo;
IF v_total = 0 THEN
RETURN NULL;
END IF;
v_tasa := (v_exitos::NUMERIC / v_total) * 100;
RETURN ROUND(v_tasa, 2);
END;
$$;
-- Probar función
SELECT
DISTINCT modulo,
tasa_exito_modulo(modulo) AS tasa_exito
FROM logs_ejecucion
ORDER BY tasa_exito DESC;
2.3 Función Tabla - Mensajes por Usuario
-- Función que retorna tabla con mensajes de un usuario
CREATE OR REPLACE FUNCTION mensajes_usuario(p_usuario_id INTEGER)
RETURNS TABLE(
mensaje_id INTEGER,
contenido TEXT,
modalidad TEXT,
enviado_en TIMESTAMPTZ,
intencion TEXT,
sentimiento TEXT
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
m.id,
m.contenido,
m.modalidad,
m.enviado_en,
m.metadata_ia->>'intencion' AS intencion,
m.metadata_ia->>'sentimiento' AS sentimiento
FROM mensajes m
WHERE m.remitente_id = p_usuario_id
ORDER BY m.enviado_en DESC;
END;
$$;
-- Probar función
SELECT * FROM mensajes_usuario(1) LIMIT 5;
2.4 Función Tabla - Estadísticas de Sesión
-- Función que retorna estadísticas de sesiones por período
CREATE OR REPLACE FUNCTION estadisticas_sesiones(
p_fecha_inicio DATE,
p_fecha_fin DATE
)
RETURNS TABLE(
fecha DATE,
total_sesiones BIGINT,
usuarios_unicos BIGINT,
promedio_duracion_minutos NUMERIC
)
LANGUAGE plpgsql
AS $$
BEGIN
RETURN QUERY
SELECT
DATE(s.iniciada_en) AS fecha,
COUNT(*) AS total_sesiones,
COUNT(DISTINCT s.usuario_id) AS usuarios_unicos,
ROUND(AVG(EXTRACT(EPOCH FROM (s.ultima_actividad - s.iniciada_en)) / 60), 2) AS promedio_duracion_minutos
FROM sesiones s
WHERE DATE(s.iniciada_en) BETWEEN p_fecha_inicio AND p_fecha_fin
GROUP BY DATE(s.iniciada_en)
ORDER BY fecha;
END;
$$;
-- Probar función
SELECT * FROM estadisticas_sesiones('2024-01-15', '2024-01-17');
2.5 Función con Manejo de Excepciones
-- Función que genera ticket con manejo de errores
CREATE OR REPLACE FUNCTION generar_ticket_seguro(
p_usuario_id INTEGER,
p_contenido TEXT
)
RETURNS INTEGER
LANGUAGE plpgsql
AS $$
DECLARE
v_sesion_id INTEGER;
v_mensaje_id INTEGER;
BEGIN
-- Buscar sesión activa del usuario
SELECT id INTO v_sesion_id
FROM sesiones
WHERE usuario_id = p_usuario_id
AND cerrada = FALSE
AND expira_en > NOW()
ORDER BY iniciada_en DESC
LIMIT 1;
IF v_sesion_id IS NULL THEN
RAISE EXCEPTION 'No hay sesión activa para el usuario %', p_usuario_id;
END IF;
-- Insertar mensaje
INSERT INTO mensajes (sesion_id, remitente_id, contenido, modalidad)
VALUES (v_sesion_id, p_usuario_id, p_contenido, 'texto')
RETURNING id INTO v_mensaje_id;
-- Registrar log
INSERT INTO logs_ejecucion (mensaje_id, modulo, tiempo_ejecucion_ms, exito)
VALUES (v_mensaje_id, 'generacion_ticket', 10, TRUE);
RETURN v_mensaje_id;
EXCEPTION
WHEN OTHERS THEN
-- Registrar error en log
RAISE NOTICE 'Error en generar_ticket_seguro: %', SQLERRM;
RETURN NULL;
END;
$$;
-- Probar función
SELECT generar_ticket_seguro(1, 'Mi primer ticket');