-- ============================================================================ -- OrbiQuant IA - Esquema AUDIT -- ============================================================================ -- Archivo: 07_audit_schema.sql -- Descripción: Logs de auditoría, eventos del sistema y seguridad -- Fecha: 2025-12-05 -- ============================================================================ SET search_path TO audit; -- ============================================================================ -- TABLA: audit_logs -- Descripción: Log general de acciones en el sistema -- ============================================================================ CREATE TABLE IF NOT EXISTS audit_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- Tabla afectada table_name VARCHAR(100) NOT NULL, record_id TEXT, -- Acción action VARCHAR(20) NOT NULL, -- 'INSERT', 'UPDATE', 'DELETE' -- Datos old_data JSONB, new_data JSONB, -- Usuario user_id UUID, user_email VARCHAR(255), -- Contexto ip_address INET, user_agent TEXT, session_id UUID, -- Timestamp created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); -- Índices para búsqueda eficiente CREATE INDEX idx_audit_logs_table ON audit_logs(table_name); CREATE INDEX idx_audit_logs_record ON audit_logs(table_name, record_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_created ON audit_logs(created_at DESC); -- Particionamiento por mes (para producción) -- CREATE TABLE audit_logs_2025_01 PARTITION OF audit_logs -- FOR VALUES FROM ('2025-01-01') TO ('2025-02-01'); -- ============================================================================ -- TABLA: security_events -- Descripción: Eventos de seguridad -- ============================================================================ CREATE TYPE security_event_type AS ENUM ( 'login_success', 'login_failed', 'logout', 'password_changed', 'password_reset_requested', 'password_reset_completed', '2fa_enabled', '2fa_disabled', '2fa_failed', 'account_locked', 'account_unlocked', 'suspicious_activity', 'api_key_created', 'api_key_revoked', 'permission_denied', 'rate_limit_exceeded' ); CREATE TYPE security_severity AS ENUM ('info', 'warning', 'error', 'critical'); CREATE TABLE IF NOT EXISTS security_events ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- Usuario user_id UUID REFERENCES auth.users(id), user_email VARCHAR(255), -- Evento event_type security_event_type NOT NULL, severity security_severity DEFAULT 'info', description TEXT, -- Contexto ip_address INET, user_agent TEXT, location JSONB, -- {country, city, lat, lon} -- Datos adicionales metadata JSONB, -- Estado acknowledged BOOLEAN DEFAULT FALSE, acknowledged_by UUID REFERENCES auth.users(id), acknowledged_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_security_events_user ON security_events(user_id); CREATE INDEX idx_security_events_type ON security_events(event_type); CREATE INDEX idx_security_events_severity ON security_events(severity); CREATE INDEX idx_security_events_created ON security_events(created_at DESC); CREATE INDEX idx_security_events_unack ON security_events(acknowledged) WHERE acknowledged = FALSE; -- ============================================================================ -- TABLA: system_events -- Descripción: Eventos del sistema (no de usuario) -- ============================================================================ CREATE TYPE system_event_type AS ENUM ( 'service_started', 'service_stopped', 'service_error', 'database_backup', 'database_restore', 'deployment', 'config_changed', 'scheduled_job_started', 'scheduled_job_completed', 'scheduled_job_failed', 'integration_connected', 'integration_disconnected', 'integration_error', 'alert_triggered', 'maintenance_started', 'maintenance_completed' ); CREATE TABLE IF NOT EXISTS system_events ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- Evento event_type system_event_type NOT NULL, service_name VARCHAR(100), description TEXT, -- Severidad severity security_severity DEFAULT 'info', -- Detalles details JSONB, error_message TEXT, stack_trace TEXT, -- Metadata hostname VARCHAR(255), environment VARCHAR(50), -- 'development', 'staging', 'production' created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_system_events_type ON system_events(event_type); CREATE INDEX idx_system_events_service ON system_events(service_name); CREATE INDEX idx_system_events_severity ON system_events(severity); CREATE INDEX idx_system_events_created ON system_events(created_at DESC); -- ============================================================================ -- TABLA: trading_audit -- Descripción: Auditoría específica de operaciones de trading -- ============================================================================ CREATE TABLE IF NOT EXISTS trading_audit ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- Referencias bot_id UUID, signal_id UUID, position_id UUID, account_id UUID, user_id UUID, -- Evento event_type VARCHAR(50) NOT NULL, -- 'signal_generated', 'order_placed', 'order_filled', etc. description TEXT, -- Datos de la operación symbol VARCHAR(20), direction VARCHAR(10), lot_size DECIMAL(10,4), price DECIMAL(20,8), -- Precios entry_price DECIMAL(20,8), stop_loss DECIMAL(20,8), take_profit DECIMAL(20,8), -- Resultado pnl DECIMAL(15,2), outcome VARCHAR(20), -- Contexto del modelo model_id UUID, confidence DECIMAL(5,4), amd_phase VARCHAR(20), -- Metadata metadata JSONB, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_trading_audit_bot ON trading_audit(bot_id); CREATE INDEX idx_trading_audit_signal ON trading_audit(signal_id); CREATE INDEX idx_trading_audit_position ON trading_audit(position_id); CREATE INDEX idx_trading_audit_account ON trading_audit(account_id); CREATE INDEX idx_trading_audit_event ON trading_audit(event_type); CREATE INDEX idx_trading_audit_symbol ON trading_audit(symbol); CREATE INDEX idx_trading_audit_created ON trading_audit(created_at DESC); -- ============================================================================ -- TABLA: api_request_logs -- Descripción: Logs de requests a la API -- ============================================================================ CREATE TABLE IF NOT EXISTS api_request_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- Usuario user_id UUID, api_key_id UUID, -- Request method VARCHAR(10) NOT NULL, path VARCHAR(500) NOT NULL, query_params JSONB, headers JSONB, body_size INT, -- Response status_code INT, response_time_ms INT, response_size INT, -- Contexto ip_address INET, user_agent TEXT, -- Error (si aplica) error_message TEXT, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); -- Índices para análisis CREATE INDEX idx_api_logs_user ON api_request_logs(user_id); CREATE INDEX idx_api_logs_path ON api_request_logs(path); CREATE INDEX idx_api_logs_status ON api_request_logs(status_code); CREATE INDEX idx_api_logs_created ON api_request_logs(created_at DESC); -- ============================================================================ -- TABLA: data_access_logs -- Descripción: Log de acceso a datos sensibles -- ============================================================================ CREATE TABLE IF NOT EXISTS data_access_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- Usuario que accedió user_id UUID NOT NULL REFERENCES auth.users(id), -- Dato accedido resource_type VARCHAR(100) NOT NULL, -- 'user_profile', 'kyc_document', 'wallet_balance', etc. resource_id UUID, resource_owner_id UUID, -- Usuario dueño del dato -- Acción action VARCHAR(50) NOT NULL, -- 'view', 'export', 'modify' -- Contexto reason TEXT, -- Justificación del acceso ip_address INET, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_data_access_user ON data_access_logs(user_id); CREATE INDEX idx_data_access_resource ON data_access_logs(resource_type, resource_id); CREATE INDEX idx_data_access_owner ON data_access_logs(resource_owner_id); CREATE INDEX idx_data_access_created ON data_access_logs(created_at DESC); -- ============================================================================ -- TABLA: compliance_logs -- Descripción: Logs de cumplimiento regulatorio -- ============================================================================ CREATE TABLE IF NOT EXISTS compliance_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), -- Usuario user_id UUID REFERENCES auth.users(id), -- Evento event_type VARCHAR(100) NOT NULL, -- 'terms_accepted', 'risk_disclosure_accepted', 'kyc_submitted', 'kyc_approved', -- 'aml_check_passed', 'suspicious_activity_flagged', etc. -- Detalles description TEXT, document_version VARCHAR(50), document_url TEXT, -- Metadata metadata JSONB, ip_address INET, -- Estado de revisión requires_review BOOLEAN DEFAULT FALSE, reviewed_by UUID REFERENCES auth.users(id), reviewed_at TIMESTAMPTZ, review_notes TEXT, created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX idx_compliance_user ON compliance_logs(user_id); CREATE INDEX idx_compliance_event ON compliance_logs(event_type); CREATE INDEX idx_compliance_review ON compliance_logs(requires_review) WHERE requires_review = TRUE; CREATE INDEX idx_compliance_created ON compliance_logs(created_at DESC); -- ============================================================================ -- VISTAS DE ADMINISTRACIÓN -- ============================================================================ -- Vista: Actividad reciente de usuarios CREATE OR REPLACE VIEW admin_user_activity AS SELECT u.id AS user_id, u.email, p.first_name, p.last_name, u.role, u.status, u.last_login_at, u.last_login_ip, (SELECT COUNT(*) FROM audit.security_events se WHERE se.user_id = u.id AND se.created_at > NOW() - INTERVAL '24 hours') AS security_events_24h, (SELECT COUNT(*) FROM audit.api_request_logs ar WHERE ar.user_id = u.id AND ar.created_at > NOW() - INTERVAL '24 hours') AS api_requests_24h FROM auth.users u LEFT JOIN public.profiles p ON p.user_id = u.id; -- Vista: Alertas de seguridad pendientes CREATE OR REPLACE VIEW admin_security_alerts AS SELECT se.*, u.email AS user_email, p.first_name, p.last_name FROM audit.security_events se LEFT JOIN auth.users u ON u.id = se.user_id LEFT JOIN public.profiles p ON p.user_id = se.user_id WHERE se.acknowledged = FALSE AND se.severity IN ('warning', 'error', 'critical') ORDER BY se.created_at DESC; -- Vista: Resumen de trading por bot CREATE OR REPLACE VIEW admin_bot_trading_summary AS SELECT ta.bot_id, b.name AS bot_name, b.risk_profile, DATE(ta.created_at) AS date, COUNT(*) FILTER (WHERE ta.event_type = 'signal_generated') AS signals_generated, COUNT(*) FILTER (WHERE ta.event_type = 'order_placed') AS orders_placed, COUNT(*) FILTER (WHERE ta.event_type = 'order_filled') AS orders_filled, SUM(ta.pnl) AS total_pnl, COUNT(*) FILTER (WHERE ta.pnl > 0) AS winning_trades, COUNT(*) FILTER (WHERE ta.pnl < 0) AS losing_trades FROM audit.trading_audit ta LEFT JOIN trading.bots b ON b.id = ta.bot_id WHERE ta.bot_id IS NOT NULL GROUP BY ta.bot_id, b.name, b.risk_profile, DATE(ta.created_at); -- ============================================================================ -- FUNCIONES DE LIMPIEZA -- ============================================================================ -- Función para limpiar logs antiguos CREATE OR REPLACE FUNCTION cleanup_old_logs(retention_days INT DEFAULT 90) RETURNS TABLE( table_name TEXT, rows_deleted BIGINT ) AS $$ DECLARE cutoff_date TIMESTAMPTZ; BEGIN cutoff_date := NOW() - (retention_days || ' days')::INTERVAL; -- API request logs (30 días por defecto) DELETE FROM audit.api_request_logs WHERE created_at < cutoff_date; RETURN QUERY SELECT 'api_request_logs'::TEXT, COUNT(*)::BIGINT FROM audit.api_request_logs WHERE created_at < cutoff_date; -- Audit logs (90 días por defecto, excepto críticos) DELETE FROM audit.audit_logs WHERE created_at < cutoff_date; -- Security events (mantener indefinidamente los críticos) DELETE FROM audit.security_events WHERE created_at < cutoff_date AND severity NOT IN ('error', 'critical'); END; $$ LANGUAGE plpgsql;