Detectores de eventos: páginas interactivas

Haz que las páginas web respondan a las personas. Aprende a registrar detectores de eventos, leer el objeto de evento y seguir los eventos mientras se propagan por la página para que un solo detector pueda manejar varios elementos. Maneja pulsaciones de teclas, clics, toques y entrada de lápiz, construye una interacción de arrastre, lee y envía formularios en JavaScript y valida los datos de entrada con las reglas integradas del navegador y tus propios mensajes. Termina construyendo controles personalizados que funcionen con teclado y lector de pantalla, además de con ratón.

PrincipianteUnidad 6 de 9 del curso Curso de Programación en JavaScript

Lo que aprenderás

  1. Haz que una página reaccione cuando ocurra algo: registra detectores en elementos, lee la información del objeto de evento y decide cuánto tiempo permanece activo cada detector.
    • Registra un detector de eventos en un elemento
      Usa addEventListener para ejecutar una función cada vez que ocurra un clic, una pulsación de tecla, una acción de formulario o un evento de página en un elemento.
    • Lee la información del objeto de evento
      Usa el objeto de evento que recibe un detector para saber qué ocurrió y dónde: su tipo, destino, destino actual y detalles específicos del evento.
    • Elimina detectores de eventos y controla cómo se ejecutan
      Deja de escuchar cuando ya no necesites un detector, usa las opciones once, signal y passive, y dispara tus propios eventos personalizados.
  2. Explica cómo un evento desciende hasta su destino y vuelve a subir por la página, cancela el comportamiento integrado del navegador cuando lo sustituyas y usa la propagación por burbujeo para manejar varios elementos con un solo detector.
    • Explica la propagación por burbujeo y la captura
      Describe las tres fases de la propagación de eventos y predice el orden en que se ejecutan los detectores de elementos anidados.
    • Evita las acciones predeterminadas y detén la propagación
      Cancela la respuesta integrada del navegador a un evento cuando tu código la sustituya y detén el recorrido de un evento solo cuando sea realmente necesario.
    • Maneja eventos de varios elementos mediante la delegación de eventos
      Usa un detector en un nodo antecesor compartido y event.target.closest() para manejar eventos de varios elementos hijos, incluidos los añadidos posteriormente.
  3. Responde a pulsaciones de teclas, cambios de foco, clics, toques y entrada de lápiz, y construye una interacción de arrastre que funcione con cualquier tipo de puntero.
    • Responde a pulsaciones de teclas y cambios de foco
      Usa keydown, keyup y los eventos de foco, elige entre event.key y event.code, y construye atajos de teclado que no estorben al usuario.
    • Maneja clics, toques y entrada de lápiz con eventos de puntero
      Usa click para la activación y los eventos de puntero para pulsar, mover y soltar con un ratón, un dedo o un lápiz; lee coordenadas y botones y elige los eventos adecuados para el paso del puntero.
    • Construye una interacción de arrastre con eventos de puntero
      Combina pointerdown, pointermove y pointerup con la captura del puntero para mover un elemento, haz que funcione en pantallas táctiles y ofrece una forma de hacer lo mismo sin arrastrar.
  4. Lee lo que introducen los usuarios mientras escriben, maneja el envío de formularios en JavaScript y comprueba los datos de entrada con las funciones de validación del navegador y tus propias reglas, con mensajes de error claros.
    • Lee campos de formulario y responde mientras los usuarios escriben
      Lee campos de texto, números, casillas de verificación, botones de opción y listas desplegables, usa los eventos input y change en los momentos adecuados y recopila un formulario completo con FormData.
    • Maneja el envío de formularios en JavaScript
      Escucha el evento submit del formulario, evita que la página se vuelva a cargar, lee los datos enviados y conoce qué desencadena un envío.
    • Usa la validación integrada del navegador
      Describe reglas con atributos HTML, lee el resultado en el objeto validity, comprueba campos desde el código y aplica estilos a los campos no válidos en el momento adecuado.
    • Escribe validaciones personalizadas con mensajes de error claros
      Añade reglas que los atributos HTML no pueden expresar, escribe mensajes útiles, muéstralos en el momento adecuado y conéctalos con sus campos.
  5. Crea elementos interactivos que funcionen con ratón, teclado y lector de pantalla: prioriza el HTML nativo, añade roles, compatibilidad con teclado y estado cuando construyas tus propios controles, y mantén el foco donde lo esperan los usuarios.
    • Prioriza los elementos nativos y permite que los controles personalizados reciban el foco
      Conoce qué proporcionan los elementos interactivos nativos y qué debe añadir un control personalizado construido a partir de un <div> o un <span>: un rol, la capacidad de recibir el foco, un nombre accesible y un indicador de foco visible.
    • Añade compatibilidad con el teclado y estado a los controles personalizados
      Haz que los botones personalizados, botones de alternancia, controles de despliegue, casillas de verificación y grupos de elementos funcionen con el teclado, y mantén su estado ARIA en consonancia con lo que ven los usuarios.
    • Mantén el foco donde lo esperan los usuarios cuando cambie la página
      Mueve el foco de forma deliberada cuando se abra, cierre o elimine contenido o se muestren errores, y nunca lo muevas de forma inesperada mientras los usuarios escriben o eligen.

Guía de estudio de Detectores de eventos

5 capítulos

1

Responde a eventos con detectores de eventos

Las páginas web responden mediante funciones registradas para eventos. La configuración elige el elemento y el tipo de evento; cada vez que ocurre después, llega un objeto de evento que describe lo sucedido. Leer ese objeto permite que un detector actúe sobre el control adecuado, incluso cuando un clic cae en un icono dentro de él. Las referencias y opciones de los detectores permiten mantener activas las respuestas de una función exactamente el tiempo necesario, y los eventos personalizados permiten que una parte de la página avise a otra.

1.1 Los detectores convierten acciones en código

Una página puede esperar a que una persona actúe. Un evento informa de algo que ocurrió y un detector de eventos es la función que se llama en respuesta. Registrar un detector configura esa respuesta; no realiza la acción inmediatamente.

Añade la función al elemento

Selecciona un elemento que ya exista, elige el nombre exacto del evento y pasa una función:

const button = document.querySelector("#like");
let likes = 0;
button.addEventListener("click", function () {
  likes = likes + 1;
  button.textContent = "Likes: " + likes;
});

La selección, la configuración de la variable y el registro se ejecutan cuando se ejecuta el script. Cada clic posterior ejecuta el cuerpo de la función. Después de tres clics, el botón muestra «Likes: 3». Mantener likes fuera de la función conserva el total entre clics; poner let likes = 0 dentro lo reiniciaría cada vez.

Pasa una referencia a la función, como showMenu, sin paréntesis. En button.addEventListener("click", showMenu()), JavaScript llama a showMenu inmediatamente y pasa su valor de retorno como detector. Para proporcionar un argumento más tarde, envuelve la llamada: button.addEventListener("click", () => showMenu("main"));.

Elige un evento que realmente ocurra

Los nombres son cadenas que distinguen mayúsculas de minúsculas y no llevan el prefijo «on». Usa "click", no "Click" ni "onclick". Se acepta un nombre inventado sin error, pero el navegador no lo emitirá para un clic. Entre las familias habituales están los eventos de puntero como pointerdown y pointerup, los eventos de teclado como keydown, los eventos de formulario como input y submit, y los eventos de foco como focusin. DOMContentLoaded tiene su propia combinación exacta de mayúsculas y minúsculas.

Configura el elemento adecuado en el momento adecuado

Un script en la cabecera puede ejecutarse antes de que exista un botón. La selección devuelve entonces null, y llamar a addEventListener sobre él genera un error. Carga un script externo con defer, colócalo después del marcado o registra la configuración en el evento DOMContentLoaded del documento. Un elemento creado posteriormente necesita configuración después de su creación o un detector en un nodo antecesor.

querySelectorAll devuelve una colección de nodos (NodeList), no un solo destino de evento. Registra detectores en sus elementos:

document.querySelectorAll(".tab").forEach((tab) => {
  tab.addEventListener("click", selectTab);
});
Permite que coexistan los detectores
MétodoQué hace el registro
addEventListener("click", fn)Añade un detector junto a otros y acepta opciones.
button.onclick = fnSustituye el único manejador de la propiedad; asigna null para eliminarlo.
Un atributo HTML onclickColoca JavaScript dentro del marcado.

Los detectores con la misma configuración de captura se ejecutan en orden de registro. En el destino del evento, los detectores de captura se ejecutan antes que los que no son de captura. Volver a añadir el mismo objeto de función con el mismo tipo y configuración de captura no añade una segunda copia.

Puntos clave

  • Pasa la propia función; usa una función envolvente cuando la llamada posterior necesite argumentos.
  • Los nombres de eventos requieren una escritura exacta, incluidas las mayúsculas y minúsculas.
  • Un elemento debe existir cuando se registra su detector.
  • Recorre una NodeList para registrar detectores en sus elementos.
  • addEventListener permite varios detectores; una propiedad de manejo de eventos contiene uno.

1.2 Identifica dónde comenzó un evento

El navegador pasa un objeto de evento a tu detector. Sus propiedades explican el evento que desencadenó esa llamada concreta. Llama al parámetro event o e; tú eliges el nombre.

button.addEventListener("click", (event) => {
  console.log(event.type);
  console.log(event.currentTarget);
});

Esto registra "click" y el botón durante la llamada al detector. Registrar el objeto completo puede ayudarte a descubrir sus otras propiedades.

Distingue el origen del detector

Imagina un botón que contiene una imagen:

<button id="save" data-id="7">
  <img src="disk.svg" alt=""> Save
</button>
PropiedadSignificadoAl hacer clic en la imagen
event.targetDónde comenzó el eventoLa imagen
event.currentTargetDónde se registró el detector que se está ejecutandoEl botón, para un detector del botón

Al hacer clic en el texto «Save», el botón también es el destino. El texto forma parte del botón; no se convierte en un elemento de destino independiente.

Esta diferencia importa al leer datos. La imagen no tiene el data-id del botón, por lo que event.target.dataset.id es undefined al hacer clic en la imagen. Un detector registrado directamente en el botón puede leer event.currentTarget.dataset.id. Un detector en un contenedor más grande puede encontrar el botón deseado con event.target.closest("button") y comprobar que existe una coincidencia.

Lee los detalles de este evento

Todo evento tiene un type, un target y un currentTarget. El booleano bubbles indica si se propaga de vuelta hacia arriba por los nodos antecesores. cancelable indica si el código puede cancelar su acción predeterminada y defaultPrevented registra si se ha producido esa cancelación. timeStamp registra el momento de creación del evento en milisegundos con respecto al origen temporal de la página.

Las distintas familias de eventos añaden detalles útiles. Un click moderno proporciona un PointerEvent, que hereda propiedades de eventos de ratón como clientX, clientY y button. Un KeyboardEvent añade key, code e indicadores de teclas modificadoras. Un detector de input puede usar event.target.value para leer el nuevo texto del campo.

Para un detector de galería, event.target.tagName podría ser "IMG" al hacer clic en una foto o "DIV" al hacer clic en el espacio entre fotos. Maneja el caso que realmente se indique. No supongas que todos los eventos dentro de un contenedor provienen del mismo tipo de hijo.

Usa el valor mientras se ejecuta el detector

En un detector con una función normal, this equivale a event.currentTarget. Una función flecha toma this del código que la rodea; no recibe el elemento como this. Usar event.currentTarget funciona con ambos estilos de función.

Después del despacho, currentTarget se restablece a null. Una consola puede mostrar ese valor posterior cuando expandes un evento registrado anteriormente. Registra event.currentTarget directamente dentro del detector para inspeccionar el elemento durante la llamada.

Puntos clave

  • target identifica el origen del evento; currentTarget identifica el elemento del detector que se está ejecutando.
  • Lee los datos del elemento que realmente los contiene.
  • Las familias de eventos añaden propiedades como coordenadas, valores de teclas y modificadores.
  • currentTarget está disponible durante la ejecución del detector y después pasa a ser null.

1.3 Controla la duración de un detector

Un detector debe permanecer activo mientras lo necesite la función a la que sirve. Para una interacción temporal con el teclado, conserva la referencia a la función para poder eliminarla cuando se cierre la interacción.

Elimina la misma función
function onKey(event) {
  console.log(event.key);
}
document.addEventListener("keydown", onKey);
// When the feature closes:
document.removeEventListener("keydown", onKey);

La eliminación requiere que coincidan el tipo de evento, el objeto de función y la configuración de captura. Una función anónima también puede eliminarse si se guarda su referencia, como en const handler = () => save();. Escribir una nueva () => save() en la llamada de eliminación crea una función distinta, aunque el código parezca idéntico.

Elige las opciones de registro
OpciónEfecto
once: trueElimina este detector después de su primera llamada.
signal: controller.signalElimina el detector cuando se cancela ese controlador.
capture: trueEscucha durante el recorrido de captura.
passive: trueIndica que el detector no cancelará la acción predeterminada.

Un aviso de bienvenida de una sola ejecución puede usar { once: true }. Una función con varios detectores puede darles una misma señal:

const controller = new AbortController();
window.addEventListener("pointermove", moveCursor, {
  signal: controller.signal
});
window.addEventListener("keydown", onKey, {
  signal: controller.signal
});
// Close the feature:
controller.abort();

La cancelación elimina ambos registros. Un controlador cancelado no se puede restablecer; crea uno nuevo para un nuevo periodo de actividad. Para eliminar con removeEventListener, repite capture: true si se usó en el registro. once, signal y passive no forman parte de los criterios de coincidencia para la eliminación.

La llamada a preventDefault() de un detector pasivo se ignora. Los navegadores suelen hacer pasivos por defecto ciertos detectores relacionados con el desplazamiento en destinos a nivel de documento, incluidos wheel, touchstart y touchmove; usa ahí { passive: false } de forma explícita cuando sea necesario cancelar la acción.

Evita la acumulación de funciones de devolución de llamada

Si cada clic en «Iniciar» añade un nuevo detector anónimo al tablero, iniciar dos veces hace que un clic en el tablero ejecute dos funciones de devolución de llamada. Registra el detector del tablero una vez, reutiliza el mismo objeto de función o cancela el grupo anterior antes de sustituirlo. Las distintas funciones de devolución de llamada con once aún pueden acumularse antes de ejecutarse.

Desconectar un elemento conserva sus detectores. Funcionan si se vuelve a insertar y dispatchEvent() puede llegar a ellos mientras está desconectado. El elemento puede ser recogido por el recolector de basura cuando ya no se puede acceder a él ni a sus detectores. Un detector de window o document permanece registrado hasta que se elimine explícitamente.

Envía un evento personalizado
const event = new CustomEvent("cart:add", {
  detail: { id: 42 },
  bubbles: true
});
item.dispatchEvent(event);

Los detectores leen event.detail.id. El despacho llama a los detectores inmediatamente, antes de terminar. bubbles: true permite que los nodos antecesores también lo reciban. Un evento enviado con dispatchEvent() tiene isTrusted igual a false; se puede confiar en los eventos despachados por el navegador incluso cuando los haya provocado una API como focus().

