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
- Las seis formas, de un vistazo
- El
forclásico: control total for...of: la opción por defectofor...insobre arrays: el error frecuenteforEach: recorrer con una función- Por qué
forEachno se puede cortar entries(),keys()yvalues()map: recorrer para transformar- La tabla comparativa
- Recorrer arrays de objetos y arrays anidados
- El error de mutar el array mientras se recorre
- Ejemplo integrado: pintar el tablero de Taller Nómada
- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- 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"]
- El
for clásico: control total
for clásico: control totalLo 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
}
for...of: la opción por defecto
for...of: la opción por defectofor...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:
- No hay índices que equivocar. Desaparecen el
i, el<, el++y el riesgo de off-by-one. - Se puede interrumpir con
break,continueyreturn, igual que unforclásico. constfunciona de verdad. Cada vuelta crea una variable nueva, así que puedes declararlaconst(algo imposible en elforclásico, donde el contador cambia).- Funciona con cualquier iterable, no solo con arrays: strings,
Map,Set,NodeListdel 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).
for...in sobre arrays: el error frecuente
for...in sobre arrays: el error frecuentefor...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 14Problema 1: los índices son strings. El + con strings concatena, así que cualquier aritmética sobre el índice falla en silencio:
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 elementosHay 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...inpara objetos;for...ofpara arrays. Si ves unfor...insobre un array en código ajeno, casi siempre es un error o código anterior a 2015.
forEach: recorrer con una función
forEach: recorrer con una funciónforEach 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:
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}`);
}
});
- Por qué
forEach no se puede cortar
forEach no se puede cortarEste 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.
entries(), keys() y values()
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íaentries() 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 reservasCompara 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);
map: recorrer para transformar
map: recorrer para transformarmap 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, intactoSus tres propiedades definitorias, que conviene grabar:
- El array resultante tiene siempre la misma longitud que el original.
mapno filtra ni descarta: si entran seis, salen seis. Para quedarte con menos,filter(04-05). - No muta el original. Devuelve uno nuevo.
- El callback debe devolver algo. Olvidar el
returnes el error número uno conmap.
// ✗ 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íaLa 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.
- 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 | Sí, espera en cada vuelta |
for...of |
No (usa entries()) |
Sí | Nada | Sí, espera en cada vuelta |
for...in |
Sí, pero como string | Sí | Nada | Sí (pero no lo uses con arrays) |
forEach |
Sí (2.º argumento) | No | undefined |
No: no espera a los callbacks |
entries() + for...of |
Sí | Sí | Nada | Sí |
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:
- ¿Vas a transformar la lista en otra lista? →
map. - ¿Vas a interrumpir el recorrido, o hay asincronía? →
for...of(conentries()si necesitas el índice). - ¿Solo quieres efectos sobre cada elemento, sin cortar? →
forEachofor...of, a gusto del equipo.
- 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 etiquetasCuidado 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); // 4Y 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
- 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 malAñadir dentro de for...of: bucle infinito, porque el iterador sigue encontrando elementos nuevos.
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
}
}
- 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];:
- Muestra cada valor con su posición usando
entries(). - Calcula el total con un
for...of. - Crea con
mapun arrayenJornadascon las horas divididas entre 8, redondeadas a un decimal. - 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)); // nullforEach 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, comprasLa 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
- ¿Qué es JavaScript?
- Configuración de tu Entorno de Desarrollo
- Tu Primer Programa en JavaScript
- Sintaxis y Conceptos Básicos de JavaScript
- Variables y Tipos de Datos
- Operadores Básicos
- Conversión de Tipos y Comparaciones
- El Proyecto del Curso: Nómada Tareas
Módulo 2: Estructuras de Control
- Sentencias Condicionales
- Bucles: for, while, do-while
- Sentencias Switch
- Control del Flujo: break, continue y Bucles Anidados
- Manejo de Errores con try-catch
Módulo 3: Funciones
- Definición y Llamada de Funciones
- Expresiones de Función y Funciones Flecha
- Parámetros y Valores de Retorno
- Ámbito y Closures
- Hoisting y el Contexto de Ejecución
- Funciones de Orden Superior
- Recursividad
Módulo 4: Objetos y Arrays
- Introducción a los Objetos
- Métodos de Objeto y la Palabra Clave
this - Arrays: Conceptos Básicos y Métodos
- Iteración sobre Arrays
- Buscar, Ordenar y Agregar Datos: find, sort y reduce
- Desestructuración de Arrays
- Desestructuración de Objetos, Spread y Rest
- JSON y Copias de Objetos
Módulo 5: Objetos y Funciones Avanzadas
- Prototipos y Herencia
- Clases y Programación Orientada a Objetos
- Encapsulación: Getters, Setters y Campos Privados
- Módulos e Importación/Exportación
- JavaScript Asíncrono: Callbacks
- Promesas y Async/Await
- El Bucle de Eventos y la Cola de Microtareas
- Iteradores y Generadores
Módulo 6: El Modelo de Objetos del Documento (DOM)
- Introducción al DOM
- Selección y Manipulación de Elementos del DOM
- Manejo de Eventos
- Propagación, Delegación y Eventos Personalizados
- Creación y Eliminación de Elementos del DOM
- Renderizado de Listas y Plantillas HTML
- Manejo y Validación de Formularios
Módulo 7: APIs del Navegador y Temas Avanzados
- Almacenamiento Local y de Sesión
- Fetch API y AJAX
- Peticiones Robustas: Errores, Timeouts y AbortController
- WebSockets
- Service Workers y Aplicaciones Web Progresivas (PWAs)
- APIs del Navegador Esenciales
- Introducción a WebAssembly
Módulo 8: Pruebas y Depuración
- Depuración de JavaScript
- Calidad de Código: ESLint, Prettier y Convenciones
- Pruebas Unitarias con Jest
- Dobles de Prueba: Mocks, Stubs y Spies
- Pruebas de Integración
- Pruebas de Extremo a Extremo con Cypress
Módulo 9: Rendimiento y Optimización
- Medir Antes de Optimizar: DevTools y Web Vitals
- Optimización del Rendimiento de JavaScript
- Gestión de Memoria
- Manipulación Eficiente del DOM
- Carga Perezosa y División de Código
Módulo 10: Frameworks y Librerías de JavaScript
- Por Qué Existen los Frameworks
- Introducción a React
- Gestión de Estado con Redux
- Conceptos Básicos de Vue.js
- Conceptos Básicos de Angular
- Elegir el Framework Adecuado
