En la lección anterior conseguiste que el programa decidiera. El problema es que decidía sobre una sola tarea: para clasificar la urgencia de las seis tareas del backlog de Taller Nómada tendrías que copiar el mismo bloque seis veces, y cuando Marta añada la séptima habría que volver a tocar el código. Un bucle resuelve exactamente eso: escribes las instrucciones una vez y le dices al programa cuántas veces debe repetirlas. En esta lección conocerás los tres bucles clásicos de JavaScript —for, while y do...while—, aprenderás a recorrer una lista de tareas por índice y dominarás los cuatro patrones que resuelven el 90 % de los problemas reales: contar, acumular, buscar el máximo y comprobar si existe algo.

Contenido

  1. Qué es un bucle y por qué lo necesitas
  2. Anatomía del bucle for
  3. El bucle while
  4. El bucle do...while
  5. Cuál elegir: tabla comparativa
  6. Recorrer una lista por índice con .length
  7. El backlog de Taller Nómada
  8. Patrón acumulador: horas totales del backlog
  9. Patrón contador: cuántas tareas lleva cada persona
  10. Patrón máximo/mínimo: la tarea más urgente
  11. Patrón bandera: ¿hay alguna tarea vencida?
  12. Bucles infinitos y cómo evitarlos
  13. for...of y for...in: un adelanto
  14. Errores Comunes y Consejos
  15. Ejercicios
  16. Conclusión

  1. Qué es un bucle y por qué lo necesitas

Un bucle (o iteración) es una estructura de control que ejecuta un bloque de sentencias repetidamente mientras se cumpla una condición. Cada pasada completa por el bloque se llama iteración.

Sin bucles, el resumen del backlog se escribe así:

// ✗ Insostenible
console.log(`1. ${titulo1} · ${horas1} h`);
console.log(`2. ${titulo2} · ${horas2} h`);
console.log(`3. ${titulo3} · ${horas3} h`);
// ...y así hasta cuarenta

Con un bucle se escribe una vez y funciona con seis tareas, con cuarenta o con ninguna. Además, si mañana hay que añadir la prioridad al resumen, cambias una línea en lugar de cuarenta.

  1. Anatomía del bucle for

El for es el bucle de referencia cuando sabes de antemano cuántas veces hay que repetir o cuando recorres una lista de principio a fin. Su cabecera contiene tres partes separadas por ;:

for (inicialización; condición; actualización) {
  // cuerpo
}

Un ejemplo mínimo: imprimir los tres primeros números de tarea.

for (let i = 1; i <= 3; i++) {
  console.log(`Procesando la tarea número ${i}`);
}

Salida:

Procesando la tarea número 1
Procesando la tarea número 2
Procesando la tarea número 3

Qué hace cada parte, y en qué orden:

Parte Cuándo se ejecuta En el ejemplo
Inicialización Una sola vez, antes de todo let i = 1
Condición Antes de cada iteración i <= 3
Cuerpo Si la condición fue verdadera console.log(...)
Actualización Después de cada iteración i++

El flujo completo, paso a paso:

flowchart TD
    A["Inicialización<br/>let i = 1"] --> B{"Condición<br/>i <= 3"}
    B -->|false| F["Fin del bucle"]
    B -->|true| C["Cuerpo<br/>console.log(...)"]
    C --> D["Actualización<br/>i++"]
    D --> B

Sigue la traza mentalmente:

Paso i Condición i <= 3 Qué ocurre
1 1 true Imprime "1", luego i pasa a 2
2 2 true Imprime "2", luego i pasa a 3
3 3 true Imprime "3", luego i pasa a 4
4 4 false El bucle termina

Dos observaciones fundamentales:

  • La condición se comprueba ANTES de cada iteración, incluida la primera. Si empiezas con let i = 5 y la condición es i <= 3, el cuerpo no se ejecuta ni una sola vez. Eso es correcto y deseable: un bucle sobre una lista vacía debe hacer cero iteraciones.
  • La actualización se ejecuta DESPUÉS del cuerpo, no antes. Por eso en la primera iteración i todavía vale 1.

2.1 La variable de control

Por convención se llama i (de índice), y j, k si hay más. Se declara con let, nunca con const: tiene que poder cambiar en cada actualización.

