🎯 1.9 · DOM y eventos: hacer la página viva

⏱ 6h 00min ⚡ 100 XP 🏅 Aprendiz 📖 Fundamentos del Web FullStack
"El DOM no es HTML: es una REPRESENTACIÓN VIVA del HTML que JavaScript puede modificar."

🎯 Objetivo del tema

Al terminar este tema serás capaz de: Al terminar este tema serás capaz de seleccionar cualquier elemento del HTML desde JavaScript, modificar su contenido, atributos y estilos, manejar eventos del usuario (clicks, teclas, envíos de formularios), y usar event delegation para manejar listas grandes de forma eficiente. Tu página dejará de ser estática para volverse interactiva.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

🗺️ Mapa del tema

Hasta ahora, tu página es un documento que se ve igual cada vez que se carga. El DOM (Document Object Model) es lo que te permite cambiar ese documento DESDE el navegador, sin recargar la página. Es la puerta de entrada a la interactividad: menús desplegables, formularios dinámicos, animaciones, actualizaciones en vivo.

1. Qué es el DOM: el árbol de la página

El navegador convierte tu HTML en un árbol de objetos que JS puede modificar.

2. Seleccionar elementos: querySelector y amigos

Los 4 métodos para encontrar elementos: getElementById, querySelector, querySelectorAll, getElementsByClassName.

3. Modificar el DOM: crear, modificar, eliminar

textContent, innerHTML, createElement, appendChild, remove. Cómo cambiar la página.

4. Eventos: addEventListener y el flujo

Click, input, submit, keydown: cómo responder a lo que hace el usuario.

5. Event delegation: el patrón profesional

Un solo listener para 1000 elementos. La técnica que usan React y todos los frameworks.

6. Formularios: leer, validar y enviar

FormData, validaciones nativas, preventDefault, y cómo NO romper la accesibilidad.

1.9.1 Qué es el DOM: el árbol de la página

Cuando el navegador carga tu HTML, no lo deja como texto: lo convierte en un ÁRBOL de objetos JavaScript llamado DOM (Document Object Model). Cada etiqueta se vuelve un nodo, con propiedades y métodos que puedes manipular. Es la diferencia entre 'un documento' y 'un documento VIVO'.

El árbol del DOM en acción

<!-- HTML original -->
<html>
  <body>
    <h1>Hola</h1>
    <p id="saludo">Mundo</p>
    <button>Clic aquí</button>
  </body>
</html>

// El navegador lo convierte en un árbol de objetos:
document
  └── html
      └── body
          ├── h1 (texto: 'Hola')
          ├── p#saludo (texto: 'Mundo')
          └── button

HTML y su representación en el DOM

⭐ Diferencia clave: El DOM no es tu archivo HTML: es una copia VIVA que el navegador mantiene en memoria. Cuando modificas el DOM con JavaScript, la página cambia VISUALMENTE, pero tu archivo .html en disco NO se modifica. Para verlo, abre DevTools > Elements.

El objeto document

document es el punto de entrada al DOM. Desde él accedes a TODO: document.body, document.head, document.title, document.forms, document.images. Todo lo que ves en la página es descendiente de document.

🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

1.9.2 Seleccionar elementos: los 4 métodos esenciales

Antes de modificar un elemento, tienes que ENCONTRARLO. JavaScript ofrece varios métodos para hacerlo, pero en 2025 solo necesitas dominar 2: querySelector y querySelectorAll. El resto son reliquias que verás en código viejo.

MétodoQué devuelveEjemploEstado
querySelectorEL PRIMER elemento que coincida con el selector CSS, o null.document.querySelector('#mi-id')⭐ Moderno. El que usarás el 90%.
querySelectorAllTODOS los elementos que coincidan (NodeList, iterable con forEach).document.querySelectorAll('.mi-clase')⭐ Moderno. El segundo más usado.
getElementByIdEl elemento con ese id, o null.document.getElementById('mi-id')Antiguo pero rápido.
getElementsByClassNameColección viva de elementos con esa clase (HTMLCollection).document.getElementsByClassName('mi-clase')Antiguo. Reemplazado por querySelectorAll.

Los 3 selectores que usarás todo el tiempo

// Por id (único)
const boton = document.querySelector('#mi-boton');

// Por clase (puede haber varios)
const tarjetas = document.querySelectorAll('.tarjeta');
tarjetas.forEach(t => console.log(t.textContent));

// Por etiqueta (raro, pero útil a veces)
const todosLosParrafos = document.querySelectorAll('p');

// Selectores complejos (los mismos que CSS)
const enlaceEnNavbar = document.querySelector('nav a[href="/blog"]');

