Chrome DevTools: La guía de debugging que todo principiante necesita (adiós console.log)

Deja de llenar tu código de console.log() para encontrar errores. Aprende debugging profesional con Chrome DevTools: breakpoints, inspección en vivo y detección de bugs como un pro

Hay un momento concreto en el que todo el mundo que aprende JavaScript descubre que llenar el código de console.log() no escala. Pones uno, otro, otro más, y al final tienes veinte logs en pantalla y no sabes cuál corresponde a qué. O peor: arreglas el bug, se te olvida quitar los console.log, y los mandas a producción.

Chrome DevTools resuelve esto de raíz. Puedes pausar la ejecución del código, inspeccionar el valor de cualquier variable en ese instante, y ver exactamente qué petición de red falló y por qué. Sin tocar el código. Sin adivinar.

Qué vas a aprender

  • Usar breakpoints para pausar y examinar código en ejecución
  • Inspeccionar y editar el DOM en tiempo real con el panel Elements
  • Analizar peticiones de red y detectar errores de API con Network
  • Medir rendimiento con el panel Performance
  • Detectar memory leaks con el panel Memory
  • Shortcuts que reducen a la mitad el tiempo de debugging

⚠️ Lo que necesitas antes de empezar:

  • JavaScript básico: variables, funciones, DOM — si aún no lo tienes, empieza por ahí
  • Google Chrome instalado (versión actualizada)
  • Un editor de código: VS Code funciona bien

El problema real con console.log

El console.log no es malo. El problema es usarlo como única herramienta de debugging. Aquí está el patrón que repite todo el mundo al principio:

El debugging amateur 😅 📋
// ❌ El debugging "a lo bestia"
function calculateTotal(items) {
    console.log('items:', items);

    let total = 0;
    console.log('total inicial:', total);

    for (let item of items) {
        console.log('item actual:', item);
        total += item.price;
        console.log('total después de sumar:', total);
    }

    console.log('total final:', total);
    return total;
}

El problema no es solo que es lento. Es que estás modificando el código para debuggearlo, lo cual puede camuflar el bug o introducir nuevos. Y cuando termines, tienes que acordarte de quitar todos esos logs. Aquí es donde la mayoría se atasca durante meses.

Proyecto: Bug Hunter Dashboard

Vamos a crear una pequeña app con tres bugs intencionales. El objetivo es encontrarlos y arreglarlos usando solo DevTools, sin tocar el código a ciegas:

index.html - Bug Hunter Dashboard 📋
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Bug Hunter Dashboard</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <header>
        <h1>🔍 Bug Hunter Dashboard</h1>
        <p>Encuentra y arregla los bugs usando Chrome DevTools</p>
    </header>

    <main>
        <!-- Sección 1: Calculadora con bugs -->
        <section id="calculator">
            <h2>🧮 Calculadora (tiene bugs)</h2>
            <div class="calculator-container">
                <input type="number" id="num1" placeholder="Número 1">
                <input type="number" id="num2" placeholder="Número 2">
                <button onclick="calculate()">Sumar</button>
                <div id="result">Resultado: -</div>
            </div>
        </section>

        <!-- Sección 2: Lista de tareas con errores -->
        <section id="todo-list">
            <h2>📝 Lista de Tareas (también tiene bugs)</h2>
            <div class="todo-container">
                <input type="text" id="todo-input" placeholder="Nueva tarea...">
                <button onclick="addTask()">Agregar</button>
                <ul id="task-list"></ul>
            </div>
        </section>

        <!-- Sección 3: API caller con fallos -->
        <section id="api-section">
            <h2>🌐 API Caller (fallos de red)</h2>
            <button onclick="fetchUserData()">Cargar Usuarios</button>
            <div id="loading" style="display: none;">Cargando...</div>
            <div id="user-data"></div>
        </section>
    </main>

    <script src="script.js"></script>