Puntos clave

  • La eliminación requiere que coincidan el tipo, la identidad de la función y la configuración de captura.
  • once termina un registro después de una llamada; cancelar una señal termina sus registros asociados.
  • Un detector pasivo no puede cancelar una acción predeterminada.
  • Las nuevas funciones de devolución de llamada añadidas en cada configuración pueden acumularse.
  • Desconectar un elemento conserva sus detectores.
  • Los eventos personalizados transportan datos en detail y se despachan de forma síncrona.
2

Sigue los eventos en su recorrido por la página

Los elementos anidados comparten una ruta de eventos. Un clic puede pasar por un contenedor antes de llegar a su destino y visitarlo de nuevo durante el burbujeo, por lo que un solo detector basta para una lista cambiante. Seguir esa ruta ayuda a explicar por qué una acción en un hijo también activa una en el padre. Distingue la cancelación del comportamiento del navegador del control de la propagación y después usa el origen del evento para elegir qué acciones debe manejar un detector compartido.

2.1 La ruta de un evento por elementos anidados

Un clic dentro de un botón también puede ser recibido por los nodos antecesores del botón. La propagación es el recorrido del evento por el árbol del documento. Seguir ese recorrido explica tanto los útiles detectores compartidos como las acciones adicionales inesperadas.

Sigue tres fases
  1. Captura: el evento desciende desde window por los nodos antecesores hacia el destino.
  2. Destino: los detectores se ejecutan en el elemento donde comenzó el evento.
  3. Burbujeo: un evento que se propaga por burbujeo vuelve a subir por los nodos antecesores hasta window.

La propiedad eventPhase indica 1 para captura, 2 en el destino y 3 para burbujeo. Un detector registrado sin opciones se ejecuta en el destino o en el recorrido de burbujeo. Un detector con { capture: true } se ejecuta en el recorrido descendente, incluido el destino si se registró allí.

Sigue un clic concreto
<div id="outer">
  <div id="inner">
    <button>Go</button>
  </div>
</div>

Supón que el botón y cada div tienen detectores de clic normales que registran sus nombres. Al hacer clic en «Go» se registra «button», «inner» y después «outer». Al hacer clic en una zona vacía de inner se registra «inner» y después «outer»; el botón no está en la ruta de ese evento.

Ahora añade un detector de captura a outer. Un clic en el botón registra primero «outer (capture)» y después «button», «inner» y «outer» de los detectores normales. El detector de captura recibe el clic antes que los descendientes. Después de outer, el evento puede continuar por body, html, document y window si hay detectores registrados allí.

Es un solo evento, no un nuevo clic creado en cada nodo antecesor. target sigue siendo el botón durante todo el recorrido. currentTarget cambia a medida que se ejecuta cada detector. En el detector exterior, leer target sigue identificando el botón original.

Comprueba qué eventos se propagan por burbujeo
Se propagan por burbujeoNo se propagan por burbujeo
click, input, change, submitinvalid
keydown, pointerdownfocus, blur
focusin, focusoutpointerenter, pointerleave
pointerover, pointeroutmouseenter, mouseleave

La propiedad bubbles del evento indica si tiene una fase de burbujeo. Los eventos sin burbujeo todavía pueden ser observados por un detector de captura de un nodo antecesor. Para manejar los cambios de foco mediante un detector normal en un contenedor, elige focusin o focusout.

Explica un error de acciones anidadas

Una tarjeta de producto abre una vista de detalles cuando se hace clic en ella. Su botón «Eliminar» elimina el producto. Al hacer clic en ese botón primero se elimina el producto, después el evento llega al detector de la tarjeta y abre la vista de detalles. La ruta del evento explica por qué se ejecutan ambas funciones de devolución de llamada.

La tarjeta puede ignorar los clics de eliminación con if (event.target.closest(".remove")) return;. Como alternativa, el botón puede detener la propagación. Filtrar en la tarjeta conserva el evento para otros nodos antecesores que puedan necesitarlo.

Puntos clave

  • La propagación sigue las fases de captura, destino y después burbujeo para los eventos que se propagan por burbujeo.
  • Los detectores normales de nodos antecesores reciben los clics de los descendientes durante el recorrido ascendente.
  • target permanece fijo mientras currentTarget cambia.
  • Usa un evento de foco que se propague por burbujeo o la captura para observar el foco desde un nodo antecesor.

2.2 Cancela una acción o detén un recorrido

Un detector puede cambiar lo que ocurre después de un evento o por dónde se propaga. Son decisiones distintas. Un evento de enlace puede seguir propagándose por burbujeo incluso después de cancelar la navegación.

Sustituye una acción del navegador

Una acción predeterminada es un comportamiento que proporciona el navegador: seguir un enlace, enviar un formulario, alternar una casilla de verificación, insertar texto, desplazar la página con la barra espaciadora o mover el foco con Tab. Usa preventDefault() cuando tu código sustituya esa acción:

help.addEventListener("click", (event) => {
  event.preventDefault();
  openHelpPanel();
});

Para un enlace a /help, esto abre el panel sin navegar. El enlace sigue ofreciendo su destino cuando JavaScript no se ejecuta.

Solo se pueden cancelar los eventos cancelables. Examina event.cancelable; defaultPrevented registra si la cancelación se realizó correctamente. input ocurre después de que cambie el valor y no es cancelable. Llamar a preventDefault() ahí no elimina el texto insertado. Un evento beforeinput cancelable es el lugar adecuado para interceptar una edición; cancelar un keydown pertinente puede impedir el efecto predeterminado de esa tecla.

Controla la propagación por separado
Método¿Detiene la acción predeterminada del navegador?¿Detiene los detectores posteriores?
preventDefault()Sí, si el evento es cancelable y el detector no es pasivoNo
stopPropagation()NoDetiene el recorrido hacia otros elementos; los detectores de este elemento siguen ejecutándose
stopImmediatePropagation()NoTambién detiene los detectores restantes de este elemento

Un enlace cuyo detector solo detiene la propagación sigue navegando a su URL. Cancelar solo la navegación sigue permitiendo que un detector de clic del documento reciba el evento. Devolver false desde una función de devolución de llamada de addEventListener no hace ninguna de las dos cosas. Una propiedad o un atributo de manejo de eventos puede cancelar su acción predeterminada devolviendo false, pero tampoco detiene la propagación.

Mantén el funcionamiento compartido

Un nodo antecesor puede cerrar un menú o contar interacciones. Detener la propagación oculta el evento a ese código. A menudo, el nodo antecesor puede examinar target o defaultPrevented.

Por ejemplo, un menú se cierra solo cuando un clic cae fuera tanto del menú como de su control de apertura:

document.addEventListener("click", (event) => {
  if (!menu.contains(event.target) &&
      !menuButton.contains(event.target)) {
    menu.hidden = true;
  }
});

contains() incluye el propio elemento y sus descendientes. Comprobar el control de apertura es importante porque el clic que abre el menú también se propaga por burbujeo hasta el documento. Sin esa segunda comprobación, el menú recién abierto se cerraría inmediatamente.

Cancela solo una acción que manejes

En un detector de teclado, comprueba la tecla antes de cancelar. Cancelar todas las teclas bloquea la escritura normal y puede impedir que Tab mueva el foco. En un detector pasivo, la cancelación se ignora por completo.

Puntos clave

  • preventDefault() cancela una acción predeterminada cuando es posible, sin detener la propagación.
  • stopPropagation() detiene el recorrido, pero no la acción predeterminada.
  • stopImmediatePropagation() también omite los detectores restantes del elemento actual.
  • Cuando ocurre input, ya es demasiado tarde para cancelar una edición.
  • Las comprobaciones de clic fuera deben incluir tanto el elemento emergente como su control de apertura.
  • Cancela solo las acciones de teclado específicas que maneja tu código.

2.3 Un detector para una lista cambiante

La delegación de eventos sitúa un detector en un nodo antecesor estable y usa el origen del evento para elegir una acción. Es útil cuando una lista tiene muchos elementos o cuando el código crea elementos nuevos después de la configuración.

Encuentra el control deseado

Un clic que se propaga por burbujeo llega a la lista independientemente de cuál de sus descendientes se haya pulsado. Parte de event.target, encuentra el control coincidente más cercano y descarta los clics que no correspondan:

const list = document.querySelector("#todos");
list.addEventListener("click", (event) => {
  const button = event.target.closest(".remove");
  if (!button || !list.contains(button)) return;
  const item = button.closest("li");
  console.log("Removing item " + item.dataset.id);
  item.remove();
});

La lista contiene elementos como <li data-id="7">Buy milk <button class="remove">Remove</button></li>. El detector pertenece a la lista, por lo que un elemento nuevo con el mismo marcado funciona inmediatamente después de insertarse.

Sigue en el código un clic en un icono. El destino es el icono, closest(".remove") encuentra su botón y button.closest("li") encuentra el elemento de lista. Su data-id identifica el elemento en el mensaje. Por último, remove() modifica la página.

Conserva las dos comprobaciones
ComprobaciónPor qué importa
!buttonUn clic en un espacio de la lista o en el texto normal de un elemento puede no encontrar ningún botón de eliminación.
!list.contains(button)closest() puede subir más allá de la lista y encontrar un nodo antecesor exterior que coincida.

Comprobar solo event.target.matches(".remove") deja fuera los clics en iconos porque el propio icono no coincide. closest() comprueba el destino y sube por sus nodos antecesores. En un detector delegado, currentTarget es la lista; no se convierte en el botón encontrado.

Dirige varias acciones

Los atributos de datos permiten que un detector de barra de herramientas elija entre acciones conocidas:

const actions = { bold: makeBold, italic: makeItalic,
  clear: clearText };
toolbar.addEventListener("click", (event) => {
  const button = event.target.closest("button[data-action]");
  if (!button || !toolbar.contains(button)) return;
  const action = actions[button.dataset.action];
  if (typeof action === "function") action();
});

Un botón con data-action="clear" selecciona clearText. La comprobación de la función maneja una acción desconocida sin intentar llamar a undefined.

Elige un evento adecuado

La delegación funciona con eventos que se propagan por burbujeo, además de los clics. Un solo detector de input en un formulario puede pasar event.target a una función que compruebe campos. Para delegar cambios de foco, usa focusin o focusout; los eventos normales focus y blur no se propagan por burbujeo. Para las transiciones al pasar el puntero, usa eventos over/out que se propaguen por burbujeo cuando necesites delegación.

Una llamada a stopPropagation() de un descendiente puede impedir que un evento llegue al contenedor. Cuando un detector delegado parezca inactivo, comprueba el tipo de evento, el destino, la coincidencia con el selector y si algo debajo del contenedor detuvo el recorrido.

Puntos clave

  • Registra el detector en un nodo antecesor que permanezca aunque cambien sus hijos.
  • closest() encuentra un control incluso cuando se hace clic en un icono anidado.
  • Descarta los resultados sin coincidencia y las coincidencias fuera del contenedor.
  • La delegación incluye automáticamente los hijos que coincidan y se añadan después.
  • Los atributos de datos pueden dirigir un detector compartido hacia acciones conocidas.
  • La delegación necesita un evento que llegue al nodo antecesor.
3

Maneja la entrada del teclado y del puntero

Una interacción puede llegar mediante teclas, un ratón, un dedo o un lápiz. El evento que elijas determina si recibes una activación, un valor de texto modificado o el movimiento de un puntero concreto. Los sistemas de coordenadas y las posiciones iniciales almacenadas hacen que el arrastre sea predecible. El foco y los controles alternativos permiten usar la misma página cuando una persona navega con el teclado o no puede realizar un gesto de arrastre.

3.1 Teclas, texto y foco

La entrada del teclado va al elemento que tiene el foco, disponible como document.activeElement, y después se propaga por burbujeo. Por tanto, un detector en el documento puede recibir teclas tanto de campos como de otros controles. Ese alcance hace útiles los atajos de teclado, pero también significa que pueden interrumpir la escritura.

Elige la propiedad por su significado
PropiedadIndicaEjemplo
keyEl valor producido, con la distribución y los modificadores"a", "A", "Enter", " "
codeLa posición física de la tecla"KeyA", "Enter", "Space"

En un teclado AZERTY, la posición marcada con Q en un teclado estadounidense produce key "a" y code "KeyQ". Usa key para el significado, como Escape para cerrar o una letra como atajo. Usa code para posiciones fijas, como las teclas de movimiento en un juego. El key de la barra espaciadora es un solo espacio, no "Space".

keydown se dispara en la pulsación inicial y puede repetirse mientras se mantiene pulsada la tecla. Las llamadas repetidas tienen event.repeat === true. keyup se dispara al soltarla. Usa keydown en lugar del evento obsoleto keypress.

Maneja un atajo con precisión
document.addEventListener("keydown", (event) => {
  if ((event.ctrlKey || event.metaKey) &&
      event.key.toLowerCase() === "s") {
    event.preventDefault();
    saveDraft();
  }
});

Esto maneja Ctrl+S y Comando+S. Convertir a minúsculas permite aceptar también "S". La cancelación sustituye la acción de guardar página del navegador solo para el atajo que coincide. Otros indicadores de teclas modificadoras son shiftKey y altKey.

Para un atajo de una sola letra en toda la página, omite los contextos de escritura antes de actuar:

if (event.target.matches("input, textarea, select") ||
    event.target.isContentEditable) return;
if (event.repeat) return;

La primera comprobación protege la entrada de texto. La segunda hace que una acción se ejecute una vez por cada pulsación física. No canceles Tab en toda la página; es la forma en que avanzan los usuarios de teclado.

Escucha el texto después de que cambie

Para una tecla normal que produce un carácter, la secuencia es keydown, beforeinput, input y después keyup. Leer value en keydown da el valor anterior a la inserción de ese carácter. Usa input para una vista previa del texto o un contador de caracteres. También maneja ediciones como pegar y dictar, que no necesitan una pulsación de tecla correspondiente.

Observa los cambios de foco

focus y blur indican que se recibe o se pierde el foco sin propagarse por burbujeo. focusin y focusout sí se propagan, por lo que un formulario puede observar sus campos mediante un solo detector. relatedTarget identifica el otro elemento implicado en el cambio, o null cuando no hay ninguno.

password.addEventListener("focus", () => {
  hint.hidden = false;
});
password.addEventListener("blur", () => {
  hint.hidden = true;
});

La indicación está disponible cuando el campo recibe el foco, también mediante Tab. Llamar a element.focus() mueve el foco desde el código; la siguiente entrada del teclado va entonces a ese elemento.

Puntos clave

  • Usa key para el significado producido y code para la posición física.
  • Protege los atajos de letras para toda la página frente a contextos de escritura y repeticiones no deseadas.
  • Usa input para reaccionar al texto editado, incluido el texto pegado.
  • focusin y focusout se propagan por burbujeo; focus y blur no.
  • relatedTarget indica el otro destino u origen del foco.

3.2 Activación y movimiento del puntero