// ✗ TypeError: Assignment to constant variable
for (const i = 0; i < 3; i++) { }

Declararla en la cabecera con let hace que solo exista dentro del bucle, lo cual es exactamente lo que quieres:

for (let i = 0; i < 3; i++) {
  console.log(i);
}
console.log(i);   // ✗ ReferenceError: i is not defined

Si necesitas conocer el valor final del contador después del bucle, declara la variable fuera. Pero casi nunca hace falta: es señal de que en realidad querías un while.

2.2 Variantes de la cabecera

Las tres partes son opcionales, aunque los ; no. Estas variantes son legales:

let i = 0;
for (; i < 3; i++) { }        // sin inicialización

for (let j = 10; j > 0; j -= 2) { }   // cuenta atrás de 2 en 2

for (let k = 0, total = 0; k < 3; k++) { }   // dos variables, separadas por coma

Contar hacia atrás es habitual cuando el orden importa:

for (let i = 5; i >= 1; i--) {
  console.log(`Quedan ${i} tareas por revisar`);
}

  1. El bucle while

while repite un bloque mientras una condición sea verdadera. No tiene inicialización ni actualización propias: te encargas tú.

let horasPendientes = 45;
let dias = 0;
const HORAS_POR_DIA = 8;

while (horasPendientes > 0) {
  horasPendientes -= HORAS_POR_DIA;
  dias++;
}

console.log(`El backlog abierto ocupa ${dias} días de trabajo.`);
// El backlog abierto ocupa 6 días de trabajo.

Es exactamente el mismo mecanismo que un for con las tres partes repartidas:

let i = 0;                 // inicialización: fuera
while (i < 3) {            // condición: en la cabecera
  console.log(i);
  i++;                     // actualización: última línea del cuerpo
}

Entonces, ¿para qué sirve while si for hace lo mismo? Para los casos en los que no sabes cuántas iteraciones harán falta. En el ejemplo de las horas no sabemos de antemano cuántos días saldrán: depende del total. Escribirlo con for obligaría a calcular antes el número de iteraciones, que es justo lo que queríamos averiguar.

  1. El bucle do...while

do...while es idéntico a while con una diferencia decisiva: el cuerpo se ejecuta al menos una vez, porque la condición se comprueba al final.

let intentos = 0;
let tareaEncontrada = false;

do {
  intentos++;
  console.log(`Intento de sincronización nº ${intentos}`);
  tareaEncontrada = intentos === 3;   // simulamos que a la tercera va la vencida
} while (!tareaEncontrada && intentos < 5);

console.log(`Sincronizado tras ${intentos} intento(s).`);

Fíjate en el ; final: en do...while es obligatorio después del while, porque la sentencia termina ahí. Es la única de las tres que lo lleva.

La diferencia con while se ve con una condición falsa desde el principio:

let n = 10;

while (n < 5) {
  console.log('while: no me ejecuto nunca');
}

do {
  console.log('do...while: me ejecuto una vez');
} while (n < 5);

Salida:

do...while: me ejecuto una vez
flowchart LR
    subgraph W["while"]
      direction TB
      W1{"¿Condición?"} -->|true| W2["Cuerpo"]
      W2 --> W1
      W1 -->|false| W3["Fin"]
    end
    subgraph D["do...while"]
      direction TB
      D1["Cuerpo"] --> D2{"¿Condición?"}
      D2 -->|true| D1
      D2 -->|false| D3["Fin"]
    end

do...while es el menos usado de los tres. Su nicho son los procesos que siempre hay que intentar una vez y luego decidir si se repiten: pedir un dato hasta que sea válido, reintentar una operación fallida, generar un identificador hasta que no esté repetido.

  1. Cuál elegir: tabla comparativa

Criterio for while do...while
Nº de iteraciones conocido ✓ Ideal △ Posible ✗ Raro
Nº de iteraciones desconocido △ Posible ✓ Ideal ✓ Si va al menos una vez
Cuerpo se ejecuta como mínimo 0 veces 0 veces 1 vez
Condición se comprueba Al principio Al principio Al final
Contador visible en la cabecera ✓ Sí ✗ No ✗ No
Recorrer una lista completa ✓ La opción natural △ Verboso ✗ Falla con lista vacía
Riesgo de bucle infinito Bajo Medio Medio
Necesita ; final No No

