Volver al blog

Optimización de performance en interfaz de gestión de viajes médicos

15 de marzo de 2024
~3 min lectura
Por Emerson Díaz
JavaScriptPerformanceHealthcareFrontendUX

Optimización de performance en interfaz de gestión de viajes médicos

En el desarrollo del sistema de gestión de ambulancias para la CCSS, uno de los mayores retos fue crear una interfaz que manejara eficientemente miles de citas médicas con múltiples filtros y estados en tiempo real.

El problema

La interfaz original tenía problemas significativos de rendimiento:

  • Carga lenta: 3+ segundos para mostrar la lista de citas
  • UI bloqueante: La búsqueda congelaba la interfaz durante la escritura
  • Sincronización compleja: Datos de citas, viajes y relaciones desactualizados
// Código original problemático
function buscarCitas() {
  // Búsqueda directa sin debounce - bloqueaba UI
  const input = document.getElementById('search').value;
  obtenerTodasLasCitas(); // Llamada en cada keystroke
}

La solución

1. Carga paralela de datos

Implementamos Promise.all para cargar múltiples endpoints simultáneamente:

async function obtenerCitas() {
  try {
    // Carga paralela en lugar de secuencial
    const [citas, viajes, relacionesViajesCitas] = await Promise.all([
      cargarCitas(),
      cargarViajes(),
      cargarRelacionesViajesCitas(),
    ]);

    citasCombinadas = combinarCitasYViajes(citas, viajes, relacionesViajesCitas);
    mostrarCitas(citasCombinadas);
  } catch (error) {
    showToast("Error", "Ocurrió un problema al obtener las citas");
  }
}

2. Optimización de búsquedas con debouncing

Agregamos debounce para evitar llamadas excesivas:

// Debounce optimizado para búsquedas
document.getElementById("searchTrips")
  .addEventListener("keyup", debounce(handleSearchTrips, 300));

function debounce(func, wait) {
  let timeout;
  return function() {
    const context = this, args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
  };
}

3. Gestión inteligente de estado

Utilizamos Map para optimizar búsquedas y Set para tracking de selecciones:

function combinarCitasYViajes(citas, viajes, relaciones) {
  // Map para O(1) lookup en lugar de O(n) con arrays
  const mapaCitas = new Map();
  citas.forEach(cita => {
    mapaCitas.set(cita.idCita, cita);
  });

  // Combinación eficiente de datos relacionales
  if (relaciones !== undefined) {
    relaciones.forEach(relacion => {
      if (mapaCitas.has(relacion.idCita)) {
        const cita = mapaCitas.get(relacion.idCita);
        const viaje = viajes.find(v => v.idViaje === relacion.idViaje);
        if (viaje) {
          cita.idUnidad = viaje.idUnidad;
          cita.idViaje = relacion.idViaje;
        }
      }
    });
  }

  return Array.from(mapaCitas.values());
}

Resultados

  • Tiempo de carga: De 3.2s a 0.8s (↓75%)
  • Responsividad: Búsqueda instantánea sin bloqueo de UI
  • Gestión de estado: Sincronización 100% confiable entre datos relacionados
  • UX mejorada: Filtros inteligentes que priorizan elementos relevantes

Técnicas clave aplicadas

Optimización de DOM

  • Destrucción/recreación eficiente de DataTables
  • Event delegation para checkboxes dinámicos
  • Lazy rendering de elementos complejos

Manejo de errores robusto

// Interceptor global para tokens expirados
axios.interceptors.response.use(
  response => response,
  error => {
    if (error.response && error.response.status === 401) {
      localStorage.removeItem('token');
      window.location.href = 'index.html';
    }
    return Promise.reject(error);
  }
);

Validación en tiempo real

function checkcamilla() {
  const checkboxes = document.querySelectorAll('.cita-checkbox:checked:not(:disabled)');
  let camilla = 0;

  checkboxes.forEach(checkbox => {
    const row = checkbox.closest('tr');
    if (row.dataset.camilla === 'Requerido') {
      camilla++;
    }
  });

  if (camilla > 1) {
    showToast('Advertencia', 'Ya hay una o más citas que requieren camilla');
  }

  return true;
}

Lecciones aprendidas

  1. Carga paralela: Promise.all puede reducir drásticamente tiempos de carga inicial
  2. Debouncing es crítico: En interfaces con búsqueda frecuente, evita sobrecargar el sistema
  3. Estructuras de datos importan: Map vs Array puede marcar la diferencia en performance
  4. Estado centralizado: Un buen manejo de estado previene bugs complejos en interfaces dinámicas

Esta optimización permitió que el personal médico gestionara hasta 500+ citas simultáneas con una experiencia de usuario fluida y confiable.