Una persona puede usar un ratón, un dedo, un lápiz o un teclado. Elige un evento según la interacción que necesites. click expresa activación; los eventos de puntero describen la pulsación, el movimiento y la liberación.

Mantén las acciones disponibles mediante el teclado

Un botón nativo emite click cuando se activa con Enter o la barra espaciadora. Un enlace con foco lo hace con Enter. Conectar una acción solo a pointerdown o mousedown deja fuera esas activaciones de teclado. Usa click para acciones como abrir un panel; usa pointerdown cuando el trabajo deba comenzar al pulsar, como al arrastrar.

Un clic con puntero sigue a una pulsación y una liberación. Cuando ocurren en elementos distintos, el clic puede tener como destino su nodo antecesor común más cercano. dblclick sigue a dos clics rápidos; event.detail cuenta los clics rápidos sucesivos. auxclick maneja los clics no principales. contextmenu puede proceder de un clic secundario, una pulsación táctil larga o un comando de menú del teclado.

Usa una sola familia de eventos de puntero

pointerdown, pointermove, pointerup y pointercancel cubren el ratón, la entrada táctil y el lápiz. pointerType identifica el dispositivo como "mouse", "touch" o "pen"; pointerId distingue los punteros activos, incluidos dos dedos.

Las familias más antiguas de eventos de ratón y táctiles siguen existiendo. Los navegadores pueden emitir eventos de ratón por compatibilidad después de los eventos de puntero. Por tanto, registrar tanto pointerdown como mousedown para la misma acción puede ejecutarla dos veces.

Adapta las coordenadas a la tarea
CoordenadasOrigen
clientX, clientYEsquina superior izquierda del área de visualización visible
pageX, pageYEsquina superior izquierda del documento, incluido el desplazamiento
offsetX, offsetYBorde del relleno del elemento de destino

Cuando el documento se ha desplazado 300 px hacia abajo y un clic está 100 px por debajo de la parte superior del área de visualización, clientY es 100 y pageY es 400. Mantén ambas posiciones en el mismo sistema de coordenadas al calcular el movimiento. Un cambio de destino anidado también puede cambiar el origen de las coordenadas offset.

button indica qué botón cambió en un evento de pulsación o liberación: 0 principal, 1 central, 2 secundario. buttons indica la combinación que se mantiene pulsada: el principal aporta 1, el secundario 2 y el central 4. Así, buttons === 3 significa que se mantienen pulsados el principal y el secundario; no significa que haya cambiado el botón central.

Elige el comportamiento de paso del puntero para todo un control

Los eventos over/out se propagan por burbujeo y pueden dispararse cuando el puntero cruza los límites de un hijo. Los eventos enter/leave no se propagan por burbujeo y describen la entrada o salida del elemento completo, incluidos sus hijos. Una información emergente puede evitar el parpadeo al cruzar los límites de los hijos con pointerenter y pointerleave.

Los usuarios de pantallas táctiles y de teclado no pasan el puntero. Ofrece la información esencial también al hacer clic o recibir el foco. Para un efecto visual de paso del puntero sin comportamiento adicional, CSS :hover es más sencillo que un detector.

Puntos clave

  • Usa click para la activación, incluida la activación nativa mediante teclado.
  • Los eventos de puntero cubren el ratón, la entrada táctil y el lápiz con una sola familia de eventos.
  • Elige coordenadas del mismo sistema para los cálculos.
  • button identifica un cambio; buttons indica la combinación que se mantiene pulsada.
  • Los eventos enter/leave tratan un elemento y sus hijos como un conjunto.
  • La información esencial debe ser accesible por otros medios además del paso del puntero.

3.3 Un arrastre que sigue al puntero

El arrastre necesita conservar información entre varios eventos. Guarda las coordenadas iniciales del puntero y la posición inicial del elemento. Cada movimiento aplica entonces la diferencia respecto a ese punto de partida, para que el elemento no salte colocando su esquina debajo del puntero.

Calcula el desplazamiento

Una caja comienza en left: 100px; top: 50px. El puntero comienza en las coordenadas del área de visualización 220, 140 y se mueve a 260, 125. El cambio horizontal es de 40 px; el cambio vertical es de −15 px. Al sumar esos cambios a la posición inicial de la caja se obtiene left: 140px; top: 35px.

Usa coordenadas de cliente coherentes para ambas lecturas del puntero. El cálculo general es la posición inicial del elemento más el desplazamiento del puntero. No sumes cada desplazamiento completo a la posición ya actualizada de la caja, pues volverías a contar el movimiento anterior.

Guarda y captura un puntero

Para una caja sin margen y con posición absoluta dentro de un contenedor con posición relativa:

let start = null;
box.addEventListener("pointerdown", (event) => {
  if (start || event.button !== 0) return;
  start = { id: event.pointerId, x: event.clientX,
    y: event.clientY, left: box.offsetLeft,
    top: box.offsetTop };
  box.setPointerCapture(event.pointerId);
});
box.addEventListener("pointermove", (event) => {
  if (!start || event.pointerId !== start.id) return;
  box.style.left = start.left +
    (event.clientX - start.x) + "px";
  box.style.top = start.top +
    (event.clientY - start.y) + "px";
});
function endDrag(event) {
  if (start && event.pointerId === start.id) start = null;
}
box.addEventListener("pointerup", endDrag);
box.addEventListener("pointercancel", endDrag);

El ID almacenado descarta los movimientos y la liberación de un segundo puntero. offsetLeft y offsetTop proporcionan las posiciones iniciales como números; los estilos necesitan el sufijo "px".

La captura del puntero mantiene los eventos posteriores de ese puntero dirigidos a la caja incluso cuando este sale de ella. El navegador libera la captura después de soltar o cancelar el puntero. releasePointerCapture(id) puede liberarla antes.

Maneja la intervención del navegador

Un gesto táctil puede convertirse en desplazamiento o zoom de la página y provocar pointercancel. Termina el arrastre tanto ahí como en pointerup; de lo contrario, un estado anterior puede seguir moviendo la caja más tarde. Aplica touch-action: none a la superficie de arrastre antes de la interacción para reservar ese gesto. Limita la regla a esa superficie para que el resto de la página pueda desplazarse.

Usa user-select: none en la superficie arrastrable para evitar seleccionar texto. Establece draggable="false" en sus imágenes para impedir el comportamiento nativo de arrastre de imágenes.

Ofrece otra forma de mover

Proporciona movimiento mediante teclado para un control con foco y botones de movimiento en los que se pueda hacer clic o tocar. Las teclas de dirección por sí solas no proporcionan una alternativa al arrastre con un solo puntero. El criterio de éxito 2.5.7 de WCAG 2.2 exige esa alternativa salvo que el arrastre sea esencial o sea un comportamiento del navegador sin modificar. Los botones nativos de movimiento pueden servir tanto a usuarios de teclado como de puntero.

La API HTML independiente de arrastrar y soltar usa dragstart, dragover y drop para transferir datos, como archivos. Su secuencia de eventos es distinta de este cambio de posición basado en el puntero.

Puntos clave

  • Calcula cada posición a partir de la posición inicial almacenada más el desplazamiento del puntero.
  • Captura el puntero activo y sigue su ID.
  • Termina el arrastre tanto en pointerup como en pointercancel.
  • Aplica las reglas táctiles y de selección a la superficie de arrastre.
  • Proporciona acceso mediante teclado y una alternativa en la que se pueda hacer clic o tocar.
4

Maneja formularios y valida los datos de entrada antes del envío

Los formularios combinan varios tipos de estado: texto, valores numéricos, opciones marcadas y nombres repetidos. Lee esos valores en el evento adecuado y después maneja el envío del formulario como una sola acción. Las restricciones HTML proporcionan reglas de validación habituales, mientras que JavaScript añade relaciones entre campos y respuestas más claras. Un formulario útil conserva el trabajo del usuario, explica cada corrección y sitúa el foco donde corresponde la siguiente edición.

4.1 Lee los valores cuando cambian

Los controles de formulario exponen distintos tipos de estado. El value de un campo de texto es una cadena de caracteres; el checked de una casilla de verificación es un booleano. Leer la propiedad adecuada es el primer paso para obtener cálculos y vistas previas fiables.

Convierte los números de forma deliberada

Incluso <input type="number"> tiene un value de tipo cadena. Con el texto "3", sumar 1 da "31". Number(input.value) + 1 da 4. Un campo numérico también ofrece valueAsNumber.

Una entrada vacía necesita una decisión propia. Number("") da 0, lo que puede hacer que una cantidad ausente parezca un cero real. Comprueba primero input.value === "" o usa valueAsNumber, que da NaN para un campo numérico vacío. Comprueba ese resultado con Number.isNaN().

Lee el estado seleccionado
ControlPropiedad que se debe leer
Texto, textarea, lista desplegablevalue
Campo numérico usado en operaciones aritméticasvalueAsNumber, o convertir un value no vacío
Casilla de verificaciónchecked para el estado marcado o desmarcado
Grupo de botones de opción llamado sizeform.elements.size.value para el valor seleccionado

El value de una casilla de verificación es su texto configurado para el envío, o "on" si no se proporciona ninguno. No indica si está marcada. Una lista desplegable indica el valor de la opción elegida. Para encontrar el propio elemento de opción seleccionado, consulta input[name="size"]:checked.

Relaciona el evento con la actualización

input informa de las ediciones del usuario mientras ocurren, incluidas la escritura y el pegado. change informa de una elección confirmada: después de que un campo de texto modificado pierda el foco, o inmediatamente cuando cambia una casilla de verificación, un botón de opción o una lista desplegable.

message.addEventListener("input", () => {
  counter.textContent = 280 - message.value.length +
    " characters left";
});

Después de «Hello», se han usado cinco caracteres y el contador muestra «275 characters left». Un detector de keydown se ejecutaría antes de que el nuevo carácter entrara en el valor y no detectaría el pegado mediante un menú. Asignar message.value desde el código no dispara ni input ni change; llama también directamente a la función de actualización.

Recopila los campos con nombre de un formulario

form.elements.email.value accede a un control por su nombre o ID. Usa form.elements["first-name"] para un nombre con guion. El envío usa nombres: un ID por sí solo no incluye un campo en los datos.

const data = new FormData(form);
const email = data.get("email");
const topics = data.getAll("topics");

FormData recopila los pares de nombre y valor actuales que cumplen los requisitos. Se omiten las casillas sin marcar, los controles deshabilitados y los campos sin nombre. get() devuelve el primer valor de un nombre; getAll() conserva los valores repetidos, como varios temas marcados. Los controles de archivo pueden proporcionar objetos de archivo; los controles de texto normales proporcionan cadenas de caracteres.

Object.fromEntries(data) resulta cómodo para nombres distintos, pero solo conserva el último par de los nombres repetidos. Elige getAll() cuando importe cada selección. Como input se propaga por burbujeo, un detector del formulario puede leer event.target para actualizar el campo que haya cambiado.

Puntos clave

  • Convierte la cadena de un campo antes de operar y trata por separado un campo numérico vacío.
  • Lee el estado de una casilla de verificación en checked.
  • Usa input para ediciones en tiempo real y change para elecciones confirmadas.
  • FormData incluye los controles con nombre que cumplen los requisitos, no todos los controles.
  • getAll() conserva los valores de nombres repetidos; la conversión a objeto no.
  • Asignar un valor mediante código no emite eventos de edición.

4.2 Maneja el envío del formulario

El envío pertenece al formulario. Escucha su evento submit para manejar por una sola vía un clic en el botón de envío, Enter en un campo de una sola línea y una llamada a requestSubmit() desde un script.

Sustituye la navegación por una actualización de la página

Normalmente, un formulario envía datos a la URL de su action y carga la respuesta. Sin una acción, el destino es la página actual. El método GET predeterminado coloca los datos en la URL. Para actualizar la página actual con JavaScript, cancela esa acción predeterminada:

const form = document.querySelector("#new-todo");
form.addEventListener("submit", (event) => {
  event.preventDefault();
  const data = new FormData(form);
  const title = data.get("title").trim();
  if (title === "") return;
  addTodo(title);
  form.reset();
});

Este formulario tiene un campo de texto habilitado llamado title. El detector elimina los espacios de los extremos, ignora un resultado vacío, añade un elemento y restablece los campos a sus valores iniciales. reset() no siempre significa dejar vacío; un control con un valor inicial vuelve a ese valor.

Si el elemento aparece brevemente y desaparece al volver a cargar la página, comprueba si falta preventDefault(). Un detector de clic solo en el botón no es el punto adecuado para manejar el formulario, especialmente cuando otra vía lo envía.

Conoce qué desencadena el envío

El envío implícito con Enter puede hacer clic en el botón de envío predeterminado habilitado. Enter en un área de texto inserta un salto de línea. Un botón dentro de un formulario es un botón de envío por defecto cuando se omite su tipo, así que un botón «Mostrar contraseña» debe usar explícitamente type="button".

Cuando hay varios botones de envío, event.submitter identifica el que se usó. Puede ser null en vías sin un botón específico, por lo que el código debe comprobarlo antes de leerlo:

const button = event.submitter;
const intent = button ? button.value : "save";
const data = button ? new FormData(form, button) :
  new FormData(form);

Un botón de envío con nombre como name="intent" value="publish" aporta ese par cuando se pasa a FormData. Una llamada a FormData(form) por sí sola no incluye el par de nombre y valor de un botón de envío.

Conserva la vía de validación
Vía de envíoValidación automáticaDetector de submit
Activación de envío o requestSubmit()Se ejecuta salvo que los atributos de validación la omitanSe ejecuta si la validación lo permite
form.submit()Se omiteSe omite

La validación integrada se ejecuta antes del evento submit. Si un campo obligatorio vacío no supera la comprobación, el navegador informa del problema y no despacha submit. Una comprobación personalizada situada solo dentro de ese detector no se ejecutará en ese intento fallido. novalidate en el formulario desactiva el paso de validación automática.

Usa requestSubmit() cuando el código necesite la vía normal de validación y del evento submit. Solicita el envío sin despachar un clic en el botón. Pasa un botón de envío concreto a requestSubmit(button) cuando deban aplicarse las opciones de envío de ese botón.

Puntos clave

  • Maneja submit en el formulario para cubrir las vías de envío del usuario y del código.
  • Cancela el envío predeterminado cuando JavaScript sustituya la navegación.
  • Asigna type="button" a los botones del formulario que no sean de envío.
  • Usa submitter para distinguir los botones de envío e incluir sus datos cuando sea necesario.
  • La validación precede a submit; requestSubmit() conserva esa vía.
  • submit() omite la validación y el evento submit.
  • reset() restablece los valores iniciales.

4.3 Deja que HTML describa las reglas de validación

HTML puede describir muchas reglas de entrada sin una comprobación personalizada de JavaScript. El navegador comprueba esas reglas durante el envío y expone los resultados para tu código. Empieza con las restricciones que correspondan a los datos que necesitas.

