Módulo 04: Estructuras de control

Objetivos del módulo

  • Entender qué es el flujo de ejecución y cómo se controla con bloques
  • Tomar decisiones con if, else if y else, y saber cuándo usar el ternario
  • Usar switch correctamente: break, fallthrough, default y el patrón switch (true)
  • Sustituir switch por objetos de búsqueda cuando sea más claro
  • Repetir código con while, do...while, for, for...of y for...in
  • Saber por qué for...in no se usa con arrays
  • Controlar los bucles con break, continue y etiquetas, y evitar los bucles infinitos
  • Construir programas completos: FizzBuzz, una tabla de multiplicar en la página y el juego “adivina el número” en el navegador y en Node.js
  • Conocer el early return y las guard clauses para escribir código más plano

1. El flujo de ejecución y los bloques

📘 Concepto: Por defecto, JavaScript ejecuta las instrucciones de arriba abajo, una detrás de otra. Las estructuras de control permiten cambiar ese orden: decidir qué código se ejecuta (condicionales) o repetir un código varias veces (bucles). Son como las bifurcaciones y las rotondas de una carretera.

Un bloque es un grupo de instrucciones entre llaves { }. Las estructuras de control trabajan con bloques, y las variables declaradas con let o const dentro de un bloque solo existen dentro de él:

{
  const mensaje = 'Dentro del bloque';
  console.log(mensaje); // Dentro del bloque
}
// console.log(mensaje); ❌ ReferenceError: mensaje is not defined

💡 Consejo: Sangra (indenta) el contenido de cada bloque con 2 espacios. No cambia cómo se ejecuta el código, pero hace evidente qué está dentro de qué. Prettier lo hará por ti al guardar.


2. Condicionales: if, else if y else

2.1 if

Ejecuta un bloque solo si la condición es verdadera (truthy):

const temperatura = 32;

if (temperatura > 30) {
  console.log('Hace mucho calor. ¡Bebe agua!');
}
console.log('Fin del programa'); // esto se ejecuta siempre

2.2 if...else

const edad = 16;

if (edad >= 18) {
  console.log('Puedes votar');
} else {
  console.log(`Podrás votar dentro de ${18 - edad} años`);
}
// Podrás votar dentro de 2 años

2.3 else if: varias alternativas

Las condiciones se comprueban en orden y solo se ejecuta el primer bloque cuya condición se cumpla:

const nota = 7.5;
let calificacion;

if (nota < 0 || nota > 10) {
  calificacion = 'Nota no válida';
} else if (nota < 5) {
  calificacion = 'Suspenso';
} else if (nota < 7) {
  calificacion = 'Aprobado';
} else if (nota < 9) {
  calificacion = 'Notable';
} else {
  calificacion = 'Sobresaliente';
}

console.log(calificacion); // Notable
flowchart TD
    A["nota"] --> B{"¿nota < 0 o > 10?"}
    B -->|Sí| X["Nota no válida"]
    B -->|No| C{"¿nota < 5?"}
    C -->|Sí| S["Suspenso"]
    C -->|No| D{"¿nota < 7?"}
    D -->|Sí| AP["Aprobado"]
    D -->|No| E{"¿nota < 9?"}
    E -->|Sí| N["Notable"]
    E -->|No| SB["Sobresaliente"]

⚠️ Importante: El orden de las condiciones importa. Si pusieras primero nota < 9, un 3 entraría ahí y nunca llegaría a “Suspenso”. Ordena de lo más restrictivo a lo más general.

2.4 Condiciones compuestas y truthy/falsy

Puedes combinar condiciones con &&, || y ! (módulo 03) y aprovechar los valores truthy/falsy (módulo 02):

const usuario = 'ana';
const contrasena = '';

if (!usuario || !contrasena) {
  console.log('Rellena usuario y contraseña'); // se ejecuta: contrasena es ''
}

const hora = 15;
const esFinDeSemana = false;
if (hora >= 9 && hora < 18 && !esFinDeSemana) {
  console.log('La tienda está abierta');
}

