La lección anterior terminó con una observación incómoda: casi todas las funciones que escribiste recorrían la lista a mano con un bucle, y no siempre con el mismo. Es normal, porque JavaScript ofrece seis formas distintas de recorrer un array y ninguna es la mejor en todos los casos. Elegir bien no es una cuestión estética: hay formas que te dan el índice y otras que no, formas que se pueden interrumpir a mitad y otras que no, formas que devuelven un resultado y formas que solo producen efectos. En esta lección las verás las seis con sus reglas exactas, construirás la tabla comparativa que resuelve la elección en cinco segundos, y las aplicarás a dos tareas reales de Nómada Tareas: pintar el tablero de Taller Nómada por consola y transformar el backlog en líneas de informe para Marta.

Contenido

  1. Las seis formas, de un vistazo
  2. El for clásico: control total
  3. for...of: la opción por defecto
  4. for...in sobre arrays: el error frecuente
  5. forEach: recorrer con una función
  6. Por qué forEach no se puede cortar
  7. entries(), keys() y values()
  8. map: recorrer para transformar
  9. La tabla comparativa
  10. Recorrer arrays de objetos y arrays anidados
  11. El error de mutar el array mientras se recorre
  12. Ejemplo integrado: pintar el tablero de Taller Nómada
  13. Errores Comunes y Consejos
  14. Ejercicios
  15. Conclusión

  1. Las seis formas, de un vistazo

Antes del detalle, aquí están las seis recorriendo el mismo array:

const responsables = ['Iván', 'Marta', 'Lucía'];

// 1. for clásico
for (let i = 0; i < responsables.length; i++) console.log(responsables[i]);

// 2. for...of
for (const persona of responsables) console.log(persona);

// 3. for...in  (¡sobre arrays casi siempre es un error!)
for (const indice in responsables) console.log(responsables[indice]);

// 4. forEach
responsables.forEach((persona) => console.log(persona));

// 5. entries() para tener índice y valor
for (const par of responsables.entries()) console.log(par[0], par[1]);

// 6. map (recorre transformando)
const enMayusculas = responsables.map((persona) => persona.toUpperCase());

Las seis imprimen (o producen) lo esperado con este array. Las diferencias aparecen en cuanto necesitas el índice, quieres salir antes de tiempo, el array tiene huecos o dentro del bucle hay operaciones asíncronas.

flowchart TD
    A["Quiero recorrer un array"] --> B{"¿Necesito<br/>transformarlo?"}
    B -->|Sí| C["map"]
    B -->|No| D{"¿Necesito<br/>interrumpir?"}
    D -->|Sí| E["for...of<br/>(o for clásico)"]
    D -->|No| F{"¿Necesito<br/>el índice?"}
    F -->|Sí| G["for...of + entries()"]
    F -->|No| H["for...of o forEach"]

  1. El for clásico: control total

Lo conoces desde 02-02. Su virtud es que tú controlas el contador, así que puedes hacer cosas que ninguna otra forma permite.

const tareas = ['T1', 'T2', 'T3', 'T4', 'T5', 'T6'];

// Recorrido normal
for (let i = 0; i < tareas.length; i++) {
  console.log(`${i + 1}. ${tareas[i]}`);
}

// Hacia atrás
for (let i = tareas.length - 1; i >= 0; i--) {
  console.log(tareas[i]);       // T6, T5, T4...
}

// De dos en dos
for (let i = 0; i < tareas.length; i += 2) {
  console.log(tareas[i]);       // T1, T3, T5
}

// Comparar cada elemento con el siguiente
for (let i = 0; i < tareas.length - 1; i++) {
  console.log(`${tareas[i]} va antes que ${tareas[i + 1]}`);
}

Esos cuatro casos —hacia atrás, con salto, parando antes del final, mirando al vecino— son la razón por la que el for clásico sigue vivo. Fuera de ellos, resulta ruidoso: hay que escribir tres expresiones, declarar una variable que solo sirve de contador y acceder con tareas[i] en lugar de trabajar con el elemento directamente. Y tiene un riesgo propio: el desbordamiento por uno (off-by-one), es decir, escribir <= en lugar de < y leer undefined en la última vuelta.

for (let i = 0; i <= tareas.length; i++) {   // ✗ una vuelta de más
  console.log(tareas[i]);                    // la última imprime undefined
}

  1. for...of: la opción por defecto

for...of recorre los valores del array, sin contador y sin índices.

