wandres.dev
CONSOLE I · Más que console.log

table, dir y group: dar forma a la salida

Las tres funciones que convierten un volcado ilegible en algo que se lee de un vistazo, con la diferencia crítica entre log y dir sobre nodos del DOM.

⏱ 15 min

Un array de cuarenta objetos volcado con console.log es una lista de cuarenta líneas plegadas que hay que expandir una por una para comparar nada. El mismo array con console.table es una rejilla ordenable donde la anomalía salta a la vista en dos segundos. La diferencia entre ambas experiencias es una palabra, y la mayoría de la gente que depura a diario no usa ninguna de las tres funciones de esta lección.

🎯 Al terminar esta lección sabrás
  • Volcar colecciones con console.table y seleccionar las columnas relevantes.
  • Distinguir el comportamiento de log y de dir sobre nodos del DOM y saber cuándo usar cada uno.
  • Estructurar salida abundante con grupos plegables anidados.
  • Construir vistas tabulares a partir de datos que no vienen en forma de tabla.

console.table

Acepta un array de objetos, un array de arrays, o un objeto de objetos, y dibuja una tabla. Las columnas son las claves; las filas, los elementos; y la primera columna es el índice o la clave.

El segundo argumento, que casi nadie usa, es una lista de columnas. Con objetos de veinte propiedades, es la diferencia entre una tabla que no cabe y una útil.

const pedidos = [
  { id: 1, cliente: 'Ana', total: 120.5, estado: 'enviado', creado: '2026-07-01', notas: '', region: 'ES' },
  { id: 2, cliente: 'Luis', total: 0, estado: 'pendiente', creado: '2026-07-02', notas: 'urgente', region: 'PT' },
  { id: 3, cliente: 'Marta', total: 89.9, estado: 'enviado', creado: '2026-07-02', notas: '', region: 'ES' }
];
console.table(pedidos, ['id', 'cliente', 'total', 'estado']);

Las columnas de la tabla se pueden ordenar pulsando en su cabecera, lo cual convierte el volcado en una herramienta de análisis: ordenar por total encuentra el pedido de cero euros inmediatamente.

La técnica que multiplica su valor es construir la tabla en vez de volcarla. Casi nunca tienes los datos en la forma exacta que quieres, y un map los transforma.

// Los nodos mas anidados de la pagina, con su profundidad
console.table(
  [...document.querySelectorAll('*')]
    .map(el => {
      let d = 0;
      for (let n = el; n.parentElement; n = n.parentElement) d++;
      return { etiqueta: el.tagName.toLowerCase(), clase: String(el.className).slice(0, 30), profundidad: d };
    })
    .sort((a, b) => b.profundidad - a.profundidad)
    .slice(0, 15)
);
// Todas las peticiones de recursos de la pagina, ordenadas por duracion
console.table(
  performance.getEntriesByType('resource')
    .map(r => ({
      recurso: r.name.split('/').pop().slice(0, 40),
      tipo: r.initiatorType,
      ms: Math.round(r.duration),
      kb: Math.round((r.transferSize || 0) / 1024)
    }))
    .sort((a, b) => b.ms - a.ms)
    .slice(0, 20)
);

Ese segundo fragmento es un diagnóstico de red completo sin abrir el panel de Network, y funciona pegado tal cual en cualquier página.

⚠️
Cuidado

console.table no funciona bien con valores anidados: un objeto dentro de una celda se muestra como una referencia poco informativa. Si tus datos tienen estructura, aplánalos en el map antes de volcarlos. Esa es la parte del trabajo que hace útil la herramienta.

log frente a dir

La diferencia más consecuente de toda la API de consola y la que menos gente conoce.

Cuando pasas un nodo del DOM a console.log, la consola lo muestra como marcado HTML expandible, igual que en el panel de Elements. Es cómodo para ver la estructura y oculta por completo el objeto: no ves sus propiedades, ni sus métodos, ni sus manejadores asignados, ni su estado interno.

