codigo0/docs/CASOS_BORDE.md

487 lines
10 KiB
Markdown
Raw Normal View History

# 🔍 Casos de Borde - Análisis Uno por Uno
## 🎯 Objetivo
Estudiar caso por caso todos los edge cases posibles en la aplicación médica.
---
## 1. Dosis Pediátricas
### Caso 1.1: Peso = 0 o negativo
**Problema:** División por cero o cálculo inválido
**Solución:**
```typescript
class PatientWeight {
static fromKg(kg: number): PatientWeight {
if (kg <= 0) {
throw new Error('Peso debe ser mayor que cero');
}
if (kg > 200) {
throw new Error('Peso excesivo, verificar entrada');
}
return new PatientWeight(kg, 'kg');
}
}
```
**Validación:** Bloquear cálculo si peso <= 0
---
### Caso 1.2: Peso fuera de rango para edad
**Problema:** Niño de 5 años con peso de 100kg (imposible)
**Solución:**
```typescript
isValidForAge(age: PatientAge): { valid: boolean; warning?: string } {
const range = age.getWeightRange();
const weightKg = this.toKg();
if (weightKg < range.min * 0.5) {
return {
valid: false,
warning: `Peso extremadamente bajo. Verificar entrada.`
};
}
if (weightKg > range.max * 2) {
return {
valid: false,
warning: `Peso extremadamente alto. Verificar entrada.`
};
}
return { valid: true };
}
```
**Validación:** Bloquear si peso < 50% del mínimo O > 200% del máximo
---
### Caso 1.3: Dosis pediátrica no definida
**Problema:** Fármaco sin dosis pediátrica pero se intenta calcular
**Solución:**
```typescript
async calculatePediatricDose(...): Promise<DoseCalculation> {
if (!drug.pediatricDose) {
throw new Error(
`No hay dosis pediátrica definida para ${drug.genericName}. ` +
`Usar dosis adulto con precaución o consultar referencia.`
);
}
// ...
}
```
**Validación:** Bloquear cálculo, mostrar advertencia
---
### Caso 1.4: Cálculo resulta en dosis < 0.01mg
**Problema:** Dosis muy pequeña, difícil de medir
**Solución:**
```typescript
if (calculatedDose < 0.01) {
warnings.push(
`Dosis calculada (${calculatedDose}mg) es muy pequeña para medir con precisión. ` +
`Considerar dilución o consultar referencia especializada.`
);
}
```
**Validación:** Advertencia, no bloquea
---
## 2. Protocolos
### Caso 2.1: Protocolo sin pasos
**Problema:** Protocolo creado sin pasos definidos
**Solución:**
```typescript
static create(...): Protocol {
if (!steps || steps.length === 0) {
throw new Error('Protocolo debe tener al menos un paso');
}
// ...
}
```
**Validación:** Bloquear creación/validación
---
### Caso 2.2: Pasos con números duplicados
**Problema:** Dos pasos con order = 3
**Solución:**
```typescript
static create(steps: ProtocolStep[]): ProtocolSequence {
const orders = steps.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');
}
// ...
}
```
**Validación:** Bloquear creación
---
### Caso 2.3: Pasos faltantes en secuencia
**Problema:** Pasos 1, 2, 4, 5 (falta el 3)
**Solución:**
```typescript
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}`
);
}
}
```
**Validación:** Bloquear creación
---
### Caso 2.4: Protocolo prerequisito no existe
**Problema:** Protocolo A requiere Protocolo B que no existe
**Solución:**
```typescript
async validateProtocolDependencies(...): Promise<DependencyValidationResult> {
for (const prereqId of protocol.prerequisites) {
const prereq = await this.protocolRepository.findById(prereqId);
if (!prereq) {
errors.push(`Protocolo prerequisito "${prereqId}" no existe`);
}
}
// ...
}
```
**Validación:** Bloquear si prerequisito no existe
---
### Caso 2.5: Dependencia circular
**Problema:** Protocolo A requiere B, B requiere A
**Solución:**
```typescript
async detectCircularDependency(
protocolId: string,
targetId: string,
visited: Set<string> = new Set()
): Promise<boolean> {
if (visited.has(protocolId)) {
return protocolId === targetId; // Circular
}
visited.add(protocolId);
const protocol = await this.protocolRepository.findById(protocolId);
for (const prereqId of protocol.prerequisites) {
if (await this.detectCircularDependency(prereqId, targetId, visited)) {
return true;
}
}
return false;
}
```
**Validación:** Bloquear creación de dependencia circular
---
## 3. Contenido Médico
### Caso 3.1: Contenido sin título
**Problema:** Crear contenido con título vacío
**Solución:**
```typescript
static create(...): ContentItem {
if (!title || title.trim().length === 0) {
throw new Error('Título es obligatorio');
}
// ...
}
```
**Validación:** Bloquear creación
---
### Caso 3.2: Intentar publicar sin validación médica
**Problema:** Publicar contenido que no ha sido revisado
**Solución:**
```typescript
async publishContent(...): Promise<void> {
if (content.status !== 'approved') {
throw new Error('Solo contenido aprobado puede publicarse');
}
if (!content.validatedBy) {
throw new Error('Contenido debe estar validado por un médico');
}
// ...
}
```
**Validación:** Bloquear publicación
---
### Caso 3.3: Contenido rechazado intenta publicarse
**Problema:** Publicar contenido que fue rechazado
**Solución:**
```typescript
const rejectedReviews = reviews.filter(r => r.status === 'rejected');
if (rejectedReviews.length > 0 && content.status === 'draft') {
// Contenido fue rechazado y está en borrador
// Permitir edición pero no publicación directa
}
```
**Validación:** Bloquear publicación hasta nueva revisión
---
### Caso 3.4: Slug duplicado
**Problema:** Dos contenidos con el mismo slug
**Solución:**
```typescript
async save(content: ContentItem): Promise<ContentItem> {
const existing = await this.findBySlug(content.slug);
if (existing && existing.id !== content.id) {
throw new Error(`Slug "${content.slug}" ya existe`);
}
// ...
}
```
**Validación:** Bloquear creación/actualización
---
## 4. Búsqueda
### Caso 4.1: Búsqueda vacía
**Problema:** Query string vacío
**Solución:**
```typescript
if (!search || search.trim().length === 0) {
return { items: [], total: 0 };
}
```
**Validación:** Retornar resultados vacíos, no error
---
### Caso 4.2: Búsqueda sin resultados
**Problema:** No hay resultados para la búsqueda
**Solución:**
```typescript
if (results.length === 0) {
return {
items: [],
total: 0,
suggestions: await this.getSearchSuggestions(search)
};
}
```
**Validación:** Retornar sugerencias, no error
---
### Caso 4.3: Búsqueda con caracteres especiales
**Problema:** SQL injection o caracteres problemáticos
**Solución:**
```typescript
const sanitized = search.trim().replace(/[%_]/g, '\\$&');
// Usar parámetros preparados siempre
```
**Validación:** Sanitizar entrada, usar parámetros preparados
---
## 5. Offline
### Caso 5.1: Contenido crítico no en cache
**Problema:** Usuario offline, contenido crítico no disponible
**Solución:**
```typescript
async getContent(id: string): Promise<ContentItem | null> {
// Intentar cache primero
const cached = await cache.get(`content:${id}`);
if (cached) return cached;
// Si offline y crítico, retornar versión mínima
if (isOffline() && isCritical(id)) {
return getMinimalCriticalContent(id);
}
return null;
}
```
**Validación:** Retornar versión mínima si crítico, error si no crítico
---
### Caso 5.2: Cache corrupto
**Problema:** Datos en cache corruptos o incompletos
**Solución:**
```typescript
try {
const cached = JSON.parse(cacheData);
if (!isValidContent(cached)) {
throw new Error('Cache corrupto');
}
return cached;
} catch (error) {
// Limpiar cache y recargar
await cache.delete(key);
return await loadFromSource(id);
}
```
**Validación:** Validar integridad, limpiar si corrupto
---
## 6. Validación Médica
### Caso 6.1: Revisor intenta aprobar su propio contenido
**Problema:** Conflicto de intereses
**Solución:**
```typescript
async approveContent(...): Promise<void> {
if (content.createdBy === reviewerId) {
throw new Error('No puedes aprobar tu propio contenido');
}
// ...
}
```
**Validación:** Bloquear auto-aprobación
---
### Caso 6.2: Múltiples revisores aprueban simultáneamente
**Problema:** Race condition
**Solución:**
```typescript
// Usar transacción con lock
await db.transaction(async (tx) => {
const content = await tx.query(
'SELECT * FROM content_items WHERE id = $1 FOR UPDATE',
[contentId]
);
if (content.status !== 'in_review') {
throw new Error('Estado cambió durante la revisión');
}
// Actualizar...
});
```
**Validación:** Usar locks de base de datos
---
## 7. Medios Audiovisuales
### Caso 7.1: Archivo demasiado grande
**Problema:** Upload de archivo > límite
**Solución:**
```typescript
const MAX_SIZE = 50 * 1024 * 1024; // 50MB
if (file.size > MAX_SIZE) {
throw new Error(`Archivo excede tamaño máximo (${MAX_SIZE / 1024 / 1024}MB)`);
}
```
**Validación:** Rechazar upload
---
### Caso 7.2: Tipo de archivo no permitido
**Problema:** Upload de archivo .exe o .bat
**Solución:**
```typescript
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'video/mp4', ...];
if (!ALLOWED_TYPES.includes(file.mimetype)) {
throw new Error(`Tipo de archivo no permitido: ${file.mimetype}`);
}
```
**Validación:** Rechazar upload
---
### Caso 7.3: Archivo corrupto o inválido
**Problema:** Imagen corrupta que no se puede procesar
**Solución:**
```typescript
try {
const image = await sharp(file.buffer).metadata();
if (!image.width || !image.height) {
throw new Error('Imagen inválida');
}
} catch (error) {
throw new Error('Archivo corrupto o inválido');
}
```
**Validación:** Validar integridad antes de guardar
---
## 📝 Resumen de Validaciones
| Caso | Severidad | Acción |
|------|-----------|--------|
| Peso <= 0 | Crítico | Bloquear |
| Dosis fuera de rango | Crítico | Bloquear |
| Paso crítico omitido | Crítico | Bloquear |
| Contenido sin validación | Crítico | Bloquear publicación |
| Peso fuera de percentiles | Advertencia | Mostrar, permitir con confirmación |
| Dosis en límites | Advertencia | Mostrar, permitir |
| Búsqueda sin resultados | Info | Mostrar sugerencias |
---
**Fin del documento**