const backlog = [
  { id: 1, titulo: 'Rediseñar la sala polivalente',         responsable: 'Iván',  horasEstimadas: 12, estado: 'en-curso' },
  { id: 2, titulo: 'Cartelería del taller de serigrafía',   responsable: 'Marta', horasEstimadas: 6,  estado: 'pendiente' },
  { id: 3, titulo: 'Actualizar la web de reservas',         responsable: 'Lucía', horasEstimadas: 14, estado: 'pendiente' },
  { id: 4, titulo: 'Inventario de tintas de serigrafía',    responsable: 'Marta', horasEstimadas: 3,  estado: 'hecha' },
  { id: 5, titulo: 'Guía de encuadernación para residentes',responsable: 'Iván',  horasEstimadas: 8,  estado: 'en-curso' },
  { id: 6, titulo: 'Presupuesto de la carpintería',         responsable: 'Iván',  horasEstimadas: 5,  estado: 'pendiente' }
];

for (const tarea of backlog) {
  console.log(`${tarea.titulo} — ${tarea.responsable}`);
}

Cuatro motivos por los que es la opción por defecto:

  1. No hay índices que equivocar. Desaparecen el i, el <, el ++ y el riesgo de off-by-one.
  2. Se puede interrumpir con break, continue y return, igual que un for clásico.
  3. const funciona de verdad. Cada vuelta crea una variable nueva, así que puedes declararla const (algo imposible en el for clásico, donde el contador cambia).
  4. Funciona con cualquier iterable, no solo con arrays: strings, Map, Set, NodeList del DOM (Módulo 6)…
// break y continue funcionan con normalidad
for (const tarea of backlog) {
  if (tarea.estado === 'hecha') continue;         // salta las terminadas
  if (tarea.horasEstimadas > 12) {
    console.log(`Demasiado grande: ${tarea.titulo}`);
    break;                                         // sale del bucle
  }
}

Su única carencia es que no da el índice. Para eso está entries() (apartado 7).

  1. for...in sobre arrays: el error frecuente

for...in está pensado para objetos, como viste en 04-01: recorre las claves. Sobre un array recorre los índices, pero como strings, y eso genera dos problemas.

const horas = [12, 6, 14];

for (const i in horas) {
  console.log(typeof i, i, horas[i]);
}
// string 0 12
// string 1 6
// string 2 14

Problema 1: los índices son strings. El + con strings concatena, así que cualquier aritmética sobre el índice falla en silencio:

for (const i in horas) {
  console.log(`Tarea ${i + 1}`);     // ✗ 'Tarea 01', 'Tarea 11', 'Tarea 21'
}

Problema 2: recorre todas las propiedades enumerables, no solo los índices. Incluidas las que alguien haya añadido al array o al prototipo:

const etiquetas = ['espacio', 'diseño'];
etiquetas.autor = 'Lucía';

for (const clave in etiquetas) console.log(clave);
// 0
// 1
// autor      ← ✗ no es un elemento

for (const valor of etiquetas) console.log(valor);
// espacio
// diseño     ← ✓ solo elementos

Hay un tercer detalle: for...in salta los huecos de un array disperso, mientras que for...of los recorre devolviendo undefined. Sumado a lo anterior, la conclusión es tajante:

for...in para objetos; for...of para arrays. Si ves un for...in sobre un array en código ajeno, casi siempre es un error o código anterior a 2015.

  1. forEach: recorrer con una función

forEach es el primero de los métodos de orden superior que estudiaste en 03-06, y ahora lo usas en su versión nativa. Recibe un callback al que llama una vez por elemento, con tres argumentos: valor, índice y el array completo.

backlog.forEach((tarea, indice, array) => {
  console.log(`${indice + 1} de ${array.length}: ${tarea.titulo}`);
});
// 1 de 6: Rediseñar la sala polivalente
// 2 de 6: Cartelería del taller de serigrafía
// ...

En la práctica el tercer argumento casi nunca se usa, y muchas veces tampoco el segundo:

backlog.forEach((tarea) => console.log(tarea.titulo));

Sus características, en positivo y en negativo:

  • Devuelve undefined. No produce nada; solo sirve para efectos (imprimir, guardar, modificar algo de fuera).
  • Da el índice sin esfuerzo, a diferencia de for...of.
  • Salta los huecos de un array disperso.
  • No se puede interrumpir: es el tema del apartado siguiente.
  • Cada elemento se procesa en una llamada a función, con su propio ámbito, lo que evita por completo el problema de la variable compartida que estudiaste en 03-04 con var.