</body>
</html>
script.js - JavaScript con bugs intencionales 📋
// 🐛 Bug #1: Calculadora suma strings en lugar de números
function calculate() {
    const num1 = document.getElementById('num1').value;
    const num2 = document.getElementById('num2').value;
    const result = num1 + num2; // ❌ Suma strings!

    document.getElementById('result').textContent = 'Resultado: ' + result;
}

// 🐛 Bug #2: Error en querySelector
function addTask() {
    const input = document.getElementById('todo-input');
    const taskList = document.querySelector('#task-lists'); // ❌ ID incorrecto!

    if (input.value.trim()) {
        const li = document.createElement('li');
        li.textContent = input.value;
        taskList.appendChild(li); // ❌ Falla porque taskList es null
        input.value = '';
    }
}

// 🐛 Bug #3: API call con URL incorrecta
async function fetchUserData() {
    const loading = document.getElementById('loading');
    const userData = document.getElementById('user-data');

    loading.style.display = 'block';

    try {
        // ❌ URL con typo
        const response = await fetch('https://jsonplaceholder.typicode.com/userss');
        const users = await response.json();

        userData.innerHTML = users.map(user => `<p>${user.name}</p>`).join('');
    } catch (error) {
        // ❌ Error handling insuficiente
        userData.innerHTML = 'Error cargando datos';
    } finally {
        loading.style.display = 'none';
    }
}
style.css - Estilos básicos 📋
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Arial', sans-serif;
    line-height: 1.6;
    color: #333;
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
}

header {
    text-align: center;
    margin-bottom: 40px;
    padding: 20px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    border-radius: 10px;
}

section {
    margin-bottom: 30px;
    padding: 20px;
    border: 1px solid #ddd;
    border-radius: 8px;
    background: #f9f9f9;
}

input, button {
    padding: 10px;
    margin: 5px;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 16px;
}

button {
    background: #007bff;
    color: white;
    cursor: pointer;
    transition: background 0.3s;
}

button:hover {
    background: #0056b3;
}

#result {
    margin-top: 10px;
    font-weight: bold;
    padding: 10px;
    background: #e9ecef;
    border-radius: 4px;
}

#task-list li {
    list-style: none;
    padding: 8px;
    background: white;
    margin: 5px 0;
    border-radius: 4px;
    border-left: 4px solid #28a745;
}

Tres bugs distintos, tres herramientas distintas de DevTools para encontrarlos. Así aprendes cuándo usar cada panel.

Cómo abrir Chrome DevTools

Antes de nada, hay que saber cómo abrir la herramienta. Tienes varias opciones:

🔧 Métodos para abrir DevTools:

  • F12 - El método más rápido
  • Ctrl+Shift+I (Windows/Linux) o Cmd+Option+I (Mac)
  • Clic derecho en cualquier elemento → "Inspeccionar"
  • Menú Chrome → Más herramientas → Herramientas para desarrolladores

La mayoría usa F12 y ya está. Una vez abierto, verás una barra de pestañas arriba. Los cuatro paneles que vas a usar más son:

Elements HTML y CSS en vivo
Console Errores y comandos
Sources Debugging de JavaScript
Network Peticiones HTTP

Panel Console: más que un simple log

La mayoría usa la Console solo para ver console.log. Pero desde ahí puedes ejecutar JavaScript directamente sobre la página, consultar el valor de cualquier variable global, y usar atajos que no existen fuera de DevTools:

Console commands esenciales 📋
// 🔍 Inspeccionar elementos directamente
$('#num1')              // Equivale a document.querySelector
$$('button')           // Equivale a document.querySelectorAll

// 📊 Logs más informativos
console.table(datos)      // Muestra arrays/objetos como tabla
console.group('Cálculos') // Agrupa logs
console.error('Error!')  // Log con estilo de error
console.warn('Cuidado!')  // Log con estilo de warning

// 🕐 Medir performance
console.time('operacion');
// ... código a medir ...
console.timeEnd('operacion');

Lo más útil que te llevas de la Console