console.dir muestra el mismo nodo como objeto JavaScript, con su lista completa de propiedades navegable. Ahí sí ves value, checked, dataset, offsetWidth, las propiedades asignadas por librerías, y todo lo demás.

const input = document.querySelector('input');
console.log(input);   // marcado HTML: <input type="text" ...>
console.dir(input);   // objeto: value, checked, validity, dataset, ...

La regla que resuelve la elección: log para ver dónde está, dir para ver qué tiene. Y el caso donde dir es imprescindible es el clásico del campo de formulario cuyo valor no coincide con lo que se ve: el atributo value del HTML es el valor inicial, y la propiedad value del objeto es el actual. Con log ves el atributo, que sigue diciendo lo que decía al cargar. Con dir ves la propiedad, que es la verdad.

Esa distinción entre atributo y propiedad explica una fracción notable de los bugs de formularios que la gente tarda una hora en entender.

Los grupos

console.group abre una sección plegable en la que se anidan todos los mensajes posteriores hasta el console.groupEnd correspondiente. console.groupCollapsed hace lo mismo pero empieza plegado, que es lo que quieres casi siempre.

Se anidan sin límite y respetan la indentación.

function procesar(pedidos) {
  console.groupCollapsed(`Procesando ${pedidos.length} pedidos`);
  for (const p of pedidos) {
    console.groupCollapsed(`Pedido ${p.id} de ${p.cliente}`);
    console.log('total', p.total);
    if (p.total === 0) console.warn('total en cero');
    console.groupEnd();
  }
  console.groupEnd();
}
procesar(pedidos);

El valor real de los grupos no es la estética: es que una salida agrupada se puede recorrer. Con cincuenta iteraciones plegadas, la que tiene un aviso dentro se ve sin expandir nada, porque el contador de la consola cuenta los avisos y el grupo que lo contiene queda marcado. Y expandir solo esa cuesta un clic.

El error clásico con grupos es olvidar un groupEnd en una rama con retorno anticipado, con lo que todo lo posterior queda anidado dentro para siempre. El patrón robusto es try y finally.

function conGrupo(titulo, fn) {
  console.groupCollapsed(titulo);
  try { return fn(); } finally { console.groupEnd(); }
}
conGrupo('validacion', () => { /* aqui puede haber return o throw */ });
Los grupos son gratis en producción y por eso merecen quedarse

Hay un uso de console.group que va más allá de la depuración puntual y que muy poca gente aplica: instrumentar permanentemente los flujos críticos de la aplicación con grupos plegados y nivel debug. El razonamiento es este. Una aplicación tiene entre cinco y quince flujos que concentran la mayoría de los problemas: la autenticación, la sincronización de estado, el envío de un formulario complejo, la cola de reintentos, la hidratación. Cuando uno de esos flujos falla en un entorno donde no puedes poner breakpoints —el móvil de un compañero, un entorno de preproducción, la máquina de alguien de soporte— lo único que tienes es lo que la aplicación cuente de sí misma. Si esos flujos están instrumentados con console.groupCollapsed y console.debug, la persona que reproduce el fallo solo tiene que activar el nivel detallado en el filtro y copiar la consola, y lo que te llega es una traza estructurada y plegable del flujo entero, con los valores en cada paso. Sin instrumentación, lo que te llega es una captura de pantalla y la frase “no funciona”. El coste de tenerlo puesto es casi nulo: al estar en nivel debug no se ve, al estar plegado no ocupa, y si te preocupa el coste de evaluar los argumentos, una comprobación de una bandera al principio de la función de instrumentación lo elimina. Lo que ganas es que tu aplicación sepa explicarse, que es la diferencia entre depurar con información y depurar con conjeturas. Y la puerta de entrada a esa capacidad no es ninguna librería de logging: son dos funciones de la consola que ya tienes.