18 KiB
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
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
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)ysearchDrugs(query)directamente - No hay preprocesamiento de la query
- Búsqueda case-insensitive mediante
.toLowerCase()
Observaciones Clave
- ✅ Búsqueda simple y determinista - Usa
.includes(), no algoritmos complejos - ✅ Offline-compatible - Todo es código TypeScript, sin llamadas externas
- ✅ Fácil de mantener - Lógica clara y directa
- ⚠️ No soporta sinónimos - "OVACE" no encuentra "Obstrucción de Vía Aérea"
- ⚠️ 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
searchProceduresysearchDrugsdirectamente - ❌ 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
// 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
// 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
ProcedureoDrugpara 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)
-
Crear
src/data/acronyms.ts- Definir interfaz
AcronymMapping - Crear array
acronymMappingscon siglas más comunes (10-15) - Implementar
expandAcronym()ygetAcronymsByTerm() - Testing: Unit tests para funciones de expansión
- Definir interfaz
-
Testing aislado
- Test:
expandAcronym("OVACE")→["obstrucción vía aérea", ...] - Test:
expandAcronym("xyz")→[](sigla desconocida) - Test: Case-insensitive funciona
- Test:
Fase 2: Integración Mínima (Riesgo bajo)
-
Modificar
SearchModal.tsx- Importar
expandAcronymdesdeacronyms.ts - Crear función
expandAcronyms(query: string): string - Aplicar expansión ANTES de llamar a
searchProcedures()ysearchDrugs() - Cambio mínimo: Solo añadir una línea de preprocesamiento
- Importar
-
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)
-
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
-
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)
- 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)
// 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)
- Búsqueda "OVACE" → Debe encontrar protocolo OVACE
- Búsqueda "parada" → Debe seguir funcionando (compatibilidad)
- Búsqueda "RCP adulto" → Debe encontrar RCP adulto
- 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:
- Añadir entrada a
acronymMappingsenacronyms.ts - Testing manual
- 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
- Nuevo archivo:
src/data/acronyms.ts- Diccionario de siglas - Modificación mínima:
src/components/layout/SearchModal.tsx- Preprocesamiento de query - 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.