INFOTEP Técnico en Programación de Páginas Web study materials
INFOTEP Técnico en Programación de Páginas Web study resources

Buscador de pacientes en un panel clínico

INFOTEP Técnico en Programación de Páginas Web · Put your knowledge to work in context.

All case studies

Buscador de pacientes en un panel clínico

Un panel clínico en vanilla JavaScript muestra 3.000 pacientes. El buscador dispara una petición fetch a /api/pacientes?q= en cada tecla; el endpoint tarda entre 300 y 900 ms y no cancela peticiones anteriores. Los resultados llegan desordenados: al escribir «ana» aparece primero la respuesta de «a» y luego la de «ana». Además, al re-renderizar la lista con innerHTML se pierden los listeners de los botones «Ver ficha», y el equipo los vuelve a añadir con un bucle, lo que duplica llamadas. Hay un setInterval de 15 s que actualiza contadores y nunca se limpia al cambiar de vista. En producción se ven errores Unhandled promise rejection y algún nombre de paciente se inserta con innerHTML. El equipo no usa frameworks y debe mantener compatibilidad con Chrome y Edge actuales. La clínica pide que el buscador sea accesible por teclado y que muestre estados de carga y error.

1. Explica cómo corregir el orden incorrecto de resultados y las peticiones innecesarias, e incluye un esquema de código.

Read the worked answer

El problema es una condición de carrera: cada tecla lanza una promesa y la respuesta más lenta puede llegar después. Solución: debounce de 250–300 ms para no consultar cada tecla y AbortController para cancelar la petición anterior. Además, guardar un requestId incremental y descartar respuestas que no correspondan al último término.

let ctrl, seq = 0;
const buscar = debounce(async (q) => {
  ctrl?.abort(); ctrl = new AbortController();
  const id = ++seq;
  setEstado('cargando');
  try {
    const r = await fetch(`/api/pacientes?q=${encodeURIComponent(q)}`, {signal: ctrl.signal});
    if (!r.ok) throw new Error('HTTP ' + r.status);
    const data = await r.json();
    if (id !== seq) return;
    render(data);
    setEstado('ok');
  } catch (e) {
    if (e.name !== 'AbortError') setEstado('error');
  }
}, 300);

El debounce reduce carga; el abort evita respuestas viejas; el seq cubre el caso de que el abort no llegue a tiempo. También hay que manejar q vacío sin llamar a la API.

2. ¿Cómo eliminarías la pérdida y duplicación de listeners al re-renderizar, y cómo evitarías el riesgo de XSS con los nombres de pacientes?

Read the worked answer

Al usar innerHTML se destruyen nodos y sus listeners; volver a añadirlos en un bucle duplica manejadores y pierde referencias. Usar delegación: un solo listener en el contenedor #lista que lea event.target.closest('button[data-id]'). Para renderizar, crear nodos con document.createElement y textContent para nombres, o sanitizar; innerHTML con datos de pacientes permite XSS. Guardar el setInterval en una variable y llamar clearInterval en beforeunload o al cambiar de vista; mejor aún, usar visibilitychange para pausar. Si se re-renderiza, no reasignar listeners globales. La delegación también reduce memoria y funciona con filas nuevas.

3. Diseña los estados de carga, error y accesibilidad del buscador para que sea usable con teclado y lectores de pantalla.

Read the worked answer

El buscador debe ser un <input type='search'> con <label> visible o aria-label, y la lista con role='listbox'/option solo si se implementa navegación con flechas; si no, usar una lista normal y anunciar resultados. Añadir una región aria-live='polite' con «3 resultados» y un estado de carga textual; errores con role='alert'. El foco no debe perderse al actualizar; si se reemplaza la lista, mantener el foco en el input. Teclado: Enter para buscar, Escape para limpiar. Los estados de carga y error deben ser visibles y no depender solo del color.

También hay que evitar anunciar cada tecla: anunciar solo cuando termine la búsqueda. Si el usuario navega con flechas, actualizar aria-activedescendant. Probar con VoiceOver/NVDA y con navegación solo por teclado.