Regla práctica: si estás recorriendo una lista o repitiendo un número conocido de veces, for. Si repites "hasta que ocurra algo", while. Si además ese algo solo puede evaluarse después del primer intento, do...while.

  1. Recorrer una lista por índice con .length

Para trabajar con el backlog necesitas guardar varios valores en una sola variable. Eso es un array: una lista ordenada de valores, escrita entre corchetes y separada por comas.

const titulos = [
  'Rediseñar la sala polivalente',
  'Cartelería del taller de serigrafía',
  'Actualizar la web de reservas'
];

De momento solo necesitas tres cosas de los arrays; el resto llega en Arrays: Conceptos Básicos y Métodos:

Necesito Se escribe Ejemplo
Cuántos elementos hay array.length titulos.length3
El elemento en la posición N array[N] titulos[0]'Rediseñar la sala polivalente'
Recorrerlos todos Un for con índice for (let i = 0; i < titulos.length; i++)

Los índices empiezan en 0. Un array de 3 elementos tiene los índices 0, 1 y 2. El último índice es siempre length - 1, y array[length] da undefined, no un error. Esta es la causa número uno de errores con bucles.

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

Salida:

1. Rediseñar la sala polivalente
2. Cartelería del taller de serigrafía
3. Actualizar la web de reservas

Esa cabecera —let i = 0, i < array.length, i++— es el patrón más repetido de todo JavaScript. Interiorízala:

  • Empieza en 0 porque es el primer índice.
  • Usa < y no <=, porque length es el número de elementos, no el último índice.
  • El i + 1 del mensaje es solo cosmético: convierte el índice a un número de orden legible para Marta.

  1. El backlog de Taller Nómada

Aún no sabes crear objetos, así que representaremos las seis tareas con arrays paralelos: un array por campo, donde la posición i de todos ellos describe la misma tarea. Este código será el punto de partida de los siguientes ejemplos y de los de las lecciones 02-03 y 02-04.

const HOY = '2026-09-20';

const ids           = [1, 2, 3, 4, 5, 6];
const titulos       = [
  'Rediseñar la sala polivalente',
  'Cartelería del taller de serigrafía',
  'Actualizar la web de reservas',
  'Inventario de tintas de serigrafía',
  'Guía de encuadernación para residentes',
  'Presupuesto de la carpintería'
];
const responsables  = ['Iván', 'Marta', 'Lucía', 'Marta', 'Iván', 'Iván'];
const prioridades   = ['alta', 'media', 'alta', 'baja', 'media', 'alta'];
const estados       = ['en-curso', 'pendiente', 'pendiente', 'hecha', 'en-curso', 'pendiente'];
const horas         = [12, 6, 14, 3, 8, 5];
const fechasLimite  = [
  '2026-09-30', '2026-10-15', '2026-10-02',
  '2026-09-12', '2026-11-05', '2026-09-05'
];

Leer la tarea número 3 significa mirar la posición 2 de todos los arrays: titulos[2], responsables[2], horas[2]... Así queda el listado completo:

i id Título Responsable Prioridad Estado Horas Fecha límite
0 1 Rediseñar la sala polivalente Iván alta en-curso 12 2026-09-30
1 2 Cartelería del taller de serigrafía Marta media pendiente 6 2026-10-15
2 3 Actualizar la web de reservas Lucía alta pendiente 14 2026-10-02
3 4 Inventario de tintas de serigrafía Marta baja hecha 3 2026-09-12
4 5 Guía de encuadernación para residentes Iván media en-curso 8 2026-11-05
5 6 Presupuesto de la carpintería Iván alta pendiente 5 2026-09-05

Los arrays paralelos son una solución provisional y frágil. Si ordenas uno y no los demás, los datos quedan descolocados sin previo aviso. La representación correcta es un array de objetos, y llegará en el Módulo 4. Mientras tanto sirven perfectamente para aprender bucles, y sufrir su fragilidad te hará valorar los objetos cuando lleguen.

Un listado completo del backlog combinando bucle y condicional:

