547 lines
16 KiB
Markdown
547 lines
16 KiB
Markdown
|
|
# 📋 Sistema de Validación de Protocolos
|
||
|
|
|
||
|
|
## 🎯 Objetivo
|
||
|
|
|
||
|
|
Crear un sistema para validar la secuencia de pasos de protocolos y las dependencias entre protocolos.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 📋 Reglas de Negocio
|
||
|
|
|
||
|
|
### 1. Validación de Secuencia de Pasos
|
||
|
|
|
||
|
|
#### Reglas de Secuencia
|
||
|
|
- ✅ Los pasos deben estar numerados secuencialmente (1, 2, 3...)
|
||
|
|
- ✅ No puede haber pasos faltantes en la secuencia
|
||
|
|
- ✅ Los pasos deben tener un orden lógico (no se puede hacer paso 3 antes del paso 1)
|
||
|
|
- ✅ Pasos críticos deben estar marcados y no pueden omitirse
|
||
|
|
- ✅ Pasos opcionales deben estar claramente marcados
|
||
|
|
|
||
|
|
#### Tipos de Pasos
|
||
|
|
- **Obligatorio:** Debe ejecutarse siempre
|
||
|
|
- **Condicional:** Se ejecuta si se cumple condición
|
||
|
|
- **Opcional:** Puede omitirse según contexto
|
||
|
|
- **Crítico:** Bloquea el protocolo si se omite
|
||
|
|
|
||
|
|
### 2. Dependencias entre Protocolos
|
||
|
|
|
||
|
|
#### Tipos de Dependencias
|
||
|
|
- **Prerequisito:** Protocolo A debe completarse antes de Protocolo B
|
||
|
|
- **Siguiente:** Protocolo A normalmente lleva a Protocolo B
|
||
|
|
- **Alternativo:** Protocolo A o Protocolo B (mutuamente excluyentes)
|
||
|
|
- **Complementario:** Protocolo A se usa junto con Protocolo B
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🏗️ Arquitectura
|
||
|
|
|
||
|
|
### Domain Layer
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// domain/entities/Protocol.ts
|
||
|
|
export interface Protocol {
|
||
|
|
readonly id: string;
|
||
|
|
readonly title: string;
|
||
|
|
readonly shortTitle: string;
|
||
|
|
readonly category: Category;
|
||
|
|
readonly priority: Priority;
|
||
|
|
readonly ageGroup: AgeGroup;
|
||
|
|
readonly steps: readonly ProtocolStep[];
|
||
|
|
readonly prerequisites: readonly string[]; // IDs de protocolos prerequisito
|
||
|
|
readonly nextProtocols: readonly string[]; // IDs de protocolos siguientes
|
||
|
|
readonly alternatives: readonly string[]; // IDs de protocolos alternativos
|
||
|
|
readonly complements: readonly string[]; // IDs de protocolos complementarios
|
||
|
|
readonly criticalSteps: readonly number[]; // Índices de pasos críticos
|
||
|
|
readonly status: ContentStatus;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ProtocolStep {
|
||
|
|
readonly order: number; // Orden en la secuencia (1, 2, 3...)
|
||
|
|
readonly type: 'obligatory' | 'conditional' | 'optional' | 'critical';
|
||
|
|
readonly content: string;
|
||
|
|
readonly condition?: string; // Condición para pasos condicionales
|
||
|
|
readonly equipment?: readonly string[];
|
||
|
|
readonly drugs?: readonly string[];
|
||
|
|
readonly warnings?: readonly string[];
|
||
|
|
readonly estimatedTime?: number; // Segundos estimados
|
||
|
|
}
|
||
|
|
|
||
|
|
// domain/value-objects/ProtocolSequence.ts
|
||
|
|
export class ProtocolSequence {
|
||
|
|
private constructor(private readonly steps: readonly ProtocolStep[]) {}
|
||
|
|
|
||
|
|
static create(steps: ProtocolStep[]): ProtocolSequence {
|
||
|
|
// Validar secuencia
|
||
|
|
const sortedSteps = [...steps].sort((a, b) => a.order - b.order);
|
||
|
|
|
||
|
|
// Verificar que no hay huecos
|
||
|
|
for (let i = 0; i < sortedSteps.length; i++) {
|
||
|
|
if (sortedSteps[i].order !== i + 1) {
|
||
|
|
throw new Error(`Paso faltante en secuencia: esperado ${i + 1}, encontrado ${sortedSteps[i].order}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verificar que no hay duplicados
|
||
|
|
const orders = sortedSteps.map(s => s.order);
|
||
|
|
const uniqueOrders = new Set(orders);
|
||
|
|
if (orders.length !== uniqueOrders.size) {
|
||
|
|
throw new Error('Hay pasos con el mismo número de orden');
|
||
|
|
}
|
||
|
|
|
||
|
|
return new ProtocolSequence(sortedSteps);
|
||
|
|
}
|
||
|
|
|
||
|
|
validateExecution(executedSteps: number[]): ProtocolValidationResult {
|
||
|
|
const errors: string[] = [];
|
||
|
|
const warnings: string[] = [];
|
||
|
|
|
||
|
|
// Verificar que todos los pasos críticos están ejecutados
|
||
|
|
const criticalSteps = this.steps
|
||
|
|
.filter(s => s.type === 'critical')
|
||
|
|
.map(s => s.order);
|
||
|
|
|
||
|
|
const missingCritical = criticalSteps.filter(step => !executedSteps.includes(step));
|
||
|
|
if (missingCritical.length > 0) {
|
||
|
|
errors.push(
|
||
|
|
`Pasos críticos no ejecutados: ${missingCritical.join(', ')}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verificar que todos los pasos obligatorios están ejecutados
|
||
|
|
const obligatorySteps = this.steps
|
||
|
|
.filter(s => s.type === 'obligatory')
|
||
|
|
.map(s => s.order);
|
||
|
|
|
||
|
|
const missingObligatory = obligatorySteps.filter(step => !executedSteps.includes(step));
|
||
|
|
if (missingObligatory.length > 0) {
|
||
|
|
errors.push(
|
||
|
|
`Pasos obligatorios no ejecutados: ${missingObligatory.join(', ')}`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verificar orden de ejecución
|
||
|
|
const sortedExecuted = [...executedSteps].sort((a, b) => a - b);
|
||
|
|
if (JSON.stringify(executedSteps) !== JSON.stringify(sortedExecuted)) {
|
||
|
|
warnings.push('Los pasos no se ejecutaron en orden secuencial');
|
||
|
|
}
|
||
|
|
|
||
|
|
// Verificar pasos condicionales
|
||
|
|
for (const step of this.steps) {
|
||
|
|
if (step.type === 'conditional' && executedSteps.includes(step.order)) {
|
||
|
|
if (!step.condition) {
|
||
|
|
warnings.push(`Paso ${step.order} es condicional pero no tiene condición definida`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
valid: errors.length === 0,
|
||
|
|
errors,
|
||
|
|
warnings,
|
||
|
|
executedSteps,
|
||
|
|
missingSteps: this.steps
|
||
|
|
.map(s => s.order)
|
||
|
|
.filter(order => !executedSteps.includes(order))
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
getNextStep(currentStep?: number): ProtocolStep | null {
|
||
|
|
if (currentStep === undefined) {
|
||
|
|
return this.steps[0] || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const currentIndex = this.steps.findIndex(s => s.order === currentStep);
|
||
|
|
if (currentIndex === -1) return null;
|
||
|
|
|
||
|
|
return this.steps[currentIndex + 1] || null;
|
||
|
|
}
|
||
|
|
|
||
|
|
getStep(order: number): ProtocolStep | null {
|
||
|
|
return this.steps.find(s => s.order === order) || null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ProtocolValidationResult {
|
||
|
|
valid: boolean;
|
||
|
|
errors: string[];
|
||
|
|
warnings: string[];
|
||
|
|
executedSteps: number[];
|
||
|
|
missingSteps: number[];
|
||
|
|
}
|
||
|
|
|
||
|
|
// domain/value-objects/ProtocolDependency.ts
|
||
|
|
export class ProtocolDependency {
|
||
|
|
private constructor(
|
||
|
|
readonly protocolId: string,
|
||
|
|
readonly dependencyType: 'prerequisite' | 'next' | 'alternative' | 'complement',
|
||
|
|
readonly targetProtocolId: string,
|
||
|
|
readonly required: boolean
|
||
|
|
) {}
|
||
|
|
|
||
|
|
static createPrerequisite(
|
||
|
|
protocolId: string,
|
||
|
|
prerequisiteId: string,
|
||
|
|
required: boolean = true
|
||
|
|
): ProtocolDependency {
|
||
|
|
return new ProtocolDependency(
|
||
|
|
protocolId,
|
||
|
|
'prerequisite',
|
||
|
|
prerequisiteId,
|
||
|
|
required
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
static createNext(
|
||
|
|
protocolId: string,
|
||
|
|
nextProtocolId: string
|
||
|
|
): ProtocolDependency {
|
||
|
|
return new ProtocolDependency(
|
||
|
|
protocolId,
|
||
|
|
'next',
|
||
|
|
nextProtocolId,
|
||
|
|
false
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
static createAlternative(
|
||
|
|
protocolId: string,
|
||
|
|
alternativeId: string
|
||
|
|
): ProtocolDependency {
|
||
|
|
return new ProtocolDependency(
|
||
|
|
protocolId,
|
||
|
|
'alternative',
|
||
|
|
alternativeId,
|
||
|
|
false
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
static createComplement(
|
||
|
|
protocolId: string,
|
||
|
|
complementId: string
|
||
|
|
): ProtocolDependency {
|
||
|
|
return new ProtocolDependency(
|
||
|
|
protocolId,
|
||
|
|
'complement',
|
||
|
|
complementId,
|
||
|
|
false
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
validate(
|
||
|
|
protocolExecuted: boolean,
|
||
|
|
targetProtocolExecuted: boolean
|
||
|
|
): { valid: boolean; error?: string } {
|
||
|
|
switch (this.dependencyType) {
|
||
|
|
case 'prerequisite':
|
||
|
|
if (this.required && protocolExecuted && !targetProtocolExecuted) {
|
||
|
|
return {
|
||
|
|
valid: false,
|
||
|
|
error: `Protocolo ${this.targetProtocolId} debe ejecutarse antes de ${this.protocolId}`
|
||
|
|
};
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'alternative':
|
||
|
|
if (protocolExecuted && targetProtocolExecuted) {
|
||
|
|
return {
|
||
|
|
valid: false,
|
||
|
|
error: `Protocolos ${this.protocolId} y ${this.targetProtocolId} son mutuamente excluyentes`
|
||
|
|
};
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
return { valid: true };
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Application Layer
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// application/services/ProtocolValidationService.ts
|
||
|
|
export class ProtocolValidationService {
|
||
|
|
constructor(
|
||
|
|
private readonly protocolRepository: IProtocolRepository,
|
||
|
|
private readonly dependencyRepository: IDependencyRepository
|
||
|
|
) {}
|
||
|
|
|
||
|
|
async validateProtocolSequence(
|
||
|
|
protocolId: string,
|
||
|
|
executedSteps: number[]
|
||
|
|
): Promise<ProtocolValidationResult> {
|
||
|
|
const protocol = await this.protocolRepository.findById(protocolId);
|
||
|
|
if (!protocol) {
|
||
|
|
throw new Error(`Protocolo ${protocolId} no encontrado`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const sequence = ProtocolSequence.create(protocol.steps);
|
||
|
|
return sequence.validateExecution(executedSteps);
|
||
|
|
}
|
||
|
|
|
||
|
|
async validateProtocolDependencies(
|
||
|
|
protocolId: string,
|
||
|
|
executedProtocols: string[]
|
||
|
|
): Promise<DependencyValidationResult> {
|
||
|
|
const protocol = await this.protocolRepository.findById(protocolId);
|
||
|
|
if (!protocol) {
|
||
|
|
throw new Error(`Protocolo ${protocolId} no encontrado`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const dependencies = await this.dependencyRepository.findByProtocol(protocolId);
|
||
|
|
const errors: string[] = [];
|
||
|
|
const warnings: string[] = [];
|
||
|
|
|
||
|
|
// Validar prerequisitos
|
||
|
|
for (const prereqId of protocol.prerequisites) {
|
||
|
|
if (!executedProtocols.includes(prereqId)) {
|
||
|
|
const prereq = await this.protocolRepository.findById(prereqId);
|
||
|
|
errors.push(
|
||
|
|
`Protocolo prerequisito "${prereq?.title || prereqId}" no ha sido ejecutado`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validar alternativos
|
||
|
|
for (const altId of protocol.alternatives) {
|
||
|
|
if (executedProtocols.includes(altId)) {
|
||
|
|
const alt = await this.protocolRepository.findById(altId);
|
||
|
|
warnings.push(
|
||
|
|
`Protocolo alternativo "${alt?.title || altId}" ya fue ejecutado. ` +
|
||
|
|
`Estos protocolos son mutuamente excluyentes.`
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sugerir protocolos siguientes
|
||
|
|
const suggestedNext: string[] = [];
|
||
|
|
for (const nextId of protocol.nextProtocols) {
|
||
|
|
if (!executedProtocols.includes(nextId)) {
|
||
|
|
suggestedNext.push(nextId);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
valid: errors.length === 0,
|
||
|
|
errors,
|
||
|
|
warnings,
|
||
|
|
suggestedNext
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
async getProtocolExecutionPath(
|
||
|
|
startProtocolId: string,
|
||
|
|
context: ProtocolContext
|
||
|
|
): Promise<ProtocolExecutionPath> {
|
||
|
|
const path: ProtocolExecutionPath = {
|
||
|
|
protocols: [],
|
||
|
|
totalEstimatedTime: 0,
|
||
|
|
warnings: []
|
||
|
|
};
|
||
|
|
|
||
|
|
const visited = new Set<string>();
|
||
|
|
const queue: string[] = [startProtocolId];
|
||
|
|
|
||
|
|
while (queue.length > 0) {
|
||
|
|
const protocolId = queue.shift()!;
|
||
|
|
if (visited.has(protocolId)) continue;
|
||
|
|
|
||
|
|
visited.add(protocolId);
|
||
|
|
const protocol = await this.protocolRepository.findById(protocolId);
|
||
|
|
if (!protocol) continue;
|
||
|
|
|
||
|
|
// Validar prerequisitos
|
||
|
|
const depValidation = await this.validateProtocolDependencies(
|
||
|
|
protocolId,
|
||
|
|
path.protocols.map(p => p.id)
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!depValidation.valid) {
|
||
|
|
path.warnings.push(...depValidation.errors);
|
||
|
|
continue; // Saltar si faltan prerequisitos
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calcular tiempo estimado
|
||
|
|
const estimatedTime = protocol.steps.reduce(
|
||
|
|
(sum, step) => sum + (step.estimatedTime || 30),
|
||
|
|
0
|
||
|
|
);
|
||
|
|
|
||
|
|
path.protocols.push({
|
||
|
|
id: protocol.id,
|
||
|
|
title: protocol.title,
|
||
|
|
estimatedTime
|
||
|
|
});
|
||
|
|
|
||
|
|
path.totalEstimatedTime += estimatedTime;
|
||
|
|
|
||
|
|
// Agregar protocolos siguientes a la cola
|
||
|
|
for (const nextId of protocol.nextProtocols) {
|
||
|
|
if (!visited.has(nextId) && !queue.includes(nextId)) {
|
||
|
|
queue.push(nextId);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return path;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface DependencyValidationResult {
|
||
|
|
valid: boolean;
|
||
|
|
errors: string[];
|
||
|
|
warnings: string[];
|
||
|
|
suggestedNext: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ProtocolExecutionPath {
|
||
|
|
protocols: Array<{
|
||
|
|
id: string;
|
||
|
|
title: string;
|
||
|
|
estimatedTime: number;
|
||
|
|
}>;
|
||
|
|
totalEstimatedTime: number;
|
||
|
|
warnings: string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ProtocolContext {
|
||
|
|
ageGroup: AgeGroup;
|
||
|
|
priority: Priority;
|
||
|
|
availableEquipment?: string[];
|
||
|
|
availableDrugs?: string[];
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🗄️ Esquema de Base de Datos
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- Tabla de pasos de protocolo
|
||
|
|
CREATE TABLE tes_content.protocol_steps (
|
||
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||
|
|
protocol_id VARCHAR(100) NOT NULL REFERENCES tes_content.content_items(id),
|
||
|
|
step_order INTEGER NOT NULL,
|
||
|
|
step_type VARCHAR(20) NOT NULL DEFAULT 'obligatory',
|
||
|
|
content TEXT NOT NULL,
|
||
|
|
condition TEXT,
|
||
|
|
equipment TEXT[],
|
||
|
|
drugs TEXT[],
|
||
|
|
warnings TEXT[],
|
||
|
|
estimated_time_seconds INTEGER,
|
||
|
|
|
||
|
|
CONSTRAINT chk_step_type CHECK (step_type IN ('obligatory', 'conditional', 'optional', 'critical')),
|
||
|
|
CONSTRAINT unique_protocol_step_order UNIQUE (protocol_id, step_order)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX idx_protocol_steps_protocol_id ON tes_content.protocol_steps(protocol_id);
|
||
|
|
CREATE INDEX idx_protocol_steps_order ON tes_content.protocol_steps(protocol_id, step_order);
|
||
|
|
|
||
|
|
-- Tabla de dependencias entre protocolos
|
||
|
|
CREATE TABLE tes_content.protocol_dependencies (
|
||
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||
|
|
protocol_id VARCHAR(100) NOT NULL REFERENCES tes_content.content_items(id),
|
||
|
|
dependency_type VARCHAR(20) NOT NULL,
|
||
|
|
target_protocol_id VARCHAR(100) NOT NULL REFERENCES tes_content.content_items(id),
|
||
|
|
required BOOLEAN NOT NULL DEFAULT false,
|
||
|
|
notes TEXT,
|
||
|
|
|
||
|
|
CONSTRAINT chk_dependency_type CHECK (
|
||
|
|
dependency_type IN ('prerequisite', 'next', 'alternative', 'complement')
|
||
|
|
),
|
||
|
|
CONSTRAINT chk_no_self_dependency CHECK (protocol_id != target_protocol_id)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX idx_protocol_dependencies_protocol ON tes_content.protocol_dependencies(protocol_id);
|
||
|
|
CREATE INDEX idx_protocol_dependencies_target ON tes_content.protocol_dependencies(target_protocol_id);
|
||
|
|
CREATE INDEX idx_protocol_dependencies_type ON tes_content.protocol_dependencies(dependency_type);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ✅ Casos de Uso
|
||
|
|
|
||
|
|
### Caso 1: Validar ejecución de protocolo
|
||
|
|
```typescript
|
||
|
|
const validation = await protocolValidationService.validateProtocolSequence(
|
||
|
|
'rcp-adulto-svb',
|
||
|
|
[1, 2, 3, 4, 5] // Pasos ejecutados
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!validation.valid) {
|
||
|
|
console.error('Errores:', validation.errors);
|
||
|
|
// Bloquear continuación del protocolo
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Caso 2: Validar dependencias antes de iniciar protocolo
|
||
|
|
```typescript
|
||
|
|
const depValidation = await protocolValidationService.validateProtocolDependencies(
|
||
|
|
'rcp-adulto-sva',
|
||
|
|
['rcp-adulto-svb'] // Protocolos ya ejecutados
|
||
|
|
);
|
||
|
|
|
||
|
|
if (!depValidation.valid) {
|
||
|
|
throw new Error(`No se puede ejecutar: ${depValidation.errors.join(', ')}`);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Caso 3: Obtener ruta de ejecución sugerida
|
||
|
|
```typescript
|
||
|
|
const path = await protocolValidationService.getProtocolExecutionPath(
|
||
|
|
'rcp-adulto-svb',
|
||
|
|
{
|
||
|
|
ageGroup: 'adulto',
|
||
|
|
priority: 'critico'
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
console.log(`Ruta sugerida: ${path.protocols.map(p => p.title).join(' → ')}`);
|
||
|
|
console.log(`Tiempo estimado: ${path.totalEstimatedTime} segundos`);
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 🧪 Tests
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
describe('ProtocolValidationService', () => {
|
||
|
|
describe('validateProtocolSequence', () => {
|
||
|
|
it('should reject if critical steps are missing', async () => {
|
||
|
|
const result = await service.validateProtocolSequence(
|
||
|
|
'rcp-adulto-svb',
|
||
|
|
[1, 2, 3] // Faltan pasos críticos 4 y 5
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(result.valid).toBe(false);
|
||
|
|
expect(result.errors).toContain(expect.stringContaining('críticos'));
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should warn if steps are out of order', async () => {
|
||
|
|
const result = await service.validateProtocolSequence(
|
||
|
|
'rcp-adulto-svb',
|
||
|
|
[1, 3, 2, 4, 5] // Orden incorrecto
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(result.warnings).toContain(expect.stringContaining('orden'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('validateProtocolDependencies', () => {
|
||
|
|
it('should reject if prerequisites are missing', async () => {
|
||
|
|
const result = await service.validateProtocolDependencies(
|
||
|
|
'rcp-adulto-sva',
|
||
|
|
[] // No hay protocolos ejecutados
|
||
|
|
);
|
||
|
|
|
||
|
|
expect(result.valid).toBe(false);
|
||
|
|
expect(result.errors).toContain(expect.stringContaining('prerequisito'));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Fin del documento**
|