Elige una restricción
AtributoRegla
requiredSe debe proporcionar un valor o una selección obligatoria.
type="email" o "url"El valor debe tener la sintaxis esperada.
min, max, stepLos valores numéricos o de fecha deben ajustarse a los límites e incrementos.
minlength, maxlengthEl texto introducido por el usuario debe cumplir una regla de longitud.
patternUn valor de texto no vacío debe coincidir con una expresión que abarque todo el valor.

Para pattern="[0-9]{5}", «12345» pasa la comprobación; «1234», «123456» y «12345a» no la pasan. El patrón abarca todo el valor, no cualquier subcadena coincidente. Una entrada vacía omite la comprobación del patrón, así que añade required cuando no se permita dejarla vacía.

La validación de correo electrónico comprueba la sintaxis, no si existe un buzón. Tanto «ann@example.com» como «ann@example» cumplen la regla de correo electrónico del navegador, mientras que «ann.example.com» no la cumple. Un patrón adicional puede imponer un formato más específico cuando la aplicación lo requiera.

Lee el motivo del fallo

El objeto validity de un campo contiene indicadores booleanos. valueMissing, typeMismatch, patternMismatch, tooShort y tooLong describen texto ausente o con formato incorrecto. rangeUnderflow, rangeOverflow y stepMismatch describen restricciones numéricas. badInput cubre una entrada que el navegador no puede convertir en un valor utilizable, y customError registra un mensaje de validez personalizada. valid es verdadero cuando ninguna restricción falla.

Para un campo numérico con min="0" max="10" step="2", 12 supera el máximo, 7 no coincide con el incremento permitido y 6 pasa la comprobación. Más de un indicador puede ser verdadero; no supongas que cada campo no válido tiene exactamente un problema.

Comprueba sin mostrar o informa de forma visible

checkValidity() devuelve un booleano y dispara invalid en los controles que no pasan la comprobación sin mostrar el mensaje del navegador. reportValidity() comprueba y también informa del problema al usuario. Llama a cualquiera de los dos en un campo o un formulario. validationMessage proporciona el mensaje en el idioma del navegador. El evento invalid no se propaga por burbujeo.

Las restricciones de longitud del texto se aplican al texto introducido por el usuario. Asignar input.value = "ab" en el código no hace que tooShort sea verdadero solo porque minlength sea 3. maxlength normalmente también impide que el usuario siga escribiendo más allá del límite.

Muestra los errores en un momento útil

:invalid se aplica inmediatamente, por lo que un campo obligatorio sin tocar ya coincide. :user-invalid espera a una interacción del usuario o a un intento de envío y puede evitar que el campo aparezca en rojo demasiado pronto. Usa también texto para explicar el error, además de un estilo visual.

novalidate omite la validación automática del envío para el formulario; formnovalidate la omite para un botón de envío. No eliminan las restricciones ni desactivan checkValidity(). Las comprobaciones del navegador ayudan a los usuarios a corregir errores; el servidor debe volver a validar los datos recibidos porque una solicitud puede evitar la página.

Puntos clave

  • Usa restricciones HTML para valores obligatorios, sintaxis, rangos, incrementos, longitud y patrones.
  • Un patrón coincide con todo el valor no vacío; usa required para rechazar una entrada vacía.
  • Lee los indicadores de validity para identificar por qué un campo no pasó la comprobación.
  • checkValidity() comprueba; reportValidity() también muestra información al usuario.
  • Los atributos que omiten la validación permiten seguir usando la API de validez.
  • Valida también los datos recibidos en el servidor.

4.4 Reglas personalizadas y mensajes de error útiles

Una regla que implica dos campos necesita código. Por ejemplo, una confirmación debe coincidir con una contraseña, o una fecha final debe ser posterior a una fecha inicial. Mantén la regla separada de cómo y cuándo se muestra su error.

Establece y borra un error personalizado

Una cadena no vacía pasada a setCustomValidity() marca el campo como no válido y establece customError. Pasar "" borra ese error personalizado; las restricciones de obligatoriedad, patrón u otras pueden seguir sin cumplirse.

function checkMatch() {
  const matches = confirm.value === password.value;
  confirm.setCustomValidity(matches ? "" :
    "The passwords do not match.");
}
password.addEventListener("input", checkMatch);
confirm.addEventListener("input", checkMatch);

Vuelve a comprobar ambas dependencias. Cambiar la primera contraseña puede hacer que una confirmación antes correcta deje de coincidir. Un mensaje que nunca se borra mantiene el campo como no válido después de que la persona corrija la diferencia. Ejecuta también la comprobación inicial cuando los campos puedan comenzar con valores.

Los campos de fecha proporcionan cadenas ISO como "2026-03-10", independientemente del formato de fecha local que se muestre. Los valores no vacíos de estos controles se pueden comparar como texto. Si la regla indica que el final debe ser posterior al inicio, rechaza end.value <= start.value, incluida la igualdad. Trata las fechas ausentes mediante required o una comprobación independiente de valor vacío.

Explica la siguiente acción útil

Un mensaje debe identificar el problema y su corrección. Para un correo electrónico ausente, di «Introduce tu dirección de correo electrónico». Para un texto de correo con formato incorrecto, di «Introduce una dirección de correo electrónico con el formato nombre@example.com». Conserva el valor introducido para que la persona pueda editarlo.

Un campo de nombre obligatorio acepta espacios porque forman una cadena no vacía. Comprueba name.value.trim() === "" cuando los espacios en blanco de los extremos no deban contar. Borra este error personalizado cuando aparezca texto significativo.

Conecta los mensajes con los controles
<label for="email">Email</label>
<input id="email" name="email" type="email"
  required aria-describedby="email-error">
<p id="email-error"></p>

Coloca el error en ese párrafo y establece aria-invalid="true" mientras el campo tenga un error. Borra el texto y elimina aria-invalid o establécelo en "false" después de la corrección. aria-describedby conecta la explicación con el campo para la tecnología de asistencia. Usa texto; un borde rojo por sí solo no puede indicar a todo el mundo qué ha fallado.

Elige el flujo de validación

Para mensajes completamente personalizados, añade novalidate y usa un detector de submit en el formulario. Actualiza las reglas personalizadas, examina la validez de los campos, escribe sus mensajes e impide el envío cuando alguno no sea válido. Mueve el foco al primer campo no válido o a un resumen de errores con enlaces. Si JavaScript también maneja localmente el envío correcto, cancela asimismo ese envío predeterminado.

Evita informar de un texto de correo electrónico incompleto en cada pulsación inicial. Una secuencia útil consiste en mostrar los errores después de perder el foco o de enviar el formulario y, después, volver a comprobar un error ya mostrado en input.

Puntos clave

  • Los mensajes personalizados no vacíos hacen que un campo sea no válido; "" borra solo ese error personalizado.
  • Vuelve a comprobar cada campo del que dependa una regla entre campos.
  • Para un orden estricto de fechas, rechaza tanto la igualdad como una fecha final anterior.
  • Explica una corrección con texto y asóciala al campo.
  • Borra los errores visibles y el estado de error ARIA después de la corrección.
  • Los mensajes personalizados al enviar necesitan un flujo explícito de validez y foco.
5

Construye controles personalizados que todos puedan usar

Elegir un elemento nativo proporciona gran parte del comportamiento de un control antes de escribir ningún detector. Los controles personalizados necesitan el mismo cuidado: un nombre claro, acceso mediante teclado, foco visible y un estado que coincida con lo que comunica la tecnología de asistencia. Los grupos necesitan un movimiento predecible entre elementos. Cuando el contenido se abre, se cierra o desaparece, un destino del foco elegido de forma deliberada permite que el usuario continúe la interacción sin volver a buscar en la página.

5.1 Empieza con un control nativo

Un control necesita algo más que una respuesta al clic. Las personas deben poder encontrarlo, llegar a él, comprender su propósito, accionarlo y ver dónde está el foco. HTML nativo ya proporciona gran parte de este comportamiento.

Elige el elemento adecuado para la tarea

Usa un <button> para una acción, un <a href> para la navegación y un control de formulario con etiqueta para la entrada de datos. <details> con <summary> proporciona un control de despliegue de contenido. Aplica estilos CSS a estos elementos para que encajen en la página. Un botón con foco se activa mediante Enter o la barra espaciadora sin un detector de teclado adicional.

Un div con un detector de clic no tiene ninguna de las características semánticas ni del comportamiento de teclado integrados del botón. Asignarle role="button" cambia lo que anuncia la tecnología de asistencia, pero no permite que reciba el foco ni convierte las pulsaciones de teclas en clics.

NecesidadBotón nativoDiv personalizado que actúa como botón
RolIntegradoAñade role="button".
Foco mediante tecladoIntegradoAñade tabindex="0".
Enter y barra espaciadoraIntegradosAñade manejo del teclado.
Nombre accesibleNormalmente su textoProporciona texto significativo o un nombre ARIA.
Comportamiento deshabilitadodisabledComprueba aria-disabled en el código.
Mantén un orden de foco predecible

tabindex="0" añade un elemento a la secuencia normal de tabulación en el orden del documento. tabindex="-1" permite dar el foco mediante código sin añadir una parada de tabulación. Los valores positivos mueven elementos por delante de la secuencia habitual y son difíciles de mantener; evítalos.

Ninguna de las dos configuraciones añade comportamiento de activación. Un botón personalizado que puede recibir el foco sigue necesitando un detector para manejarlo con el teclado. Un rol, una parada de tabulación y una función de devolución de llamada resuelven partes distintas de la interacción.

Da un nombre a cada control

Un lector de pantalla usa un nombre accesible para distinguir los controles. Un botón de texto puede usar su texto visible. Un campo de texto o una lista desplegable necesita una etiqueta asociada, como <label for="email">Email</label> junto con id="email". aria-labelledby puede hacer referencia a texto visible en otro lugar; aria-label puede proporcionar un nombre cuando no hay texto visible adecuado.

Un botón de cierre que solo contiene un icono puede ser <button type="button" aria-label="Close">×</button>. El nombre describe la acción en lugar de hacer que el usuario deduzca qué significa el símbolo.

Muestra y aplica el estado

Mantén un indicador de foco visible. Si sustituyes el contorno predeterminado, proporciona una alternativa clara con :focus-visible, por ejemplo outline: 3px solid #1a73e8. Eliminarlo sin sustituirlo oculta la posición del usuario de teclado.

Un botón nativo deshabilitado sale de la secuencia de tabulación y no puede activarse normalmente. aria-disabled="true" solo comunica que no está disponible. La acción de clic y teclado de un control personalizado debe comprobar ese estado y terminar sin actuar. Centralizar esa comprobación en la función de activación mantiene la coherencia entre ambas vías de entrada.

Puntos clave

  • Usa un elemento nativo que corresponda a la acción o a la entrada de datos.
  • Un rol ARIA proporciona significado semántico, no comportamiento.
  • Usa tabindex="0" para el acceso normal con Tab y -1 para dar el foco solo desde el código.
  • Proporciona un nombre accesible y un indicador de foco visible.
  • Haz que el código de activación respete aria-disabled.

5.2 Mantén unidos el comportamiento de teclado y el estado

Un control personalizado debe dar el mismo resultado independientemente de cómo se active. Mantén la acción en una sola función, dirige a ella el uso del teclado y del puntero, y actualiza el estado que leen tanto los estilos como la tecnología de asistencia.

Da activación mediante teclado a un botón personalizado
fakeButton.addEventListener("click", activate);
fakeButton.addEventListener("keydown", (event) => {
  if (event.key === "Enter" || event.key === " ") {
    event.preventDefault();
    activate();
  }
});

Esto es para un botón personalizado que puede recibir el foco. Cancelar la barra espaciadora impide que la página se desplace durante la acción. Un botón nativo ya produce clics al activarse con el teclado, por lo que añadirle una segunda vía de teclado puede ejecutar la acción dos veces.

Elige el estado que corresponda al control
ControlEstado
Botón de alternancia con una etiqueta establearia-pressed="true" o "false"
Botón que abre un control de desplieguearia-expanded="true" o "false"
Casilla de verificación o interruptor personalizadoaria-checked="true" o "false"

Un botón de alternancia «Silenciar» conserva esa etiqueta y cambia aria-pressed. Si el diseño cambia la etiqueta de la acción a «Activar sonido», omite aria-pressed. Una casilla de verificación usa role="checkbox" y la barra espaciadora para alternar su estado; un interruptor usa role="switch" para el estado de activado o desactivado.

Los atributos contienen cadenas de caracteres. Compara getAttribute("aria-expanded") con "true", no con el booleano true:

detailsButton.addEventListener("click", () => {
  const open = detailsButton.getAttribute(
    "aria-expanded") === "true";
  detailsButton.setAttribute("aria-expanded", String(!open));
  panel.hidden = open;
});

Cuando open era verdadero, el nuevo estado es falso y el panel queda oculto. aria-controls puede identificar el panel por su ID. Los elementos nativos <details> y <summary> proporcionan el comportamiento de despliegue sin este script.

Usa el atributo de estado como fuente para los estilos, por ejemplo [aria-pressed="true"], para que los estados visible y anunciado cambien juntos. Cierra los elementos emergentes con Escape y devuelve el foco a su control de apertura.

Da al grupo una sola parada de tabulación

Las pestañas, las barras de herramientas y los grupos de botones de opción usan Tab para entrar o salir del grupo y las teclas de dirección para moverse dentro de él. El índice de tabulación móvil mantiene el elemento actual en 0 y los demás en -1. Al moverse, establece el elemento anterior en -1, el nuevo en 0 y da el foco al nuevo elemento.

Para tres elementos, moverse a la derecha desde el índice 2 vuelve al 0 con (2 + 1) % 3. Moverse a la izquierda desde 0 usa (0 - 1 + 3) % 3 para llegar a 2. Añade la longitud antes de la operación de resto porque en JavaScript -1 % 3 es −1.

Comprueba con el teclado: entra en el control, ve el foco, actívalo con las teclas adecuadas, muévete dentro de un grupo y cierra un elemento emergente con Escape. Comprueba el estado visible y el estado ARIA en cada cambio.

Puntos clave

  • Dirige el clic, Enter y la barra espaciadora de un botón personalizado a una sola acción.
  • Los botones nativos ya proporcionan la activación por clic mediante teclado.
  • Elige aria-pressed, aria-expanded o aria-checked según el estado correspondiente.
  • Compara los valores ARIA como cadenas y actualiza los estilos a partir del mismo estado.
  • El índice de tabulación móvil da al grupo una parada de tabulación y movimiento con las teclas de dirección.
  • Escape cierra un elemento emergente y devuelve el foco a su control de apertura.

5.3 Mueve el foco cuando cambie la página

El foco determina adónde va la siguiente acción del teclado. Si el control que tiene el foco desaparece, el usuario puede perder su posición. Trata el foco como parte de la actualización de la página, especialmente al abrir un cuadro de diálogo, eliminar un elemento o informar de un error.

Abre un cuadro de diálogo y vuelve a su control de apertura

Recuerda el elemento que tiene el foco antes de abrir. Mueve el foco a la nueva interacción y después restablécelo al cerrar:

let opener;
openButton.addEventListener("click", () => {
  opener = document.activeElement;
  dialog.showModal();
});
dialog.addEventListener("close", () => {
  if (opener && opener.isConnected) opener.focus();
});

showModal() en un cuadro de diálogo nativo mueve el foco a su interior y hace que el resto de la página sea inerte, por lo que no se puede hacer clic en ella ni darle el foco mientras el cuadro modal está abierto. Escape normalmente provoca un evento cancel seguido del cierre y un evento close. Un detector de cancel puede impedir el cierre si la aplicación necesita ese comportamiento.

Elige una ubicación inicial útil para el foco en el cuadro de diálogo, como su primer control pertinente. Para un elemento emergente personalizado, el código debe mover el foco de forma deliberada. Cuando el control de apertura original ya no exista, elige un control útil cercano en lugar de intentar dar el foco a un elemento desconectado.

Permite que un destino útil reciba el foco

Un encabezado o un resumen de errores normalmente no puede recibir el foco. Asígnale tabindex="-1" antes de llamar a .focus(). Podrá recibir el foco de forma deliberada sin convertirse en una parada adicional durante la navegación normal con Tab. Usa 0 solo cuando pertenezca a esa secuencia normal.

Conserva la posición después de eliminar

Antes de eliminar un elemento de lista cuyo botón de eliminación tiene el foco, encuentra el siguiente destino útil. Da el foco al botón del siguiente elemento; si no hay un siguiente elemento, usa el botón del anterior. Si la lista queda vacía, da el foco a su encabezado con tabindex="-1".

Encontrar el destino antes de eliminar evita perder la relación con los elementos vecinos. No envíes el foco a la parte superior del documento después de cada eliminación; la persona debe poder seguir trabajando cerca del cambio.

Mantén el foco en una interacción que continúa

Un elemento emergente puede cerrarse cuando el foco sale de toda su área. El evento focusout, que se propaga por burbujeo, proporciona el destino:

menu.addEventListener("focusout", (event) => {
  if (!menu.contains(event.relatedTarget)) {
    menu.hidden = true;
  }
});

Moverse entre dos controles del menú no lo cierra porque el destino permanece dentro. Un destino null también está fuera; contains(null) es falso. No devuelvas automáticamente el foco al control de apertura cuando el usuario se haya movido de forma intencionada a otro control de la página.

Haz que los cambios sean predecibles

Después de una validación personalizada fallida de un formulario, da el foco al primer campo no válido o a un resumen de errores que enlace a ellos. La validación integrada ya da el foco a un campo que no supera la comprobación. Mantén disponible el texto del error para que la persona sepa qué corregir.

No saltes a un campo nuevo mientras el usuario escribe ni navegues en cuanto cambie una lista desplegable. Deja que una acción clara confirme el cambio. El movimiento del foco debe ayudar a realizar una acción deliberada o a recuperarse de un error, sin interrumpir la entrada normal.

Puntos clave

  • Recuerda el control de apertura y restablece el foco en un destino disponible cuando se cierre un cuadro de diálogo.
  • Usa tabindex="-1" para un encabezado o resumen que reciba el foco desde el código.
  • Después de eliminar, da el foco a un elemento vecino o al encabezado de la lista vacía.
  • Usa relatedTarget para distinguir los movimientos de foco internos de la salida de un elemento emergente.
  • Da el foco a un destino de error después de una validación fallida; evita los saltos de foco inesperados.

Ejemplos de Detectores de eventos

5 Lecciones Interactivas

1

Construye e inspecciona detectores de eventos

13 pasos
Registra un contador, inspecciona clics anidados y corrige la duración de los detectores.
  1. 1
    IntroducciónHaz que un botón responda

    Un detector conecta un evento con una función. Construye un pequeño contador eligiendo el botón, registrando su acción y conservando el total entre llamadas. Después inspecciona un click anidado y controla la duración del detector.

    Observa la diferencia entre el código de configuración, que se ejecuta ahora, y la función de devolución de llamada, a la que el navegador llama cuando ocurre el evento elegido.

  2. 2
    EjemploAlmacena el total fuera de la función de devolución de llamada

    Selecciona el botón existente y almacena su recuento antes del registro:

    const button = document.querySelector("#like");
    let likes = 0;
    button.addEventListener("click", () => {
      likes = likes + 1;
      button.textContent = "Likes: " + likes;
    });
    

    Cada llamada suma al total anterior. Después de tres clics, la etiqueta es «Likes: 3». Un recuento declarado dentro de la función de devolución de llamada comenzaría de nuevo en cada llamada.

  3. 3
    ConsejoPasa la acción sin llamarla

    addEventListener("click", showMenu) pasa una función para usarla más tarde. showMenu() la llamaría durante la configuración. Cuando se necesiten argumentos, usa () => showMenu("main").

    Carga el script después de que exista el botón, por ejemplo con defer, y conserva la escritura exacta del evento. Un detector de "Click" no recibe los eventos "click" normales.

  4. 4
    DesafíoCorrige un registro

    Un botón debe abrir el panel de consejos en cada activación. Actualmente, la configuración usa button.addEventListener("click", openTips()). ¿Qué debe cambiar y por qué?

    ---reveal---

    Pasa openTips sin paréntesis. Así proporcionas la función de devolución de llamada que se ejecutará para un click posterior. Llamar a openTips() durante la configuración abre el panel inmediatamente y pasa su resultado en lugar de conservar la acción para el evento.

  5. 5
    ExplicaciónLee las dos propiedades de elementos

    Para un botón que contiene una imagen, hacer clic en la imagen la convierte en el target. En el detector del botón, currentTarget es el botón.

    target indica dónde comenzó el evento; el target actual indica dónde se registró esta función de devolución de llamada. Esa diferencia explica por qué leer el dataset de un botón desde target puede fallar cuando el click cae en su imagen.

  6. 6
    EjemploLee los datos del botón que los contiene

    Supón que button tiene data-id="7" y contiene un icono:

    button.addEventListener("click", (event) => {
      console.log(event.currentTarget.dataset.id);
    });
    

    El resultado es "7" tanto para un click en el icono como para un click en el fondo del botón. La misma expresión funciona en un detector con una función flecha o normal. El this de una función flecha no identifica automáticamente el botón.

  7. 7
    DesafíoIdentifica el elemento del detector

    Una sección contiene un botón con un span. Se registra un detector de click solo en la sección y se hace clic en el span. ¿Qué identifican target y currentTarget durante esa función de devolución de llamada?

    ---reveal---

    target identifica el span donde comenzó el evento. currentTarget identifica la sección donde se registró el detector. El origen del evento no se sustituye por el nodo antecesor que lo recibe.

  8. 8
    ExplicaciónConserva la referencia para eliminar

    La eliminación necesita el mismo tipo de evento, objeto de función de devolución de llamada y configuración de captura:

    const handler = () => closePanel();
    button.addEventListener("click", handler);
    button.removeEventListener("click", handler);
    

    Una función anónima se puede eliminar si se guarda de esta forma. Escribir una nueva () => closePanel() en la llamada de eliminación crea otro objeto y no coincide con el registro anterior.

  9. 9
    ConexiónTermina varios registros a la vez

    Una función temporal puede pasar una señal de AbortController a varios registros. Llamar a controller.abort() elimina esos detectores, incluidas las funciones de devolución de llamada guardadas o anónimas.

    Usa un controlador nuevo para otro periodo de actividad. Para una sola respuesta de ejecución única, { once: true } elimina ese registro después de que se ejecute. Ninguna de las dos opciones impide que se acumulen funciones de devolución de llamada distintas antes de que termine su periodo de actividad.

  10. 10
    DesafíoDiagnostica una configuración repetida

    Al abrir un panel se registra cada vez un nuevo detector anónimo de teclado en el documento. Al cerrarlo se oculta el panel, pero el detector permanece. Después de tres aperturas, una tecla desencadena tres acciones. ¿Cómo se puede corregir el ciclo de vida?

    ---reveal---

    Elimina los registros cuando se cierre el panel, por ejemplo cancelando su señal compartida, y crea un controlador nuevo al volver a abrirlo. Como alternativa, guarda una función de devolución de llamada y elimina esa misma referencia. Ocultar el panel no elimina un detector del documento.

  11. 11
    Idea claveDespacha una notificación inmediatamente

    Para avisar a otro código, crea un CustomEvent con sus datos en detail. Establece bubbles: true si los nodos antecesores deben recibirlo:

    item.dispatchEvent(new CustomEvent("cart:add", {
      detail: { id: 42 }, bubbles: true
    }));
    

    Los detectores se ejecutan antes de que termine la llamada. El evento despachado tiene isTrusted en falso. Es una notificación que envía tu código, no una solicitud para que el navegador espere una acción del usuario.

  12. 12
    DesafíoCombina origen y duración

    El manejador de click de un botón usa su propio data-key y debe actuar solo en la primera activación. Contiene un icono SVG. ¿Qué propiedad y opción mantienen correctos ambos requisitos?

    ---reveal---

    Lee event.currentTarget.dataset.key en el detector directo del botón y regístralo con { once: true }. La propiedad identifica el botón que contiene los datos incluso para un click en el icono. La opción elimina ese registro después de su primera llamada.

  13. 13
    RepasoComprueba un detector en tres puntos

    Cuando una interacción falle, examina la configuración, los datos de la función de devolución de llamada y su duración. Confirma que el elemento existe y que el nombre del evento es exacto; examina target y currentTarget durante la llamada; después busca eliminaciones que falten o registros repetidos. Estas comprobaciones distinguen un detector que nunca recibe un evento de uno que lo recibe correctamente pero actúa sobre el elemento equivocado.

2

Sigue eventos y delega acciones

13 pasos
Predice el orden de los detectores, sustituye acciones predeterminadas del navegador y maneja nuevos elementos de lista.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónSigue un clic por una tarjeta
  2. 2
    EjemploLee el recorrido ascendente
  3. 3
    ExplicaciónAñade el recorrido descendente
  4. 4
    DesafíoSigue un árbol más pequeño
  5. 5
    ExplicaciónSustituye la navegación
  6. 6
    Idea claveDetener el recorrido es una decisión distinta
  7. 7
    DesafíoMantén activo un observador padre
  8. 8
    ExplicaciónMueve el detector a la lista
  9. 9
    EjemploEncuentra y comprueba el botón de eliminación
  10. 10
    DesafíoManeja un icono anidado
  11. 11
    ConsejoUsa un evento que llegue al contenedor
  12. 12
    DesafíoMantén separadas la apertura y el cierre
  13. 13
    RepasoElige un punto de actuación para cada acción
3

Maneja teclas y construye un arrastre con puntero

16 pasos
Elige eventos de entrada, trabaja con coordenadas y haz que un arrastre sea robusto y fácil de usar.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónElige el nivel de entrada adecuado
  2. 2
    ExplicaciónDistingue el significado de la posición
  3. 3
    ConsejoProtege la escritura y maneja las repeticiones
  4. 4
    DesafíoElige un evento para una vista previa
  5. 5
    ConexiónPermite que la activación incluya el teclado
  6. 6
    EjemploCambia de sistema de coordenadas
  7. 7
    DesafíoExamina un clic con la página desplazada
  8. 8
    Idea claveEvita que el paso del puntero sea la única vía
  9. 9
    EjemploCalcula un movimiento de arrastre
  10. 10
    ExplicaciónGuarda el inicio y captura su puntero
  11. 11
    EjemploAplica el desplazamiento en cada movimiento
  12. 12
    ConsejoTermina incluso si interviene el navegador
  13. 13
    DesafíoPredice un arrastre que continúa
  14. 14
    ConexiónOfrece movimiento sin arrastrar
  15. 15
    DesafíoDiagnostica dos fallos de arrastre
  16. 16
    RepasoRelaciona el evento con la tarea
4

Construye una interacción completa de formulario

16 pasos
Lee campos, maneja el envío y guía la corrección mediante validación nativa y personalizada.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónSigue un formulario desde la edición hasta el envío
  2. 2
    EjemploLee un número sin concatenar
  3. 3
    ExplicaciónLee opciones y nombres repetidos
  4. 4
    DesafíoConserva cada tema seleccionado
  5. 5
    ExplicaciónManeja el envío en el formulario
  6. 6
    EjemploLee, añade y restablece
  7. 7
    DesafíoConserva la vía normal de envío
  8. 8
    EjemploLee los fallos independientes de restricciones
  9. 9
    Idea claveUn patrón no exige un valor
  10. 10
    DesafíoComprueba un rango con incrementos
  11. 11
    ExplicaciónAñade una regla que conecte dos campos
  12. 12
    ConsejoDistingue la validez del momento de mostrar el error
  13. 13
    DesafíoCorrige una regla de fechas desactualizada
  14. 14
    ConexiónAsume la responsabilidad al omitir los mensajes del navegador
  15. 15
    DesafíoCompleta el flujo de recuperación
  16. 16
    RepasoComprueba las vías válidas y no válidas
5

Construye controles y conserva la posición del usuario de teclado

14 pasos
Elige el comportamiento nativo, sincroniza el estado personalizado y gestiona el foco durante los cambios de página.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónHaz que una interacción funcione con el teclado
  2. 2
    ExplicaciónUsa el comportamiento que HTML ya proporciona
  3. 3
    ConsejoPermite recibir el foco por la vía prevista
  4. 4
    DesafíoCorrige una acción inaccesible
  5. 5
    EjemploMantén una sola función de activación
  6. 6
    ExplicaciónExpresa el tipo de estado adecuado
  7. 7
    DesafíoInvierte correctamente un control de despliegue
  8. 8
    EjemploMueve una parada de tabulación dentro de un grupo
  9. 9
    ConexiónRecuerda desde dónde se abrió un cuadro de diálogo
  10. 10
    EjemploMantén dentro un movimiento de foco interno
  11. 11
    DesafíoElige el foco después de eliminar
  12. 12
    DesafíoSigue una actualización de grupo
  13. 13
    DesafíoMantén predecible un elemento emergente
  14. 14
    RepasoPrueba la secuencia completa del control

Ejercicios de Detectores de eventos

50 Preguntas de Práctica en 5 sets

Set 1: Sigue acciones anidadas y delegación10 Preguntas de Práctica

Pregunta 1 de 10

Un botón está dentro de una sección que está dentro de un elemento main. Cada uno tiene un detector de clic normal. ¿En qué orden se ejecutan al hacer clic en el botón?
  1. Main → sección → botón.
  2. Sección → botón → main.
  3. Botón → sección → main.
  4. Botón → main → sección.
Mostrar respuestaOcultar respuesta
Respuesta: C
Los detectores normales se ejecutan en el destino y después en el recorrido de burbujeo. El clic comienza en el botón, visita su sección y después su nodo antecesor main. El orden que comienza por main describiría detectores de captura en el recorrido de los nodos antecesores, que no son los registros indicados aquí.

Pregunta 2 de 10