for (let i = 0; i < ids.length; i++) {
  const marca = estados[i] === 'hecha' ? '✓' : '○';
  const alerta = fechasLimite[i] < HOY && estados[i] !== 'hecha' ? ' ⚠ VENCIDA' : '';
  console.log(
    `${marca} [${ids[i]}] ${titulos[i]} · ${responsables[i]} · ${prioridades[i]} · ${horas[i]} h${alerta}`
  );
}

Salida:

○ [1] Rediseñar la sala polivalente · Iván · alta · 12 h
○ [2] Cartelería del taller de serigrafía · Marta · media · 6 h
○ [3] Actualizar la web de reservas · Lucía · alta · 14 h
✓ [4] Inventario de tintas de serigrafía · Marta · baja · 3 h
○ [5] Guía de encuadernación para residentes · Iván · media · 8 h
○ [6] Presupuesto de la carpintería · Iván · alta · 5 h ⚠ VENCIDA

Ahí tienes las dos estructuras del módulo trabajando juntas: el bucle recorre, el condicional decide. La tarea 4 no se marca como vencida pese a que su fecha ya pasó, porque está hecha: la regla R10 exige ambas condiciones.

  1. Patrón acumulador: horas totales del backlog

El acumulador es una variable declarada antes del bucle que va sumando (o concatenando) en cada iteración.

let totalHoras = 0;
let horasAbiertas = 0;

for (let i = 0; i < horas.length; i++) {
  totalHoras += horas[i];
  if (estados[i] !== 'hecha') {
    horasAbiertas += horas[i];
  }
}

console.log(`Horas estimadas totales:  ${totalHoras} h`);   // 48 h
console.log(`Horas de trabajo abierto: ${horasAbiertas} h`); // 45 h

Los tres elementos del patrón:

  1. Inicializar fuera del bucle, y con el valor neutro: 0 para sumas, 1 para productos, '' para textos. Si lo declaras dentro, se reinicia en cada iteración y el resultado es siempre el último valor.
  2. Acumular dentro, con +=.
  3. Usarlo después, cuando el bucle ha terminado.

Declararlo con let es obligatorio: const impediría reasignarlo.

Un acumulador con condición —como horasAbiertas— es la base de todos los informes de Marta: sumar solo lo que cumple un criterio. En el Módulo 4 esto se escribirá en una línea con reduce, pero el mecanismo que aprendes aquí es exactamente el mismo.

  1. Patrón contador: cuántas tareas lleva cada persona

Un contador es un acumulador que suma 1 en lugar de un valor. Con tres personas en el equipo bastan tres contadores y una cadena else if:

let tareasIvan = 0;
let tareasMarta = 0;
let tareasLucia = 0;

for (let i = 0; i < responsables.length; i++) {
  if (responsables[i] === 'Iván') {
    tareasIvan++;
  } else if (responsables[i] === 'Marta') {
    tareasMarta++;
  } else if (responsables[i] === 'Lucía') {
    tareasLucia++;
  } else {
    console.warn(`Responsable no reconocido en la tarea ${ids[i]}: ${responsables[i]}`);
  }
}

console.log(`Iván:  ${tareasIvan} tareas`);   // Iván:  3 tareas
console.log(`Marta: ${tareasMarta} tareas`);  // Marta: 2 tareas
console.log(`Lucía: ${tareasLucia} tareas`);  // Lucía: 1 tareas

El reparto confirma el diagnóstico de la lección 01-08: Iván está sobrecargado y Lucía tiene hueco. Exactamente el tipo de dato que Marta no podía ver con su pared de notas adhesivas.

Este código tiene un olor evidente: tres variables casi idénticas y una cadena de else if que crece con cada persona nueva. Si mañana entra alguien al equipo, hay que tocar cuatro sitios. La solución limpia son los bucles anidados sobre un array de responsables (lección 02-04) y, definitivamente, un objeto contador en el Módulo 4. Reconocer la incomodidad ahora es parte del aprendizaje.

Combinando contador y acumulador obtenemos la carga real de Iván, necesaria para la regla R7 (nadie supera 40 h):

let horasIvan = 0;

for (let i = 0; i < responsables.length; i++) {
  if (responsables[i] === 'Iván' && estados[i] !== 'hecha') {
    horasIvan += horas[i];
  }
}

