codigo0/docs/LOGS_AUDITORIA.md

494 lines
11 KiB
Markdown
Raw Normal View History

# 📊 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<string, unknown>;
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<string, unknown>;
ipAddress?: string;
userAgent?: string;
}): Promise<void> {
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<string, unknown>;
validatedBy?: string;
}): Promise<void> {
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<string, unknown>;
}): Promise<void> {
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<string, unknown>;
}): Promise<void> {
// 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<string, number>;
byAgeGroup: Record<string, number>;
};
// Protocolos ejecutados
protocolExecutions: {
total: number;
byProtocol: Record<string, number>;
averageStepsCompleted: number;
criticalErrorsDetected: number;
};
// Contenido médico
contentStats: {
total: number;
byStatus: Record<string, number>;
averageReviewTime: number; // horas
approvalRate: number; // porcentaje
};
// Errores críticos
criticalErrors: {
total: number;
byType: Record<string, number>;
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**