Un uso típico en el proyecto: notificar a los responsables de las tareas vencidas.

const HOY = '2026-09-20';

backlog.forEach((tarea) => {
  if (tarea.fechaLimite < HOY && tarea.estado !== 'hecha') {
    console.log(`⚠ ${tarea.responsable}: «${tarea.titulo}» venció el ${tarea.fechaLimite}`);
  }
});

  1. Por qué forEach no se puede cortar

Este es el punto que más confunde. Un break dentro de forEach es directamente un error de sintaxis:

backlog.forEach((tarea) => {
  if (tarea.id === 3) break;      // ✗ SyntaxError: Illegal break statement
});

La razón es estructural. break sirve para salir de un bucle, y dentro de forEach no estás en un bucle: estás dentro del cuerpo de una función que forEach invoca una y otra vez. Recuerda cómo implementaste miForEach en 03-06:

function miForEach(array, callback) {
  for (let i = 0; i < array.length; i++) {
    callback(array[i], i, array);       // ← el bucle está AQUÍ, fuera de tu función
  }
}

Tu callback no tiene ningún poder sobre ese for. Y return dentro del callback tampoco corta nada: simplemente termina esa llamada y forEach pasa al elemento siguiente. Funciona como un continue, no como un break:

backlog.forEach((tarea) => {
  if (tarea.estado === 'hecha') return;    // ← actúa como continue
  console.log(tarea.titulo);
});

¿Qué hacer cuando necesitas parar de verdad?

Situación Solución
Necesitas parar en cuanto encuentres algo for...of con break
Buscas un elemento find / findIndex (04-05)
Solo quieres saber si alguno cumple some (04-05)
Quieres saber si todos cumplen every (04-05)
Quieres quedarte con varios filter (04-05)

Y un antipatrón que verás y no debes copiar: lanzar una excepción para forzar la salida.

// ✗ Nunca hagas esto: usar el sistema de errores como control de flujo
try {
  backlog.forEach((t) => { if (t.id === 3) throw new Error('encontrado'); });
} catch (e) { }

Es lento, oculta errores reales y confunde a quien lea el código. Si necesitas break, usa for...of.

  1. entries(), keys() y values()

Estos tres métodos devuelven iteradores (una estructura que produce valores bajo demanda; los verás a fondo en 05-08), pensados para usarse con for...of.

const responsables = ['Iván', 'Marta', 'Lucía'];

// keys(): los índices
for (const i of responsables.keys()) console.log(i);          // 0, 1, 2

// values(): los valores (equivale a for...of directo)
for (const v of responsables.values()) console.log(v);        // Iván, Marta, Lucía

// entries(): pares [índice, valor]
for (const par of responsables.entries()) {
  console.log(`${par[0]} → ${par[1]}`);
}
// 0 → Iván
// 1 → Marta
// 2 → Lucía

entries() resuelve exactamente la carencia de for...of: te da índice y valor, conservando la posibilidad de usar break. Con desestructuración —que estudiarás en 04-06— se escribe de la forma en que realmente se usa:

for (const [indice, tarea] of backlog.entries()) {
  console.log(`${indice + 1}. ${tarea.titulo}`);
  if (indice === 2) break;             // ← aquí break SÍ funciona
}
// 1. Rediseñar la sala polivalente
// 2. Cartelería del taller de serigrafía
// 3. Actualizar la web de reservas

Compara las tres alternativas cuando necesitas índice y valor:

// A. for clásico: funciona, pero es ruidoso
for (let i = 0; i < backlog.length; i++) console.log(i, backlog[i].titulo);

// B. forEach: legible, pero no se puede cortar
backlog.forEach((t, i) => console.log(i, t.titulo));

// C. for...of + entries(): legible Y se puede cortar   ← la mejor de las tres
for (const [i, t] of backlog.entries()) console.log(i, t.titulo);

  1. map: recorrer para transformar

map merece un apartado aparte porque cambia el propósito del recorrido. forEach y for...of recorren para hacer algo; map recorre para producir un array nuevo con un elemento por cada elemento original.

Lo implementaste a mano en 03-06 como miMap; el nativo funciona igual:

const titulos = backlog.map((tarea) => tarea.titulo);
console.log(titulos.length);     // 6
console.log(titulos[0]);         // 'Rediseñar la sala polivalente'