console.log(`Carga abierta de Iván: ${horasIvan} h de 40`);   // 25 h de 40

if (horasIvan > 40) {
  console.error('R7 incumplida: Iván supera las 40 horas semanales.');
} else {
  console.log(`Margen disponible: ${40 - horasIvan} h`);      // Margen disponible: 15 h
}

  1. Patrón máximo/mínimo: la tarea más urgente

Para encontrar el valor extremo de una lista se guarda un candidato provisional y se sustituye cada vez que aparece uno mejor. Buscamos la tarea abierta cuya fecha límite sea la más próxima:

let fechaMasProxima = '';
let tituloMasUrgente = '';
let idMasUrgente = 0;

for (let i = 0; i < fechasLimite.length; i++) {
  const esCandidata = estados[i] !== 'hecha';
  const esLaPrimera = fechaMasProxima === '';
  const venceAntes = fechasLimite[i] < fechaMasProxima;

  if (esCandidata && (esLaPrimera || venceAntes)) {
    fechaMasProxima = fechasLimite[i];
    tituloMasUrgente = titulos[i];
    idMasUrgente = ids[i];
  }
}

console.log(`Más urgente: [${idMasUrgente}] ${tituloMasUrgente} (vence ${fechaMasProxima})`);
// Más urgente: [6] Presupuesto de la carpintería (vence 2026-09-05)

Las tres claves del patrón:

  • El valor inicial debe ser imposible o marcar "todavía no hay candidato". Aquí usamos '', y la variable esLaPrimera detecta ese caso. La alternativa habitual —inicializar con el primer elemento— no sirve aquí porque el primer elemento podría estar hecho y quedar descartado.
  • La comparación de fechas ISO como texto funciona, como ya sabes desde la lección 01-08.
  • Hay que arrastrar todos los datos del ganador a la vez. Como los campos están en arrays paralelos, al encontrar un candidato mejor hay que copiar su fecha, su título y su id en el mismo bloque. Olvidar uno produce un resultado mezclado: la fecha de una tarea con el título de otra. Es la fragilidad de los arrays paralelos en acción.

Para el máximo el patrón es idéntico invirtiendo la comparación. La tarea con más horas:

let maxHoras = -1;
let tituloMasLargo = '';

for (let i = 0; i < horas.length; i++) {
  if (horas[i] > maxHoras) {
    maxHoras = horas[i];
    tituloMasLargo = titulos[i];
  }
}

console.log(`La más larga: ${tituloMasLargo} (${maxHoras} h)`);
// La más larga: Actualizar la web de reservas (14 h)

-1 como valor inicial es seguro porque la regla R3 garantiza que ninguna tarea tiene horas negativas. Inicializar a 0 funcionaría también, pero -1 deja claro que es un centinela y no un dato.

  1. Patrón bandera: ¿hay alguna tarea vencida?

Una bandera (flag) es una variable booleana que empieza en false y se pone a true en cuanto ocurre lo que buscas. Sirve para responder preguntas de sí o no sobre toda la lista.

let hayVencidas = false;
let cuantasVencidas = 0;

for (let i = 0; i < fechasLimite.length; i++) {
  if (fechasLimite[i] < HOY && estados[i] !== 'hecha') {
    hayVencidas = true;
    cuantasVencidas++;
  }
}

if (hayVencidas) {
  console.error(`Atención: ${cuantasVencidas} tarea(s) vencida(s) en el tablero.`);
} else {
  console.log('Ninguna tarea vencida. Todo en plazo.');
}
// Atención: 1 tarea(s) vencida(s) en el tablero.

La bandera contraria —comprobar que todas cumplen algo— se inicializa al revés, en true, y se pone a false en cuanto aparece un contraejemplo:

let todasAsignadas = true;

for (let i = 0; i < responsables.length; i++) {
  if (responsables[i] === null) {
    todasAsignadas = false;
  }
}

console.log(todasAsignadas ? 'Todas las tareas tienen responsable.' : 'Hay tareas sin asignar.');
Pregunta Valor inicial Se cambia a Cuándo
¿Existe alguna que cumpla X? false true Al encontrar la primera que cumple
¿Cumplen todas X? true false Al encontrar la primera que no cumple

