Patrones de Desarrollo de SADC Clínico
Patrones para construir Sistemas de Apoyo a la Decisión Clínica que se integren en los flujos de trabajo de la HCE. Los módulos SADC son críticos para la seguridad del paciente: tolerancia cero a falsos negativos.
Cuándo Usar
- Implementar comprobación de interacciones farmacológicas
- Construir motores de validación de dosis
- Implementar sistemas de puntuación clínica (NEWS2, qSOFA, APACHE, GCS)
- Diseñar sistemas de alerta para valores clínicos anormales
- Construir entrada de órdenes de medicación con controles de seguridad
- Integrar interpretación de resultados de laboratorio con contexto clínico
Cómo Funciona
El motor SADC es una biblioteca de funciones puras sin efectos secundarios. Ingrese datos clínicos, obtenga alertas. Esto lo hace completamente comprobable.
Tres módulos principales:
checkInteractions(newDrug, currentMeds, allergies)— Comprueba un nuevo fármaco contra medicamentos actuales y alergias conocidas. DevuelveInteractionAlert[]ordenado por gravedad. Utiliza el modelo de datosDrugInteractionPair.validateDose(drug, dose, route, weight, age, renalFunction)— Valida una dosis prescrita según reglas basadas en peso, ajustadas por edad y función renal. DevuelveDoseValidationResult.calculateNEWS2(vitals)— Puntuación Nacional de Alerta Temprana 2 a partir deNEWS2Input. DevuelveNEWS2Resultcon puntuación total, nivel de riesgo y guía de escalamiento.
Interfaz HCE
↓ (el usuario ingresa datos)
Motor SADC (funciones puras, sin efectos secundarios)
├── Comprobador de Interacciones Farmacológicas
├── Validador de Dosis
├── Puntuación Clínica (NEWS2, qSOFA, etc.)
└── Clasificador de Alertas
↓ (devuelve alertas)
Interfaz HCE (muestra alertas en línea, bloquea si es crítico)
Comprobación de Interacciones Farmacológicas
interface DrugInteractionPair {
drugA: string; // nombre genérico
drugB: string; // nombre genérico
severity: 'critical' | 'major' | 'minor';
mechanism: string;
clinicalEffect: string;
recommendation: string;
}
function checkInteractions(
newDrug: string,
currentMedications: string[],
allergyList: string[]
): InteractionAlert[] {
if (!newDrug) return [];
const alerts: InteractionAlert[] = [];
for (const current of currentMedications) {
const interaction = findInteraction(newDrug, current);
if (interaction) {
alerts.push({ severity: interaction.severity, pair: [newDrug, current],
message: interaction.clinicalEffect, recommendation: interaction.recommendation });
}
}
for (const allergy of allergyList) {
if (isCrossReactive(newDrug, allergy)) {
alerts.push({ severity: 'critical', pair: [newDrug, allergy],
message: `Reactividad cruzada con alergia documentada: ${allergy}`,
recommendation: 'No prescribir sin consulta de alergología' });
}
}
return alerts.sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity));
}
Los pares de interacción deben ser bidireccionales: si el fármaco A interactúa con el fármaco B, entonces el fármaco B interactúa con el fármaco A.
Validación de Dosis
interface DoseValidationResult {
valid: boolean;
message: string;
suggestedRange: { min: number; max: number; unit: string } | null;
factors: string[];
}
function validateDose(
drug: string,
dose: number,
route: 'oral' | 'iv' | 'im' | 'sc' | 'topical',
patientWeight?: number,
patientAge?: number,
renalFunction?: number
): DoseValidationResult {
const rules = getDoseRules(drug, route);
if (!rules) return { valid: true, message: 'No hay reglas de validación disponibles', suggestedRange: null, factors: [] };
const factors: string[] = [];
// SEGURIDAD: si las reglas requieren peso pero falta el peso, BLOQUEAR (no pasar)
if (rules.weightBased) {
if (!patientWeight || patientWeight <= 0) {
return { valid: false, message: `Se requiere peso para ${drug} (fármaco mg/kg)`,
suggestedRange: null, factors: ['weight_missing'] };
}
factors.push('weight');
const maxDose = rules.maxPerKg * patientWeight;
if (dose > maxDose) {
return { valid: false, message: `La dosis excede el máximo para ${patientWeight}kg`,
suggestedRange: { min: rules.minPerKg * patientWeight, max: maxDose, unit: rules.unit }, factors };
}
}
// Ajuste por edad (cuando las reglas definen rangos de edad y se proporciona la edad)
if (rules.ageAdjusted && patientAge !== undefined) {
factors.push('age');
const ageMax = rules.getAgeAdjustedMax(patientAge);
if (dose > ageMax) {
return { valid: false, message: `Excede el máximo ajustado por edad para ${patientAge}años`,
suggestedRange: { min: rules.typicalMin, max: ageMax, unit: rules.unit }, factors };
}
}
// Ajuste renal (cuando las reglas definen rangos de eGFR y se proporciona eGFR)
if (rules.renalAdjusted && renalFunction !== undefined) {
factors.push('renal');
const renalMax = rules.getRenalAdjustedMax(renalFunction);
if (dose > renalMax) {
return { valid: false, message: `Excede el máximo ajustado por función renal para eGFR ${renalFunction}`,
suggestedRange: { min: rules.typicalMin, max: renalMax, unit: rules.unit }, factors };
}
}
// Máximo absoluto
if (dose > rules.absoluteMax) {
return { valid: false, message: `Excede el máximo absoluto ${rules.absoluteMax}${rules.unit}`,
suggestedRange: { min: rules.typicalMin, max: rules.absoluteMax, unit: rules.unit },
factors: [...factors, 'absolute_max'] };
}
return { valid: true, message: 'Dentro del rango',
suggestedRange: { min: rules.typicalMin, max: rules.typicalMax, unit: rules.unit }, factors };
}
Puntuación Clínica: NEWS2
interface NEWS2Input {
respiratoryRate: number; oxygenSaturation: number; supplementalOxygen: boolean;
temperature: number; systolicBP: number; heartRate: number;
consciousness: 'alert' | 'voice' | 'pain' | 'unresponsive';
}
interface NEWS2Result {
total: number; // 0-20
risk: 'low' | 'low-medium' | 'medium' | 'high';
components: Record<string, number>;
escalation: string;
}
Las tablas de puntuación deben coincidir exactamente con la especificación del Royal College of Physicians.
Gravedad de Alerta y Comportamiento UI
| Gravedad | Comportamiento UI | Acción Clínica Requerida |
|---|---|---|
| Crítica | Bloquear acción. Modal no descartable. Rojo. | Debe documentar el motivo de anulación para continuar |
| Mayor | Banner de advertencia en línea. Naranja. | Debe reconocer antes de continuar |
| Menor | Nota informativa en línea. Amarillo. | Solo conciencia, no se requiere acción |
Las alertas críticas NUNCA deben descartarse automáticamente ni implementarse como notificaciones toast. Los motivos de anulación deben almacenarse en el registro de auditoría.
Pruebas de SADC (Tolerancia Cero a Falsos Negativos)
describe('SADC — Seguridad del Paciente', () => {
INTERACTION_PAIRS.forEach(({ drugA, drugB, severity }) => {
it(`detecta ${drugA} + ${drugB} (${severity})`, () => {
const alerts = checkInteractions(drugA, [drugB], []);
expect(alerts.length).toBeGreaterThan(0);
expect(alerts[0].severity).toBe(severity);
});
it(`detecta ${drugB} + ${drugA} (inverso)`, () => {
const alerts = checkInteractions(drugB, [drugA], []);
expect(alerts.length).toBeGreaterThan(0);
});
});
it('bloquea fármaco mg/kg cuando falta el peso', () => {
const result = validateDose('gentamicin', 300, 'iv');
expect(result.valid).toBe(false);
expect(result.factors).toContain('weight_missing');
});
it('maneja datos de fármaco mal formados con gracia', () => {
expect(() => checkInteractions('', [], [])).not.toThrow();
});
});
Criterio de aprobación: 100%. Una sola interacción omitida es un evento de seguridad del paciente.
Anti-Patrones
- Hacer que las comprobaciones SADC sean opcionales o se puedan omitir sin una razón documentada
- Implementar comprobaciones de interacción como notificaciones toast
- Usar tipos
anypara datos de fármacos o clínicos - Codificar pares de interacción en lugar de usar una estructura de datos mantenible
- Capturar errores silenciosamente en el motor SADC (debe mostrar fallos en voz alta)
- Omitir la validación basada en peso cuando no está disponible el peso (debe bloquear, no pasar)
Ejemplos
Ejemplo 1: Comprobación de Interacción Farmacológica
const alerts = checkInteractions('warfarin', ['aspirin', 'metformin'], ['penicillin']);
// [{ severity: 'critical', pair: ['warfarin', 'aspirin'],
// message: 'Riesgo aumentado de sangrado', recommendation: 'Evitar la combinación' }]
Ejemplo 2: Validación de Dosis
const ok = validateDose('paracetamol', 1000, 'oral', 70, 45);
// { valid: true, suggestedRange: { min: 500, max: 4000, unit: 'mg' } }
const bad = validateDose('paracetamol', 5000, 'oral', 70, 45);
// { valid: false, message: 'Excede el máximo absoluto 4000mg' }
const noWeight = validateDose('gentamicin', 300, 'iv');
// { valid: false, factors: ['weight_missing'] }
Ejemplo 3: Puntuación NEWS2
const result = calculateNEWS2({
respiratoryRate: 24, oxygenSaturation: 93, supplementalOxygen: true,
temperature: 38.5, systolicBP: 100, heartRate: 110, consciousness: 'voice'
});
// { total: 13, risk: 'high', escalation: 'Revisión clínica urgente. Considerar UCI.' }