Los 3 selectores más comunes

⭐ Mismo lenguaje: querySelector acepta CUALQUIER selector CSS válido: '.clase', '#id', 'div > p', 'a[href*="blog"]', etc. Si sabes CSS, ya sabes querySelector. Es la misma sintaxis.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

1.9.3 Modificar el DOM: crear, modificar, eliminar

Una vez que tienes un elemento, puedes modificar TODO de él: su texto, su HTML interno, sus atributos, sus clases, sus estilos. También puedes crear elementos nuevos desde cero o eliminar existentes. El DOM es tu caja de herramientas.

AcciónPropiedad/MétodoEjemplo
Cambiar texto planoelement.textContenttitulo.textContent = 'Nuevo título';
Cambiar HTML internoelement.innerHTMLcontenedor.innerHTML = '<p>Hola</p>';
Cambiar un atributoelement.setAttribute / .getAttributeimg.src = 'nueva.jpg';
Cambiar un estiloelement.style.propiedadcaja.style.backgroundColor = 'red';
Agregar/quitar claseelement.classList.add/remove/toggleboton.classList.add('activo');
Crear elemento nuevodocument.createElementconst p = document.createElement('p');
Agregar al DOMpadre.appendChild(hijo)lista.appendChild(nuevoItem);
Eliminar del DOMelement.remove()tarjeta.remove();

Ejemplo: agregar dinámicamente un item a una lista

// 1. Encontrar la lista
const lista = document.querySelector('#mi-lista');

// 2. Crear un <li> nuevo
const nuevoItem = document.createElement('li');
nuevoItem.textContent = 'Nueva tarea';
nuevoItem.classList.add('pendiente');

// 3. Agregarlo al DOM
lista.appendChild(nuevoItem);

// Listo: el <li> aparece en la página sin recargarla.

Crear y agregar un elemento

⭐ Seguridad XSS: Regla de seguridad: usa textContent en vez de innerHTML cuando插es datos del usuario. innerHTML interpreta HTML, así que un usuario malicioso podría inyectar <script>. textContent es seguro: inserta texto plano.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

1.9.4 Eventos: addEventListener y el flujo

Los eventos son señales que el navegador envía cuando pasa algo: el usuario hizo clic, escribió en un input, movió el mouse, presionó una tecla, envió un formulario, la página terminó de cargar. addEventListener te permite 'escuchar' esos eventos y responder.

Los 10 eventos más usados

EventoCuándo se disparaUso típico
clickClick en un elemento.Botones, tarjetas clicables.
inputEl usuario escribe en un input.Búsqueda en vivo, validación.
changeEl valor de un input cambió y se 'confirmó'.Selects, checkboxes.
submitSe envió un formulario.Validar antes de enviar, evitar recarga.
keydown / keyupTecla presionada / soltada.Atajos de teclado, juegos.
mouseenter / mouseleaveMouse entra / sale del elemento.Tooltips, menús hover.
scrollEl usuario hizo scroll.Lazy loading, animaciones al scroll.
DOMContentLoadedEl HTML se cargó (antes de imágenes/CSS).Inicializar scripts.
loadTODO se cargó (HTML, CSS, imágenes).Ocultar loader.
resizeLa ventana cambió de tamaño.Recalcular layouts.

La sintaxis moderna: addEventListener

// Forma moderna (SIEMPRE usa esta)
const boton = document.querySelector('#mi-boton');
boton.addEventListener('click', (evento) => {
  console.log('Hiciste clic');
  console.log('Coordenadas:', evento.clientX, evento.clientY);
});

// Forma antigua (NUNCA uses esta en código nuevo)
boton.onclick = () => console.log('Hiciste clic');

addEventListener vs onclick

⭐ addEventListener > onclick: addEventListener permite MÚLTIPLES listeners al mismo evento y mismo elemento. onclick solo permite UNO (sobreescribe al anterior). Es otra razón para preferir la versión moderna.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

1.9.5 Event delegation: el patrón profesional

Imagina una lista de 1000 tareas, cada una con su botón de eliminar. ¿Pondrías 1000 listeners? NO. El event delegation es el patrón que usa UN SOLO listener en el padre, y detecta en qué hijo se hizo clic. Es lo que usan React, Vue, y todos los frameworks.

El problema y la solución

<!-- HTML: 1000 items en una lista -->
<ul id="lista-tareas">
  <li><button class="eliminar">X</button> Tarea 1</li>
  <li><button class="eliminar">X</button> Tarea 2</li>
  ...
  <li><button class="eliminar">X</button> Tarea 1000</li>
