Tres horas. Media hora para entender qué es el DOM y cómo se localiza y se modifica un elemento, y dos horas y media preparando tu página para que el código pueda trabajar con ella sin romperse al primer cambio de diseño.
Hoy · Hoja de ruta
- 1. Aprende: Qué es el DOM y por qué no es tu fichero HTML, cómo se localiza un elemento, y cómo se cambia su contenido y su aspecto sin meter diseño en el código.
- 2. Haz: Explora el DOM de tu web, prepárala con anclajes pensados para el código y cambia el estado visual del catálogo desde JavaScript.
- 3. Comprueba: Ninguna selección depende de la posición ni de una clase de estilo, y cambias clases en lugar de estilos en línea.
Antes de empezar · 5 minutos, sin apuntes
- Cuando el navegador lee tu HTML, ¿qué crees que hace con él?
- Si el diseñador cambia la clase
.tarjetapor.card, ¿se rompe tu código? - ¿Qué riesgo ves en insertar texto escrito por un usuario dentro del HTML?
Se explica
25 minutos · conceptos y demostración
Hoy empieza la tercera capa, y la idea que la gobierna es la misma de las dos anteriores: cada cosa en su sitio. El código dice qué está pasando; el CSS, cómo se ve. Todo lo que hagamos hoy sirve para no mezclarlas.
El documento, convertido en objetos
Cuando el navegador lee tu HTML construye en memoria un árbol de objetos: cada etiqueta pasa a ser un objeto con propiedades y métodos. Ese árbol es el DOM, y es lo que tu código manipula.
DOM · Document Object Model
La representación en memoria del documento. Tu fichero .html es el punto de partida; el DOM es lo que existe mientras la página está abierta, y puede acabar siendo muy distinto del fichero.
- Fichero HTML
- El navegador lo analiza
- Árbol DOM en memoria
- Se pinta en pantalla
Compruébalo: en la pestaña Elements borra un párrafo; desaparece de la pantalla, pero tu fichero sigue intacto. Recarga y vuelve. Lo que ves en Elements no es tu código, es el DOM.
En sentido inverso: si tu código crea diez artículos, aparecerán en Elements y no constarán en el archivo. Por eso, cuando en la sesión 3 pintes el catálogo, «ver código fuente» no te servirá de nada: hay que mirar Elements.
document // el documento entero
document.body // el cuerpo
document.title // el título de la pestaña
const main = document.querySelector("main");
main.children // sus elementos hijos
main.parentElement // su contenedor
main.firstElementChild
main.nextElementSibling
Un detalle que confunde a todo el mundo: el DOM también guarda como nodos los espacios y saltos de línea entre etiquetas. Por eso usamos siempre las propiedades que hablan de elementos (children, firstElementChild) y no las que hablan de nodos en general (childNodes, firstChild).
Si el código se ejecuta antes de que exista el documento, además, no encontrará nada:
const titulo = document.querySelector("h1");
console.log(titulo); // null si el script se ejecutó demasiado pronto
Esto ya lo resolviste en la UD3: con defer, o con type="module", el navegador espera a tener el documento completo. Recuérdalo, porque ese null es el error número uno de la unidad.
Los dos métodos que vas a usar
const titulo = document.querySelector("h1"); // el primero, o null
const tarjetas = document.querySelectorAll(".producto"); // todos, siempre una lista
Aceptan cualquier selector CSS, exactamente los que aprendiste en la UD2:
document.querySelector("#buscador");
document.querySelector(".catalogo .producto");
document.querySelector("[data-categoria='teclados']");
document.querySelectorAll("article:not(.agotado)");
Existen métodos más antiguos —getElementById, getElementsByClassName— que verás en tutoriales; con estos dos sirve.
const tarjetas = document.querySelectorAll(".producto");
tarjetas.length // sí
tarjetas.forEach(...) // sí
tarjetas.map(...) // TypeError: no es una función
[...tarjetas].map(...) // convertido en array, ya sí
Es una NodeList. Tiene length y forEach, pero no los métodos de la UD3. Los tres puntos la convierten en un array de verdad.
Una lista vacía, además, no es null: si el selector no encuentra nada, querySelectorAll devuelve una lista de longitud cero, mientras que querySelector devuelve null. Confundir esos dos casos es el segundo error más común de la unidad.
Anclajes pensados para el código
No selecciones por clases de estilo ni por posición
Si tu código busca .tarjeta-azul o el tercer elemento de una lista, cualquier retoque de diseño lo romperá, y quien lo rompa no sabrá que lo ha roto.
Usa un atributo data- pensado para eso: data-js="buscador". Así el HTML declara explícitamente «aquí hay un punto de conexión con el código», y las clases quedan libres para el CSS.
<input type="search" id="buscador" data-js="buscador" aria-label="Buscar productos">
<ul class="catalogo" data-js="catalogo"></ul>
<p data-js="resultado" role="status"></p>
Esas referencias se buscan una sola vez, al arrancar:
// Mal: busca en el árbol cada vez que el usuario escribe
function alEscribir() {
document.querySelector("[data-js='catalogo']").textContent = "";
}
// Bien: se busca una vez, al arrancar
const elementos = {
buscador: document.querySelector("[data-js='buscador']"),
catalogo: document.querySelector("[data-js='catalogo']"),
resultado: document.querySelector("[data-js='resultado']")
};
Además de ser más rápido, agrupa en un sitio todo lo que tu código espera encontrar en la página. Si algo sale null, se ve al arrancar y no en mitad de un evento.
Cambiar el contenido
const titulo = document.querySelector("h1");
titulo.textContent = "Catálogo de productos"; // texto plano: lo habitual
titulo.innerHTML = "Catálogo <strong>2026</strong>"; // interpreta etiquetas
Por defecto, textContent
Insertar con innerHTML un texto que venga de fuera —lo que alguien escribió en un campo, lo que devolvió una API— permite colar etiquetas y código en tu página. Es la vía de entrada de los ataques de inyección.
textContent escribe texto y solo texto: el contenido se muestra literalmente, sin interpretarse ni ejecutarse. Usa innerHTML únicamente con marcado que hayas escrito tú, y nunca con datos del usuario.
Cambiar el aspecto sin escribir diseño
tarjeta.classList.add("agotado");
tarjeta.classList.remove("agotado");
tarjeta.classList.toggle("destacado");
tarjeta.classList.contains("agotado"); // true / false
JavaScript pone clases; el CSS decide cómo se ven
Es la separación de capas de siempre, ahora en la tercera. Tu código dice qué está pasando —«esto está agotado», «esto está cargando»— y la hoja de estilos dice cómo se representa.
Si en lugar de eso escribes elemento.style.backgroundColor = "grey", has metido una decisión de diseño dentro del comportamiento: nadie la encontrará buscando en el CSS, y no responderá al modo oscuro ni al tema de la UD2.
Hay un caso en el que tocar style sí es legítimo: cuando el valor es un dato y la regla sigue viviendo en la hoja de estilos.
elemento.style.setProperty("--altura-imagen", "220px");
Cambiar así una variable CSS de las que definiste en la UD2 no mete diseño en el código: mete un número, y el diseño lo sigue decidiendo el CSS.
Los atributos se leen y se escriben con sus propios métodos:
enlace.getAttribute("href");
enlace.setAttribute("href", "productos.html");
enlace.removeAttribute("target");
enlace.hasAttribute("download");
Los data- tienen además un acceso propio, que será la forma de saber en la sesión 2 a qué producto corresponde el botón que se acaba de pulsar:
tarjeta.dataset.id; // "7" ← siempre texto
Number(tarjeta.dataset.id); // 7
tarjeta.dataset.categoria = "teclados";
Conviene observar de nuevo el mismo detalle de la UD3: el valor llega como texto.
Se trabaja
150 minutos · práctica sobre tu propio proyecto
Hoy la página deja de ser algo que solo se mira y pasa a ser algo con lo que el código habla. Los dos primeros pasos preparan esa conversación; los tres últimos la ponen a prueba.
Paso 1 · Explora tu propia web · 30 min
Desde la consola de tu página de productos:
- Cuenta cuántos enlaces hay:
document.querySelectorAll("a").length. - Obtén el texto del
h1. - Recorre las secciones y muestra su primer encabezado.
- Cambia el título de la pestaña y observa el efecto.
- Borra un elemento desde Elements y explica por escrito por qué el fichero no cambia.
- Dibuja en papel el árbol de las tres primeras ramas de tu página.
- Compara
main.childrenconmain.childNodesy anota cuántos elementos devuelve cada uno y por qué no coinciden.
Antes de continuar: puedes explicar en una frase la diferencia entre lo que escribiste y lo que el navegador construyó.
Paso 2 · Preparar el terreno · 35 min
Sobre tu página de productos:
- Añade atributos
data-jsa los seis elementos con los que vas a trabajar. - Crea
js/dom.jsque los seleccione y los exporte en un objeto. - Escribe una comprobación que avise por consola si alguno es
null, diciendo qué anclaje falta por su nombre. - Selecciona todas las tarjetas y cuéntalas.
- Convierte la
NodeListen array y obtén sus textos conmap. - Quita a propósito un
data-jsdel HTML y comprueba que tu aviso lo detecta al arrancar.
El apartado 6 es el que importa: un anclaje que falta debe delatarse al cargar la página, no veinte minutos después dentro de un evento.
Paso 3 · Estado visual desde el código · 40 min
Es el trabajo central de la sesión. Con el catálogo todavía escrito a mano en HTML:
- Marca con la clase
agotadolos productos sin stock, leyendo el dato dedataset. Recuerda convertir. - Escribe en un párrafo cuántos productos hay disponibles.
- Añade un botón que alterne una clase de vista compacta en la lista, aplicándola a todas las tarjetas de una vez.
- Cambia el
titlede la pestaña para incluir el número de resultados. - Define en el CSS cómo se ve
.agotadoy cómo se ve la vista compacta. Todo el aspecto vive ahí, y tu JavaScript no contiene un solo color.
Paso 4 · Clases frente a estilos en línea · 20 min
Repite el apartado 1 del paso anterior con element.style en lugar de classList, y compara las dos versiones:
| Pregunta | Con classList |
Con style |
|---|---|---|
| ¿Dónde está escrito el color del estado agotado? | ||
| ¿Cuántos ficheros hay que tocar para cambiarlo? | ||
| ¿Responde al modo oscuro de la UD2? | ||
| ¿Qué gana la regla en la cascada, y por qué? | ||
¿Puede el CSS sobrescribirlo sin !important? |
La última fila conecta con la sesión 1 de la UD2: un estilo en línea gana a casi todo, y eso lo convierte en un problema de cascada además de en un problema de organización.
Quédate con la versión de classList y borra la otra.
Paso 5 · Un selector que no falla en silencio · 25 min
document.querySelector devuelve null cuando no encuentra nada, y ese null viaja tranquilamente hasta que algo intenta usarlo. Escribe tu propia función de selección que no lo permita:
export function seleccionar(ancla, contexto = document) {
// devuelve el elemento, o lanza un error que diga qué ancla faltaba
}
- Escríbela y úsala en
dom.jspara los seis anclajes. - El mensaje del error debe incluir el nombre del ancla y ser suficiente para arreglarlo sin abrir el código.
- Escribe también
seleccionarTodos(ancla), que devuelva un array en vez de unaNodeList. Decide si esta debe lanzar error con cero resultados, y justifica tu decisión: no es la misma que la anterior. - Prueba las dos quitando anclajes del HTML.
- Anota en un comentario en qué caso preferirías seguir usando
querySelectora secas.
Ampliación si has completado el trabajo
Primero termina y comprueba los cinco pasos. Los dos retos enseñan, cada uno por su lado, por qué las reglas de hoy no eran manías.
Reto 1 · El rediseño que rompe el código
Este JavaScript funciona hoy:
const tarjetas = document.querySelectorAll(".card-azul");
const boton = document.querySelector("main div div:nth-child(3) button");
const titulo = document.querySelector("section.destacada h2");
tarjetas[0].classList.add("primero");
boton.textContent = "Comprar";
Mañana el equipo de diseño hace cuatro cambios, todos razonables y ninguno pensado para fastidiarte:
1. Renombra .card-azul como .card--primaria, porque el azul cambió a verde.
2. Envuelve el contenido en un <div class="wrapper"> más para centrarlo.
3. La sección destacada pasa de ser <section> a <article>.
4. El catálogo se reordena, y la tarjeta que iba primera ahora va tercera.
- Para cada uno de los cuatro cambios, di qué línea del JavaScript se rompe y qué error exacto aparecería en consola. Dos de ellos no dan error: producen un
nullo una lista vacía y el fallo se manifiesta más tarde. Identifícalos. - Reescribe las cuatro selecciones con anclajes
data-jsy comprueba que los cuatro cambios de diseño dejan de afectarte. - La línea de
tarjetas[0]sigue siendo frágil aunque usesdata-js. Explica por qué y propón qué debería decidir cuál es «la primera». - Escribe en tres líneas qué le dirías al equipo de diseño para que en el futuro sepa qué puede tocar sin avisarte. La respuesta no es «que no toque nada».
Reto 2 · Por qué innerHTML con datos ajenos es un problema
Monta una página mínima con un campo de texto y un contenedor donde se muestre lo escrito usando innerHTML.
- Escribe en el campo
<strong>hola</strong>y observa qué aparece. - Escribe ahora
<img src="x" onerror="alert('código ejecutado')">. Anota qué pasa. No hace falta que entiendas el detalle: basta con que veas que se ha ejecutado código que tú no escribiste. - Cambia
innerHTMLportextContenty repite las dos pruebas. Describe la diferencia. - Ahora el caso realista: tu catálogo pinta el nombre del producto con
innerHTMLporque quieres que algunos lleven<em>. Un producto viene de un formulario y su nombre contiene una comilla y un signo de menor que. Propón dos soluciones distintas, y di cuál elegirías y por qué. - Este ataque tiene nombre en la disciplina. Búscalo, escríbelo, y explica en dos líneas por qué el problema no está en el usuario que escribe, sino en el código que confía.
classList y style contestada, y las dos funciones de selección con errores claros.Cierre
5 minutos · comprobación y recuerdo
Lista de verificación de la sesión
- Explicas qué es el DOM y por qué no es tu fichero.
- Seleccionas por anclajes pensados para el código, no por clases de estilo.
- Guardas las referencias una sola vez y avisas si falta alguna.
- Cambias texto con
textContenty aspecto con clases. - Tu JavaScript no contiene ningún color ni medida de diseño.
- Sabes por qué
innerHTMLcon datos ajenos es un riesgo.
Antes de cerrar · 3 minutos, sin mirar
- ¿Qué es el DOM?
- ¿Por qué «ver código fuente» no muestra lo que crea tu código?
- ¿Qué devuelve
querySelectorsi no encuentra nada? ¿YquerySelectorAll? - ¿Por qué no seleccionamos por clases de CSS?
- ¿Por qué cambiamos una clase en lugar de un estilo en línea?
- ¿De qué tipo es siempre lo que devuelve
dataset?
Ver respuestas
1 · El árbol de objetos que el navegador construye a partir del HTML y que existe mientras la página está abierta.
2 · Porque el código fuente es el fichero recibido, y lo que creas después solo existe en el DOM: se ve en Elements.
3 · null el primero; una lista vacía el segundo.
4 · Porque las clases son del diseño y cambian: el código quedaría atado a decisiones visuales.
5 · Para que el aspecto siga viviendo en el CSS, responda al tema y se pueda sobrescribir sin pelear con la cascada.
6 · Texto. Hay que convertirlo si se va a operar con él.
Microprueba semanal 1 · 5–10 minutos
Individual, sin IA y sin apuntes.
- Explica en dos líneas la diferencia entre tu fichero HTML y el DOM.
- Escribe la selección de un elemento por atributo
data-, y di qué devuelve si no lo encuentra. - ¿Por qué cambiamos una clase en lugar de un estilo en línea?