2.5 Usa siempre llaves

JavaScript permite omitir las llaves si el bloque tiene una sola instrucción, pero es fuente de errores:

const saldo = 50;

// ❌ Parece que las dos líneas están dentro del if, pero solo la primera lo está
if (saldo < 0)
  console.log('Saldo negativo');
  console.log('Se ha enviado un aviso al banco'); // ¡se ejecuta SIEMPRE!

// ✅ Con llaves no hay dudas
if (saldo < 0) {
  console.log('Saldo negativo');
  console.log('Se ha enviado un aviso al banco');
}

💡 Consejo: Escribe siempre las llaves. ESLint tiene la regla curly para exigirlo.


3. Ternario frente a if

El operador ternario (módulo 03) es una expresión que devuelve un valor; if es una instrucción que ejecuta código. Elige según lo que necesites:

Situación Usa Ejemplo
Elegir entre dos valores Ternario const texto = stock > 0 ? 'Disponible' : 'Agotado';
Valor dentro de una plantilla Ternario `${n} ${n === 1 ? 'año' : 'años'}`
Ejecutar acciones distintas if/else Guardar, mostrar mensajes, llamar a funciones
Más de dos alternativas if/else if o un objeto de búsqueda Calificaciones, tarifas por tramos
const puntos = 120;

// ✅ Ternario: elegimos un valor
const nivel = puntos >= 100 ? 'Oro' : 'Plata';

// ✅ if: ejecutamos acciones
if (puntos >= 100) {
  console.log('¡Enhorabuena! Has subido de nivel');
  console.log('Te enviamos un regalo por correo');
}

// ❌ Ternario usado para ejecutar acciones: funciona, pero se lee mal
puntos >= 100 ? console.log('Oro') : console.log('Plata');

4. switch

switch compara un valor con una lista de casos posibles. Es una alternativa a una cadena de else if cuando todas las condiciones comparan la misma variable con valores concretos.

4.1 Sintaxis

const metodoPago = 'bizum';

switch (metodoPago) {
  case 'tarjeta':
    console.log('Introduce los datos de tu tarjeta');
    break;
  case 'bizum':
    console.log('Introduce tu número de teléfono');
    break;
  case 'transferencia':
    console.log('Te enviaremos el IBAN por correo');
    break;
  default:
    console.log('Método de pago no válido');
}
// Introduce tu número de teléfono
Parte Función
switch (valor) La expresión que se va a comparar
case x: Si valor === x, empieza a ejecutar desde aquí
break Sale del switch
default: Se ejecuta si ningún case coincide (opcional, normalmente al final)

⚠️ Importante: switch compara con igualdad estricta (===). Si el valor es el texto '2' y el caso es el número 2, no coinciden. Cuidado con los datos que vienen de prompt o de formularios, que siempre son texto.

4.2 El fallthrough: cuando olvidas el break

Si un case no termina en break, la ejecución continúa en el siguiente case sin comprobarlo. Es el llamado fallthrough (“caer a través”):

const nivel = 2;

switch (nivel) {
  case 1:
    console.log('Nivel 1');
  case 2:
    console.log('Nivel 2');
  case 3:
    console.log('Nivel 3');
  default:
    console.log('Fin');
}
// Nivel 2
// Nivel 3   ← ¡se ha "caído" al siguiente caso!
// Fin

Casi siempre es un error, pero se puede usar a propósito para agrupar casos que comparten el mismo código:

function tipoDia(dia) {
  switch (dia) {
    case 'sábado':
    case 'domingo':
      return 'fin de semana';
    case 'lunes':
    case 'martes':
    case 'miércoles':
    case 'jueves':
    case 'viernes':
      return 'laborable';
    default:
      return 'día no válido';
  }
}

console.log(tipoDia('domingo')); // fin de semana
console.log(tipoDia('martes'));  // laborable
console.log(tipoDia('festivo')); // día no válido

