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
- Qué es un bucle y por qué lo necesitas
- Anatomía del bucle
for - El bucle
while - El bucle
do...while - Cuál elegir: tabla comparativa
- Recorrer una lista por índice con
.length - El backlog de Taller Nómada
- Patrón acumulador: horas totales del backlog
- Patrón contador: cuántas tareas lleva cada persona
- Patrón máximo/mínimo: la tarea más urgente
- Patrón bandera: ¿hay alguna tarea vencida?
- Bucles infinitos y cómo evitarlos
for...ofyfor...in: un adelanto- Errores Comunes y Consejos
- Ejercicios
- Conclusión
- 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 cuarentaCon 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.
- Anatomía del bucle
for
forEl 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 ;:
Un ejemplo mínimo: imprimir los tres primeros números de tarea.
Salida:
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 = 5y la condición esi <= 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
itodaví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.
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 definedSi 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 comaContar hacia atrás es habitual cuando el orden importa:
- El bucle
while
whilewhile 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.
- El bucle
do...while
do...whiledo...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:
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.
- 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 | Sí |
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.
- Recorrer una lista por índice con
.length
.lengthPara 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.length → 3 |
| 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.
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
0porque es el primer índice. - Usa
<y no<=, porquelengthes el número de elementos, no el último índice. - El
i + 1del mensaje es solo cosmético: convierte el índice a un número de orden legible para Marta.
- 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.
- 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 hLos tres elementos del patrón:
- Inicializar fuera del bucle, y con el valor neutro:
0para sumas,1para productos,''para textos. Si lo declaras dentro, se reinicia en cada iteración y el resultado es siempre el último valor. - Acumular dentro, con
+=. - 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.
- 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 tareasEl 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
}
- 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 variableesLaPrimeradetecta 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.
- 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.
- 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.
Actualizar en la dirección equivocada.
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.jsconCtrl + Cpara 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.
for...of y for...in: un adelanto
for...of y for...in: un adelantoJavaScript 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):
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.
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ántasen-cursoy cuántashecha. - 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
- ¿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
