# 📊 Sistema de Logs y Auditoría ## 🎯 Objetivo Definir qué información registrar en logs y qué datos de relevancia médica almacenar (sin datos sensibles del paciente). --- ## 🔒 Principio: NO Datos Sensibles ### ❌ NO Registrar: - Nombres de pacientes - DNI/NIE de pacientes - Direcciones de pacientes - Teléfonos de pacientes - Historial médico completo - Cualquier información que identifique a un paciente ### ✅ SÍ Registrar: - Acciones del sistema - Cambios en contenido médico - Validaciones realizadas - Errores críticos - Estadísticas agregadas (sin identificar pacientes) - Metadatos de operaciones --- ## 📋 Tipos de Logs ### 1. Audit Logs (Auditoría) **Propósito:** Rastrear quién hizo qué y cuándo ```typescript interface AuditLog { id: string; userId: string; userRole: string; action: 'create' | 'update' | 'delete' | 'submit' | 'approve' | 'reject' | 'publish'; entityType: 'content' | 'drug' | 'protocol' | 'glossary' | 'media'; entityId: string; changes?: { field: string; oldValue: unknown; newValue: unknown; }[]; metadata?: Record; ipAddress?: string; userAgent?: string; timestamp: Date; } ``` **Ejemplos:** ```typescript // Crear contenido { userId: 'user-123', userRole: 'editor', action: 'create', entityType: 'content', entityId: 'rcp-adulto-svb', timestamp: '2025-01-25T10:00:00Z' } // Aprobar contenido { userId: 'user-456', userRole: 'reviewer', action: 'approve', entityType: 'content', entityId: 'rcp-adulto-svb', changes: [ { field: 'status', oldValue: 'in_review', newValue: 'approved' } ], metadata: { reviewId: 'review-789', clinicalSources: ['ERC Guidelines 2021'] }, timestamp: '2025-01-25T11:00:00Z' } ``` --- ### 2. Validation Logs (Validaciones) **Propósito:** Registrar todas las validaciones médicas realizadas ```typescript interface ValidationLog { id: string; validationType: 'dose' | 'protocol' | 'content'; contentId?: string; drugId?: string; protocolId?: string; result: 'valid' | 'invalid' | 'warning'; errors: string[]; warnings: string[]; context: { ageGroup?: string; weightRange?: string; route?: string; }; validatedBy?: string; timestamp: Date; } ``` **Ejemplos:** ```typescript // Validación de dosis { validationType: 'dose', drugId: 'adrenalina', result: 'invalid', errors: ['Dosis excede máximo seguro'], warnings: [], context: { ageGroup: 'adulto', weightRange: '70-80kg', route: 'IV' }, timestamp: '2025-01-25T12:00:00Z' } // Validación de protocolo { validationType: 'protocol', protocolId: 'rcp-adulto-svb', result: 'warning', errors: [], warnings: ['Paso crítico 4 no ejecutado'], timestamp: '2025-01-25T13:00:00Z' } ``` --- ### 3. Error Logs (Errores) **Propósito:** Registrar errores críticos del sistema ```typescript interface ErrorLog { id: string; level: 'error' | 'critical'; errorCode: string; errorMessage: string; stack?: string; context: { userId?: string; contentId?: string; action?: string; }; timestamp: Date; } ``` **Ejemplos:** ```typescript // Error crítico de dosis { level: 'critical', errorCode: 'DOSE_LETHAL', errorMessage: 'Dosis letal detectada: 10mg de adrenalina IV', context: { userId: 'user-123', drugId: 'adrenalina', action: 'administer_drug' }, timestamp: '2025-01-25T14:00:00Z' } ``` --- ### 4. Performance Logs (Rendimiento) **Propósito:** Monitorear rendimiento del sistema ```typescript interface PerformanceLog { endpoint: string; method: string; duration: number; // ms statusCode: number; timestamp: Date; } ``` --- ## 🗄️ Esquema de Base de Datos ```sql -- Tabla de audit logs (ya existe, mejorar) CREATE TABLE tes_content.audit_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID NOT NULL REFERENCES tes_content.users(id), user_role VARCHAR(50) NOT NULL, action VARCHAR(50) NOT NULL, entity_type VARCHAR(50) NOT NULL, entity_id VARCHAR(100) NOT NULL, changes JSONB, metadata JSONB, ip_address INET, user_agent TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT chk_action CHECK ( action IN ('create', 'update', 'delete', 'submit', 'approve', 'reject', 'publish', 'archive') ) ); CREATE INDEX idx_audit_logs_user ON tes_content.audit_logs(user_id); CREATE INDEX idx_audit_logs_entity ON tes_content.audit_logs(entity_type, entity_id); CREATE INDEX idx_audit_logs_action ON tes_content.audit_logs(action); CREATE INDEX idx_audit_logs_created ON tes_content.audit_logs(created_at DESC); -- Tabla de validation logs CREATE TABLE tes_content.validation_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), validation_type VARCHAR(50) NOT NULL, content_id VARCHAR(100), drug_id UUID, protocol_id VARCHAR(100), result VARCHAR(20) NOT NULL, errors TEXT[], warnings TEXT[], context JSONB, validated_by UUID REFERENCES tes_content.users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT chk_validation_type CHECK ( validation_type IN ('dose', 'protocol', 'content') ), CONSTRAINT chk_result CHECK ( result IN ('valid', 'invalid', 'warning') ) ); CREATE INDEX idx_validation_logs_type ON tes_content.validation_logs(validation_type); CREATE INDEX idx_validation_logs_result ON tes_content.validation_logs(result); CREATE INDEX idx_validation_logs_created ON tes_content.validation_logs(created_at DESC); -- Tabla de error logs CREATE TABLE tes_content.error_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), level VARCHAR(20) NOT NULL, error_code VARCHAR(100) NOT NULL, error_message TEXT NOT NULL, stack TEXT, context JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT chk_level CHECK (level IN ('error', 'critical')) ); CREATE INDEX idx_error_logs_level ON tes_content.error_logs(level); CREATE INDEX idx_error_logs_code ON tes_content.error_logs(error_code); CREATE INDEX idx_error_logs_created ON tes_content.error_logs(created_at DESC); ``` --- ## 🔧 Implementación ### Service de Logging ```typescript // infrastructure/services/LoggingService.ts export class LoggingService { async logAudit(params: { userId: string; userRole: string; action: string; entityType: string; entityId: string; changes?: Array<{ field: string; oldValue: unknown; newValue: unknown }>; metadata?: Record; ipAddress?: string; userAgent?: string; }): Promise { await db.query( `INSERT INTO tes_content.audit_logs ( user_id, user_role, action, entity_type, entity_id, changes, metadata, ip_address, user_agent ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [ params.userId, params.userRole, params.action, params.entityType, params.entityId, JSON.stringify(params.changes || []), JSON.stringify(params.metadata || {}), params.ipAddress, params.userAgent ] ); } async logValidation(params: { validationType: 'dose' | 'protocol' | 'content'; contentId?: string; drugId?: string; protocolId?: string; result: 'valid' | 'invalid' | 'warning'; errors: string[]; warnings: string[]; context?: Record; validatedBy?: string; }): Promise { await db.query( `INSERT INTO tes_content.validation_logs ( validation_type, content_id, drug_id, protocol_id, result, errors, warnings, context, validated_by ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [ params.validationType, params.contentId, params.drugId, params.protocolId, params.result, params.errors, params.warnings, JSON.stringify(params.context || {}), params.validatedBy ] ); } async logError(params: { level: 'error' | 'critical'; errorCode: string; errorMessage: string; stack?: string; context?: Record; }): Promise { await db.query( `INSERT INTO tes_content.error_logs ( level, error_code, error_message, stack, context ) VALUES ($1, $2, $3, $4, $5)`, [ params.level, params.errorCode, params.errorMessage, params.stack, JSON.stringify(params.context || {}) ] ); // Si es crítico, también notificar if (params.level === 'critical') { await this.notifyCriticalError(params); } } private async notifyCriticalError(error: { errorCode: string; errorMessage: string; context?: Record; }): Promise { // Enviar notificación a administradores // Email, Slack, etc. } } ``` --- ## 📊 Datos de Relevancia Médica (Sin Identificar Pacientes) ### Estadísticas Agregadas ```typescript interface MedicalStatistics { // Validaciones de dosis doseValidations: { total: number; valid: number; invalid: number; warnings: number; byDrug: Record; byAgeGroup: Record; }; // Protocolos ejecutados protocolExecutions: { total: number; byProtocol: Record; averageStepsCompleted: number; criticalErrorsDetected: number; }; // Contenido médico contentStats: { total: number; byStatus: Record; averageReviewTime: number; // horas approvalRate: number; // porcentaje }; // Errores críticos criticalErrors: { total: number; byType: Record; blockedActions: number; }; } ``` --- ## ✅ Casos de Uso ### Caso 1: Registrar creación de contenido ```typescript await loggingService.logAudit({ userId: req.user.id, userRole: req.user.role, action: 'create', entityType: 'content', entityId: content.id, ipAddress: req.ip, userAgent: req.get('user-agent') }); ``` ### Caso 2: Registrar validación de dosis ```typescript await loggingService.logValidation({ validationType: 'dose', drugId: 'adrenalina', result: validation.valid ? 'valid' : 'invalid', errors: validation.errors, warnings: validation.warnings, context: { ageGroup: 'adulto', weightRange: '70-80kg' } }); ``` ### Caso 3: Registrar error crítico ```typescript await loggingService.logError({ level: 'critical', errorCode: 'DOSE_LETHAL', errorMessage: 'Dosis letal detectada', context: { drugId: 'adrenalina', dose: 10, userId: user.id } }); ``` --- ## 🔍 Consultas Útiles ### Obtener historial de cambios de contenido ```sql SELECT * FROM tes_content.audit_logs WHERE entity_type = 'content' AND entity_id = $1 ORDER BY created_at DESC; ``` ### Estadísticas de validaciones ```sql SELECT validation_type, result, COUNT(*) as count FROM tes_content.validation_logs WHERE created_at >= NOW() - INTERVAL '30 days' GROUP BY validation_type, result; ``` ### Errores críticos recientes ```sql SELECT * FROM tes_content.error_logs WHERE level = 'critical' AND created_at >= NOW() - INTERVAL '24 hours' ORDER BY created_at DESC; ``` --- **Fin del documento**