📘 Concepto: Dentro de una función, return sale de la función entera, así que hace innecesario el break. Verás las funciones en el módulo 05.

4.3 Variables dentro de un case

Todos los case comparten el mismo bloque, así que declarar la misma variable en dos casos da error. La solución es poner llaves:

const accion = 'sumar';

switch (accion) {
  case 'sumar': {
    const resultado = 2 + 3;
    console.log(resultado);
    break;
  }
  case 'restar': {
    const resultado = 2 - 3; // ✅ sin llaves daría SyntaxError: ya declarada
    console.log(resultado);
    break;
  }
}

4.4 El patrón switch (true)

Para comparar rangos con switch, se usa un truco: comparar true con condiciones booleanas. Se ejecuta el primer case cuya condición sea true:

const imc = 27.3;

switch (true) {
  case imc < 18.5:
    console.log('Bajo peso');
    break;
  case imc < 25:
    console.log('Peso normal');
    break;
  case imc < 30:
    console.log('Sobrepeso'); // ← este
    break;
  default:
    console.log('Obesidad');
}

💡 Consejo: switch (true) es legal y se ve en código real, pero muchos equipos consideran más claro un if/else if para rangos. Úsalo si a tu equipo le resulta legible.


5. Objetos de búsqueda en lugar de switch

Muchos switch solo sirven para traducir un valor en otro. En esos casos, un objeto literal usado como tabla de búsqueda (lookup table) es más corto, más fácil de ampliar y separa los datos de la lógica:

// ❌ Con switch: mucho código repetido
function precioBebidaSwitch(bebida) {
  switch (bebida) {
    case 'cafe':
      return 1.2;
    case 'te':
      return 1.5;
    case 'zumo':
      return 2.3;
    default:
      return null;
  }
}

// ✅ Con un objeto de búsqueda
const PRECIOS = {
  cafe: 1.2,
  te: 1.5,
  zumo: 2.3,
};

const bebida = 'te';
console.log(PRECIOS[bebida] ?? 'No disponible');      // 1.5
console.log(PRECIOS.chocolate ?? 'No disponible');    // No disponible

📘 Concepto: Con objeto[variable] accedes a la propiedad cuyo nombre está guardado en la variable. Si no existe, obtienes undefined, y con ?? das un valor por defecto. Lo verás a fondo en el módulo 07.

También puedes guardar funciones como valores, para ejecutar acciones distintas:

const operaciones = {
  sumar: (a, b) => a + b,
  restar: (a, b) => a - b,
  multiplicar: (a, b) => a * b,
};

const op = 'multiplicar';
const calcular = operaciones[op];

if (calcular) {
  console.log(calcular(6, 7)); // 42
} else {
  console.log('Operación desconocida');
}

⚠️ Importante: Un objeto normal “hereda” propiedades como toString, así que PRECIOS['toString'] no es undefined (es una función). Si las claves vienen del usuario, comprueba con Object.hasOwn(PRECIOS, clave) o usa un Map (módulo 11).


6. El bucle while

📘 Concepto: Un bucle repite un bloque de código mientras se cumpla una condición. Cada repetición se llama iteración. while comprueba la condición antes de cada vuelta, así que si es falsa desde el principio, el bloque no se ejecuta nunca.

let cuenta = 3;

while (cuenta > 0) {
  console.log(cuenta);
  cuenta--; // ¡imprescindible! Si no, la condición nunca cambia
}
console.log('¡Despegue!');
// 3
// 2
// 1
// ¡Despegue!

while es ideal cuando no sabes de antemano cuántas vueltas darás:

// ¿Cuántos años tarda en duplicarse un ahorro al 3 % anual?
let capital = 1000;
let anios = 0;

while (capital < 2000) {
  capital *= 1.03;
  anios++;
}

console.log(`Se duplica en ${anios} años`); // Se duplica en 24 años

7. El bucle do...while

Igual que while, pero comprueba la condición después de cada vuelta. Por eso el bloque se ejecuta al menos una vez:

let n = 0;