Un contenedor tiene detectores de clic de captura y normal. Su botón hijo tiene un detector de clic normal. ¿Qué se registra al hacer clic en el botón?
  1. Captura del contenedor → botón → detector normal del contenedor.
  2. Botón → captura del contenedor → detector normal del contenedor.
  3. Detector normal del contenedor → botón → captura del contenedor.
  4. Captura del contenedor → detector normal del contenedor → botón.
Mostrar respuestaOcultar respuesta
Respuesta: A
El detector de captura observa el recorrido descendente antes de que se maneje el destino. Después se ejecuta el detector del botón en el destino, seguido del detector normal de burbujeo del contenedor. El orden de registro no adelanta un detector de burbujeo de un nodo antecesor a la fase de destino.

Pregunta 3 de 10

Un formulario tiene un detector normal de focus, pero dar el foco a su campo hijo no lo llama. ¿Cómo puede el formulario observar el foco de sus descendientes sin usar captura?
  1. Escuchar blur en el formulario.
  2. Añadir bubbles: true como opción del detector.
  3. Leer currentTarget en la función de focus existente.
  4. Escuchar focusin en el formulario.
Mostrar respuestaOcultar respuesta
Respuesta: D
focusin se propaga por burbujeo desde el campo hasta su formulario, por lo que un detector normal del formulario puede observarlo. focus y blur no se propagan por burbujeo. Una opción del detector no puede cambiar si el evento del navegador se propaga; leer una propiedad dentro de una función que nunca se ejecuta no resuelve la ausencia del evento.

Pregunta 4 de 10

El detector de un enlace solo llama a event.stopPropagation(). ¿Qué ocurre con su navegación normal?
  1. Se cancela la navegación.
  2. El enlace sigue navegando.
  3. El enlace necesita dos clics.
  4. Los detectores en nodos antecesores lo bloquean.
Mostrar respuestaOcultar respuesta
Respuesta: B
Detener la propagación impide el recorrido hacia otros elementos, pero la navegación es una acción predeterminada independiente del enlace. Como no se indica ninguna cancelación, esa acción permanece disponible. Usa preventDefault() en un detector que pueda cancelar el evento cuando el código sustituya la navegación por una acción en la página.

Pregunta 5 de 10

El detector de submit de un formulario llama a preventDefault(). También hay un detector normal de submit en una sección que lo rodea. ¿Qué puede observar el detector de la sección?
  1. Se omite porque la cancelación detiene el burbujeo.
  2. Se ejecuta y lee defaultPrevented como falso.
  3. Se ejecuta solo si también llama a preventDefault().
  4. Se ejecuta y puede leer defaultPrevented como verdadero.
Mostrar respuestaOcultar respuesta
Respuesta: D
Cancelar la acción predeterminada del formulario no impide que el evento submit se propague por burbujeo. El detector de la sección sigue ejecutándose y puede ver que se ha cancelado la acción predeterminada. La propagación tendría que detenerse por separado; no es necesario repetir la cancelación en el nodo antecesor para recibir el evento.

Pregunta 6 de 10

Un botón abre un menú. Un detector de clic del documento lo cierra después con ese mismo clic. El menú también debe cerrarse ante clics realmente externos y el registro de eventos a nivel de documento debe seguir funcionando. ¿Qué cambio satisface las tres necesidades?
  1. Cerrar solo cuando ni el menú ni su control de apertura contengan el destino.
  2. Detener la propagación en cada clic del botón del menú antes de abrirlo.
  3. Cancelar el clic del botón del menú antes de alternar el estado oculto.
  4. Cerrar solo cuando el propio menú no contenga el destino.
Mostrar respuestaOcultar respuesta
Respuesta: A
El clic de apertura se propaga por burbujeo hasta el documento y está fuera del menú, pero dentro de su control de apertura. Comprobar ambas regiones conserva el menú recién abierto y sigue cerrándolo ante otros clics externos. Detener la propagación ocultaría este evento al registro del documento; cancelar una acción predeterminada no cambia ninguna de las dos comprobaciones de pertenencia.

Pregunta 7 de 10

Un detector delegado de una barra de herramientas recibe un clic cuyo destino es un SVG dentro de un botón. ¿Qué expresión encuentra el botón desde ese destino?
  1. event.target.closest("button")
  2. event.currentTarget.closest("button")
  3. event.target.matches("button")
  4. event.target.querySelector("button")
Mostrar respuestaOcultar respuesta
Respuesta: A
closest() comienza en el SVG pulsado y sube hasta el botón. matches() solo comprueba el SVG, por lo que no puede identificar el control que lo rodea. Buscar hacia abajo con querySelector() también sigue la dirección equivocada para un botón que contiene el destino del evento.

Pregunta 8 de 10

Una lista vacía recibe un detector de clic delegado. Después, el código añade dentro de un elemento un botón de eliminación que coincide con el selector. ¿Qué registro necesita el nuevo botón?
  1. Registrar también la misma función en el nuevo botón.
  2. Ningún registro nuevo; su clic llega a la lista.
  3. Eliminar y volver a añadir el detector de la lista tras la inserción.
  4. Despachar DOMContentLoaded de nuevo tras añadir el botón.
Mostrar respuestaOcultar respuesta
Respuesta: B
El evento se propaga por burbujeo desde el nuevo botón hasta la lista existente, donde ya se ejecuta la función delegada. La función selecciona el control pertinente a partir del destino del evento. Añadir otro detector directo es innecesario y puede duplicar el trabajo si ambas funciones realizan la misma acción.

Pregunta 9 de 10

Un detector delegado usa closest(".action") dentro de un contenedor anidado en un elemento exterior .action. Un clic en un espacio vacío encuentra ese elemento exterior. ¿Qué comprobación limita el manejo al contenedor?
  1. Terminar cuando event.currentTarget === container.
  2. Terminar solo cuando la coincidencia sea igual a event.target.
  3. Terminar cuando la coincidencia no tenga elementos hijos.
  4. Terminar cuando la coincidencia esté fuera de container.
Mostrar respuestaOcultar respuesta
Respuesta: D
closest() puede subir más allá del contenedor del detector y devolver un nodo antecesor exterior. Después de comprobar si no hay coincidencia, usa container.contains(match) para rechazar ese resultado. Comprobar currentTarget === container rechazaría todos los eventos de este detector, incluidos los clics válidos en sus propios controles hijos.

Pregunta 10 de 10

Una lista delega los clics de eliminación con closest(".remove") y comprobaciones de pertenencia. Los botones nuevos funcionan, salvo en una fila cuyo detector del icono llama a stopPropagation(). El relleno del botón de esa misma fila sí funciona. ¿Por qué ocurre esta diferencia?
  1. La comprobación de pertenencia rechaza todos los SVG descendientes.
  2. Las filas nuevas necesitan detectores directos en todos sus iconos.
  3. Los clics en el icono se detienen antes de llegar a la lista.
  4. closest busca hacia abajo cuando el destino es un icono.
Mostrar respuestaOcultar respuesta
Respuesta: C
El detector del icono bloquea el recorrido ascendente, por lo que la función de la lista nunca recibe ese clic. Al hacer clic en el relleno del botón se evita el detector del icono y el evento aún puede propagarse por burbujeo hasta la lista. closest() puede manejar un icono como destino, pero no puede ayudar a una función delegada que nunca recibe el evento.
Set 2: Configura e inspecciona detectores de eventos10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un botón debe llamar a showHints solo cuando se active. ¿Qué registro consigue eso?
  1. button.addEventListener("click", showHints());
  2. button.addEventListener("click", showHints);
  3. button.addEventListener("onclick", showHints);
  4. button.addEventListener("click", () => showHints);
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un script en la cabecera selecciona un botón cuyo marcado aparece después y falla al llamar a addEventListener. ¿Qué cambio de carga permite esperar a que se analice el HTML antes de seleccionar?
  1. Cambiar el nombre del parámetro del detector a event.
  2. Usar onclick como nombre del evento registrado.
  3. Mover la selección a una llamada inmediata de función.
  4. Añadir defer a la etiqueta del script externo.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Un script externo usa const items = document.querySelectorAll(".choice"); items.addEventListener("click", choose);. Todas las opciones ya existen. ¿Por qué falla y cómo debe cambiar la configuración?
  1. El resultado es una NodeList; registra el detector en cada elemento.
  2. El resultado es una NodeList; usa el nombre de evento "onclick".
  3. Faltan paréntesis en la función; registra choose() en la lista.
  4. El resultado es un elemento; registra un detector de captura en él.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Se asignan dos funciones distintas en este orden: button.onclick = first; button.onclick = second;. ¿Qué ocurre al hacer clic una vez en el botón?
  1. Se ejecuta first, seguida de second.
  2. Solo se ejecuta first.
  3. Solo se ejecuta second.
  4. Se ejecuta second, seguida de first.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Se registra un detector de clic en un botón que contiene un span. Se hace clic en el span. ¿Qué son target y currentTarget durante ese detector?
  1. target: botón; currentTarget: span.
  2. target: span; currentTarget: botón.
  3. target: span; currentTarget: span.
  4. target: botón; currentTarget: botón.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un detector con función flecha en <button data-code="r8"><span>Run</span></button> lee event.target.dataset.code. ¿Por qué puede devolver undefined y qué expresión de un detector directo lee el código del botón?
  1. El span puede ser el destino; lee event.currentTarget.dataset.code.
  2. El botón puede perder sus datos; lee event.type.dataset.code.
  3. El span puede ser el destino; lee this.dataset.code en la función flecha.
  4. El botón puede ser el destino; lee event.currentTarget.value.code.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Un detector registra su objeto de evento. Al terminar la interacción, al expandir el registro se muestra currentTarget: null. ¿Cómo puede quien desarrolla inspeccionar el elemento del detector durante la llamada?
  1. Leer event.target después de la llamada como elemento del detector.
  2. Añadir once: true para que el objeto conserve su destino actual.
  3. Registrar de nuevo el objeto de evento después de completar el despacho.
  4. Registrar event.currentTarget dentro del detector.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Se registró una función de devolución de llamada con { capture: true }. Se pasan el mismo tipo y función a removeEventListener, pero no se proporciona un tercer argumento. ¿Por qué permanece la función?
  1. La eliminación exige repetir todas las opciones originales.
  2. Las funciones de captura solo se pueden eliminar con abort().
  3. La eliminación busca el registro que no usa captura.
  4. La eliminación compara el texto fuente de la función, no su identidad.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Cada clic en «Iniciar» registra board.addEventListener("click", () => mark()). Al iniciar dos veces, un clic en el tablero coloca dos marcas. ¿Qué cambio de configuración evita esa acumulación?
  1. Añadir once: true a cada nueva función del tablero.
  2. Eliminar una nueva () => mark() antes de añadirla.
  3. Establecer board.onclick = null antes de añadir cada función.
  4. Registrar una función guardada durante la configuración inicial.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Un botón que no tiene otros detectores recibe estos registros y después se pulsa dos veces. ¿Cuántas veces se ejecuta log en total?

button.addEventListener("click", log, { once: true });
button.addEventListener("click", log);
button.addEventListener("click", log, true);
button.removeEventListener("click", log);
  1. 2
  2. 1
  3. 3
  4. 4
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 3: Maneja la entrada de teclado, puntero y arrastre10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un atajo debe seguir la letra producida en distintas distribuciones de teclado. ¿Qué propiedad debe comprobar?
  1. event.code
  2. event.repeat
  3. event.button
  4. event.key
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Una vista previa no detecta el texto pegado desde un menú contextual y muestra lo escrito con un carácter de retraso. Actualmente lee el campo en keydown. ¿Qué cambio corrige ambos síntomas?
  1. Leer el campo en un detector de keyup.
  2. Leer event.code en el detector de keydown.
  3. Leer el campo en un detector de input.
  4. Llamar a preventDefault() en cada keydown.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Un atajo del documento con «n» sin modificadores debe abrir un panel una vez por pulsación sin interrumpir la entrada de texto. Actualmente, mantener pulsada N en un div editable lo abre repetidamente. ¿Qué condiciones deben comprobarse antes de abrirlo?
  1. Omitir destinos editables y llamadas repetidas de keydown.
  2. Omitir solo llamadas repetidas; permitir todos los destinos editables.
  3. Omitir solo elementos input; permitir todas las llamadas repetidas.
  4. Cancelar primero cada tecla y después omitir destinos editables.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

La acción de un botón nativo funciona con ratón, pero no al activarlo con la barra espaciadora. Su único detector usa pointerdown. ¿Qué evento debe manejar la acción?
  1. pointermove
  2. click
  3. mousedown
  4. pointerenter
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