</ul>

// ❌ MAL: 1000 listeners, uno por botón
document.querySelectorAll('.eliminar').forEach(btn => {
  btn.addEventListener('click', eliminar);
});
// Si después agregas un item, NO tendrá listener.

// ✅ BIEN: UN listener en el padre, y veo qué hijo se clickeó
document.querySelector('#lista-tareas').addEventListener('click', (e) => {
  if (e.target.classList.contains('eliminar')) {
    e.target.closest('li').remove();  // elimina el <li> padre
  }
});
// Funciona para items actuales Y futuros.

Event delegation: 1 listener para N elementos

⭐ El truco: Event delegation funciona gracias a la 'burbuja' de eventos: cuando haces clic en un hijo, el evento SUBE hasta el padre. Si el padre tiene un listener, lo recibe. Con e.target sabes en qué hijo empezó el evento. Es elegante y eficiente.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

1.9.6 Formularios: leer, validar y enviar

Los formularios son el punto de contacto entre el usuario y el servidor. JavaScript te permite leer sus valores SIN recargar la página, validar antes de enviar, y mostrar mensajes de error útiles. Es una de las tareas más comunes del frontend.

El patrón completo: leer, validar, enviar

const formulario = document.querySelector('#mi-formulario');
formulario.addEventListener('submit', (evento) => {
  evento.preventDefault();  // evita que se recargue la página

  // 1. LEER los datos con FormData
  const datos = new FormData(formulario);
  const nombre = datos.get('nombre');
  const email = datos.get('email');
  console.log({ nombre, email });

  // 2. VALIDAR (tú decides las reglas)
  if (nombre.length < 2) {
    alert('El nombre debe tener al menos 2 caracteres');
    return;
  }
  if (!email.includes('@')) {
    alert('Email inválido');
    return;
  }

  // 3. ENVIAR al servidor (en el Tema 2A usaremos PHP)
  fetch('/api/usuarios', {
    method: 'POST',
    body: datos
  }).then(respuesta => console.log('Enviado'));
});

Patrón completo de formulario

Validación nativa del navegador (gratis y accesible)

<!-- El navegador valida SOLO con estos atributos -->
<form>
  <input type="text" name="nombre" required minlength="2" maxlength="50">
  <input type="email" name="email" required>
  <input type="number" name="edad" min="18" max="120">
  <input type="url" name="sitio" placeholder="https://...">
  <input type="text" pattern="[A-Za-z]{3,}" title="Solo letras, mínimo 3">
  <button type="submit">Enviar</button>
</form>

// Si el usuario no cumple, el navegador muestra el mensaje nativo
// y NO envía el formulario. Sin JavaScript.

Validación HTML5 nativa

⭐ Lo nativo primero: Combina validación HTML5 (gratis y accesible) con JavaScript (para mensajes personalizados y reglas complejas). Lo que el navegador puede validar por ti, déjalo validar al navegador. Tu JS solo agrega lo que HTML no puede.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

📚 Contenido ampliado

Material adicional, referencias externas verificadas y ejemplos extendidos.

🤖 AI Mission

El DOM se aprende experimentando. La IA explica, tú tocas el código.

Misión: Toma la TODO list del Tema 1.8 y hazla interactiva: que el usuario pueda agregar, marcar como completada y eliminar tareas, TODO sin recargar la página. Pídele a la IA que te sugiera 2 mejoras de UX (event delegation, animaciones, localStorage). Implementa solo lo que entiendas. Anota en tu cuaderno qué bugs tuviste y cómo los resolviste (la mayoría son errores de selectores).

Pasos sugeridos

  1. Abre tu TODO list del Tema 1.8. Identifica: ¿dónde están los elementos del DOM? ¿qué eventos necesitas?
  2. Agrega un listener al formulario: al hacer submit, crea un <li> nuevo y agrégalo a la <ul>.
  3. Agrega event delegation a la <ul>: si el target tiene clase 'eliminar', elimina el <li> padre.
  4. Agrega event delegation a la <ul>: si el target tiene clase 'toggle', marca/desmarca como completada.
  5. Usa localStorage para que la lista persista al recargar.
  6. Prueba: agrega 5 tareas, marca 2 como hechas, elimina 1, recarga la página. ¿Se mantienen?
  7. Pídele a la IA: 'Tengo esta TODO list. Sugiere 2 mejoras de UX usando event delegation, animaciones CSS, o localStorage. NO me des código, dime la técnica y por qué.'
  8. Implementa solo las 2 que ENTIENDAS. Anota en tu cuaderno, bajo 'Bugs y soluciones': qué errores tuviste, qué selector falló, cómo lo resolviste.

