codigo0/docs/ESTRATEGIA_BUSQUEDA_SIGLAS.md

609 lines
18 KiB
Markdown
Raw Normal View History

2026-01-19 08:10:16 +00:00
# Estrategia Técnica: Búsqueda por Siglas y Acrónimos TES
## Arquitectura Frontend - PWA Sanitaria Crítica
**Objetivo:** Añadir soporte para búsqueda por siglas comunes del ámbito TES sin romper la búsqueda existente.
**Principio rector:** Compatibilidad total hacia atrás. La búsqueda actual debe seguir funcionando exactamente igual.
---
## 1. Análisis del Estado Actual
### Funciones de Búsqueda Existentes
**`src/data/procedures.ts`**
```typescript
export const searchProcedures = (query: string): Procedure[] => {
const lowerQuery = query.toLowerCase();
return procedures.filter(
(p) =>
p.title.toLowerCase().includes(lowerQuery) ||
p.shortTitle.toLowerCase().includes(lowerQuery) ||
p.steps.some((s) => s.toLowerCase().includes(lowerQuery))
);
};
```
**`src/data/drugs.ts`**
```typescript
export const searchDrugs = (query: string): Drug[] => {
const lowerQuery = query.toLowerCase();
return drugs.filter(
(d) =>
d.genericName.toLowerCase().includes(lowerQuery) ||
d.tradeName.toLowerCase().includes(lowerQuery) ||
d.indications.some((i) => i.toLowerCase().includes(lowerQuery))
);
};
```
### Punto de Integración Actual
**`src/components/layout/SearchModal.tsx`** (líneas 52-104)
- Llama a `searchProcedures(query)` y `searchDrugs(query)` directamente
- No hay preprocesamiento de la query
- Búsqueda case-insensitive mediante `.toLowerCase()`
### Observaciones Clave
1.**Búsqueda simple y determinista** - Usa `.includes()`, no algoritmos complejos
2.**Offline-compatible** - Todo es código TypeScript, sin llamadas externas
3.**Fácil de mantener** - Lógica clara y directa
4. ⚠️ **No soporta sinónimos** - "OVACE" no encuentra "Obstrucción de Vía Aérea"
5. ⚠️ **No soporta acrónimos** - "EAP" no encuentra "Enfermedad Arterial Periférica"
---
## 2. Propuesta de Arquitectura
### 2.1. Dónde Integrar el Mapeo de Siglas
**OPCIÓN RECOMENDADA: Capa de Preprocesamiento en SearchModal**
```
Usuario escribe "OVACE"
SearchModal.tsx (expandAcronyms)
Query expandida: "OVACE" → "OVACE OR obstrucción vía aérea OR cuerpo extraño"
searchProcedures(query expandida) + searchDrugs(query expandida)
Resultados combinados
```
**Ventajas:**
- ✅ No toca funciones de búsqueda existentes (`searchProcedures`, `searchDrugs`)
- ✅ Cambio localizado en un solo componente
- ✅ Fácil de desactivar si hay problemas (feature flag)
- ✅ Permite expandir a otros tipos de búsqueda en el futuro
**Alternativa (NO recomendada):**
- Modificar `searchProcedures` y `searchDrugs` directamente
- ❌ Riesgo: Cambia comportamiento de funciones usadas en otros lugares
- ❌ Riesgo: Más difícil de testear y revertir
### 2.2. Estructura de Archivos
```
src/data/
├── procedures.ts [NO TOCAR]
├── drugs.ts [NO TOCAR]
└── acronyms.ts [NUEVO] ← Diccionario de siglas
```
**`src/data/acronyms.ts`** - Nuevo archivo
- Diccionario estático de siglas → términos expandidos
- Type-safe con TypeScript
- Sin dependencias externas
**`src/components/layout/SearchModal.tsx`** - Modificar mínimamente
- Añadir función `expandAcronyms()` antes de llamar a búsqueda
- Mantener toda la lógica existente intacta
### 2.3. Formato del Diccionario de Siglas
```typescript
// src/data/acronyms.ts
export interface AcronymMapping {
acronym: string; // "OVACE"
expandedTerms: string[]; // ["obstrucción vía aérea", "cuerpo extraño"]
context?: 'procedure' | 'drug' | 'both'; // Opcional: filtrar contexto
priority?: number; // Opcional: prioridad en resultados
}
export const acronymMappings: AcronymMapping[] = [
{
acronym: 'OVACE',
expandedTerms: ['obstrucción vía aérea', 'cuerpo extraño', 'vía aérea'],
context: 'procedure',
},
{
acronym: 'RCP',
expandedTerms: ['reanimación cardiopulmonar', 'parada cardiorrespiratoria', 'PCR'],
context: 'both',
},
{
acronym: 'PCR',
expandedTerms: ['parada cardiorrespiratoria', 'reanimación cardiopulmonar', 'RCP'],
context: 'both',
},
{
acronym: 'SVB',
expandedTerms: ['soporte vital básico'],
context: 'procedure',
},
{
acronym: 'SVA',
expandedTerms: ['soporte vital avanzado'],
context: 'procedure',
},
{
acronym: 'EAP',
expandedTerms: ['enfermedad arterial periférica'],
context: 'both',
},
{
acronym: 'IAM',
expandedTerms: ['infarto agudo miocardio', 'síndrome coronario agudo'],
context: 'both',
},
{
acronym: 'SCACEST',
expandedTerms: ['síndrome coronario agudo con elevación ST', 'infarto con elevación ST'],
context: 'both',
},
{
acronym: 'IOT',
expandedTerms: ['intubación orotraqueal', 'vía aérea avanzada'],
context: 'procedure',
},
{
acronym: 'DEA',
expandedTerms: ['desfibrilador externo automático'],
context: 'procedure',
},
{
acronym: 'ROSC',
expandedTerms: ['retorno circulación espontánea'],
context: 'procedure',
},
{
acronym: 'GCS',
expandedTerms: ['glasgow', 'escala glasgow'],
context: 'both',
},
{
acronym: 'AVDN',
expandedTerms: ['alerta verbal dolor no responde'],
context: 'procedure',
},
{
acronym: 'FR',
expandedTerms: ['frecuencia respiratoria'],
context: 'both',
},
{
acronym: 'FC',
expandedTerms: ['frecuencia cardiaca'],
context: 'both',
},
{
acronym: 'TA',
expandedTerms: ['tensión arterial'],
context: 'both',
},
{
acronym: 'SpO2',
expandedTerms: ['saturación oxígeno', 'saturación'],
context: 'both',
},
{
acronym: 'ETCO2',
expandedTerms: ['capnografía', 'dióxido carbono espirado'],
context: 'procedure',
},
{
acronym: 'FV',
expandedTerms: ['fibrilación ventricular'],
context: 'procedure',
},
{
acronym: 'TVSP',
expandedTerms: ['taquicardia ventricular sin pulso'],
context: 'procedure',
},
{
acronym: 'AESP',
expandedTerms: ['actividad eléctrica sin pulso'],
context: 'procedure',
},
{
acronym: 'ACR',
expandedTerms: ['asistolia', 'parada cardiorrespiratoria'],
context: 'procedure',
},
{
acronym: 'TCE',
expandedTerms: ['traumatismo craneoencefálico'],
context: 'both',
},
{
acronym: 'TEP',
expandedTerms: ['tromboembolismo pulmonar'],
context: 'both',
},
{
acronym: 'EPOC',
expandedTerms: ['enfermedad pulmonar obstructiva crónica'],
context: 'both',
},
{
acronym: 'INR',
expandedTerms: ['razón normalizada internacional', 'coagulación'],
context: 'both',
},
];
/**
* Expande una sigla/acrónimo a sus términos relacionados
* @param acronym - La sigla a expandir (ej: "OVACE")
* @returns Array de términos expandidos (ej: ["obstrucción vía aérea", "cuerpo extraño"])
*/
export function expandAcronym(acronym: string): string[] {
const upperAcronym = acronym.toUpperCase();
const mapping = acronymMappings.find(m => m.acronym.toUpperCase() === upperAcronym);
return mapping ? mapping.expandedTerms : [];
}
/**
* Obtiene todas las siglas que contienen un término
* Útil para búsqueda inversa: "vía aérea" → ["OVACE", "IOT"]
*/
export function getAcronymsByTerm(term: string): string[] {
const lowerTerm = term.toLowerCase();
return acronymMappings
.filter(m =>
m.expandedTerms.some(expanded => expanded.toLowerCase().includes(lowerTerm))
)
.map(m => m.acronym);
}
```
### 2.4. Algoritmo de Expansión de Query
**Estrategia: Búsqueda OR con términos expandidos**
```typescript
// Pseudocódigo de expandAcronyms()
function expandAcronyms(query: string): string {
const terms = query.split(/\s+/); // Dividir por espacios
const expandedTerms: string[] = [];
for (const term of terms) {
// Si el término es una sigla conocida, expandir
const expanded = expandAcronym(term);
if (expanded.length > 0) {
// Añadir término original + términos expandidos
expandedTerms.push(term);
expandedTerms.push(...expanded);
} else {
// Si no es sigla, mantener término original
expandedTerms.push(term);
}
}
// Retornar query original + términos expandidos
// La búsqueda actual con .includes() encontrará cualquiera de estos términos
return expandedTerms.join(' ');
}
```
**Ejemplo de funcionamiento:**
```
Input: "OVACE"
↓ expandAcronyms()
Output: "OVACE obstrucción vía aérea cuerpo extraño"
↓ searchProcedures()
Encuentra: Procedimiento con shortTitle "OVACE" ✅
Encuentra: Procedimiento con title "Obstrucción de Vía Aérea" ✅
```
**Input: "RCP adulto"**
↓ expandAcronyms()
Output: "RCP reanimación cardiopulmonar parada cardiorrespiratoria PCR adulto"
↓ searchProcedures()
Encuentra: Procedimientos con "RCP" en shortTitle ✅
Encuentra: Procedimientos con "reanimación" en title ✅
---
## 3. Compatibilidad con Búsquedas Actuales
### 3.1. Garantías de Compatibilidad
**✅ Búsquedas existentes siguen funcionando:**
- "parada" → encuentra "parada cardiorrespiratoria" (como antes)
- "adrenalina" → encuentra fármaco (como antes)
- "glasgow" → encuentra calculadora (como antes)
**✅ Búsquedas nuevas funcionan:**
- "OVACE" → encuentra "Obstrucción de Vía Aérea" (nuevo)
- "EAP" → encuentra protocolos relacionados (nuevo)
- "SVB" → encuentra "Soporte Vital Básico" (nuevo)
**✅ Búsquedas mixtas funcionan:**
- "RCP adulto" → encuentra RCP adulto (término original + expandido)
- "OVACE pediátrico" → encuentra OVACE pediátrico
### 3.2. Estrategia de Búsqueda OR (No AND)
**Problema potencial:** Si expandimos "OVACE" a "obstrucción vía aérea cuerpo extraño", y el usuario busca "OVACE pediátrico", podríamos encontrar resultados que solo contengan "pediátrico" sin "OVACE".
**Solución:** Mantener término original en la query expandida
```
Input: "OVACE pediátrico"
Expansión: "OVACE obstrucción vía aérea cuerpo extraño pediátrico"
Búsqueda: .includes("OVACE") OR .includes("obstrucción") OR .includes("pediátrico")
```
**Resultado:** Encuentra protocolos que contengan:
- "OVACE" Y "pediátrico" ✅ (mejor match)
- "obstrucción" Y "pediátrico" ✅ (match parcial)
- Solo "OVACE" ⚠️ (match parcial, pero mejor que nada)
**Mejora futura (opcional):** Sistema de scoring para priorizar matches completos.
### 3.3. Case-Insensitive
La expansión debe ser case-insensitive:
- "ovace" → expande igual que "OVACE"
- "Rcp" → expande igual que "RCP"
Ya está cubierto porque `expandAcronym()` compara con `.toUpperCase()`.
---
## 4. Qué NO Tocar (Zonas de Riesgo)
### 4.1. Archivos que NO deben modificarse
**❌ `src/data/procedures.ts`**
- Función `searchProcedures()` - NO TOCAR
- Array `procedures` - NO TOCAR
- Interfaces `Procedure` - NO TOCAR
- **Razón:** Cambiar esto afectaría toda la aplicación
**❌ `src/data/drugs.ts`**
- Función `searchDrugs()` - NO TOCAR
- Array `drugs` - NO TOCAR
- Interfaces `Drug` - NO TOCAR
- **Razón:** Cambiar esto afectaría toda la aplicación
**❌ Estructura de datos existente**
- No añadir campos nuevos a `Procedure` o `Drug` para siglas
- No modificar `title`, `shortTitle`, etc. para incluir siglas
- **Razón:** Cambios estructurales requieren migración y testing exhaustivo
### 4.2. Componentes que NO deben modificarse (excepto SearchModal)
**❌ `src/pages/SoporteVital.tsx`**
- No añadir lógica de búsqueda aquí
- **Razón:** La búsqueda está centralizada en SearchModal
**❌ `src/pages/Farmacos.tsx`**
- No añadir lógica de búsqueda aquí
- **Razón:** La búsqueda está centralizada en SearchModal
**❌ Otros componentes de búsqueda**
- Si existen otros puntos de búsqueda, evaluar caso por caso
- **Razón:** Mantener consistencia
### 4.3. Service Worker y Offline
**❌ NO modificar `public/sw.js` o lógica de Service Worker**
- El diccionario de siglas es código TypeScript compilado
- Se incluirá automáticamente en el bundle
- **Razón:** No requiere cambios en SW, ya funciona offline
---
## 5. Orden de Implementación Recomendado
### Fase 1: Preparación (Sin riesgo)
1. **Crear `src/data/acronyms.ts`**
- Definir interfaz `AcronymMapping`
- Crear array `acronymMappings` con siglas más comunes (10-15)
- Implementar `expandAcronym()` y `getAcronymsByTerm()`
- **Testing:** Unit tests para funciones de expansión
2. **Testing aislado**
- Test: `expandAcronym("OVACE")``["obstrucción vía aérea", ...]`
- Test: `expandAcronym("xyz")``[]` (sigla desconocida)
- Test: Case-insensitive funciona
### Fase 2: Integración Mínima (Riesgo bajo)
3. **Modificar `SearchModal.tsx`**
- Importar `expandAcronym` desde `acronyms.ts`
- Crear función `expandAcronyms(query: string): string`
- Aplicar expansión ANTES de llamar a `searchProcedures()` y `searchDrugs()`
- **Cambio mínimo:** Solo añadir una línea de preprocesamiento
4. **Testing de integración**
- Test manual: Buscar "OVACE" → debe encontrar protocolo OVACE
- Test manual: Buscar "parada" → debe seguir funcionando (compatibilidad)
- Test manual: Buscar "OVACE pediátrico" → debe encontrar resultados relevantes
### Fase 3: Validación y Expansión (Riesgo bajo)
5. **Validar con usuarios TES**
- Probar búsquedas reales: "EAP", "SVB", "SVA", "IOT", etc.
- Verificar que no se rompe búsqueda existente
- Recopilar feedback sobre siglas faltantes
6. **Expandir diccionario**
- Añadir más siglas basadas en feedback
- Validar que no hay conflictos (misma sigla → diferentes significados)
- Documentar decisiones (ej: "PCR" puede ser "Parada" o "Proteína C Reactiva")
### Fase 4: Optimización (Opcional, bajo riesgo)
7. **Mejoras opcionales**
- Sistema de scoring para priorizar matches completos
- Búsqueda fuzzy para typos (ej: "OVAC" → "OVACE")
- Cache de expansiones para performance
- **Nota:** Solo si hay necesidad real, no pre-optimizar
---
## 6. Riesgos Identificados y Mitigación
### 🔴 RIESGO ALTO: Falsos positivos
**Problema:** Expandir "EAP" a "enfermedad arterial periférica" podría encontrar resultados no relevantes si el término "arterial" aparece en otros contextos.
**Mitigación:**
- Mantener término original en query expandida (prioridad al match exacto)
- Considerar contexto opcional en `AcronymMapping` (`context: 'procedure' | 'drug'`)
- Testing exhaustivo con casos reales
### 🟡 RIESGO MEDIO: Performance con muchas siglas
**Problema:** Si el diccionario crece mucho (100+ siglas), la expansión podría ser lenta.
**Mitigación:**
- Diccionario pequeño inicial (20-30 siglas más comunes)
- Expansión solo si el término coincide exactamente (no fuzzy matching inicial)
- Si es necesario, cache de expansiones
### 🟡 RIESGO MEDIO: Ambiguidad de siglas
**Problema:** "PCR" puede ser "Parada Cardiorrespiratoria" o "Proteína C Reactiva".
**Mitigación:**
- Incluir ambos significados en `expandedTerms`
- La búsqueda encontrará ambos, usuario decide cuál es relevante
- Documentar ambigüedades en comentarios del código
### 🟢 RIESGO BAJO: Cambios en comportamiento de búsqueda
**Problema:** Usuarios acostumbrados a que "OVACE" no encuentra nada podrían confundirse si ahora sí encuentra.
**Mitigación:**
- Esto es una mejora, no un problema
- Documentar en changelog
- Si hay quejas, se puede desactivar fácilmente (feature flag)
---
## 7. Testing Strategy
### Tests Unitarios (Recomendado)
```typescript
// tests/acronyms.test.ts
describe('expandAcronym', () => {
it('expande OVACE correctamente', () => {
expect(expandAcronym('OVACE')).toContain('obstrucción vía aérea');
});
it('es case-insensitive', () => {
expect(expandAcronym('ovace')).toEqual(expandAcronym('OVACE'));
});
it('retorna array vacío para sigla desconocida', () => {
expect(expandAcronym('XYZ')).toEqual([]);
});
});
```
### Tests de Integración (Manual inicialmente)
1. **Búsqueda "OVACE"** → Debe encontrar protocolo OVACE
2. **Búsqueda "parada"** → Debe seguir funcionando (compatibilidad)
3. **Búsqueda "RCP adulto"** → Debe encontrar RCP adulto
4. **Búsqueda "EAP"** → Debe encontrar protocolos relacionados
### Tests de Regresión
- Todas las búsquedas que funcionaban antes deben seguir funcionando
- No debe haber cambios en resultados para queries sin siglas
---
## 8. Consideraciones de Mantenimiento
### 8.1. Añadir Nuevas Siglas
**Proceso simple:**
1. Añadir entrada a `acronymMappings` en `acronyms.ts`
2. Testing manual
3. Deploy
**No requiere:**
- Cambios en funciones de búsqueda
- Migración de datos
- Cambios en UI
### 8.2. Documentación
**Mantener documentado:**
- Lista de siglas soportadas (en comentarios o README)
- Decisiones sobre ambigüedades (ej: "PCR" tiene 2 significados)
- Fuentes de las siglas (manual TES, protocolos oficiales)
### 8.3. Versionado
**Si hay cambios breaking (poco probable):**
- Versionar el diccionario
- Mantener versiones anteriores para compatibilidad
- Migración gradual si es necesario
---
## 9. Resumen Ejecutivo
### Cambios Propuestos
1. **Nuevo archivo:** `src/data/acronyms.ts` - Diccionario de siglas
2. **Modificación mínima:** `src/components/layout/SearchModal.tsx` - Preprocesamiento de query
3. **Sin cambios:** `src/data/procedures.ts`, `src/data/drugs.ts` - Funciones de búsqueda intactas
### Beneficios
- ✅ Búsqueda por siglas funciona (OVACE, EAP, SVB, etc.)
- ✅ Compatibilidad total con búsqueda existente
- ✅ Offline-compatible (todo en código TypeScript)
- ✅ Fácil de mantener (diccionario simple)
- ✅ Fácil de revertir (cambio localizado)
### Riesgos
- 🟡 Falsos positivos (mitigado con término original)
- 🟡 Performance (mitigado con diccionario pequeño)
- 🟢 Cambio de comportamiento (mejora, no problema)
### Esfuerzo Estimado
- **Fase 1 (Preparación):** 2-3 horas
- **Fase 2 (Integración):** 1-2 horas
- **Fase 3 (Validación):** 2-4 horas (testing con usuarios)
- **Total:** 5-9 horas
### Recomendación Final
**✅ PROCEDER con la implementación**
La estrategia es segura, localizada y reversible. El riesgo es bajo y el beneficio es alto para usuarios TES que usan siglas constantemente.
---
*Documento de arquitectura - No incluye código de implementación, solo diseño y estrategia.*