🎯 Objetivo del tema
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.
El navegador convierte tu HTML en un árbol de objetos que JS puede modificar.
Los 4 métodos para encontrar elementos: getElementById, querySelector, querySelectorAll, getElementsByClassName.
textContent, innerHTML, createElement, appendChild, remove. Cómo cambiar la página.
Click, input, submit, keydown: cómo responder a lo que hace el usuario.
Un solo listener para 1000 elementos. La técnica que usan React y todos los frameworks.
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')
└── buttonHTML y su representación en el DOM
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.
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étodo | Qué devuelve | Ejemplo | Estado |
|---|---|---|---|
| querySelector | EL PRIMER elemento que coincida con el selector CSS, o null. | document.querySelector('#mi-id') | ⭐ Moderno. El que usarás el 90%. |
| querySelectorAll | TODOS los elementos que coincidan (NodeList, iterable con forEach). | document.querySelectorAll('.mi-clase') | ⭐ Moderno. El segundo más usado. |
| getElementById | El elemento con ese id, o null. | document.getElementById('mi-id') | Antiguo pero rápido. |
| getElementsByClassName | Colecció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
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ón | Propiedad/Método | Ejemplo |
|---|---|---|
| Cambiar texto plano | element.textContent | titulo.textContent = 'Nuevo título'; |
| Cambiar HTML interno | element.innerHTML | contenedor.innerHTML = '<p>Hola</p>'; |
| Cambiar un atributo | element.setAttribute / .getAttribute | img.src = 'nueva.jpg'; |
| Cambiar un estilo | element.style.propiedad | caja.style.backgroundColor = 'red'; |
| Agregar/quitar clase | element.classList.add/remove/toggle | boton.classList.add('activo'); |
| Crear elemento nuevo | document.createElement | const p = document.createElement('p'); |
| Agregar al DOM | padre.appendChild(hijo) | lista.appendChild(nuevoItem); |
| Eliminar del DOM | element.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
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
| Evento | Cuándo se dispara | Uso típico |
|---|---|---|
| click | Click en un elemento. | Botones, tarjetas clicables. |
| input | El usuario escribe en un input. | Búsqueda en vivo, validación. |
| change | El valor de un input cambió y se 'confirmó'. | Selects, checkboxes. |
| submit | Se envió un formulario. | Validar antes de enviar, evitar recarga. |
| keydown / keyup | Tecla presionada / soltada. | Atajos de teclado, juegos. |
| mouseenter / mouseleave | Mouse entra / sale del elemento. | Tooltips, menús hover. |
| scroll | El usuario hizo scroll. | Lazy loading, animaciones al scroll. |
| DOMContentLoaded | El HTML se cargó (antes de imágenes/CSS). | Inicializar scripts. |
| load | TODO se cargó (HTML, CSS, imágenes). | Ocultar loader. |
| resize | La 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
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 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
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
- Abre tu TODO list del Tema 1.8. Identifica: ¿dónde están los elementos del DOM? ¿qué eventos necesitas?
- Agrega un listener al formulario: al hacer submit, crea un <li> nuevo y agrégalo a la <ul>.
- Agrega event delegation a la <ul>: si el target tiene clase 'eliminar', elimina el <li> padre.
- Agrega event delegation a la <ul>: si el target tiene clase 'toggle', marca/desmarca como completada.
- Usa localStorage para que la lista persista al recargar.
- Prueba: agrega 5 tareas, marca 2 como hechas, elimina 1, recarga la página. ¿Se mantienen?
- 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é.'
- 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
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.
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.
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.
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.
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
- Crea galeria.html con una <ul class='galeria'> y 6 <li> con <img> adentro.
- Crea galeria.css con grid: repeat(auto-fit, minmax(200px, 1fr)) y gap.
- Crea galeria.js. Usa DOMContentLoaded para asegurar que el HTML esté listo.
- Agrega un listener a cada <img> (o usa event delegation en la <ul>): al hacer clic, abre el lightbox.
- El lightbox es un <div> que CREAS con createElement: fondo semi-transparente negro, la imagen centrada, y un botón 'X' para cerrar.
- Cuando se hace clic en el fondo oscuro (no en la imagen), cerrar el lightbox.
- Cuando se presiona Escape, cerrar el lightbox.
- Usa classList.add('abierto') para mostrar el lightbox, classList.remove('abierto') para ocultarlo (con CSS que use .abierto { display: flex; }).
- Verifica accesibilidad: el lightbox debe tener role='dialog' y aria-modal='true'.
- Prueba: clic en una imagen, cerrar con X, cerrar con clic en fondo, cerrar con Escape.
- Ejecuta Lighthouse Accessibility. Debe ser 90+.
- 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.