do {
  n++;
} while (n < 0); // la condición es falsa desde el principio...

console.log(n); // 1 ← ...pero el bloque se ejecutó una vez

Su uso típico es pedir un dato hasta que sea válido (en el navegador):

let edad;

do {
  edad = Number(prompt('¿Cuántos años tienes?'));
} while (!Number.isInteger(edad) || edad < 0);

console.log(`Tienes ${edad} años`);
Bucle Comprueba la condición Mínimo de ejecuciones
while Antes de cada vuelta 0
do...while Después de cada vuelta 1

8. El bucle for clásico

Cuando sabes cuántas veces quieres repetir algo, for reúne en una sola línea las tres partes de un bucle con contador:

for (inicialización; condición; actualización) {
  // cuerpo
}
for (let i = 1; i <= 5; i++) {
  console.log(`Vuelta número ${i}`);
}
// Vuelta número 1 ... Vuelta número 5
Parte Cuándo se ejecuta En el ejemplo
Inicialización Una vez, al principio let i = 1
Condición Antes de cada vuelta; si es falsa, termina i <= 5
Cuerpo En cada vuelta console.log(...)
Actualización Al final de cada vuelta i++
flowchart TD
    A["Inicialización<br/>let i = 1"] --> B{"Condición<br/>i <= 5"}
    B -->|true| C["Cuerpo"]
    C --> D["Actualización<br/>i++"]
    D --> B
    B -->|false| E["Fin del bucle"]

Variantes habituales:

// Hacia atrás y de dos en dos
for (let i = 10; i >= 0; i -= 2) {
  console.log(i); // 10, 8, 6, 4, 2, 0
}

// Recorrer un array por su índice
const ciudades = ['Madrid', 'Sevilla', 'Bilbao'];
for (let i = 0; i < ciudades.length; i++) {
  console.log(`${i + 1}. ${ciudades[i]}`);
}
// 1. Madrid
// 2. Sevilla
// 3. Bilbao

⚠️ Importante: Los índices de un array van de 0 a length - 1. La condición correcta es i < array.length, no i <= array.length (en el módulo 01 depuraste exactamente este error).


9. El bucle for...of

for...of (ES2015) recorre directamente los valores de cualquier iterable: arrays, cadenas, Map, Set… No necesitas índices ni length:

const frutas = ['manzana', 'pera', 'uva'];

for (const fruta of frutas) {
  console.log(fruta);
}
// manzana
// pera
// uva

💡 Consejo: Fíjate en que usamos const: en cada vuelta se crea una variable nueva con el valor correspondiente. Usa let solo si necesitas reasignarla dentro del bucle.

Con cadenas de texto, recorre los caracteres (incluso los emojis, que ocupan dos “unidades” internas):

for (const letra of 'Hola 👋') {
  console.log(letra); // H, o, l, a, ' ', 👋
}
console.log('Hola 👋'.length); // 7 ← length cuenta el emoji como 2

¿Y si necesitas también el índice? Usa entries():

const frutas = ['manzana', 'pera'];

for (const [indice, fruta] of frutas.entries()) {
  console.log(indice, fruta);
}
// 0 manzana
// 1 pera

📘 Concepto: [indice, fruta] es desestructuración: extrae los dos valores del par [0, 'manzana'] en dos variables. La verás en detalle en los módulos 06 y 07.


10. El bucle for...in

for...in recorre las claves (nombres de propiedades) de un objeto:

const coche = { marca: 'Seat', modelo: 'Ibiza', anio: 2022 };

for (const clave in coche) {
  console.log(`${clave}: ${coche[clave]}`);
}
// marca: Seat
// modelo: Ibiza
// anio: 2022

10.1 Por qué no usar for...in con arrays

const letras = ['a', 'b'];
letras.extra = 'x'; // los arrays son objetos: se les pueden añadir propiedades