const enMinutos = backlog.map((tarea) => tarea.horasEstimadas * 60);
console.log(enMinutos);          // [ 720, 360, 840, 180, 480, 300 ]

console.log(backlog.length);     // 6  ← el original, intacto

Sus tres propiedades definitorias, que conviene grabar:

  1. El array resultante tiene siempre la misma longitud que el original. map no filtra ni descarta: si entran seis, salen seis. Para quedarte con menos, filter (04-05).
  2. No muta el original. Devuelve uno nuevo.
  3. El callback debe devolver algo. Olvidar el return es el error número uno con map.
// ✗ El error clásico: llaves sin return
const mal = backlog.map((tarea) => { tarea.titulo; });
console.log(mal);        // [ undefined, undefined, undefined, undefined, undefined, undefined ]

// ✓ Con return explícito
const bienA = backlog.map((tarea) => { return tarea.titulo; });

// ✓ O con retorno implícito, sin llaves
const bienB = backlog.map((tarea) => tarea.titulo);

Esta distinción viene directamente de 03-02: una flecha sin llaves devuelve la expresión; una flecha con llaves necesita return explícito.

Aplicación al proyecto: convertir el backlog en líneas de informe. Compara el mismo trabajo con forEach y con map:

const MARCAS = { hecha: '✓', 'en-curso': '▸', pendiente: '○' };

// Con forEach: hay que crear un array auxiliar y empujar
const lineasA = [];
backlog.forEach((tarea) => {
  lineasA.push(`${MARCAS[tarea.estado]} [${tarea.id}] ${tarea.titulo}`);
});

// Con map: el array es el resultado, sin variables auxiliares
const lineasB = backlog.map((tarea) => `${MARCAS[tarea.estado]} [${tarea.id}] ${tarea.titulo}`);

console.log(lineasB.join('\n'));
// ▸ [1] Rediseñar la sala polivalente
// ○ [2] Cartelería del taller de serigrafía
// ○ [3] Actualizar la web de reservas
// ✓ [4] Inventario de tintas de serigrafía
// ▸ [5] Guía de encuadernación para residentes
// ○ [6] Presupuesto de la carpintería

La versión con map es más corta, no necesita lineasA declarado antes, y sobre todo dice lo que hace: «cada tarea se convierte en una línea». Regla de estilo: si dentro de un forEach lo único que haces es push a un array, lo que querías era map.

  1. La tabla comparativa

Esta es la tabla de referencia de la lección:

Forma ¿Da índice? ¿Se puede cortar? ¿Qué devuelve? ¿Y con await dentro?
for clásico Sí (lo llevas tú) Sí (break, continue, return) Nada , espera en cada vuelta
for...of No (usa entries()) Nada , espera en cada vuelta
for...in Sí, pero como string Nada Sí (pero no lo uses con arrays)
forEach Sí (2.º argumento) No undefined No: no espera a los callbacks
entries() + for...of Nada
map Sí (2.º argumento) No Array nuevo No directamente: produce promesas

Las dos últimas columnas anticipan un problema real que resolverás en Promesas y Async/Await. Adelanto en dos líneas: si dentro de un forEach pones un await, el forEach no espera y el programa continúa antes de que las operaciones terminen; con for...of, en cambio, cada vuelta espera a la anterior. No necesitas entenderlo aún, pero sí saber que la elección del bucle importará cuando llegue la asincronía.

Cómo elegir, en tres reglas:

  1. ¿Vas a transformar la lista en otra lista? → map.
  2. ¿Vas a interrumpir el recorrido, o hay asincronía? → for...of (con entries() si necesitas el índice).
  3. ¿Solo quieres efectos sobre cada elemento, sin cortar? → forEach o for...of, a gusto del equipo.

  1. Recorrer arrays de objetos y arrays anidados

Un array de objetos se recorre exactamente igual; lo único que cambia es que dentro del bucle trabajas con propiedades:

for (const tarea of backlog) {
  console.log(`${tarea.titulo}: ${tarea.horasEstimadas} h (${tarea.responsable})`);
}

Cuando el objeto contiene a su vez un array —como etiquetas— aparece un bucle dentro de otro, la técnica de 02-04:

const conEtiquetas = [
  { id: 1, titulo: 'Rediseñar la sala polivalente', etiquetas: ['espacio', 'diseño'] },
  { id: 4, titulo: 'Inventario de tintas de serigrafía', etiquetas: ['serigrafía', 'almacén'] },
  { id: 7, titulo: 'Revisar la guillotina', etiquetas: [] }
];