El atajo $_ devuelve el resultado de la última expresión que evaluaste. Si ejecutas document.querySelector('.mi-clase') y quieres seguir trabajando con ese elemento, escribe $_ en lugar de repetir todo el selector.

Bug #1: la calculadora que no suma

Prueba la calculadora del dashboard: mete 5 y 3, pulsa Sumar. Resultado: "53". No 8. El bug más clásico de JavaScript: concatenar strings en lugar de sumar números.

Sin DevTools, lo resolverías así:

El enfoque amateur 😅 📋
function calculate() {
    const num1 = document.getElementById('num1').value;
    console.log('num1:', num1, typeof num1); // ❌ Modificando código

    const num2 = document.getElementById('num2').value;
    console.log('num2:', num2, typeof num2); // ❌ Más ruido
}

Con DevTools, no necesitas tocar el código. Usas un breakpoint:

Cómo poner un breakpoint y usarlo

  1. Abre el panel Sources en DevTools
  2. En el árbol de archivos de la izquierda, busca y abre script.js
  3. Haz clic en el número de línea donde está const num1 = ...
  4. Aparece un punto azul — eso es el breakpoint
  5. Vuelve a la calculadora y pulsa Sumar
  6. La ejecución se pausa exactamente en esa línea

Con el código pausado, pasa el ratón por encima de num1. Verás que el valor es "5" entre comillas, no el número 5. En el panel de la derecha (Scope) también lo ves: tipo string. Ahí está el problema.

También puedes abrir la Console mientras el código está pausado y escribir typeof num1. Te responde "string". Con eso ya sabes exactamente qué arreglar, sin haber añadido ni una línea de log.

La solución (una vez identificado el problema) 📋
function calculate() {
    // ✅ Convertir a números explícitamente
    const num1 = parseFloat(document.getElementById('num1').value);
    const num2 = parseFloat(document.getElementById('num2').value);
    const result = num1 + num2;

    document.getElementById('result').textContent = 'Resultado: ' + result;
}

Bug #2: el elemento que no existe

Ahora prueba añadir una tarea a la lista. No funciona. La Console te dice exactamente por qué — aquí es donde la mayoría no mira y acaba revisando el código durante media hora:

Leer el error de la Console

  1. Abre la Console en DevTools
  2. Intenta agregar una tarea
  3. Aparece un error en rojo: Cannot read properties of null (reading 'appendChild')
  4. Eso significa que taskList es null — el selector no encontró ningún elemento

En la Console, puedes verificarlo directamente:

Debugging en Console 📋
// 🔍 Investigar qué elementos existen
document.querySelector('#task-lists')  // null - no existe!
document.querySelector('#task-list')   // <ul id="task-list"></ul> - ¡aquí está!

// 🎯 El problema: typo en el ID
// ❌ Código busca: '#task-lists' (con S)
// ✅ HTML tiene: '#task-list' (sin S)

⚠️ El error más común con querySelector

Cuando el selector no encuentra nada, devuelve null. Si luego intentas usar ese null como si fuera un elemento (null.appendChild(...)), peta. Siempre que veas este tipo de error, lo primero es probar el selector directamente en Console para ver qué devuelve.

Bug #3: la petición de red que falla

Para el tercer bug usamos el panel Network. Abre ese panel antes de pulsar "Cargar Usuarios", para que registre lo que ocurre:

Leer el panel Network

  1. Abre el panel Network en DevTools
  2. Pulsa "Cargar Usuarios" en la app
  3. Aparece una fila en rojo — petición fallida
  4. Haz clic en esa fila
  5. Ve a la pestaña Headers → ahí ves la URL exacta que se usó
  6. Ve a Response → verás el error 404 del servidor

El código usa /userss (doble s) en lugar de /users. Una letra de diferencia, imposible de ver leyendo el código. El panel Network lo deja en evidencia en dos segundos.