El documento se ha desplazado 420 px hacia abajo. Un evento de puntero indica clientY 75. ¿Cuál es su coordenada vertical relativa al documento?
  1. 495 px
  2. 345 px
  3. 75 px
  4. 420 px
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una tarjeta de ayuda oculta su información emergente cuando el puntero pasa del fondo de la tarjeta a una insignia hija. Su detector de ocultación usa pointerout. ¿Qué evento detecta mejor la salida de toda la tarjeta?
  1. pointerover
  2. pointermove
  3. pointerleave
  4. pointerdown
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Una caja comienza en left: 65px; top: 90px. Su puntero comienza en 180, 210 y se mueve a 155, 245 en coordenadas de cliente. ¿A qué posición debe llegar la caja?
  1. left: 155px; top: 245px
  2. left: 90px; top: 55px
  3. left: 25px; top: 35px
  4. left: 40px; top: 125px
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Una caja guarda correctamente las posiciones iniciales y captura su primer toque. Un segundo toque sobrescribe después esas posiciones y soltar cualquiera de los dos termina el movimiento. ¿Qué cambio de estado corrige ambos fallos?
  1. Capturar cada puntero nuevo y sobrescribir los inicios en cada pulsación.
  2. Guardar el ID del puntero activo y comprobar la pulsación, el movimiento y el final.
  3. Usar coordenadas de página y permitir que cualquier puntero termine la interacción.
  4. Ignorar pointercancel y mantener pasivos todos los detectores de movimiento.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Un arrastre táctil tiene captura del puntero y cálculos correctos, pero un gesto de desplazamiento del navegador lo cancela. El siguiente movimiento del puntero todavía puede mover la caja. ¿Qué dos cambios resuelven el control del gesto y el estado desactualizado?
  1. Aplicar user-select: none localmente; borrar el estado solo en pointerup.
  2. Capturar el puntero de nuevo en cada movimiento; ignorar la cancelación.
  3. Aplicar touch-action: none localmente; borrar el estado en pointercancel.
  4. Cancelar cada keydown del documento; borrar el estado al soltar una tecla.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una lista reordenable admite el arrastre y el movimiento con las teclas de dirección. Un usuario de pantalla táctil no puede arrastrar con precisión. ¿Qué incorporación le proporciona una alternativa adecuada?
  1. Botones de movimiento en los que se pueda hacer clic o tocar.
  2. Un asa de arrastre más larga en cada elemento.
  3. Instrucciones que describan las teclas de dirección.
  4. Un menú que solo aparezca al pasar el puntero formado por indicaciones de movimiento.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 4: Lee datos de formulario y diagnostica la validación10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un campo numérico contiene "6". ¿Qué produce input.value + 2?
  1. La cadena "62".
  2. El número 8.
  3. El número 62.
  4. La cadena "8".
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un formulario tiene campos de temas marcados llamados topic con los valores "art" y "code" en el orden del documento, un topic sin marcar con el valor "music" y una nota deshabilitada con nombre. ¿Qué devuelve new FormData(form).getAll("topic") y por qué es arriesgada la conversión a objeto?
  1. ["art", "code", "music"]; la conversión a objeto solo conserva el primer nombre repetido.
  2. ["art", "code"]; la conversión a objeto añade controles deshabilitados a la colección.
  3. ["code"]; la conversión a objeto recopila todos los valores de temas en un array.
  4. ["art", "code"]; la conversión a objeto solo conserva el último nombre repetido.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Un botón «Mostrar detalles» dentro de un formulario solo debe mostrar un panel. ¿Qué tipo explícito impide que envíe el formulario?
  1. type="submit"
  2. type="reset"
  3. type="text"
  4. type="button"
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un formulario tiene un campo obligatorio vacío. Una barra de herramientas llama a form.submit() y el detector del formulario que impide la navegación nunca se ejecuta. ¿Qué llamada restablece el flujo esperado y qué ocurre antes de que se corrija ese campo?
  1. Usar requestSubmit(); el detector de submit se ejecuta antes de que la validación lo bloquee.
  2. Usar submit() de nuevo; la segunda llamada ejecuta el detector tras la validación.
  3. Usar requestSubmit(); la validación bloquea el envío antes de que se dispare submit.
  4. Usar dispatchEvent(new Event("submit")); realiza la validación integrada normal.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Un campo de texto tiene pattern="[A-Z]{3}" y es opcional. ¿Un valor vacío supera esta restricción de patrón?
  1. No; todo patrón implica que la entrada sea obligatoria.
  2. Sí; el patrón omite los valores vacíos.
  3. No; el valor vacío provoca typeMismatch.
  4. Sí; el patrón solo se aplica a dígitos escritos.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Un campo numérico tiene min="2" max="14" step="4". El usuario introduce 12. ¿Qué falla?
  1. stepMismatch, porque los valores permitidos son 2, 6, 10, 14.
  2. rangeOverflow, porque 12 supera el último valor permitido inferior a 14.
  3. Ninguna restricción; 12 está entre el mínimo y el máximo.
  4. rangeUnderflow, porque el incremento comienza en cero.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Un formulario usa novalidate y comprueba los campos en el orden del documento al enviarse. Su campo de cantidad es obligatorio y tiene min="2" max="14" step="4"; ahora contiene 10 y se borró un mensaje personalizado anterior con setCustomValidity(""). El siguiente campo es un correo electrónico obligatorio que está vacío. ¿Qué debe bloquear este intento y recibir el foco?
  1. La cantidad, porque borrar un mensaje personalizado la deja no válida.
  2. El correo electrónico, porque falta; la cantidad cumple sus restricciones.
  3. Ningún campo, porque novalidate desactiva la API de validez.
  4. La cantidad, porque 10 no coincide con el incremento de cuatro desde su mínimo.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Una confirmación de contraseña no era válida. Ahora los valores coinciden, pero permanece un mensaje de validez personalizada anterior que no está vacío. ¿Qué debe hacer la función de comprobación?
  1. Volver a asignar confirm.value a sí mismo.
  2. Establecer solamente confirm.required = false.
  3. Llamar a confirm.setCustomValidity("").
  4. Llamar repetidamente a form.requestSubmit().
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Una reserva exige una fecha final no vacía estrictamente posterior a la fecha inicial. Ambos campos de fecha son obligatorios. La comprobación actual solo rechaza end.value < start.value y solo se ejecuta al editar la fecha final. ¿Qué dos cambios corrigen los fallos restantes entre campos?
  1. Rechazar <= y volver a comprobar cuando cambie cualquiera de las fechas.
  2. Rechazar < y volver a comprobar solo cuando la fecha final pierda el foco.
  3. Rechazar >= y volver a comprobar cuando cambie cualquiera de las fechas.
  4. Rechazar === y volver a comprobar solo cuando la fecha inicial pierda el foco.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Se muestra un mensaje de correo electrónico no válido mientras se corrige el campo. ¿Qué secuencia lo elimina rápidamente sin mostrar errores desde la primera pulsación en campos aún sin tocar?
  1. Volver a comprobar solo al perder el foco, incluso después de mostrar un error.
  2. Mostrar todos los campos vacíos como erróneos al cargar la página.
  3. Borrar todos los mensajes en cada keydown antes de leer la validez.
  4. Volver a comprobar errores mostrados en input; empezar a informar al perder el foco o enviar.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 5: Construye acceso mediante teclado y conserva el foco10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Un div tiene role="button" y un detector de clic. ¿Qué proporciona el propio rol?
  1. Una parada normal de tabulación y un contorno de foco visible.
  2. Significado semántico de botón para la tecnología de asistencia.
  3. Activación con Enter y la barra espaciadora sin más código.
  4. Comportamiento nativo de deshabilitación mediante aria-disabled.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Un encabezado de error de un formulario debe recibir el foco después de un envío fallido, pero no debe añadir una parada durante la navegación normal con Tab. ¿Qué configuración es adecuada?
  1. Establecer tabindex="1" y llamar a .focus().
  2. Establecer role="alert" y confiar en el foco con Tab.
  3. Establecer tabindex="-1" y llamar a .focus().
  4. Establecer tabindex="0" y eliminar su contorno.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Un botón personalizado que puede recibir el foco dirige click y keydown a activate(). Tiene aria-disabled="true". La función comienza con if (button.getAttribute("aria-disabled") === true) return;, pero ambas vías siguen actuando. ¿Qué corrección aplica el estado deshabilitado y permite actuar cuando el estado es la cadena «false»?
  1. Comparar con "true" dentro de la función de activación compartida.
  2. Comprobar si el atributo devuelto es verdadero en contexto booleano dentro de activate.
  3. Comparar con el booleano true solo en el detector de clic.
  4. Mover la comprobación sin cambios a un detector de pointerdown.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Un botón personalizado de despliegue abre un panel de ayuda. ¿Qué atributo ARIA indica si ese panel está abierto?
  1. aria-checked
  2. aria-pressed
  3. aria-disabled
  4. aria-expanded
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Un control de despliegue tiene actualmente aria-expanded="true" y un panel visible. Su manejador de clic lee el estado expandido anterior. ¿Cuál debe ser el siguiente estado?
  1. aria-expanded="true"; panel.hidden = true.
  2. aria-expanded="false"; panel.hidden = false.
  3. aria-expanded="true"; panel.hidden = false.
  4. aria-expanded="false"; panel.hidden = true.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Una barra de herramientas de cuatro elementos usa índice de tabulación móvil. El elemento 0 tiene el foco. Después de ArrowLeft, ¿qué índice y cambios de tabindex mantienen la navegación en funcionamiento?
  1. Dar el foco a −1; dejar el elemento 0 en 0 y los demás en −1.
  2. Dar el foco a 3; establecer el elemento 0 en −1 y el 3 en 0.
  3. Dar el foco a 3; dejar tanto el elemento 0 como el 3 en 0.
  4. Dar el foco a 1; establecer el elemento 0 en 0 y el 1 en −1.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Un botón de alternancia nativo llama a la misma función de inversión desde click y desde keydown de Enter. Enter deja su estado pulsado sin cambios, mientras que un clic con puntero lo alterna una vez. ¿Qué cambio corrige la vía de teclado sin perder accesibilidad?
  1. Mantener la acción de clic y eliminar la activación adicional de Enter.
  2. Mantener ambos detectores y añadir una inversión en keydown de la barra espaciadora.
  3. Sustituir click por pointerdown y no mantener ningún detector de teclado.
  4. Mantener ambos detectores y llamar a preventDefault después de cada clic.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Un cuadro de diálogo modal se cierra mientras su control de apertura sigue existiendo. ¿Adónde debe volver normalmente el foco?
  1. Al cuerpo de la página después de cada cierre.
  2. Al primer enlace del documento.
  3. Al control que lo abrió.
  4. Al botón de cierre oculto del cuadro de diálogo.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Un elemento emergente tiene dos campos. El foco pasa del primero al segundo y un detector de focusout que se propaga por burbujeo oculta el elemento. ¿Qué debe comprobar el detector antes de ocultarlo?
  1. Si relatedTarget está fuera del elemento emergente.
  2. Si target es igual al primer campo.
  3. Si currentTarget es igual al elemento emergente.
  4. Si la tecla usada para moverse fue Tab.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una lista tiene dos elementos y un encabezado con tabindex="-1". El botón de eliminación con foco del último elemento lo elimina; después también se elimina el elemento restante. ¿Qué secuencia de foco permite continuar usando el teclado?
  1. Primero el cuerpo de la página, después el botón eliminado anterior.
  2. Primero el botón del elemento anterior, después el encabezado de la lista.
  3. Primero el encabezado, después el hermano siguiente del elemento eliminado.
  4. Primero el botón eliminado, después el primer enlace del documento.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

¿Te atascas con una pregunta tuya? Ayuda con Tareas

Términos Clave

80 Términos Clave, cada uno con su definición

A

Acción predeterminada
Una acción predeterminada es un comportamiento proporcionado por el navegador asociado a un evento, como seguir un enlace, introducir texto o enviar un formulario.
La acción predeterminada del enlace de ayuda navegaría a su destino salvo que se cancele el clic.
Activación
La activación es la solicitud del usuario de realizar la acción de un control, representada por click para el uso con puntero y con teclado nativo.
Un botón nativo para avanzar responde mediante click tanto si se pulsa con la barra espaciadora como con un ratón.
Alternativa con un solo puntero
Una alternativa con un solo puntero realiza una función de arrastre mediante clics o toques sin exigir que el puntero permanezca pulsado mientras se mueve.
Los botones de movimiento permiten que un usuario de pantalla táctil cambie de posición un elemento sin realizar un gesto de arrastre.
Atributo aria-describedby
El atributo aria-describedby asocia un control con texto descriptivo enumerando los ID de los elementos que contienen ese texto.
El campo de correo electrónico hace referencia a email-error para que la tecnología de asistencia pueda conectar el error con él.
Atributo aria-invalid
El atributo aria-invalid comunica que el valor introducido en un control tiene un error; el código debe actualizarlo cuando cambie el error.
El campo de correo electrónico tiene aria-invalid="true" mientras se muestra junto a él su mensaje de corrección.
Atributo de datos
Un atributo de datos es un atributo HTML que comienza con data- y almacena información de la aplicación disponible mediante el dataset de un elemento.
Una barra de herramientas lee data-action="clear" como button.dataset.action para seleccionar una acción conocida.
Atributo novalidate
El atributo de formulario novalidate omite la validación automática de restricciones durante el envío y deja disponibles las restricciones y los métodos de validez.
Un flujo de errores personalizado usa novalidate para que su detector de submit pueda examinar cada campo por sí mismo.
Atributo pattern
El atributo pattern define una restricción de expresión regular para todo el valor de los campos de texto compatibles, que se aplica solo cuando su valor no está vacío.
Un patrón de cinco dígitos rechaza seis dígitos, pero necesita required para rechazar un campo vacío.
Atributo tabindex
El atributo tabindex controla la participación en la tabulación normal y la capacidad de recibir el foco desde el código: 0 sigue el orden normal y −1 queda fuera de esa secuencia.
El encabezado de error usa tabindex="-1" para que el código pueda darle el foco sin añadir una parada normal de tabulación.

B

Botón de alternancia
Un botón de alternancia activa o desactiva el estado de una acción, normalmente con una etiqueta visible estable y aria-pressed para indicar el estado actual.
Un botón de silenciar conserva la palabra «Silenciar» mientras su valor aria-pressed cambia entre verdadero y falso.
Botón de envío
Un botón de envío es el botón usado para un envío, expuesto como event.submitter cuando un botón concreto inició esa vía.
Un formulario distingue entre borrador y publicación leyendo el valor del botón de envío.

C

Captura del puntero
La captura del puntero dirige los eventos posteriores de un puntero activo especificado a un elemento incluso después de que el puntero salga de sus límites.
Una caja arrastrada captura el puntero que inició la interacción para que un movimiento rápido no salga de su área de eventos.
Comprobación de clic fuera del elemento
Una comprobación de clic fuera del elemento verifica si un evento se originó fuera tanto de un elemento emergente abierto como de su control de apertura antes de cerrar el elemento emergente.
Un detector del documento que ignora los clics en el botón de un menú evita que el clic de apertura cierre el menú.
Control de apertura
Un control de apertura es el control que inicia una interacción con un elemento emergente o cuadro de diálogo y suele ser el destino adecuado para el foco cuando esa interacción se cierra.
El cuadro de diálogo guarda el botón de apertura para poder restablecer el foco después del evento close.
Control de despliegue de contenido
Un control de despliegue de contenido es un control con un panel que muestra u oculta contenido, y cuyo botón comunica su estado mediante aria-expanded.
Un botón de detalles cambia su estado expandido junto con la visibilidad del panel de ayuda que controla.
Control nativo
Un control nativo es un elemento HTML con significado semántico y comportamiento de interacción integrados, como un botón, un enlace, una casilla de verificación o una lista desplegable.
Un botón nativo con estilos conserva su activación con Enter y la barra espaciadora sin un detector de teclado personalizado.
Controlador de cancelación (AbortController)
Un controlador de cancelación proporciona una señal que puede agrupar la duración de detectores y elimina los detectores asociados cuando se llama a su método abort().
Al cerrar una herramienta de dibujo se cancela la señal usada para sus detectores de documento y ventana.
Coordenadas del área de visualización
Las coordenadas del área de visualización son posiciones del puntero medidas desde la esquina superior izquierda del área visible mediante clientX y clientY.
Un cálculo de movimiento resta dos lecturas de coordenadas de cliente sin añadir el desplazamiento de la página.
Coordenadas del documento
Las coordenadas del documento son posiciones del puntero medidas desde la esquina superior izquierda del documento mediante pageX y pageY, incluido el desplazamiento.
Un clic 100 px por debajo de la parte superior del área de visualización después de 300 px de desplazamiento tiene pageY 400.
Cuadro de diálogo modal
Un cuadro de diálogo modal es una interacción de diálogo que hace que el resto de la página sea inerte mientras está abierto y limita la interacción del usuario a su área activa.
Llamar a showModal() abre el cuadro de diálogo nativo y mueve el foco a su contenido.

D