for (const tarea of conEtiquetas) {
  if (tarea.etiquetas.length === 0) {
    console.log(`${tarea.titulo}: sin etiquetas`);
    continue;
  }
  for (const etiqueta of tarea.etiquetas) {
    console.log(`${tarea.titulo} → #${etiqueta}`);
  }
}
// Rediseñar la sala polivalente → #espacio
// Rediseñar la sala polivalente → #diseño
// Inventario de tintas de serigrafía → #serigrafía
// Inventario de tintas de serigrafía → #almacén
// Revisar la guillotina: sin etiquetas

Cuidado con break en bucles anidados: rompe solo el bucle más interno. Si necesitas salir de los dos, usa una etiqueta de bucle (02-04) o extrae el doble bucle a una función y usa return:

function buscarTareaConEtiqueta(tareas, buscada) {
  for (const tarea of tareas) {
    for (const etiqueta of tarea.etiquetas) {
      if (etiqueta === buscada) return tarea;      // sale de LOS DOS bucles
    }
  }
  return null;
}

console.log(buscarTareaConEtiqueta(conEtiquetas, 'almacén').id);   // 4

Y para una matriz (array de arrays) el patrón es el mismo:

const horasPorSemana = [
  [8, 6, 7, 5, 4],      // semana 1
  [6, 6, 8, 8, 2]       // semana 2
];

for (const [s, semana] of horasPorSemana.entries()) {
  let total = 0;
  for (const horas of semana) total += horas;
  console.log(`Semana ${s + 1}: ${total} h`);
}
// Semana 1: 30 h
// Semana 2: 30 h

  1. El error de mutar el array mientras se recorre

Ya lo apuntamos en 04-03; aquí toca entenderlo bien, porque cada forma de recorrer reacciona de un modo distinto.

Borrar mientras recorres con índices: los elementos se desplazan y te saltas alguno.

const tareas = ['a', 'b', 'b', 'c'];
for (let i = 0; i < tareas.length; i++) {
  if (tareas[i] === 'b') tareas.splice(i, 1);
}
console.log(tareas);      // [ 'a', 'b', 'c' ]   ✗ queda una 'b'

Borrar dentro de forEach: mismo problema, y además forEach fija el rango al empezar, así que puede leer posiciones que ya no existen.

const tareas2 = ['a', 'b', 'b', 'c'];
tareas2.forEach((t, i) => { if (t === 'b') tareas2.splice(i, 1); });
console.log(tareas2);     // [ 'a', 'b', 'c' ]   ✗ igual de mal

Añadir dentro de for...of: bucle infinito, porque el iterador sigue encontrando elementos nuevos.

// ✗ NO EJECUTES ESTO
// for (const t of tareas) { tareas.push(t); }

Las tres soluciones correctas, en orden de preferencia:

// 1. La mejor: no mutar, crear un array nuevo (filter, lección 04-05)
const sinBes = ['a', 'b', 'b', 'c'].filter((t) => t !== 'b');
console.log(sinBes);      // [ 'a', 'c' ]

// 2. Recorrer hacia atrás: los índices pendientes no se mueven
const tareas3 = ['a', 'b', 'b', 'c'];
for (let i = tareas3.length - 1; i >= 0; i--) {
  if (tareas3[i] === 'b') tareas3.splice(i, 1);
}
console.log(tareas3);     // [ 'a', 'c' ]   ✓

// 3. Recorrer una copia y modificar el original
const tareas4 = ['a', 'b', 'b', 'c'];
for (const t of tareas4.slice()) {
  if (t === 'b') tareas4.splice(tareas4.indexOf(t), 1);
}
console.log(tareas4);     // [ 'a', 'c' ]   ✓

Regla de oro: el array que estás recorriendo es de solo lectura mientras dura el recorrido. Si tienes que cambiarlo, produce otro array.

Un matiz que sí es seguro: modificar los objetos de dentro no cambia la estructura del array y por tanto no rompe el recorrido.

for (const tarea of backlog) {
  if (tarea.fechaLimite < HOY && tarea.estado !== 'hecha') {
    tarea.prioridad = 'alta';      // ✓ seguro: cambia el objeto, no la lista
  }
}

  1. Ejemplo integrado: pintar el tablero de Taller Nómada