Los códigos de estado que más verás son: 200 (OK), 404 (URL no encontrada), 401 (no autenticado), 500 (error del servidor). Cuando una petición falla, el color rojo en Network es la primera señal.

Panel Elements: el DOM en vivo

El panel Elements es donde puedes ver el HTML real de la página y modificarlo sin recargar. Útil para probar cambios de CSS antes de escribirlos en el archivo:

🎨 Funciones útiles del panel Elements:

  • Inspeccionar elemento: Clic derecho → "Inspeccionar" en cualquier parte de la página
  • Editar HTML: Doble clic en cualquier tag para editarlo
  • Editar CSS: Panel "Styles" a la derecha
  • Add/remove clases: Panel "Styles" → .cls
  • Ver computed styles: Pestaña "Computed"
Experimentar con CSS en vivo 📋
/* Prueba cambiar estos estilos en el panel Elements: */

/* 🎨 Cambiar color de fondo */
header {
    background: #ff6b6b; /* Rojo en lugar del gradiente */
}

/* 🔲 Cambiar tamaño de botones */
button {
    padding: 15px 30px; /* Botones más grandes */
    font-size: 18px;
}

Panel Performance: encontrar código lento

A veces no hay un error visible, la app simplemente va lenta. Para eso está el panel Performance. Permite grabar lo que hace el navegador durante unos segundos y ver exactamente dónde se gasta el tiempo:

Función lenta para probar Performance 📋
// 🐌 Función intencionalmente lenta
function slowFunction() {
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
        result += Math.random();
    }
    return result;
}

// 📊 Medir performance
console.time('Función lenta');
slowFunction();
console.timeEnd('Función lenta');

📊 Usar Performance panel:

  1. Abre el panel Performance
  2. Haz clic en Record (círculo rojo)
  3. Ejecuta la función lenta en la Console
  4. Para la grabación después de unos segundos
  5. Analiza el flame graph - verás dónde se gasta el tiempo

Sources Panel: breakpoints avanzados

El panel Sources es donde vive el debugging de verdad. Ya viste cómo poner un breakpoint básico. Aquí están las herramientas que lo rodean y que marcan la diferencia:

Breakpoints Pausar ejecución en líneas específicas
Step Over Ejecutar línea actual y pasar a la siguiente
Step Into Entrar dentro de funciones llamadas
Call Stack Ver qué funciones llamaron a la actual

Breakpoints condicionales

Los breakpoints normales se activan siempre que pasa el código por esa línea. Pero si tienes un bucle de 1000 iteraciones y el bug solo aparece en la iteración 743, necesitas un breakpoint condicional:

Función con loop para probar breakpoints condicionales 📋
function processItems(items) {
    for (let i = 0; i < items.length; i++) {
        const item = items[i];
        // ⭐ Pon un breakpoint condicional aquí
        // Condición: i === 5
        console.log('Procesando item:', item);
    }
}

// Probar con un array
processItems(['a', 'b', 'c', 'd', 'e', 'f', 'g']);

🎯 Crear breakpoint condicional:

  1. Clic derecho en el número de línea (no clic normal)
  2. Selecciona "Add conditional breakpoint"
  3. Escribe la condición: i === 5
  4. Ejecuta la función - solo se pausará cuando i sea 5

Memory leaks: cuando la app se ralentiza sin error visible

Un memory leak ocurre cuando el código acumula memoria sin liberarla. La app funciona al principio, pero después de unos minutos va cada vez más lenta. El culpable más habitual son los event listeners que se añaden pero nunca se eliminan:

Código con memory leak intencional 📋
// 🐛 Memory leak - listeners que nunca se limpian
function createMemoryLeak() {
    const element = document.createElement('div');
    const data = new Array(1000000).fill('memory');

    // ❌ Event listener que nunca se limpia
    element.addEventListener('click', function() {
        console.log(data.length); // Mantiene referencia a 'data'
    });

    // El elemento no se usa, pero el listener mantiene todo en memoria
}

