Llevas un rato haciendo páginas con HTML y CSS. Se ven bien. Pero cuando alguien hace click en algo... no pasa nada. La página es un cartel estático. Eso es exactamente lo que el DOM te permite cambiar.
El DOM es la parte de JavaScript que conecta tu código con lo que el usuario ve en pantalla. Aquí es donde todo el mundo se atasca al principio, porque nadie explica bien qué está pasando por debajo. Este tutorial lo hace diferente: cada concepto viene con un ejemplo que puedes probar ahora mismo.
¿Qué es el DOM (sin tecnicismos)?
El DOM es como una versión "viva" de tu HTML que JavaScript puede tocar, cambiar y manipular. Imagínate que tu página HTML es un documento de papel, pero el DOM es la versión digital que puedes editar en tiempo real.
HTML estático
Tu código HTML original. Como un documento impreso que no se puede cambiar.
DOM dinámico
La versión "viva" que JavaScript puede modificar: cambiar texto, colores, mostrar/ocultar elementos.
Resultado
Páginas que reaccionan, cambian y responden a las acciones del usuario.
Los 3 poderes básicos del DOM
Encontrar elementos
Le dices a JavaScript: "busca el botón con id='mi-boton'"
document.querySelector('#mi-boton')
Cambiar contenido
Le dices: "cambia el texto de ese elemento por esto otro"
elemento.textContent = "Nuevo texto"
Reaccionar a eventos
Le dices: "cuando hagan click aquí, ejecuta esta función"
elemento.addEventListener('click', función)
De 0 a interactivo: ejemplos que funcionan
Vamos a crear ejemplos progresivos. Cada uno será más interesante que el anterior, y todos funcionan de verdad.
Nivel 1: Tu primer botón que hace algo
Empezamos con lo más básico: un botón que cambia su propio texto cuando le haces click.
🎮 Demo interactivo:
👆 Este botón cambiará cuando le hagas click
HTML necesario:
<button id="mi-boton">Haz click aquí</button>
El JavaScript:
// 1. Encuentra el botón
const miBoton = document.querySelector('#mi-boton');
// 2. Escucha cuando le hagan click
miBoton.addEventListener('click', function() {
// 3. Cambia el texto
miBoton.textContent = '¡Funcionó!';
});
🤓 ¿Qué está pasando?
- Línea 2: Buscamos el elemento con id "mi-boton" y lo guardamos en una variable
- Línea 5: Le decimos "cuando te hagan click, ejecuta esta función"
- Línea 7: Cambiamos el texto del botón
Nivel 2: Mostrar y ocultar contenido
Ahora algo más útil: un botón que hace aparecer y desaparecer contenido. Esto es súper común en páginas reales.
🎮 Demo interactivo:
👆 Este botón revela contenido oculto
HTML estructurado:
<button id="boton-secreto">Revelar secreto</button>
<div id="contenido-secreto" class="oculto">
¡Este contenido estaba oculto!
</div>
CSS para ocultar:
.oculto {
display: none;
}
.visible {
display: block;
}
JavaScript inteligente:
const boton = document.querySelector('#boton-secreto');
const contenido = document.querySelector('#contenido-secreto');
boton.addEventListener('click', function() {
// Si está oculto, mostrarlo. Si está visible, ocultarlo
if (contenido.classList.contains('oculto')) {
contenido.classList.remove('oculto');
contenido.classList.add('visible');
boton.textContent = 'Ocultar secreto';
} else {
contenido.classList.add('oculto');
contenido.classList.remove('visible');
boton.textContent = 'Revelar secreto';
}
});
Nivel 3: Lista interactiva
Ahora vamos con algo más avanzado: agregar elementos a una lista dinámicamente. Esto es la base de muchas aplicaciones web.
🎮 Demo interactivo:
👆 Escribe algo y agrégalo a la lista
HTML base:
<input type="text" id="mi-input" placeholder="Escribe algo...">
<button id="agregar-btn">Agregar</button>
<ul id="mi-lista"></ul>
JavaScript más potente:
const input = document.querySelector('#mi-input');
const boton = document.querySelector('#agregar-btn');
const lista = document.querySelector('#mi-lista');
boton.addEventListener('click', function() {
// Obtener el texto del input
const texto = input.value;
// Solo agregar si hay algo escrito
if (texto !== '') {
// Crear un nuevo elemento li
const nuevoItem = document.createElement('li');
nuevoItem.textContent = texto;
// Agregarlo a la lista
lista.appendChild(nuevoItem);
// Limpiar el input
input.value = '';
}
});
Nivel 4: Contador interactivo
Algo que parece "de aplicación real": un contador que sube y baja.
🎮 Demo interactivo:
👆 Un contador funcional con botones
HTML limpio:
<div class="contador">
<button id="restar">-</button>
<span id="numero">0</span>
<button id="sumar">+</button>
</div>
JavaScript profesional:
// Variables para los elementos
const botonRestar = document.querySelector('#restar');
const botonSumar = document.querySelector('#sumar');
const numeroDisplay = document.querySelector('#numero');
// Variable para llevar la cuenta
let contador = 0;
// Función para sumar
botonSumar.addEventListener('click', function() {
contador++;
numeroDisplay.textContent = contador;
});
// Función para restar
botonRestar.addEventListener('click', function() {
contador--;
numeroDisplay.textContent = contador;
});
Los errores que comete todo el mundo al empezar con el DOM
Error #1: Script en el lugar incorrecto
<head>
<script src="script.js"></script>
</head>
<head>
<script src="script.js" defer></script>
</head>
Por qué: Sin defer, JavaScript se ejecuta antes de que el HTML termine de cargar. Este es el error número uno de principiantes — el código parece correcto pero falla siempre, y la consola no da ningún error claro.
Error #2: Usar innerHTML sin control
elemento.innerHTML = userInput;
elemento.textContent = userInput;
Por qué: `innerHTML` puede ejecutar código malicioso. `textContent` es más seguro.
Error #3: IDs que no existen
const boton = document.querySelector('#boton-que-no-existe');
boton.addEventListener('click', ...); // Error!
const boton = document.querySelector('#mi-boton');
if (boton) {
boton.addEventListener('click', ...);
}
Por qué: Si el elemento no existe, el código se rompe. Siempre verifica primero.
Error #4: onclick en HTML
<button onclick="miFuncion()">Click</button>
// HTML
<button id="mi-boton">Click</button>
// JavaScript
document.querySelector('#mi-boton')
.addEventListener('click', miFuncion);
Por qué: Separar HTML y JavaScript es más limpio y profesional.
Error #5: No usar nombres descriptivos
const x = document.querySelector('#btn');
const y = 0;
const botonContador = document.querySelector('#contador-btn');
let numeroActual = 0;
Por qué: En 1 semana no recordarás qué hace "x" o "y".
Seleccionar elementos: las 3 formas que verás en todo el código
Hasta aquí has usado querySelector para todo. Pero cuando leas código de otros — tutoriales de YouTube, proyectos de GitHub, código de trabajo — vas a encontrar otras dos variantes. Aquí están las tres juntas para que sepas qué hace cada una:
// getElementById — el más antiguo, solo busca por id
const boton = document.getElementById('mi-boton');
// ⚠️ Sin el # — aquí el id va sin símbolo
// querySelector — selector CSS, devuelve el primero que coincida
const boton = document.querySelector('#mi-boton');
// Con el # para ids, con . para clases, sin nada para etiquetas
// querySelectorAll — devuelve TODOS los que coincidan
const botones = document.querySelectorAll('.boton-card');
// Devuelve una NodeList — iterar con forEach
botones.forEach(function(boton) {
boton.addEventListener('click', miFuncion);
});
El error clásico con querySelectorAll:
querySelectorAll devuelve una NodeList, no un array. La mayoría de métodos de array como map o filter no funcionan directamente. forEach sí funciona. Si necesitas array de verdad: Array.from(document.querySelectorAll('.clase')).
¿Cuándo usar cada una? En código nuevo usa querySelector y querySelectorAll siempre — son más flexibles. getElementById lo verás en código antiguo o en tutoriales de hace 5 años y hace exactamente lo mismo para ids.
Cambiar atributos con setAttribute
Los atributos HTML son cosas como src de una imagen, href de un enlace, disabled de un botón, o cualquier data-* que pongas tú. JavaScript puede leerlos y cambiarlos en tiempo real.
// Leer un atributo
const url = enlace.getAttribute('href');
// Cambiar un atributo
imagen.setAttribute('src', 'nueva-foto.jpg');
enlace.setAttribute('href', 'https://otro-sitio.com');
// Deshabilitar / habilitar un botón
boton.setAttribute('disabled', ''); // deshabilita
boton.removeAttribute('disabled'); // vuelve a habilitarlo
// Para propiedades booleanas hay un atajo más directo
boton.disabled = true;
input.required = true;
Demo — deshabilitar y habilitar un botón:
Estado: habilitado
Modificar estilos con .style
Ya sabes que classList es la forma correcta de cambiar estilos: añades o quitas una clase y el CSS hace el resto. Pero a veces necesitas un valor calculado en tiempo de ejecución — una posición basada en un click, un tamaño que depende de datos del servidor. Para eso existe .style.
// Los nombres de propiedad van en camelCase, no kebab-case
elemento.style.color = 'red';
elemento.style.backgroundColor = '#f0f0f0'; // background-color → backgroundColor
elemento.style.fontSize = '24px'; // font-size → fontSize
elemento.style.display = 'none';
// Para quitar un estilo inline, asigna string vacío
elemento.style.color = '';
Cuándo NO usar .style:
Si el estado es fijo (activo / inactivo, visible / oculto), usa classList. Los estilos inline tienen la máxima especificidad CSS y son difíciles de sobrescribir desde tu hoja de estilos. .style tiene sentido cuando el valor es dinámico: la posición exacta de un tooltip, el ancho de una barra de progreso al 73%, el color calculado a partir de un dato.
Un ejemplo real de esto último es cambiar de tema con JavaScript y el DOM: como el estado es fijo (claro o oscuro), lo correcto es alternar una clase en el body con classList.toggle, no usar .style a mano.
Más eventos que el click
El 80% de los tutoriales solo usan click. En proyectos reales hay otros tres que vas a necesitar sí o sí:
input — reaccionar mientras el usuario escribe
const campo = document.querySelector('#email');
const error = document.querySelector('#error-msg');
campo.addEventListener('input', function() {
if (campo.value.includes('@')) {
error.textContent = '';
} else {
error.textContent = 'Necesita una @';
}
});
submit — interceptar formularios
Sin preventDefault(), el formulario recarga la página. Esto es lo primero que tienes que aprender si trabajas con formularios y JavaScript:
const formulario = document.querySelector('#mi-formulario');
formulario.addEventListener('submit', function(event) {
event.preventDefault(); // ← evita el reload
// Ahora puedes leer los datos y hacer lo que quieras
const email = document.querySelector('#email').value;
console.log('Email enviado:', email);
});
keydown — detectar teclas concretas
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
cerrarModal();
}
if (event.key === 'Enter') {
confirmar();
}
});
Event delegation: un listener para gobernarlos a todos
Este es el concepto que separa a quien entiende el DOM de quien lo usa sin entenderlo. Cuando tienes una lista de elementos y quieres que cada uno reaccione a un click, el instinto es poner un listener en cada uno. El problema: si los elementos se crean dinámicamente (como en el demo3), los listeners que pusiste antes no existen en los nuevos.
La solución es poner el listener en el elemento padre y dejar que el evento suba:
// ❌ Esto falla con elementos creados dinámicamente
document.querySelectorAll('li').forEach(function(li) {
li.addEventListener('click', eliminar);
// Los li que crees DESPUÉS no tienen este listener
});
// ✅ Event delegation: el listener está en el padre
const lista = document.querySelector('#mi-lista');
lista.addEventListener('click', function(event) {
// event.target es el elemento exacto donde hicieron click
if (event.target.tagName === 'LI') {
event.target.remove();
}
});
// Ahora funciona con todos los li, incluso los que añadas después
Aquí event.target es el elemento donde cayó el click. El evento sube por el árbol DOM (se llama bubbling) hasta llegar al padre donde tienes el listener. Eso es todo.
Demo — lista con eliminación por event delegation:
Haz click en el ✕ de cada item para eliminarlo — un solo listener en la lista hace todo
Proyecto completo: Todo junto
Aquí tienes un proyecto que combina todo lo anterior. Mira el código fuente cuando lo veas funcionar — es más fácil entenderlo cuando ya has pasado por los niveles anteriores:
🎮 Demo final - Mini aplicación:
Mi Primera Aplicación DOM
Items en la lista: 0
👆 Una mini-aplicación que combina todo lo que aprendiste
🏆 Desafío para ti:
¿Puedes crear tu propia versión de esta aplicación? Intenta agregar estas funciones:
- ✅ Botón para eliminar elementos individuales
- ✅ Cambiar el color de fondo cuando agregues algo
- ✅ Mostrar un mensaje cuando la lista esté vacía
- ✅ Contar cuántas palabras has agregado en total
¿Qué sigue después del DOM?
Con esto ya puedes hacer que una página reaccione al usuario. El siguiente paso lógico es conectar esa página con datos reales que vienen de internet — y para eso existe la Fetch API.
🎯 Tu siguiente paso
Dominas lo básico del DOM. Ahora aprende a conectar tu página con servicios reales.
👉 APIs para principiantes: Tu primera llamada fetch() con JavaScriptAprende a traer datos de internet: clima, noticias, usuarios. El siguiente nivel de interactividad.
Una cosa antes de seguir:
Cuando algo no funcione, abre la consola del navegador (F12 → Console). El 90% de los errores con el DOM aparecen ahí con el mensaje exacto. Cannot read properties of null casi siempre significa que el elemento no existe o el script se ejecutó antes de que el HTML cargara.