📓 Entregable: Capturas de la TODO list interactiva funcionando, código de app.js con los 3 listeners (submit, eliminar, toggle), y media página de cuaderno con los bugs y soluciones que encontraste.

🚫 Errores típicos de razonamiento

Error 1: Usar innerHTML para insertar datos del usuario.
Por qué: innerHTML interpreta HTML, así que si un usuario escribe '<script>alert("hack")</script>' como nombre, el navegador lo EJECUTARÍA. Es el ataque XSS (Cross-Site Scripting), uno de los más comunes de la web. Solución: usa textContent (que inserta texto plano) o sanitiza los datos antes de insertarlos.
Error 2: Poner el listener en cada elemento de una lista larga (1000 items = 1000 listeners).
Por qué: Es ineficiente y no escala: cada listener consume memoria. Además, si después agregas un item dinámicamente, NO tendrá listener. La solución profesional: event delegation. Un solo listener en el padre, y dentro usas e.target para saber qué hijo se clickeó. Es el patrón que usan React, Vue, y todos los frameworks.
Error 3: Olvidar preventDefault() en un formulario y que se recargue la página.
Por qué: El comportamiento por defecto de un <form> es recargar la página al hacer submit. Si quieres manejar el envío con JavaScript SIN recargar, necesitas event.preventDefault(). Olvidarlo significa que tu JavaScript funciona, pero la página se recarga y el usuario pierde todo. Es uno de los errores más comunes del principiante.
Error 4: Usar onclick= en HTML en vez de addEventListener en JS.
Por qué: onclick='miFuncion()' en el HTML mezcla JavaScript con HTML, es difícil de mantener, y solo permite UN handler por elemento (sobreescribe los anteriores). addEventListener permite múltiples, es la forma estándar, y mantiene el JS separado del HTML. Nunca uses onclick en HTML en código nuevo.
Error 5: Seleccionar elementos antes de que el DOM esté listo (script en el <head> sin defer).
Por qué: Si pones <script src='app.js'></script> en el <head> sin defer, se ejecuta ANTES de que el HTML esté listo, así que document.querySelector('#mi-boton') devuelve null (el botón aún no existe). Soluciones: (1) usa DOMContentLoaded, (2) pon el <script> al final del <body>, (3) usa type='module' (que es defer por defecto), (4) usa el atributo defer en el script.

🧪 Laboratorio práctico

El DOM se aprende tocando. Cada bug que resuelves es una lección.

Laboratorio: Galería de imágenes interactiva con lightbox

Objetivo: Crear una galería de 6+ imágenes donde al hacer clic en una, se abra un lightbox (modal) con la imagen en grande, fondo oscuro, y botón para cerrar. Todo con JavaScript vanilla, sin frameworks. Practicarás: querySelector, addEventListener, event delegation, createElement, remove, y classList.

Pasos

  1. Crea galeria.html con una <ul class='galeria'> y 6 <li> con <img> adentro.
  2. Crea galeria.css con grid: repeat(auto-fit, minmax(200px, 1fr)) y gap.
  3. Crea galeria.js. Usa DOMContentLoaded para asegurar que el HTML esté listo.
  4. Agrega un listener a cada <img> (o usa event delegation en la <ul>): al hacer clic, abre el lightbox.
  5. El lightbox es un <div> que CREAS con createElement: fondo semi-transparente negro, la imagen centrada, y un botón 'X' para cerrar.
  6. Cuando se hace clic en el fondo oscuro (no en la imagen), cerrar el lightbox.
  7. Cuando se presiona Escape, cerrar el lightbox.
  8. Usa classList.add('abierto') para mostrar el lightbox, classList.remove('abierto') para ocultarlo (con CSS que use .abierto { display: flex; }).
  9. Verifica accesibilidad: el lightbox debe tener role='dialog' y aria-modal='true'.
  10. Prueba: clic en una imagen, cerrar con X, cerrar con clic en fondo, cerrar con Escape.
  11. Ejecuta Lighthouse Accessibility. Debe ser 90+.
  12. Haz commit: 'feat: galería interactiva con lightbox'.

📓 Entregable: Capturas de: (1) galería cerrada, (2) lightbox abierto, (3) consola de DevTools sin errores, (4) Lighthouse 90+, (5) commit en Git.

📓 Tu cuaderno: Anota pseudocódigo, diagramas, errores que encontraste y respuestas a "explica sin código". La escritura manual refuerza tu razonamiento. Tu profesor puede pedirte que subas fotos de páginas específicas.