// ✅ Versión correcta
function createProperCleanup() {
    const element = document.createElement('div');
    const controller = new AbortController();

    element.addEventListener('click', function() {
        console.log('click');
    }, { signal: controller.signal });

    // Cleanup when needed
    controller.abort(); // Removes all listeners
}

Simular móviles con DevTools

DevTools tiene un modo de simulación de dispositivos que te permite ver cómo queda tu página en un iPhone, un Android, o cualquier resolución personalizada. Esto es lo más rápido para detectar problemas de responsive sin sacar el teléfono:

📱 Device simulation:

  • Ctrl+Shift+M - Activar device toolbar
  • Seleccionar dispositivo - iPhone, iPad, etc.
  • Custom dimensions - Tamaños específicos
  • Network throttling - Simular conexión lenta
  • Rotate device - Portrait/landscape

Detectar vulnerabilidades XSS con DevTools

La Console también sirve para detectar problemas de seguridad. Si tu código usa innerHTML con datos que vienen del usuario, estás abriendo la puerta a ataques XSS. DevTools te deja verificarlo:

Detectar problemas de seguridad en Console 📋
// 🔍 Comprobar Content Security Policy
// Si ves errores CSP en Console, significa que hay problemas de seguridad

// ⚠️ Ejemplo de código peligroso (para detectar)
function unsafeCode(userInput) {
    // ❌ NUNCA hagas esto - XSS vulnerability
    document.getElementById('output').innerHTML = userInput;

    // ✅ Versión segura
    document.getElementById('output').textContent = userInput;
}

// 🔍 Test XSS en Console
unsafeCode('<script>alert("XSS")</script>');

Shortcuts que ahorran tiempo

Ctrl+Shift+C Modo inspector (pick element)
Ctrl+Shift+J Abrir Console directamente
F8 Pause/Resume script execution
F10 Step over (siguiente línea)
Console shortcuts avanzados 📋
// 🎯 Shortcuts útiles en Console

// Limpiar console
clear();                    // o Ctrl+L

// Última expresión evaluada
$_                         // Resultado de la última operación

// Último elemento inspeccionado
$0                         // Último elemento seleccionado
$1                         // Penúltimo elemento

// Buscar funciones
dir(window)                // Lista todas las propiedades de window
keys(localStorage)       // Claves del localStorage
values(localStorage)     // Valores del localStorage

Más bugs para practicar

La teoría se asienta practicando. Aquí tienes tres bugs más para encontrar con DevTools, cada uno con una causa distinta:

Ejercicios adicionales para practicar 📋
// 🐛 Bug #4: Array manipulation error
function removeItem(arr, index) {
    // ❌ Problema: modifica el array original
    return arr.splice(index, 1);
}

// 🐛 Bug #5: Async/await sin error handling
async function loadData() {
    // ❌ Si la API falla, la app crashea
    const response = await fetch('/api/data');
    const data = await response.json();
    return data;
}

// 🐛 Bug #6: Event listener leak
function setupButton() {
    const button = document.querySelector('#my-button');
    // ❌ Añade listener cada vez que se llama
    button.addEventListener('click', handleClick);
}

Cómo sacarle partido al ejercicio

  1. Crea el Bug Hunter Dashboard con los seis bugs
  2. Antes de mirar el código, intenta identificar cada bug solo con DevTools
  3. Anota qué panel usaste para cada uno y cuánto tardaste
  4. La segunda vez que lo hagas, verás que tardas la mitad

El siguiente paso natural es aprender a escribir tests. Un breakpoint te dice dónde está el bug cuando ya existe. Un test te avisa antes de que llegue a producción.

El siguiente paso: que los bugs no lleguen a producción

DevTools te ayuda a encontrar bugs. Los tests te ayudan a evitar que aparezcan. Si quieres combinar las dos habilidades, empieza por aquí:

Métodos de array en JavaScript: map, filter y reduce

El siguiente bloque de JavaScript que más vas a usar, con ejemplos que puedes probar ahora mismo en la Console de DevTools