En ambos casos el bucle sigue recorriendo la lista aunque la respuesta ya se conozca. Eso es trabajo desperdiciado, y en la lección 02-04 aprenderás a cortar en seco con break.

  1. Bucles infinitos y cómo evitarlos

Un bucle infinito es aquel cuya condición nunca llega a ser falsa. En el navegador la pestaña se congela; en Node.js el proceso se queda colgado hasta que lo matas con Ctrl + C.

Las tres causas habituales:

Olvidar la actualización.

// ✗ i vale 0 para siempre
let i = 0;
while (i < 6) {
  console.log(titulos[i]);
}

Actualizar en la dirección equivocada.

// ✗ i crece, la condición pide que baje
for (let i = 6; i > 0; i++) { }

Modificar el contador dentro del cuerpo sin darte cuenta.

// ✗ i vuelve a 0 en la primera tarea sin asignar y nunca sale
for (let i = 0; i < responsables.length; i++) {
  if (responsables[i] === null) {
    i = 0;
  }
}

Cómo protegerte:

  • Comprueba antes de ejecutar que la condición puede volverse falsa. Localiza qué variable aparece en la condición y verifica que el cuerpo o la actualización la mueven hacia el final.
  • No modifiques la variable de control dentro del cuerpo. Si crees que lo necesitas, casi seguro que querías un while.
  • Añade un límite de seguridad en bucles cuya condición depende de datos externos:
let intentos = 0;
const MAX_INTENTOS = 1000;

while (!hayVencidas && intentos < MAX_INTENTOS) {
  intentos++;
  // ...
}

if (intentos === MAX_INTENTOS) {
  console.error('Límite de seguridad alcanzado: revisa la condición del bucle.');
}
  • Si ya has colgado la pestaña, ciérrala y usa node fichero.js con Ctrl + C para probar bucles dudosos: es mucho más fácil de detener.

Hay una excepción legítima: while (true) con una salida explícita dentro. Se usa en bucles de reintento y siempre lleva un break, que verás en la próxima lección.

  1. for...of y for...in: un adelanto

JavaScript moderno tiene dos bucles más, pensados para recorrer colecciones sin gestionar el índice a mano. Los presentamos aquí para que los reconozcas; el detalle sobre arrays está en Iteración sobre Arrays.

for...of recorre los valores de un array (o de cualquier cosa iterable, como un texto):

for (const titulo of titulos) {
  console.log(titulo);
}

Más limpio que el for clásico cuando solo necesitas el valor y no la posición. Fíjate en que la variable puede ser const, porque en cada iteración se crea de nuevo. El problema en nuestro caso es que con arrays paralelos sí necesitamos el índice para consultar los otros campos, así que seguiremos usando el for clásico durante este módulo.

for...in recorre las claves (en un array, los índices, y además como texto):

for (const i of titulos) { }   // valores
for (const i in titulos) {     // índices, pero '0', '1', '2'... como strings
  console.log(i + 1);          // ✗ '01', '11', '21' — concatena, no suma
}

Esa trampa de la concatenación es el motivo principal de la norma universal: for...in es para objetos, no para arrays. Recorre también propiedades heredadas y no garantiza el orden. Cuando llegues a los objetos en el Módulo 4 le encontrarás su sitio.

Bucle Qué te da Úsalo para
for (let i = 0; ...) El índice numérico Arrays cuando necesitas la posición
for...of El valor Arrays y textos cuando solo quieres el valor
for...in La clave como texto Objetos, no arrays

Errores Comunes y Consejos

Usar <= con .length.

for (let i = 0; i <= titulos.length; i++) { }   // ✗ una iteración de más

En la última vuelta titulos[6] vale undefined, y el resumen imprime "undefined · undefined". Con .length va siempre <.

Empezar en 1. for (let i = 1; i < titulos.length; i++) se salta la primera tarea silenciosamente. Los índices empiezan en 0.

Declarar el acumulador dentro del bucle.

for (let i = 0; i < horas.length; i++) {
  let total = 0;        // ✗ se reinicia en cada vuelta
  total += horas[i];
}

El acumulador vale siempre lo mismo que el último elemento, y además desaparece al salir. Va antes del bucle.