for (const i in letras) {
  console.log(i, typeof i);
}
// 0 string    ← ¡los índices son TEXTOS, no números!
// 1 string
// extra string ← ¡recorre también propiedades que no son elementos!
Problema de for...in con arrays Consecuencia
Las claves son strings i + 1 da '01' en vez de 1
Recorre propiedades añadidas y heredadas enumerables Aparecen elementos “fantasma”
Está pensado para objetos Es más lento y confuso con arrays

⚠️ Importante: Regla fácil: for...of para valores de arrays y cadenas; for...in para claves de objetos. Y aun para objetos, el código moderno suele preferir for (const [clave, valor] of Object.entries(obj)), que verás en el módulo 07.

10.2 Tabla comparativa de bucles

Bucle Recorre Úsalo cuando
while Mientras se cumpla la condición No sabes cuántas vueltas darás
do...while Ídem, al menos una vez Pedir datos hasta que sean válidos
for Un contador Necesitas el índice, saltos, o recorrer al revés
for...of Valores de un iterable Recorrer arrays, cadenas, Map, Set (lo más habitual)
for...in Claves de un objeto Recorrer propiedades de un objeto literal

11. break, continue y etiquetas

11.1 break: salir del bucle

const numeros = [3, 8, 15, 4, 23, 42];

for (const numero of numeros) {
  if (numero > 20) {
    console.log(`Primer número mayor que 20: ${numero}`);
    break; // dejamos de buscar
  }
}
// Primer número mayor que 20: 23

11.2 continue: saltar a la siguiente vuelta

const movimientos = [4, -2, 7, 0, -5, 9];
let sumaPositivos = 0;

for (const m of movimientos) {
  if (m <= 0) {
    continue; // ignora este y pasa al siguiente
  }
  sumaPositivos += m;
}

console.log(sumaPositivos); // 20

11.3 Etiquetas: salir de bucles anidados

break y continue afectan solo al bucle más interno. Para salir de varios a la vez, pon una etiqueta (un nombre seguido de :) al bucle externo:

externo: for (let fila = 1; fila <= 3; fila++) {
  for (let col = 1; col <= 3; col++) {
    if (fila * col === 4) {
      console.log(`Encontrado: ${fila} x ${col}`);
      break externo; // sale de LOS DOS bucles
    }
    console.log(fila, col);
  }
}
// 1 1
// 1 2
// 1 3
// 2 1
// Encontrado: 2 x 2

💡 Consejo: Las etiquetas son poco frecuentes. Si necesitas salir de varios bucles anidados, a menudo es más claro meter los bucles en una función y usar return (sección 14).


12. Bucles infinitos y cómo evitarlos

Un bucle infinito es uno cuya condición nunca llega a ser falsa. En el navegador congela la pestaña; en Node.js, el programa no termina nunca (párralo con Ctrl + C).

// ❌ Olvidar actualizar la variable
let i = 0;
while (i < 5) {
  console.log(i); // i siempre vale 0
}

// ❌ Actualizar en la dirección equivocada
for (let j = 0; j < 10; j--) { /* j cada vez más pequeño */ }

// ❌ Comparar decimales con igualdad exacta
let x = 0;
while (x !== 1) {
  x += 0.1; // 0.1 + 0.1 + ... nunca da exactamente 1 (da 0.9999999999999999)
}

Cómo evitarlos:

Causa Solución
La variable de control no cambia Comprueba que el cuerpo la actualiza en cada vuelta
Cambia en la dirección equivocada Revisa ++ frente a -- y < frente a >
Comparación exacta con decimales Usa < o <= en lugar de !== / ===
Condiciones que dependen de datos externos Añade un límite de seguridad (máximo de intentos)
// ✅ Límite de seguridad
const MAX_VUELTAS = 1000;
let vueltas = 0;
let valor = 0;

while (valor !== 1 && vueltas < MAX_VUELTAS) {
  valor += 0.1;
  vueltas++;
}

if (vueltas === MAX_VUELTAS) {
  console.warn('Se alcanzó el límite de vueltas: revisa la condición');
}