Marta quiere ver el tablero por consola, agrupado por estado, con numeración y con un pie de resumen. Cada bloque del código usa la forma de recorrer que mejor le encaja, y ese es el ejercicio real de esta lección.

'use strict';

const HOY = '2026-09-20';
const MARCAS = { pendiente: '○', 'en-curso': '▸', hecha: '✓' };
const ORDEN_ESTADOS = ['en-curso', 'pendiente', 'hecha'];
const NOMBRES_ESTADO = { pendiente: 'PENDIENTES', 'en-curso': 'EN CURSO', hecha: 'TERMINADAS' };

/** Devuelve la línea de una tarea. Usa map + join para las etiquetas. */
function lineaDeTarea(tarea, hoy) {
  const vencida = tarea.fechaLimite < hoy && tarea.estado !== 'hecha';
  const etiquetas = tarea.etiquetas.map((e) => `#${e}`).join(' ');
  return `${MARCAS[tarea.estado]} [${tarea.id}] ${tarea.titulo} · ${tarea.responsable} · ` +
         `${tarea.horasEstimadas} h · ${etiquetas}${vencida ? ' ⚠ VENCIDA' : ''}`;
}

function pintarTablero(tareas, hoy) {
  console.log(`═══ Tablero de Taller Nómada — ${hoy} ═══`);

  // for...of sobre los estados: recorrido con orden controlado por nosotros
  for (const estado of ORDEN_ESTADOS) {
    // Aquí necesitamos numeración dentro de cada bloque: contador explícito
    let numero = 0;
    const cabecera = NOMBRES_ESTADO[estado];
    const lineas = [];

    // for...of sobre las tareas, con continue para saltar las de otro estado
    for (const tarea of tareas) {
      if (tarea.estado !== estado) continue;
      numero++;
      lineas.push(`  ${numero}. ${lineaDeTarea(tarea, hoy)}`);
    }

    console.log(`\n${cabecera} (${lineas.length})`);
    if (lineas.length === 0) {
      console.log('  — sin tareas —');
      continue;
    }
    // forEach: solo efectos, sin necesidad de cortar
    lineas.forEach((linea) => console.log(linea));
  }

  // Pie de resumen: acumuladores en un solo recorrido (patrón de 02-02)
  let abiertas = 0;
  let horasAbiertas = 0;
  let vencidas = 0;
  for (const tarea of tareas) {
    if (tarea.estado === 'hecha') continue;
    abiertas++;
    horasAbiertas += tarea.horasEstimadas;
    if (tarea.fechaLimite < hoy) vencidas++;
  }

  console.log(`\n───────────────────────────────────────`);
  console.log(`${abiertas} abiertas · ${horasAbiertas} h pendientes · ${vencidas} vencida(s)`);
}

pintarTablero(backlogCompleto, HOY);

Salida (con el backlog canónico de 04-01):

═══ Tablero de Taller Nómada — 2026-09-20 ═══

EN CURSO (2)
  1. ▸ [1] Rediseñar la sala polivalente · Iván · 12 h · #espacio #diseño
  2. ▸ [5] Guía de encuadernación para residentes · Iván · 8 h · #encuadernación #documentación

PENDIENTES (3)
  1. ○ [2] Cartelería del taller de serigrafía · Marta · 6 h · #serigrafía #comunicación
  2. ○ [3] Actualizar la web de reservas · Lucía · 14 h · #web #reservas
  3. ○ [6] Presupuesto de la carpintería · Iván · 5 h · #carpintería #compras ⚠ VENCIDA

TERMINADAS (1)
  1. ✓ [4] Inventario de tintas de serigrafía · Marta · 3 h · #serigrafía #almacén

───────────────────────────────────────
5 abiertas · 45 h pendientes · 1 vencida(s)

Repasa las decisiones tomadas, porque son el resumen práctico de la tabla del apartado 9:

Dónde Forma elegida Por qué
Recorrer los estados for...of con continue Orden fijo, y hace falta saltar bloques
Filtrar tareas por estado for...of con continue Hace falta interrumpir con continue y llevar un contador propio
Convertir etiquetas en #etiqueta map + join Transformación pura de una lista en otra
Imprimir las líneas ya montadas forEach Solo efectos, no hay nada que cortar
Calcular el resumen for...of con acumuladores Tres totales en un solo recorrido

En la lección siguiente verás que ese doble bucle de filtrado y ese bloque de acumuladores se escriben en dos líneas con filter y reduce. Pero conviene haberlo hecho antes a mano: los métodos de 04-05 no son magia, son estos bucles con nombre.

