michangarrito/database/schemas/19-audit.sql
rckrdmrd 5a49ad0185 [INTEGRATION] feat: Integrate template-saas scopes and database objects
## Documentation
- Align MCH-029 to MCH-032 with template-saas modules (SAAS-008 to SAAS-015)
- Create MCH-034 (Analytics) and MCH-035 (Reports) from SAAS-016/017
- Update PLAN-DESARROLLO.md with Phase 7 and 8
- Update _MAP.md indexes (35 total epics)

## Database (5 new schemas, 14 tables)
- Add storage schema: buckets, files, signed_urls
- Add webhooks schema: endpoints, deliveries
- Add audit schema: logs, retention_policies
- Add features schema: flags, tenant_flags (14 seeds)
- Add analytics schema: metrics, events, reports, report_schedules
- Add auth.oauth_connections for MCH-030
- Add timestamptz_to_date() IMMUTABLE function
- Update EXPECTED_SCHEMAS in recreate-database.sh

## Analysis Reports
- ANALISIS-INTEGRACION-TEMPLATE-SAAS-2026-01-13.md
- VALIDACION-COHERENCIA-2026-01-13.md
- GAP-ANALYSIS-BD-2026-01-13.md
- REPORTE-EJECUCION-2026-01-13.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 07:10:55 -06:00

203 lines
6.4 KiB
PL/PgSQL