📘 Concepto: A veces se usa un bucle infinito a propósito, como while (true) { ... }, con un break dentro cuando se cumple una condición. Es correcto siempre que el break sea alcanzable.


13. Ejemplos completos

13.1 FizzBuzz

Un clásico de las entrevistas de programación: recorre los números del 1 al 100; si el número es múltiplo de 3 escribe “Fizz”, si es múltiplo de 5 escribe “Buzz”, si es múltiplo de ambos “FizzBuzz” y, si no, el número.

// fizzbuzz.mjs
for (let i = 1; i <= 100; i++) {
  if (i % 15 === 0) {
    console.log('FizzBuzz'); // múltiplo de 3 y de 5 (es decir, de 15)
  } else if (i % 3 === 0) {
    console.log('Fizz');
  } else if (i % 5 === 0) {
    console.log('Buzz');
  } else {
    console.log(i);
  }
}
// 1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz, 16...

⚠️ Importante: La comprobación de 15 va primero. Si comprobaras antes i % 3, el 15 entraría ahí y escribiría “Fizz” en lugar de “FizzBuzz”.

13.2 Tabla de multiplicar en la página

Por primera vez vas a crear contenido en la página desde JavaScript. Usaremos dos herramientas sencillas que estudiarás a fondo en el módulo 14: document.createElement() crea un elemento y document.body.append() lo añade al final de la página.

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Tabla de multiplicar</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 2rem; }
    p { margin: 0.25rem 0; font-size: 1.2rem; }
    .destacado { font-weight: bold; color: rebeccapurple; }
  </style>
  <script type="module">
    const numero = Number(prompt('¿Qué tabla quieres ver? (1-10)', '7'));

    if (!Number.isInteger(numero) || numero < 1 || numero > 10) {
      document.body.append('Número no válido. Recarga la página e inténtalo de nuevo.');
    } else {
      const titulo = document.createElement('h1');
      titulo.textContent = `Tabla del ${numero}`;
      document.body.append(titulo);

      for (let i = 1; i <= 10; i++) {
        const linea = document.createElement('p');
        linea.textContent = `${numero} × ${i} = ${numero * i}`;

        if (i % 2 === 0) {
          linea.classList.add('destacado'); // resaltamos las filas pares
        }

        document.body.append(linea);
      }
    }
  </script>
</head>
<body>
</body>
</html>

💡 Consejo: append también acepta texto directamente (como en el mensaje de error). Y usamos textContent en lugar de innerHTML para escribir texto: es más seguro, porque nunca interpreta etiquetas HTML (lo verás en el módulo 14).

13.3 Adivina el número (navegador)

El ordenador piensa un número del 1 al 100 y tú tienes 7 intentos para adivinarlo. Esta versión usa prompt y alert, que bloquean la página mientras esperan respuesta: sirven para practicar, pero no se usan en webs reales (allí usarás formularios y eventos, módulos 15 y 16).

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <title>Adivina el número</title>
  <script type="module">
    const MAX_INTENTOS = 7;
    const secreto = Math.floor(Math.random() * 100) + 1; // entero entre 1 y 100
    let intentos = 0;
    let acertado = false;

    while (intentos < MAX_INTENTOS && !acertado) {
      const respuesta = prompt(`Intento ${intentos + 1}/${MAX_INTENTOS}. Escribe un número del 1 al 100:`);

      if (respuesta === null) {
        break; // el usuario ha pulsado "Cancelar"
      }

      const numero = Number(respuesta);

      if (!Number.isInteger(numero) || numero < 1 || numero > 100) {
        alert('Escribe un número entero entre 1 y 100.');
        continue; // no cuenta como intento
      }

      intentos++;

      if (numero === secreto) {
        acertado = true;
      } else if (numero < secreto) {
        alert('Más alto ⬆️');
      } else {
        alert('Más bajo ⬇️');
      }
    }

    const mensaje = acertado
      ? `¡Correcto! Era el ${secreto}. Lo has conseguido en ${intentos} intentos.`
      : `Fin del juego. El número era el ${secreto}.`;

    alert(mensaje);
    document.body.append(mensaje);
  </script>