Errores Comunes y Consejos

1. Usar for...in con arrays. Índices como string y propiedades inesperadas. Usa for...of.

2. Intentar break dentro de forEach. Es un SyntaxError. Cambia a for...of, o usa find/some (04-05).

3. Creer que return dentro de forEach corta el recorrido. Solo termina esa vuelta; funciona como continue.

4. Olvidar el return en el callback de map. Obtienes un array lleno de undefined. Recuerda: flecha con llaves → hace falta return.

5. Usar map por sus efectos secundarios. Si no vas a usar el array que devuelve, lo que querías era forEach:

backlog.map((t) => console.log(t.titulo));      // ✗ crea un array de undefined que nadie usa
backlog.forEach((t) => console.log(t.titulo));  // ✓

6. Mutar el array mientras lo recorres. Recorre hacia atrás, recorre una copia, o —mucho mejor— construye un array nuevo.

7. Recorrer varias veces lo mismo por comodidad. Si necesitas tres totales, sácalos en un solo recorrido con tres acumuladores, no en tres bucles. Con seis tareas es irrelevante; con veinte mil no lo es (Módulo 9).

Consejo profesional. Escribe el bucle pensando en quien lo lea dentro de seis meses. for (const tarea of backlog) se entiende sin pensar; for (let i = 0; i < b.length; i++) obliga a comprobar mentalmente los límites. Reserva el for clásico para los cuatro casos en que su control extra es imprescindible: hacia atrás, con salto, parando antes del final o comparando con el vecino.

Ejercicios

Ejercicio 1 — Cuatro recorridos. Con el array const horas = [12, 6, 14, 3, 8, 5];:

  1. Muestra cada valor con su posición usando entries().
  2. Calcula el total con un for...of.
  3. Crea con map un array enJornadas con las horas divididas entre 8, redondeadas a un decimal.
  4. Muestra solo los tres primeros valores, cortando el recorrido con break.

Ejercicio 2 — Primer incumplimiento. Escribe primeraTareaSobrecargada(tareas, limite) que devuelva la primera tarea cuyas horasEstimadas superen el límite, o null si no hay ninguna. Debe dejar de recorrer en cuanto la encuentre. Explica por qué forEach no sirve aquí. Pruébala con limite = 10 sobre el backlog.

Ejercicio 3 — Informe de etiquetas por responsable. Con el backlog canónico (campos responsable y etiquetas), escribe lineasPorResponsable(tareas) que devuelva un array de strings, uno por tarea, con el formato Iván · Rediseñar la sala polivalente · espacio, diseño. Después escribe imprimirInforme(tareas) que lo imprima numerado. Usa map para lo primero y forEach con índice para lo segundo, y justifica la elección.

Soluciones

Ejercicio 1

const horas = [12, 6, 14, 3, 8, 5];

// 1. entries() da índice y valor, y permitiría break
for (const par of horas.entries()) {
  console.log(`Posición ${par[0]}: ${par[1]} h`);
}

// 2. Acumulador con for...of
let total = 0;
for (const h of horas) total += h;
console.log(`Total: ${total} h`);        // Total: 48 h

// 3. map devuelve un array nuevo de la misma longitud
const enJornadas = horas.map((h) => Number((h / 8).toFixed(1)));
console.log(enJornadas);                 // [ 1.5, 0.8, 1.8, 0.4, 1, 0.6 ]

// 4. break necesita un bucle de verdad: for...of, no forEach
for (const [i, h] of horas.entries()) {
  if (i === 3) break;
  console.log(h);                        // 12, 6, 14
}

Las 48 h del punto 2 son el total canónico del backlog. En el punto 3, toFixed devuelve un string, por eso lo envolvemos en Number(...): es la conversión explícita de 01-07.

Ejercicio 2

function primeraTareaSobrecargada(tareas, limite) {
  for (const tarea of tareas) {
    if (tarea.horasEstimadas > limite) return tarea;   // sale del bucle y de la función
  }
  return null;
}

const encontrada = primeraTareaSobrecargada(backlog, 10);
console.log(encontrada?.titulo ?? 'ninguna');   // 'Rediseñar la sala polivalente'
console.log(primeraTareaSobrecargada(backlog, 100));   // null