Confundir el índice con el valor. horas[i] es el número de horas; i es la posición. totalHoras += i suma 0+1+2+3+4+5 = 15, un número que parece plausible y no lo es. Cuando un total salga raro, comprueba esto primero.

Modificar .length dentro del bucle. Añadir o quitar elementos mientras recorres cambia la condición sobre la marcha y produce saltos o bucles infinitos. Recorre para leer; construye la lista modificada aparte.

Olvidar el ; de do...while. Es el único que lo lleva, y la ASI que viste en la lección 01-04 no siempre te salva.

Consejo: nombra bien la variable de control. i está aceptado universalmente para índices, pero si el bucle es largo, let indiceTarea se lee mejor a los tres meses.

Consejo: si el cuerpo del bucle pasa de veinte líneas, algo va mal. En el Módulo 3 aprenderás a extraer esa lógica a funciones; de momento, al menos separa los cálculos en variables con nombre antes del if.

Consejo: imprime la traza cuando algo no cuadre. Un console.log(i, titulos[i], totalHoras) como primera línea del cuerpo te muestra el estado en cada vuelta y resuelve la mayoría de los fallos en segundos.

Ejercicios

Ejercicio 1 — Resumen del taller

Partiendo del backlog de la sección 7, escribe un único bucle for que calcule y muestre:

  • El número total de tareas.
  • Cuántas están pendiente, cuántas en-curso y cuántas hecha.
  • El porcentaje de avance, entendido como tareas hechas sobre el total, redondeado a un decimal.

Recuerda de la lección 01-06 que Math.round(x * 10) / 10 redondea a un decimal.

Ejercicio 2 — Cuenta atrás de vencimientos

Escribe un bucle while que, partiendo de horasAbiertas = 45 y una jornada de 8 horas, calcule cuántos días laborables se necesitan para vaciar el backlog, imprimiendo en cada vuelta las horas que van quedando. Después responde: ¿por qué aquí while es mejor elección que for?

Ejercicio 3 — Tarea de mayor peso

Usando la conversión de prioridad a peso de la lección anterior (alta = 3, media = 2, baja = 1), calcula para cada tarea abierta un índice de esfuerzo igual a peso * horas, y encuentra la tarea abierta con el índice más alto. Muestra el listado completo con su índice y, al final, la ganadora.

Soluciones

Ejercicio 1

let pendientes = 0;
let enCurso = 0;
let hechas = 0;

for (let i = 0; i < estados.length; i++) {
  if (estados[i] === 'pendiente') {
    pendientes++;
  } else if (estados[i] === 'en-curso') {
    enCurso++;
  } else if (estados[i] === 'hecha') {
    hechas++;
  } else {
    console.warn(`Estado desconocido en la tarea ${ids[i]}: ${estados[i]}`);
  }
}

const total = estados.length;
const avance = Math.round((hechas / total) * 100 * 10) / 10;

console.log(`Total de tareas: ${total}`);          // 6
console.log(`Pendientes: ${pendientes}`);          // 3
console.log(`En curso:   ${enCurso}`);             // 2
console.log(`Hechas:     ${hechas}`);              // 1
console.log(`Avance: ${avance} %`);                // 16.7 %

Tres contadores actualizados en el mismo recorrido: recorrer la lista una sola vez y calcular todo lo que necesites en esa pasada es siempre preferible a hacer tres bucles. El else final protege contra estados no previstos, aplicando la norma de la lección 02-01 de cerrar siempre las cadenas.

El cálculo del porcentaje merece atención: hechas / total da 0.1666...; multiplicar por 100 da 16.666...; y el truco de multiplicar por 10, redondear y dividir entre 10 deja 16.7 como número. toFixed(1) daría el texto '16.7', y si más adelante lo sumaras a otra cosa obtendrías una concatenación en lugar de una suma.

Ejercicio 2

let horasAbiertas = 45;
const HORAS_POR_DIA = 8;
let dias = 0;

while (horasAbiertas > 0) {
  dias++;
  horasAbiertas -= HORAS_POR_DIA;
  const restantes = horasAbiertas > 0 ? horasAbiertas : 0;
  console.log(`Día ${dias}: quedan ${restantes} h`);
}

