codigo0/docs/ESTRATEGIA_BUSQUEDA_SIGLAS.md

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) 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

// 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 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)

  1. 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
  2. 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)

  1. 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
  2. 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)

  1. 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)

  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.