forEach no sirve por dos motivos: no admite break, y un return dentro del callback solo termina esa vuelta, así que seguiría recorriendo las seis tareas aunque la respuesta esté en la primera. Con for...of y return sales del bucle y de la función en la misma instrucción. En 04-05 verás que esto tiene nombre propio y una sola línea: tareas.find((t) => t.horasEstimadas > limite).

Ejercicio 3

function lineasPorResponsable(tareas) {
  return tareas.map((tarea) =>
    `${tarea.responsable} · ${tarea.titulo} · ${tarea.etiquetas.join(', ')}`
  );
}

function imprimirInforme(tareas) {
  const lineas = lineasPorResponsable(tareas);
  lineas.forEach((linea, i) => console.log(`${i + 1}. ${linea}`));
}

imprimirInforme(backlog);
// 1. Iván · Rediseñar la sala polivalente · espacio, diseño
// 2. Marta · Cartelería del taller de serigrafía · serigrafía, comunicación
// 3. Lucía · Actualizar la web de reservas · web, reservas
// 4. Marta · Inventario de tintas de serigrafía · serigrafía, almacén
// 5. Iván · Guía de encuadernación para residentes · encuadernación, documentación
// 6. Iván · Presupuesto de la carpintería · carpintería, compras

La justificación de las elecciones es la regla del apartado 9. lineasPorResponsable transforma una lista de tareas en una lista de strings de la misma longitud: eso es map por definición, y además deja la función pura y probable en el Módulo 8. imprimirInforme solo produce efectos (imprimir) y necesita el índice para numerar: forEach con su segundo argumento encaja exactamente y no hay nada que interrumpir. Separar ambas cosas —una función que construye el texto y otra que lo imprime— es la misma separación entre calcular y mostrar que aplicaste en 03-01 con describirTarea.

Conclusión

Ya no recorres arrays por costumbre, sino por decisión. Conoces las seis formas y sus reglas: el for clásico cuando necesitas control total del contador (hacia atrás, con salto, parando antes del final o mirando al vecino); for...of como opción por defecto, porque elimina los índices, admite break y continue y funciona con cualquier iterable; for...in reservado a objetos, nunca a arrays, porque da índices en forma de string y arrastra propiedades que no son elementos; forEach para efectos, con índice cómodo pero sin posibilidad de interrumpir; entries() cuando necesitas índice y valor sin renunciar al break; y map cuando lo que quieres no es recorrer, sino producir una lista nueva de la misma longitud.

La tabla comparativa —¿da índice?, ¿se puede cortar?, ¿qué devuelve?, ¿espera al await?— resuelve la elección en segundos, y su última columna te deja avisado de un problema que resolverás en 05-06. Has visto también por qué forEach no admite break (el bucle está fuera de tu función, como demostró tu propia miForEach de 03-06), cómo recorrer arrays de objetos y arrays anidados sin perderte, y por qué mutar la lista mientras la recorres es una fuente inagotable de bugs, con sus tres soluciones. Y has pintado el tablero de Taller Nómada eligiendo en cada bloque la forma adecuada, con el resultado de siempre: 5 abiertas, 45 h pendientes, 1 vencida.

Pero fíjate en lo que ha costado ese resultado. Para agrupar por estado has escrito un bucle dentro de otro con un continue. Para el resumen has necesitado tres variables acumuladoras. Para encontrar la primera tarea sobrecargada has escrito una función entera. Y para ordenar el tablero por fecha límite todavía no tienes herramienta ninguna. Todo eso son operaciones tan habituales que el lenguaje las trae ya resueltas y con nombre: buscar es find, comprobar es some y every, seleccionar es filter, ordenar es sort y acumular es reduce. Es el arsenal completo de Buscar, Ordenar y Agregar Datos: find, sort y reduce, donde además te espera la trampa más famosa de JavaScript: por qué [10, 9, 100].sort() no devuelve lo que crees.

Curso de JavaScript: De Principiante a Avanzado

Módulo 1: Introducción a JavaScript

Módulo 2: Estructuras de Control

Módulo 3: Funciones

Módulo 4: Objetos y Arrays

Módulo 5: Objetos y Funciones Avanzadas

Módulo 6: El Modelo de Objetos del Documento (DOM)

Módulo 7: APIs del Navegador y Temas Avanzados

Módulo 8: Pruebas y Depuración

Módulo 9: Rendimiento y Optimización

Módulo 10: Frameworks y Librerías de JavaScript

Módulo 11: Proyecto Final

© Copyright 2026. Todos los derechos reservados