Tres horas. Media hora para saber qué rompe JavaScript cuando se usa sin cuidado y cómo se ordena un diagnóstico, y dos horas y media auditando tu interfaz, montando una nueva y reparando una rota.
Hoy · Hoja de ruta
- 1. Aprende: Qué rompe JavaScript con facilidad, dónde debe quedar el foco cuando cambia el contenido, y cuál es la primera pregunta de todo diagnóstico de interfaz.
- 2. Haz: Audita tu interfaz con el teclado, monta una de un dominio desconocido y diagnostica cinco fallos en una ajena.
- 3. Entrega: El sitio completo, las tres listas de auditoría, la revisión del compañero y tus notas.
Antes de empezar · 5 minutos, sin apuntes
- Recorre tu página entera con el tabulador. ¿Sabes siempre dónde estás?
- Cuando el catálogo se vuelve a pintar, ¿dónde queda el foco?
- Ante un botón que no responde, ¿qué compruebas primero?
Se explica
25 minutos · conceptos y demostración
JavaScript es la capa que más fácilmente estropea lo que las dos anteriores hicieron bien. Hoy se nombran esos destrozos y se ordena el método para encontrarlos.
Lo que JavaScript rompe con facilidad
| Problema | Cómo se ve | Cómo se arregla |
|---|---|---|
| Controles falsos | Un contenedor genérico que hace de botón | Usar el elemento correcto |
| Foco perdido | Tras pintar, el foco vuelve al principio | Devolverlo a un punto con sentido |
| Cambios mudos | El contenido cambia y nadie lo anuncia | Una región activa |
| Foco invisible | El contorno se quitó en el CSS | :focus-visible de la UD2 |
| Trampa de foco | Un panel del que no se sale con el tabulador | Gestionar el foco al abrir y cerrar |
| Movimiento forzado | Animaciones para quien pidió que no las hubiera | Respetar la preferencia del sistema |
El foco después de pintar
function actualizar() {
const activo = document.activeElement?.dataset.js;
pintarCatalogo(aplicarFiltros(estado), elementos.catalogo);
if (activo) elementos[activo]?.focus();
}
Al sustituir el contenido de un contenedor, el elemento que tenía el foco deja de existir y el foco vuelve al documento. Para quien navega con teclado eso significa empezar de cero cada vez que escribe una letra.
El foco es la posición de quien no ve la pantalla
Cuando cambias contenido, pregúntate dónde debería quedar el foco: en el control que se usó, en el primer resultado nuevo, o en el mensaje de error que acaba de aparecer.
Un panel que se abre lleva el foco dentro; al cerrarse, lo devuelve al control que lo abrió. Es la regla que hace usable un diálogo sin ratón.
Las preferencias del sistema se respetan también desde el código, con la misma consulta que usaste en el CSS de la UD2:
const sinMovimiento = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!sinMovimiento) elemento.classList.add("con-animacion");
Rendimiento: las tres cosas que importan
- No trabajar de más. Un
debounceen lo que se dispara en ráfaga. - No tocar el DOM en bucle. Construir aparte, insertar una vez.
- No mezclar leer y escribir. Leer una medida obliga al navegador a recalcular; hacerlo dentro de un bucle que además escribe multiplica ese coste.
// Costoso: lee y escribe alternativamente
for (const tarjeta of tarjetas) {
tarjeta.style.setProperty("--alto", `${tarjeta.offsetHeight}px`);
}
// Mejor: primero se lee todo, después se escribe todo
const alturas = tarjetas.map((t) => t.offsetHeight);
tarjetas.forEach((t, i) => t.style.setProperty("--alto", `${alturas[i]}px`));
Con doscientas tarjetas la diferencia es perceptible; con dos mil, separa una interfaz fluida de una inutilizable.
Cómo se diagnostica una interfaz
La pregunta que ordena el diagnóstico
En una interfaz, empieza siempre por separar el problema en dos: ¿llega el evento? Un mensaje en la primera línea del manejador lo responde.
Si no llega, el problema está en la selección, en el registro de la escucha o en el momento en que se ejecutó tu código. Si llega, el problema está en tu lógica, y ahí ya sabes trabajar desde la UD3.
Los fallos de esta unidad caen casi siempre en una de cinco familias, y cada una se busca en un sitio distinto:
| Familia | Síntoma típico |
|---|---|
| Selección | Todo es null: el código se ejecuta antes de tiempo |
| Evento | Los elementos creados después no responden |
| Tipo | Un filtro numérico no filtra: el valor era texto |
| Asincronía | La lista sale vacía: se leyó antes de que llegara |
| Render | Cada filtrado duplica el contenido |
Revisar una interfaz ajena
La revisión de hoy no se hace leyendo código, al menos no al principio. Se hace usándola:
- Usar la interfaz solo con el teclado y anotar dónde se atasca uno.
- Simular red lenta y sin red, y describir qué ve el usuario.
- Buscar algo que no exista y comprobar el estado vacío.
- Localizar en el código dónde vive el estado y explicarlo.
- Señalar una decisión bien tomada y una mejorable, con su razón.
Los tres primeros puntos no requieren entender el código de nadie, y son los que encuentran más defectos.
Se trabaja
150 minutos · auditoría, interfaz nueva y diagnóstico
El primer paso arregla lo tuyo. El segundo y el tercero comprueban si sabrías hacerlo otra vez y repararlo cuando lo escribió otro.
Paso 1 · Auditoría de accesibilidad · 35 min
- Recorre toda la interfaz con el tabulador y anota cada punto donde te pierdes.
- Comprueba que ningún control es un contenedor genérico disfrazado.
- Arregla el foco tras cada render.
- Verifica que el resumen de resultados se anuncia.
- Comprueba el contraste y la visibilidad del foco.
- Mide con la pestaña Performance cuánto tarda un render de cien tarjetas, y aplica la separación entre lectura y escritura si mezclas las dos.
Antes de continuar: escribe una letra en el buscador y comprueba que el foco sigue dentro del campo después de que la lista se repinte.
Paso 2 · Una interfaz nueva · 45 min
Es el trabajo central de la sesión. Recibirás un fichero JSON de un dominio que no has visto —cartelera de cine, ofertas de empleo, rutas de senderismo— y una lista de requisitos.
- Cargar los datos con fetch y sus cuatro estados
- Pintar la lista desde los datos
- Un buscador y dos filtros combinables
- Estado único y render idempotente
- Resultado anunciado y manejable con teclado
Sin copiar y pegar tu proyecto: puedes mirar tus apuntes, pero el código se escribe hoy. Empieza por el estado, no por el HTML: si sabes qué campos necesita, el resto se deduce.
Paso 3 · El diagnóstico · 30 min
Sobre una interfaz que viene con cinco fallos, uno de cada familia de la explicación. Lo que se evalúa es el diagnóstico, no la velocidad de corrección.
| Fallo | Síntoma | Hipótesis | Cómo la compruebas | Corrección |
|---|---|---|---|---|
| 1 | ||||
| 2 | ||||
| 3 | ||||
| 4 | ||||
| 5 |
Rellena las tres primeras columnas antes de tocar nada. Si tu hipótesis resulta equivocada, no la borres: anota al lado por qué la descartaste. Esa columna es la que enseña a diagnosticar.
Paso 4 · La auditoría final · 20 min
Auditoría · arquitectura
- Hay un único objeto de estado, y nada se decide leyendo el DOM.
- Los manejadores cambian el estado y llaman a actualizar.
- El render es idempotente: llamarlo dos veces no duplica nada.
- Las funciones de la UD3 siguen sin conocer el DOM.
- Hay una escucha delegada por contenedor, no una por elemento.
Auditoría · comportamiento
- Los cuatro estados están contemplados: cargando, error, vacío y datos.
- Toda petición comprueba
respuesta.ok. - Los valores de formulario se convierten al leerlos.
- Un valor guardado inválido no rompe la página.
- No hay errores en consola al cargar ni al usar la interfaz.
Auditoría · accesibilidad y calidad
- Todo se maneja con teclado y el foco siempre se ve.
- Los cambios de contenido se anuncian.
- El marcado generado es semántico y válido en el W3C.
- Los errores de formulario están asociados a su campo.
- No hay
innerHTMLcon datos de fuera. - No queda código de depuración ni comentado.
Paso 5 · Revisión por pares y cierre de la entrega · 20 min
Intercambia proyectos y aplica los cinco puntos de la explicación, empezando por usar la interfaz sin ratón y con la red simulada. Devuelve las cinco respuestas por escrito, recibe la tuya y decide qué aceptas.
El sitio completo con su carpeta js/ organizada en módulos; el catálogo cargado desde una API con sus cuatro estados; búsqueda, filtros y orden gobernados por un único estado; el formulario validado y accesible; las preferencias persistidas; las tres listas de auditoría marcadas; la revisión del compañero por escrito; y un NOTAS.md con las decisiones que tomaste y lo que dejaste fuera.
Prepara respuestas de un minuto para estas cinco preguntas:
Las preguntas de la defensa
- Enseña el estado de tu aplicación y explica qué guarda cada campo.
- Escribe una letra en el buscador y cuenta todo lo que ocurre, en orden.
- ¿Qué pasa si el servidor tarda cinco segundos? ¿Y si devuelve un 500?
- ¿Qué parte de tu código tendrías que cambiar si mañana los datos llegaran de otra API?
- Enseña un fallo que te costó encontrar y cuenta cómo lo encontraste.
La cuarta vuelve a ser la de siempre: si has separado datos, lógica, estado y render, la respuesta debería ser «solo la función que llama a fetch».
| Criterio de evaluación | Puntos |
|---|---|
| Render desde datos: idempotente y con su estado vacío | 2 |
| Estado único: ninguna decisión se toma leyendo el DOM | 2 |
| Eventos y delegación | 1,5 |
| Formulario validado con errores accesibles y foco correcto | 1,5 |
| Carga de datos con sus cuatro estados | 1,5 |
| Accesibilidad: teclado, foco y cambios anunciados | 1,5 |
No puntúa que la interfaz sea vistosa. Puntúa que aguante: contenido que cambia, red que falla, búsquedas sin resultados y alguien que no usa el ratón.
Ampliación si has completado el trabajo
Primero termina la entrega. El primer reto es el componente más difícil de hacer accesible; el segundo convierte el diagnóstico en prevención.
Reto 1 · El panel que no deja escapar el foco
Un panel de filtros que se abre sobre el contenido es el ejemplo clásico de algo que funciona con ratón y es inservible sin él.
- Móntalo con un botón que lo abre y un botón que lo cierra.
- Al abrirlo, lleva el foco dentro. Decide a qué elemento y justifícalo.
- Mientras está abierto, el tabulador no debe salir del panel: al llegar al último elemento vuelve al primero, y al revés con
Shift + Tab. Impleméntalo. Escapelo cierra, y al cerrarse el foco vuelve al botón que lo abrió, no al principio del documento.- Mientras está abierto, el contenido de detrás no debería ser alcanzable ni por el tabulador ni por un lector de pantalla. Averigua con qué atributo se marca eso.
- Ahora busca el elemento HTML nativo que hace todo lo anterior sin una línea de JavaScript. Léelo, pruébalo, y escribe en tres líneas qué te ha costado reconstruir a mano de lo que él trae hecho. Es la misma lección de la UD1 con
detailsy de la sesión 2 con el botón falso.
Reto 2 · La comprobación que lo habría delatado
Vuelve a los cinco fallos del paso 3. Para cada uno, añade al código una comprobación que lo hubiera hecho evidente en el momento de producirse.
| Fallo | Dónde va la comprobación | Qué comprueba | Qué dice cuando salta |
|---|---|---|---|
| Selección nula | |||
| Evento que no llega | |||
| Valor de tipo equivocado | |||
| Dato leído antes de llegar | |||
| Render que duplica |
Tres criterios para escribirlas:
- Cerca del origen, no donde aparece el síntoma.
- Ruidosas: que detengan o avisen de forma inequívoca, no un mensaje perdido entre otros cincuenta.
- Que digan qué se esperaba, no solo que algo falló.
El quinto es el más interesante: una comprobación de idempotencia se puede escribir de verdad. Pinta dos veces con los mismos datos y compara el número de hijos del contenedor. Si no coincide, el render no era idempotente y el programa lo dice solo.
Cierre
5 minutos · comprobación y recuerdo
Lista de verificación de la entrega
- Todo se puede usar sin ratón y el foco no se pierde al repintar.
- Los cambios importantes se anuncian.
- Has montado una interfaz completa desde datos ajenos.
- Separas fallos de evento de fallos de lógica.
- Las tres listas de auditoría están marcadas.
NOTAS.mdrecoge tus decisiones y lo que dejaste fuera.
Antes de cerrar · 3 minutos, sin mirar
- ¿Dónde debe quedar el foco cuando se repinta una lista?
- Nombra tres cosas que JavaScript rompe con facilidad.
- ¿Cuál es la primera pregunta ante una interfaz que no responde?
- Nombra las cinco familias de fallo de esta unidad.
- ¿Por qué no conviene leer y escribir el DOM alternativamente en un bucle?
- Describe, en orden, lo que ocurre al escribir una letra en el buscador.
Ver respuestas
1 · En un punto con sentido: el control que se usó, el primer resultado o el mensaje que acaba de aparecer. Nunca al principio del documento.
2 · Por ejemplo: controles falsos, foco perdido, cambios que no se anuncian, foco invisible o movimiento no solicitado.
3 · Si el evento llega, con un mensaje en la primera línea del manejador.
4 · Selección, evento, tipo, asincronía y render.
5 · Porque leer una medida obliga al navegador a recalcular, y alternarlo con escrituras multiplica ese coste.
6 · Salta el evento input, el manejador cambia el estado, se llama a actualizar, se aplican los filtros sobre los datos, se vuelve a pintar el contenedor y se anuncia el número de resultados.
Microprueba semanal 6 · 10 minutos
Individual, sin IA y sin apuntes.
- Describe, en orden, todo lo que ocurre desde que se escribe una letra en el buscador hasta que cambia la lista.
- Un botón creado por tu código no responde. Escribe las tres comprobaciones, en orden.
- Señala dos decisiones de accesibilidad de tu interfaz y di a quién ayuda cada una.