-- ============================================================================= -- 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';