Checklist de Verificación
🧠 Motor CDSS
Interacciones sin respuesta ante fallo del lookup (retorna vacío en catch — false negative)
Alerta de sobredosis de insulina es toast dismissable — no bloquea prescripción
NEWS2: SpO2 rango 92–93 retorna score 2, spec RCP marca 1 para 92–93% en escala 1
NEWS2: rango temperatura correcto según spec RCP
NEWS2: AVPU penalización +3 si no A — correcto
Tipo de insulina desconocido asume max=100U por defecto — no debe permitirse sin validación
🔒 Protección PHI
patient_id en console.log/console.error dentro de checkInteractions (exposición en logs de servidor)
Resultado completo de búsqueda de pacientes logueado con JSON.stringify (nombre, DNI, historial)
Parámetro ?dob= (fecha de nacimiento) en query string de URL — PHI en logs de acceso HTTP
Sin RLS en tablas patients, medications, encounters — cualquier sesión autenticada accede a todos los datos
Sin service_role en código cliente detectado
Sin PHI en localStorage/sessionStorage detectado en fragmentos revisados
📋 Flujo Clínico
Encuentros editables hasta 24h sin generar addendum — modificación directa de registros clínicos
Sin entrada en audit trail para UPDATE de encounters
Alerta CDSS retorna array (procesable programáticamente)
Sin override reason logging cuando clínico bypasea alerta de insulina
🗄️ Integridad de Datos
ON DELETE CASCADE en medications y encounters — borrar paciente elimina historia clínica
Sin detección de edición concurrente en endpoint PUT /encounters
UUIDs para todas las PKs — correcto
Timestamps con TIMESTAMPTZ (timezone-aware) — correcto
Consultas parametrizadas en encounters API ($1, $2) — sin SQL injection
SQL injection en checkInteractions: interpolación directa de patientId sin parametrizar
Mapa de Exposición PHI
| Vector | Archivo | Datos expuestos | Estado | Riesgo LOPDGDD / ENS |
|---|---|---|---|---|
| console.log línea 8 | drug-interactions.js | patient_id + combinación de fármacos | EXPUESTO | Infracción muy grave Art. 83.5 RGPD — hasta €20M |
| console.error línea 12 | drug-interactions.js | patient_id en stack trace de errores | EXPUESTO | Infracción muy grave — datos de salud (cat. especial) |
| Query string URL | encounter-api.js | ?name=... &dob=... en GET /api/patients/search | EXPUESTO | PHI en logs de acceso de proxy/LB/CDN |
| console.log línea 18 | encounter-api.js | JSON.stringify de registros completos de pacientes (nombre, NIF, historial) | EXPUESTO | Brecha de datos Art. 33 RGPD — notificación AEPD <72h |
| RLS deshabilitado | schema.sql | Tablas patients, medications, encounters sin políticas Row-Level Security | SIN AISLAR | Cross-facility data leak — violación segregación datos |
| Audit trail ausente | encounter-api.js | Modificaciones de historial sin registro de quién/cuándo/qué cambió | INCOMPLETO | Incumplimiento Art. 32 RGPD — medidas técnicas adecuadas |
Detalle de Incidencias
CRÍTICO
PATIENT SAFETY
Fallo silencioso en detector de interacciones farmacológicas
Impacto
Si el lookup de interacciones falla (red, timeout, error de BD), la función retorna [] como si no hubiera interacciones. Un médico prescribiendo warfarina a un paciente con AINEs no recibiría alerta. Potencial hemorragia grave o muerte. Un único false negative es peor que mil false positives.
Corrección requerida
Propagar el error o retornar un estado explícito de error. Nunca silenciar excepciones en lógica CDSS.
// CORRECTO: propagar error, nunca silenciar
} catch(e) {
logger.error('CDSS_FAIL', { module: 'drug-interactions' });
throw new CDSSUnavailableError('Interaction check failed');
}
CRÍTICO
PATIENT SAFETY
Alerta de sobredosis de insulina no bloquea prescripción (toast dismissable)
Impacto
Una dosis de insulina de 200U rápida (4x el máximo) pasa con un toast de advertencia que el médico puede ignorar. La función devuelve true siempre. Sin logging del override. Riesgo de hipoglucemia grave o muerte por sobredosis.
Corrección requerida
Alertas críticas deben ser modales no dismissables que requieran confirmación explícita. El override debe registrarse con usuario, timestamp y motivo.
if (dose > max) {
return { allowed: false, reason: 'DOSE_EXCEEDED',
requiresOverride: true,
overrideFields: ['reason', 'clinician_id'] };
}
CRÍTICO
PHI
Datos completos de pacientes en logs de servidor (JSON.stringify)
Impacto
La búsqueda de pacientes loguea nombre completo, NIF y fechas de nacimiento con JSON.stringify(patients.data). En un volumen de 85.000 pacientes, cada búsqueda filtra datos a los logs del servidor. Constituye brecha de datos bajo RGPD Art. 33 — notificación obligatoria a AEPD en <72h.
Corrección requerida
Eliminar todo log de PHI. Usar solo contadores o identificadores técnicos (hash, count) en logs.
// CORRECTO: log sin PHI
console.log(`Patient search: results=${patients.data?.length ?? 0}`);
ALTO
PHI
Sin Row-Level Security en todas las tablas clínicas
Impacto
Sin políticas RLS en Supabase, cualquier usuario autenticado puede leer registros de pacientes de otras clínicas o centros. Cross-facility data isolation no verificado. Viola segregación de datos obligatoria en entornos multi-tenant sanitarios.
Corrección requerida
Habilitar RLS en todas las tablas con datos de paciente y crear políticas que filtren por facility_id o provider_id del JWT.
ALTER TABLE patients ENABLE ROW LEVEL SECURITY;
CREATE POLICY "facility_isolation" ON patients
USING (facility_id = auth.jwt()->>'facility_id');
ALTO
CLINICAL
ON DELETE CASCADE elimina historia clínica al borrar paciente
Impacto
Las tablas medications y encounters tienen ON DELETE CASCADE. Borrar un paciente (incluso accidentalmente) destruye toda su historia farmacológica y clínica irreversiblemente. Datos de salud legalmente requeridos pueden desaparecer.
Corrección requerida
Cambiar a soft-delete con campo deleted_at. Nunca CASCADE DELETE en registros clínicos.
-- Cambiar CASCADE por RESTRICT
ALTER TABLE medications
DROP CONSTRAINT medications_patient_id_fkey,
ADD CONSTRAINT medications_patient_id_fkey
FOREIGN KEY (patient_id) REFERENCES patients(id)
ON DELETE RESTRICT;
ALTO
CLINICAL
Edición directa de encuentros — sin addendum, sin audit trail
Impacto
El endpoint PUT /encounters sobrescribe las notas clínicas directamente sin registrar el cambio. En medicina, la historia clínica debe ser append-only con addendum. La ausencia de audit trail imposibilita auditorías y es incumplimiento RGPD Art. 32.
Corrección requerida
Implementar modelo addendum: insertar nueva entrada con referencia al original + razón de corrección, nunca UPDATE directo.
// Crear addendum, no editar encuentro original
await db.query(
'INSERT INTO encounter_addenda (encounter_id, clinician_id, notes, reason) VALUES ($1,$2,$3,$4)',
[encounterId, req.user.id, req.body.notes, req.body.reason]);
ALTO
TECHNICAL
SQL injection en checkInteractions (interpolación directa de patientId)
Impacto
La query WHERE patient_id = ${'{'}patientId{'}'} interpola directamente el ID sin parametrizar. Un patientId malicioso puede exfiltrar datos de toda la base de datos o destruirla.
Corrección requerida
Usar siempre consultas parametrizadas como en el resto del código.
// CORRECTO: parametrizado
const meds = await db.query(
'SELECT * FROM medications WHERE patient_id = $1',
[patientId]);
MEDIO
CLINICAL
NEWS2: puntuación SpO2 92–93% incorrecta (score 2 en lugar de 1, Escala 1 RCP)
Impacto
El umbral SpO2 92–93% devuelve score 2 pero la especificación NEWS2 del Royal College of Physicians asigna score 1 para ese rango en Escala 1. Podría clasificar pacientes erróneamente como HIGH risk activando respuestas clínicas innecesarias.
Corrección requerida
Verificar contra tabla publicada RCP NEWS2 (2017). SpO2 92–93% = +1. SpO2 ≤91% = +3. SpO2 94–95% = +1. SpO2 ≥96% = 0.
// Corrección según RCP NEWS2 Escala 1
if (vitals.spo2 <= 91) score += 3;
else if (vitals.spo2 <= 93) score += 2; // ← verificar
else if (vitals.spo2 <= 95) score += 1;
else score += 0; // ≥96%
Resumen de Precisión Clínica
| Componente | Estado | Spec de referencia | Observación |
|---|---|---|---|
| Drug interaction checker | FALLA (false negatives) | Base de datos propia | Fallo silencioso — bloquear despliegue |
| Validación dosis insulina (tipos conocidos) | PARCIAL (no bloquea) | Vademécum local + ficha técnica | Detecta exceso pero no impide prescripción |
| NEWS2 — Frecuencia respiratoria | CORRECTO | RCP NEWS2 2017 | Umbrales 8/11/20/24 correctos |
| NEWS2 — SpO2 Escala 1 | REVISAR | RCP NEWS2 2017 | Rango 92–93% posiblemente sobreestimado +1 punto |
| NEWS2 — Temperatura | CORRECTO | RCP NEWS2 2017 | Umbrales 35.0/36.0/38.0/39.0 correctos |
| NEWS2 — AVPU | CORRECTO | RCP NEWS2 2017 | Penalización +3 si no Alert — correcto |
| NEWS2 — Risk stratification | CORRECTO | RCP NEWS2 2017 | Umbrales ≥7 HIGH, ≥5 MEDIUM correctos |
| ICD-10 / SNOMED mappings | NO REVISADO | CIE-10-ES (MSSSI) | No incluido en fragmentos enviados — revisar en siguiente iteración |