</head>
<body>
</body>
</html>

📘 Concepto: Math.random() devuelve un decimal aleatorio entre 0 (incluido) y 1 (excluido). Multiplicado por 100 da un valor entre 0 y 99,999…; Math.floor lo deja en un entero de 0 a 99, y el + 1 lo desplaza al rango 1-100.

13.4 Adivina el número (Node.js con readline)

En Node.js no existe prompt. Para leer lo que escribe el usuario en la terminal se usa el módulo node:readline/promises. Su método question() muestra una pregunta y espera la respuesta; como esa espera es asíncrona, se usa await (módulo 12). En un módulo ES puedes usar await directamente en el nivel superior.

// adivina.mjs
import * as readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';

const rl = readline.createInterface({ input, output });

const MAX_INTENTOS = 7;
const secreto = Math.floor(Math.random() * 100) + 1;
let intentos = 0;
let acertado = false;

console.log('He pensado un número del 1 al 100. ¡Adivínalo!');

while (intentos < MAX_INTENTOS && !acertado) {
  const respuesta = await rl.question(`Intento ${intentos + 1}/${MAX_INTENTOS}: `);
  const numero = Number(respuesta);

  if (!Number.isInteger(numero) || numero < 1 || numero > 100) {
    console.log('Escribe un número entero entre 1 y 100.');
    continue;
  }

  intentos++;

  if (numero === secreto) {
    acertado = true;
  } else if (numero < secreto) {
    console.log('Más alto ⬆️');
  } else {
    console.log('Más bajo ⬇️');
  }
}

if (acertado) {
  console.log(`¡Correcto! Era el ${secreto}. Lo has conseguido en ${intentos} intentos.`);
} else {
  console.log(`Has agotado los intentos. El número era el ${secreto}.`);
}

rl.close(); // importante: si no cierras la interfaz, el programa no termina
PS C:\cursos> node adivina.mjs
He pensado un número del 1 al 100. ¡Adivínalo!
Intento 1/7: 50
Más bajo ⬇️
Intento 2/7: hola
Escribe un número entero entre 1 y 100.
Intento 2/7: 25
Más alto ⬆️
Intento 3/7: 42
¡Correcto! Era el 42. Lo has conseguido en 3 intentos.

💡 Consejo: Compara las dos versiones: la lógica del juego (el bucle, las comprobaciones, los contadores) es idéntica. Solo cambia cómo se pide y se muestra la información. Separar la lógica de la entrada/salida es una idea clave que aprovecharás cuando conozcas las funciones.


14. Early return y guard clauses (adelanto)

Cuando hay muchas condiciones, el código tiende a anidarse en forma de “flecha” y se vuelve difícil de seguir:

// ❌ Código en "flecha": cada condición añade un nivel de sangría
function procesarPedido(pedido) {
  if (pedido) {
    if (pedido.productos.length > 0) {
      if (pedido.pagado) {
        console.log('Enviando pedido...');
      } else {
        console.log('El pedido no está pagado');
      }
    } else {
      console.log('El pedido está vacío');
    }
  } else {
    console.log('No hay pedido');
  }
}

Una guard clause (cláusula de guarda) comprueba los casos problemáticos al principio y sale de la función con return (early return, retorno temprano). Lo que queda al final es el “camino feliz”, sin anidar:

// ✅ Con guard clauses: plano y legible
function procesarPedido(pedido) {
  if (!pedido) {
    console.log('No hay pedido');
    return;
  }
  if (pedido.productos.length === 0) {
    console.log('El pedido está vacío');
    return;
  }
  if (!pedido.pagado) {
    console.log('El pedido no está pagado');
    return;
  }

  console.log('Enviando pedido...'); // el camino feliz
}

procesarPedido({ productos: ['libro'], pagado: true }); // Enviando pedido...
procesarPedido({ productos: [], pagado: true });        // El pedido está vacío