Datos de formulario (FormData)
FormData es una colección de pares de nombre y valor de formulario que cumplen los requisitos, puede conservar nombres repetidos y omite los controles que no se envían.
Cuando varias casillas comparten el nombre «topics», data.getAll("topics") obtiene juntas todas las seleccionadas.
Delegación de eventos
La delegación de eventos maneja eventos de varios descendientes mediante un detector compartido en un nodo antecesor que identifica el control pertinente.
El único detector de clic de una lista sigue funcionando para los nuevos botones de eliminación insertados después.
Despacho síncrono
El despacho síncrono significa que los detectores se ejecutan dentro de la llamada a dispatchEvent(), antes de que ese método devuelva el control al código que lo llamó.
Un mensaje registrado después de dispatchEvent() aparece después de los mensajes de sus detectores.
Detector de eventos
Un detector de eventos es una función registrada en un destino de evento para que pueda ejecutarse cuando se despache allí un tipo de evento especificado.
El detector del contador aumenta el total almacenado cada vez que su botón recibe un clic.
Detector de eventos pasivo
Un detector de eventos pasivo es un detector registrado con el compromiso de no cancelar la acción predeterminada, lo que permite que el navegador continúe sin esperar una cancelación.
Llamar a preventDefault() en un detector táctil pasivo no detiene el desplazamiento del navegador.

E

Eliminación de espacios en blanco iniciales y finales
La eliminación de espacios en blanco iniciales y finales quita esos espacios de un texto antes de que una regla o acción use su contenido significativo.
Comprobar name.value.trim() === "" detecta un nombre compuesto solo por espacios.
Estado ARIA
Un estado ARIA es un valor de atributo que comunica la condición actual de un control, como marcado, desplegado, pulsado o deshabilitado.
El valor aria-checked del interruptor se actualiza cuando cambia su aspecto visible de activado o desactivado.
Estado de arrastre
El estado de arrastre es la información almacenada que identifica un arrastre activo y las coordenadas iniciales del puntero y la posición inicial del elemento.
Durante un arrastre, cada movimiento del puntero compara las coordenadas actuales con el inicio guardado para situar una caja arrastrada.
Estado de validez (ValidityState)
Un estado de validez es el objeto expuesto mediante la propiedad validity de un control, con indicadores booleanos que identifican las restricciones incumplidas.
Una cantidad puede indicar stepMismatch aunque esté entre su mínimo y su máximo.
Evento
Un evento es una señal que informa de algo que ha ocurrido, como un clic, la edición de un campo, una pulsación de tecla o la finalización del análisis del HTML.
Un evento click permite que la página responda cuando una persona activa el botón para avanzar en la galería.
Evento change
Un evento change informa de un cambio confirmado por el usuario, como salir de un campo de texto editado o elegir un nuevo estado de una casilla de verificación o lista desplegable.
Un campo de texto modificado desencadena change cuando el usuario lo deja para pasar al siguiente control.
Evento de puntero
Un evento de puntero describe la interacción con dispositivos señaladores mediante una familia de eventos compartida por el ratón, la entrada táctil y el lápiz.
El mismo detector de pointermove puede leer el movimiento de un ratón o de un contacto táctil.
Evento input
Un evento input informa de una edición del usuario después de que cambie el valor de un control, incluidas las ediciones de texto realizadas sin pulsar una tecla física.
Pegar una frase en un campo de mensaje actualiza un contador mediante el detector de input del campo.
Evento keydown
Un evento keydown informa de una pulsación de tecla antes de su efecto normal de edición y puede repetirse mientras la tecla física permanece pulsada.
El atajo comprueba si se pulsó Escape en keydown antes de cerrar el panel abierto.
Evento personalizado (CustomEvent)
Un evento personalizado es un evento creado por un script que puede transportar datos de la aplicación en detail y configurarse para propagarse por burbujeo hasta los nodos antecesores.
Un elemento del carrito despacha "cart:add" con su identificador de elemento en detail del evento.
Evento pointercancel
Un evento pointercancel informa de que el navegador ha cancelado la interacción del puntero, por lo que un arrastre debe borrar su estado activo.
Un gesto que el navegador toma para desplazar la página puede terminar el arrastre personalizado mediante pointercancel.
Evento submit
Un evento submit es un evento cancelable de formulario que se despacha durante la vía normal de envío después de que la validación automática lo permita.
Un detector de submit de un formulario maneja un envío válido añadiendo un elemento localmente y cancelando la navegación.

F

Fase de captura
La fase de captura es el recorrido descendente del evento por los nodos antecesores hacia su destino, observado por los detectores registrados para captura.
El detector de captura de un contenedor se ejecuta antes de que el detector de un botón descendiente maneje el clic.
Fase de destino
La fase de destino es la parte del despacho que ocurre en el origen del evento, donde los detectores de captura se ejecutan antes que los detectores que no son de captura.
Ambos detectores del botón pulsado indican eventPhase 2 mientras se ejecutan.
Foco
El foco es el estado de la página que identifica el elemento que recibe actualmente la entrada del teclado, disponible mediante document.activeElement.
Llamar a search.focus() dirige el siguiente carácter escrito al campo de búsqueda.
Fondo inerte
Un fondo inerte es contenido de la página que temporalmente no está disponible para clics ni foco normales mientras una interacción modal está activa.
La página detrás de un cuadro de diálogo modal nativo abierto no puede recibir el foco normal hasta que se cierre.

G

Gestión del foco
La gestión del foco elige de forma deliberada un destino útil para el teclado cuando el contenido se abre, se cierra, desaparece o presenta errores al usuario.
Al eliminar una fila con foco, este se mueve al control de una fila vecina en lugar de perder la posición.
Grupo de controles compuesto
Un grupo de controles compuesto es un conjunto de controles relacionados, como pestañas o una barra de herramientas, por el que se navega internamente con las teclas de dirección mientras Tab entra y sale de él.
El grupo de pestañas conserva una parada de tabulación para que los usuarios puedan pasar el grupo sin visitar cada pestaña.

I

Identidad del detector de eventos
La identidad del detector combina el tipo de evento, el objeto de la función de devolución de llamada y la configuración de captura, y determina el registro duplicado y la eliminación.
Una función flecha nueva no puede eliminar una función de devolución de llamada anterior solo porque sus cuerpos coincidan.
Indicador de foco
Un indicador de foco es una marca visible que identifica el elemento que tiene el foco para que los usuarios de teclado puedan localizar su posición en la página.
Un contorno :focus-visible muestra qué botón de la barra de herramientas recibirá la siguiente acción de teclado.
Indicador de tecla modificadora
Un indicador de tecla modificadora es una propiedad booleana de un evento de teclado que describe si Mayús, Control, Alt o Meta se mantiene pulsada durante el evento.
El atajo para guardar acepta ctrlKey o metaKey junto con la letra S.

Í

Índice de tabulación móvil (roving tabindex)
El índice de tabulación móvil es una técnica de navegación de grupo que da a un elemento una parada normal de tabulación y mueve esa parada y el foco cuando las teclas de dirección cambian de elemento.
En una barra de herramientas, Tab entra en el elemento actual y las teclas de dirección se mueven a sus controles vecinos.

M

Máscara de bits de botones
Una máscara de bits de botones representa los botones del puntero que se mantienen pulsados combinando indicadores numéricos en la propiedad buttons del evento.
Mantener pulsados los botones principal y secundario da buttons 3, al combinar los indicadores 1 y 2.
Método checkValidity
El método checkValidity() devuelve si se cumplen las restricciones y dispara eventos invalid para los fallos sin mostrar mensajes del navegador.
Un flujo de envío personalizado comprueba el formulario antes de decidir qué errores mostrar junto a los campos.
Método closest
El método closest() comprueba un elemento y después sus nodos antecesores para encontrar la coincidencia más cercana con un selector; devuelve null si no existe ninguna.
Partiendo de un icono, closest("button") encuentra el botón de acción que lo rodea.
Método contains
El método contains() comprueba si un nodo es el propio elemento o uno de sus descendientes y devuelve un resultado booleano.
Un detector delegado en una lista comprueba list.contains(button) antes de permitir la acción del botón.
Método preventDefault
El método preventDefault() cancela la acción predeterminada de un evento cancelable en un detector no pasivo sin detener su propagación.
Un detector de submit llama a preventDefault() antes de añadir un elemento local sin navegar.
Método reportValidity
El método reportValidity() comprueba las restricciones y también solicita al navegador que muestre mensajes de validación para los fallos no manejados.
Un botón de comprobación explícita llama a reportValidity() para ayudar al usuario a encontrar un campo que no pasa la validación.
Método requestSubmit
El método requestSubmit() solicita el envío de un formulario mediante la vía normal de validación y del evento submit, sin disparar un clic en el botón.
Una barra de herramientas invoca form.requestSubmit() para que sigan ejecutándose la validación y el detector de envío del formulario.
Método stopImmediatePropagation
El método stopImmediatePropagation() detiene el recorrido del evento y también omite los detectores restantes registrados en el destino actual del evento.
Cuando la primera función de devolución de llamada invoca este método, se omite una función posterior del mismo elemento.
Método stopPropagation
El método stopPropagation() impide que un evento continúe hacia otros elementos y deja activos los demás detectores de su elemento actual.
Una acción anidada puede impedir que su clic llegue al detector de burbujeo de la tarjeta que la contiene.

N

Nombre accesible
Un nombre accesible es el texto que usa la tecnología de asistencia para identificar un control, proporcionado por contenido adecuado, una etiqueta o un nombre ARIA.
Un botón de cierre que solo contiene un icono usa aria-label="Close" para indicar qué hace su símbolo.

O

Objeto de evento
Un objeto de evento es el valor que se pasa a un detector y que contiene propiedades compartidas de eventos y detalles específicos de esa familia de eventos.
Un detector de teclado lee key del evento recibido para decidir si debe cerrar un panel.
Opción once
La opción de detector once solicita la eliminación automática de ese registro después de su primera invocación para el tipo de evento seleccionado.
Un aviso de bienvenida puede usar once: true para el clic que lo descarta durante este periodo de actividad.

P

Propagación
La propagación es el recorrido de un evento por el árbol del documento, incluidos el recorrido de captura, la fase de destino y cualquier recorrido de burbujeo.
Un clic en un botón puede ser observado por detectores tanto del botón como de la tarjeta que lo contiene.
Propagación por burbujeo
La propagación por burbujeo es el recorrido ascendente de un evento por los nodos antecesores después de la fase de destino, disponible solo para eventos cuyo valor bubbles es verdadero.
El evento input de un campo hijo se propaga por burbujeo hasta un detector registrado en el formulario que lo contiene.
Propiedad cancelable
La propiedad cancelable es un booleano que indica si un evento permite cancelar su acción predeterminada mediante preventDefault().
Un evento input informa de una edición ya realizada, por lo que su propiedad cancelable es falsa.
Propiedad checked
La propiedad checked es el estado booleano que indica si una casilla de verificación o un botón de opción está seleccionado, independientemente de su valor de envío.
Una vista previa lee checkbox.checked para decidir si muestra una sección opcional.
Propiedad code
La propiedad code identifica una posición física de tecla independientemente del carácter producido por la distribución actual del teclado.
Un juego usa code cuando sus controles de movimiento deben permanecer en posiciones físicas fijas.
Propiedad CSS touch-action
La propiedad CSS touch-action declara qué gestos táctiles del navegador se permiten cuando un gesto comienza en un elemento.
Usar touch-action: none en la superficie de arrastre reserva el movimiento de esa área para el arrastre.
Propiedad currentTarget
La propiedad currentTarget identifica el objeto cuyo detector se está ejecutando y se restablece a null cuando termina el despacho del evento.
Dentro de un detector registrado en un botón, currentTarget permite que el código lea los datos de ese botón.
Propiedad de manejo de eventos
Una propiedad de manejo de eventos es una propiedad de un elemento, como onclick, que contiene una función de devolución de llamada de evento y la sustituye cuando se le asigna otra.
Asignar otra función a button.onclick sustituye el manejador anterior de esa propiedad.
Propiedad defaultPrevented
La propiedad defaultPrevented es un booleano que registra si la acción predeterminada de un evento se ha cancelado correctamente.
Un detector de un nodo antecesor puede examinar defaultPrevented antes de decidir cómo manejar un clic.
Propiedad key
La propiedad key indica el valor producido por una tecla, teniendo en cuenta la distribución del teclado y modificadores como Mayús.
Mayús puede cambiar el valor key de una letra de "a" a "A" mientras su posición permanece igual.
Propiedad pointerId
La propiedad pointerId identifica un puntero individual durante una interacción para que los contactos simultáneos puedan manejarse por separado.
Dos dedos tienen ID distintos, lo que permite que el código de arrastre siga solo el que lo inició.
Propiedad relatedTarget
La propiedad relatedTarget identifica el otro elemento implicado en una transición de foco, o es null cuando no hay ningún elemento disponible.
El detector de focusout de un menú comprueba si el destino permanece dentro del menú.
Propiedad target
La propiedad target identifica el origen del evento, que puede ser un elemento anidado dentro del control cuya acción se está manejando.
Al hacer clic en la imagen dentro de un botón de guardar, la imagen se convierte en el target del clic.
Propiedad value
La propiedad value expone como texto el valor actual de un control de formulario, incluidos los campos numéricos cuyo contenido aún necesita conversión.
Un campo de cantidad que contiene "8" necesita conversión antes de que el código añada un incremento numérico.
Propiedad valueAsNumber
La propiedad valueAsNumber lee directamente el valor numérico de un campo compatible y devuelve NaN cuando no hay un valor numérico utilizable.
Un campo numérico vacío produce NaN, que el código detecta con Number.isNaN().

R

Referencia a función
Una referencia a función es un valor que identifica un objeto de función y permite que el código pase esa función para usarla más tarde sin llamarla.
Pasar openPanel registra la función; pasar openPanel() la llama durante la configuración.
Restablecimiento del formulario
El restablecimiento de un formulario devuelve los controles a sus valores y estados de selección iniciales, que pueden incluir valores predeterminados no vacíos del marcado.
Después de que un formulario añade un elemento, form.reset() restablece los valores iniciales de los campos del formulario.
Resumen de errores
Un resumen de errores es una región de la página que recopila los problemas de un formulario y enlaces a sus campos, y proporciona un destino útil para el foco después de un fallo.
Después de un envío no válido, el resumen que puede recibir el foco permite que el usuario elija qué campo corregir.
Rol ARIA
Un rol ARIA comunica el propósito de un elemento a la tecnología de asistencia sin añadir el comportamiento de teclado ni las reglas de foco correspondientes a ese propósito.
Un div con role="button" sigue necesitando código para la activación y una parada de tabulación adecuada.

V

Validación de restricciones
La validación de restricciones comprueba si los valores de un formulario cumplen reglas HTML como entradas obligatorias, sintaxis de tipo, rangos, incrementos y patrones.
Una cantidad fuera de rango bloquea el envío normal antes de que se ejecute el detector de submit del formulario.
Validez personalizada
La validez personalizada es una restricción definida por la aplicación mediante setCustomValidity(), en la que un mensaje no vacío marca el control como no válido.
Una confirmación que no coincide recibe un mensaje personalizado que se borra cuando ambas contraseñas coinciden.

Sobre esta unidad

Por StudeliInformar de un error

Crea tus propios Materiales de Estudio

Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.

Comenzar