## 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>
203 lines
6.4 KiB
PL/PgSQL
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';
|