📘 Concepto: La misma idea funciona en los bucles con continue: descarta pronto los elementos que no te interesan y deja el código importante sin anidar. Profundizarás en return en el módulo 05.


15. Ejercicios

Ejercicio 1: Tarifa del cine

Escribe un programa que, a partir de una constante edad y otra dia (por ejemplo, 'miércoles'), calcule el precio de la entrada: 4 € para menores de 12 años, 5 € para mayores de 65 y 8 € para el resto. Los miércoles hay un 50 % de descuento para todos. Usa if/else if/else y muestra el precio con toFixed(2).

Ejercicio 2: Calculadora con switch y con objeto

Con las constantes a, b y operador ('+', '-', '*', '/', '%', '**'), calcula el resultado primero con un switch (controla la división entre cero y el operador no válido con default). Después reescríbelo usando un objeto de búsqueda con funciones flecha y compara ambas versiones.

Ejercicio 3: Números primos

Muestra todos los números primos entre 2 y 100. Un número es primo si solo es divisible entre 1 y él mismo. Pista: para cada número, usa un bucle interno que pruebe divisores desde 2 y un break en cuanto encuentres uno. Optimización: basta con probar divisores hasta la raíz cuadrada del número (d * d <= n).

Ejercicio 4: Estadísticas de un array

Dado const temperaturas = [12.5, 15, 9.8, 21.3, 18, 7.2, 14.6];, calcula con un for...of la temperatura máxima, la mínima y la media, y cuenta cuántos días superaron los 15 grados. Muestra los resultados con console.table.

Ejercicio 5: Pirámide de asteriscos

Pide (con prompt en el navegador o con readline en Node) una altura n y dibuja una pirámide centrada con asteriscos. Para n = 4:

   *
  ***
 *****
*******

Pista: en la fila i (empezando en 1) hay n - i espacios y 2 * i - 1 asteriscos. Puedes usar ' '.repeat(cantidad).

Ejercicio 6: Tabla de multiplicar completa en el DOM

Amplía el ejemplo 13.2 para que muestre todas las tablas del 1 al 10, cada una con su título h2, usando dos bucles for anidados. Resalta con una clase CSS los resultados que sean múltiplos de 10.

Ejercicio 7: Cajero automático

Crea en Node.js (con readline) un menú que se repita hasta que el usuario elija “Salir”: 1) Consultar saldo, 2) Ingresar, 3) Retirar, 4) Salir. El saldo empieza en 500 €. No se puede retirar más de lo que hay ni cantidades negativas. Usa do...while o while (true) con break, y switch para las opciones.

Ejercicio 8: Mejora el juego

Mejora “Adivina el número” (cualquiera de las dos versiones): al terminar, pregunta si se quiere jugar otra vez; guarda el mejor resultado (menor número de intentos) entre partidas; y añade niveles de dificultad (fácil: 1-50 con 10 intentos; normal: 1-100 con 7; difícil: 1-500 con 9) elegidos con un objeto de búsqueda.


Resumen

Estructura Sintaxis Úsala para
if if (cond) { ... } Ejecutar código si se cumple una condición
if/else if/else if (a) {} else if (b) {} else {} Varias alternativas (orden importante)
Ternario cond ? valor1 : valor2 Elegir entre dos valores
switch switch (x) { case 1: ... break; default: ... } Comparar un valor con casos concretos (===)
switch (true) case x < 10: Rangos (alternativa a else if)
Objeto de búsqueda TABLA[clave] ?? defecto Traducir valores sin switch
while while (cond) { ... } Repetir sin saber cuántas veces
do...while do { ... } while (cond); Repetir al menos una vez
for for (let i = 0; i < n; i++) { ... } Repetir con contador o índice
for...of for (const v of iterable) { ... } Valores de arrays y cadenas
for...in for (const k in objeto) { ... } Claves de objetos (no arrays)
break break; / break etiqueta; Salir del bucle
continue continue; Saltar a la siguiente vuelta
Guard clause if (!dato) { return; } Salir pronto y evitar anidar