console.log(`El backlog abierto ocupa ${dias} día(s) laborable(s).`);

Salida:

Día 1: quedan 37 h
Día 2: quedan 29 h
Día 3: quedan 21 h
Día 4: quedan 13 h
Día 5: quedan 5 h
Día 6: quedan 0 h
El backlog abierto ocupa 6 día(s) laborable(s).

El ternario evita mostrar -3 h en la última vuelta, porque restar 8 a 5 se pasa del cero.

Por qué while: el número de iteraciones no se conoce antes de empezar; es precisamente lo que el bucle calcula. Con un for habría que escribir for (let d = 0; d < Math.ceil(45 / 8); d++), es decir, resolver el problema en la cabecera para después simular que lo resolvemos en el cuerpo. La regla se cumple: cuando la condición de parada depende de un estado que evoluciona, while.

Ejercicio 3

let maxEsfuerzo = -1;
let tituloGanadora = '';

for (let i = 0; i < ids.length; i++) {
  if (estados[i] === 'hecha') {
    console.log(`[${ids[i]}] ${titulos[i]} — cerrada, no puntúa`);
  } else {
    let peso;
    if (prioridades[i] === 'alta') {
      peso = 3;
    } else if (prioridades[i] === 'media') {
      peso = 2;
    } else {
      peso = 1;
    }

    const esfuerzo = peso * horas[i];
    console.log(`[${ids[i]}] ${titulos[i]} — esfuerzo ${esfuerzo}`);

    if (esfuerzo > maxEsfuerzo) {
      maxEsfuerzo = esfuerzo;
      tituloGanadora = titulos[i];
    }
  }
}

console.log(`\nMayor esfuerzo: ${tituloGanadora} (${maxEsfuerzo})`);

Salida:

[1] Rediseñar la sala polivalente — esfuerzo 36
[2] Cartelería del taller de serigrafía — esfuerzo 12
[3] Actualizar la web de reservas — esfuerzo 42
[4] Inventario de tintas de serigrafía — cerrada, no puntúa
[5] Guía de encuadernación para residentes — esfuerzo 16
[6] Presupuesto de la carpintería — esfuerzo 15

Mayor esfuerzo: Actualizar la web de reservas (42)

Tres detalles que conviene notar. Primero, let peso; se declara dentro del bucle a propósito: es un valor distinto en cada iteración, no un acumulador, así que debe reiniciarse. Segundo, maxEsfuerzo y tituloGanadora sí van fuera, porque deben sobrevivir entre vueltas. Y tercero, esta cadena de tres else if repetida en cada iteración es exactamente el caso que resolverá el switch de la próxima lección de forma más compacta.

Conclusión

Ya sabes repetir. Conoces el for con su triple cabecera —inicialización, condición y actualización— y el orden exacto en que se ejecutan; el while, para cuando no sabes cuántas vueltas harán falta; y el do...while, para lo que hay que intentar al menos una vez. Sabes recorrer una lista con for (let i = 0; i < array.length; i++), que es la cabecera más escrita de todo JavaScript, y por qué los índices van de 0 a length - 1.

Sobre todo, dominas los cuatro patrones que resuelven casi cualquier recorrido: el acumulador para sumar horas, el contador para repartir tareas por persona, el máximo/mínimo para encontrar la más urgente y la bandera para responder si existe alguna vencida. Los cuatro comparten la misma estructura: una variable declarada antes del bucle, actualizada dentro y consultada después. Y sabes reconocer y evitar un bucle infinito antes de congelar la pestaña.

Aplicado a Nómada Tareas, ya puedes generar el informe que Marta nunca tuvo: 48 horas estimadas en total, 45 de trabajo abierto, un 16,7 % de avance, una tarea vencida y una carga muy desigual —Iván con tres tareas y 25 horas frente a la única de Lucía—.

Habrás notado dos incomodidades. Una: convertir prioridad en un peso obligaba a repetir la misma cadena de else if una y otra vez. Dos: los bucles siguen recorriendo la lista entera aunque la respuesta ya se conozca. La primera se ataca en Sentencias Switch, donde aprenderás una construcción pensada exactamente para elegir entre varios valores de una misma variable, y la segunda en la lección siguiente, con break y continue.

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