-- =============================================================================
-- MICHANGARRITO - 19 AUDIT (MCH-031: Auditoria Empresarial)
-- =============================================================================
-- Sistema de auditoria y logs para compliance y trazabilidad
-- Registra todas las acciones significativas en el sistema
-- =============================================================================
-- Tipos de accion auditada
CREATE TYPE audit_action AS ENUM (
-- CRUD generales
'create',
'read',
'update',
'delete',
-- Autenticacion
'login',
'logout',
'login_failed',
'password_change',
'pin_change',
-- Ventas
'sale_create',
'sale_cancel',
'sale_refund',
-- Inventario
'stock_adjust',
'stock_transfer',
-- Fiados
'credit_grant',
'credit_payment',
'credit_writeoff',
-- Configuracion
'settings_change',
'permissions_change',
-- Exportacion
'data_export',
'report_generate'
);
-- Tipos de recurso
CREATE TYPE audit_resource_type AS ENUM (
'user',
'tenant',
'product',
'category',
'sale',
'payment',
'customer',
'credit',
'order',
'subscription',
'webhook',
'settings',
'report'
);
-- Logs de auditoria (tabla principal)
CREATE TABLE IF NOT EXISTS audit.logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
-- Actor
user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL,
user_name VARCHAR(100), -- Snapshot del nombre (por si se elimina usuario)
-- Accion
action audit_action NOT NULL,
-- Recurso afectado
resource_type audit_resource_type NOT NULL,
resource_id UUID,
resource_name VARCHAR(255), -- Snapshot del nombre
-- Cambios
old_values JSONB, -- Valores anteriores (para updates)
new_values JSONB, -- Valores nuevos
-- Contexto
ip_address VARCHAR(45),
user_agent TEXT,
request_id VARCHAR(100), -- Para correlacion de logs
-- Metadata adicional
metadata JSONB DEFAULT '{}',
-- Timestamp (sin updated_at, los logs son inmutables)
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indices optimizados para consultas frecuentes
CREATE INDEX idx_audit_logs_tenant ON audit.logs(tenant_id);
CREATE INDEX idx_audit_logs_user ON audit.logs(user_id);
CREATE INDEX idx_audit_logs_action ON audit.logs(action);
CREATE INDEX idx_audit_logs_resource ON audit.logs(resource_type, resource_id);
CREATE INDEX idx_audit_logs_created ON audit.logs(created_at DESC);
CREATE INDEX idx_audit_logs_tenant_created ON audit.logs(tenant_id, created_at DESC);
-- Indice para busquedas por rango de fecha (particionado logico)
-- Usa funcion IMMUTABLE timestamptz_to_date() definida en 02-functions.sql
CREATE INDEX idx_audit_logs_date ON audit.logs(public.timestamptz_to_date(created_at));
COMMENT ON TABLE audit.logs IS 'Logs de auditoria inmutables para compliance';
COMMENT ON COLUMN audit.logs.old_values IS 'Snapshot de valores antes del cambio';
COMMENT ON COLUMN audit.logs.new_values IS 'Snapshot de valores despues del cambio';
COMMENT ON COLUMN audit.logs.request_id IS 'ID de correlacion para rastrear requests';
-- Politicas de retencion por tenant
CREATE TABLE IF NOT EXISTS audit.retention_policies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE UNIQUE,
-- Configuracion de retencion
retention_days INTEGER NOT NULL DEFAULT 90,
-- Por tipo de accion (override)
retention_overrides JSONB DEFAULT '{}',
-- Ejemplo: {"login": 30, "sale_create": 365, "data_export": 180}
-- Timestamps
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TRIGGER update_audit_retention_policies_updated_at
BEFORE UPDATE ON audit.retention_policies
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
COMMENT ON TABLE audit.retention_policies IS 'Politicas de retencion de logs por tenant';
COMMENT ON COLUMN audit.retention_policies.retention_days IS 'Dias de retencion por defecto';
COMMENT ON COLUMN audit.retention_policies.retention_overrides IS 'Override por tipo de accion: {"login": 30}';
-- =============================================================================
-- FUNCIONES DE AUDITORIA
-- =============================================================================
-- Funcion para registrar log de auditoria
CREATE OR REPLACE FUNCTION audit.log_action(
p_tenant_id UUID,
p_user_id UUID,
p_user_name VARCHAR(100),
p_action audit_action,
p_resource_type audit_resource_type,
p_resource_id UUID DEFAULT NULL,
p_resource_name VARCHAR(255) DEFAULT NULL,
p_old_values JSONB DEFAULT NULL,
p_new_values JSONB DEFAULT NULL,
p_ip_address VARCHAR(45) DEFAULT NULL,
p_user_agent TEXT DEFAULT NULL,
p_request_id VARCHAR(100) DEFAULT NULL,
p_metadata JSONB DEFAULT '{}'
) RETURNS UUID AS $$
DECLARE
v_log_id UUID;
BEGIN
INSERT INTO audit.logs (
tenant_id, user_id, user_name, action,
resource_type, resource_id, resource_name,
old_values, new_values,
ip_address, user_agent, request_id, metadata
) VALUES (
p_tenant_id, p_user_id, p_user_name, p_action,
p_resource_type, p_resource_id, p_resource_name,
p_old_values, p_new_values,
p_ip_address, p_user_agent, p_request_id, p_metadata
) RETURNING id INTO v_log_id;
RETURN v_log_id;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION audit.log_action IS 'Registra una accion en el log de auditoria';
-- Funcion para limpiar logs antiguos segun politica de retencion
CREATE OR REPLACE FUNCTION audit.cleanup_old_logs()
RETURNS INTEGER AS $$
DECLARE
v_deleted INTEGER := 0;
v_tenant RECORD;
BEGIN
FOR v_tenant IN
SELECT t.id as tenant_id, COALESCE(p.retention_days, 90) as retention_days
FROM public.tenants t
LEFT JOIN audit.retention_policies p ON p.tenant_id = t.id
LOOP
DELETE FROM audit.logs
WHERE tenant_id = v_tenant.tenant_id
AND created_at < NOW() - (v_tenant.retention_days || ' days')::INTERVAL;
v_deleted := v_deleted + (SELECT COUNT(*) FROM audit.logs WHERE tenant_id = v_tenant.tenant_id);
END LOOP;
RETURN v_deleted;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION audit.cleanup_old_logs IS 'Elimina logs antiguos segun politicas de retencion';