Todo programa toma decisiones y repite acciones. Son las dos operaciones que hacen que el código sea útil: reaccionar a condiciones distintas y procesar listas de datos. En este capítulo verás las herramientas de JavaScript para hacer ambas cosas, con ejemplos del Overwatch Team Builder que tenemos como hilo conductor.
Antes de continuar, asegúrate de tener claros los conceptos del capítulo anterior: comparaciones con
===y valores truthy y falsy. Los usarás aquí desde el primer ejemplo. El operador ternario lo presentamos en este mismo capítulo, así que no hace falta que lo conozcas de antes.
if / else if / else: tomar una decisión#
La estructura más básica de control de flujo. Evalúa una condición y ejecuta un bloque u otro en función del resultado.
// winrate = victorias / partidas, entre 0 y 1
const winrate = 0.65;
// si el winrate es 60% o más → excelente
if (winrate >= 0.60) {
console.log('Rendimiento excelente');
// si no era >= 0.60 pero sí >= 0.50 → sólido
} else if (winrate >= 0.50) {
console.log('Rendimiento sólido');
// si no cumplió ninguna de las anteriores
} else {
console.log('Hay que revisar la estrategia');
}Dos reglas que evitan errores frecuentes:
Orden de mayor a menor exigencia. Si pones el else if (winrate >= 0.50) antes que el
if (winrate >= 0.60), un héroe con 0.65 de winrate caerá siempre en “Sólido”. La condición
más laxa se cumple primero y eclipsa a las siguientes. Orden correcto: la condición más
estricta, arriba.
=== siempre, no ==. El operador == hace coerción de tipos ("1" == 1 es true).
=== no: comprueba valor Y tipo. En producción, == es una fuente clásica de bugs difíciles
de rastrear.
El operador ternario: un if/else que devuelve un valor#
A veces no quieres ejecutar un bloque u otro, sino elegir un valor según una condición. Para eso está el operador ternario, la única expresión de JavaScript con tres partes:
// winrate entre 0 y 1
const winrate = 0.65;
// La forma es: condición ? valorSiSeCumple : valorSiNoSeCumple
// 0.65 >= 0.6 → 'Excelente'
const nivel = winrate >= 0.6 ? 'Excelente' : 'A revisar';
// Nivel: Excelente
console.log('Nivel: ' + nivel);Léelo como una pregunta: “¿winrate >= 0.6? Si sí, 'Excelente'; si no, 'A revisar'”. A
diferencia del if, el ternario devuelve un valor, así que puedes asignarlo a una variable o
meterlo dentro de un template literal.
Puedes encadenarlo para cubrir más de dos casos. Es el mismo árbol de decisión del if / else if / else de arriba, pero escrito como expresión:
// winrate entre 0 y 1
const winrate = 0.65;
// Se evalúa de izquierda a derecha: si un "?" no se cumple, se pasa al siguiente.
const nivel =
// ¿60% o más?
winrate >= 0.6
// sí → 'Excelente'
? 'Excelente'
// no → ¿50% o más?
: winrate >= 0.5
// sí → 'Sólido'
? 'Sólido'
// no → 'A revisar'
: 'A revisar';
// Nivel: Excelente
console.log('Nivel: ' + nivel);Cuándo usar cada uno: el ternario brilla para elegir un valor corto (un texto, un número). Si
dentro de cada rama hay varias líneas de lógica, usa if/else, que se lee mejor. Y encadenar
muchos ternarios se vuelve ilegible enseguida: con tres casos o más, valora si un if no es más
claro.
Operadores lógicos: && , || y !#
Ya los viste en el capítulo anterior (“Valores y comparaciones”): && (y), || (o) y !
(no) sirven para combinar condiciones. Aquí los repasamos rápido y luego entramos en lo que
ese capítulo no cubrió: el cortocircuito, que es el comportamiento que el código real
aprovecha a diario.
&& (y): devuelve el primer valor falsy que encuentra, o el último si todos son truthy.
// si la variable activo es true y partidas supera 10, se entra al if
const activo = true;
// número de partidas del héroe
const partidas = 50;
// Solo se entra si AMBAS condiciones son true.
// activo=true y partidas(50) > 10 → se imprime
if (activo && partidas > 10) {
console.log('Héroe válido para el ranking');
}|| (o): devuelve el primer valor truthy que encuentra, o el último si todos son falsy.
Se usa mucho para valores por defecto:
// rol podría ser "" (vacío) o undefined si el dato no llegó
const rolRecibido = '';
// si rolRecibido es falsy (vacío, undefined, null...), usa 'Desconocido'
const rol = rolRecibido || 'Desconocido';
// "Desconocido": rolRecibido era falsy
console.log(rol);! (no): invierte el valor de verdad de una expresión.
// si activo es false, !activo es true y se entra al if
const activo = false;
// ! invierte el valor: true → false, false → true
if (!activo) {
// solo se llega aquí si activo era false
console.log('Este héroe está retirado del equipo');
}Cortocircuito: la parte que aún no habías visto#
JavaScript evalúa && y || de izquierda a derecha y se detiene en cuanto conoce el
resultado. No evalúa lo que queda si no hace falta. A esto se le llama cortocircuito y es
importante entenderlo porque el código real lo usa constantemente.
Con &&, si el primer operando es falsy, el resultado ya es falsy: el segundo operando no
se evalúa. Esto es útil para evitar errores como dividir entre cero:
// partidas podría ser 0 si el héroe aún no ha jugado ninguna
const partidas = 0;
// victorias del héroe
const victorias = 78;
// Si partidas es 0 (falsy), el segundo operando nunca se evalúa.
// Evitamos dividir victorias entre 0, que daría Infinity.
// "partidas > 0" es false → && para aquí y devuelve false
if (partidas > 0 && (victorias / partidas) >= 0.60) {
console.log('Winrate excelente');
} else {
// se ejecuta este porque partidas es 0
console.log('Sin partidas registradas');
}Con ||, si el primer operando es truthy, el resultado ya es ese valor: el segundo operando
no se evalúa. Lo viste arriba en el ejemplo del valor por defecto. Ahora con más contexto:
// '' es falsy → devuelve el segundo operando ('Desconocido')
const nombreRol = '' || 'Desconocido';
// imprime 'Desconocido'
console.log(nombreRol);
// 'Daño' es truthy → devuelve el primero y no evalúa el segundo
const rolConfirmado = 'Daño' || 'Desconocido';
// imprime 'Daño'
console.log(rolConfirmado);El cortocircuito no es un caso raro: lo encontrarás en casi cualquier codebase para valores por defecto compactos y para proteger expresiones que podrían fallar si el dato no existe.
switch: elegir entre casos concretos#
if/else if es para rangos o condiciones complejas. switch es para casos discretos:
cuando una variable puede tomar un valor concreto de entre varios posibles.
// el rol del héroe que queremos clasificar
const rol = 'Tanque';
// evalúa rol una vez y lo compara con cada case
switch (rol) {
// si rol === 'Tanque'
case 'Tanque':
console.log('Línea frontal: absorbe daño y protege al equipo');
// sale del switch; sin esto, caería al case siguiente
break;
// si rol === 'Daño'
case 'Daño':
console.log('Atacante: maximiza el daño al rival');
break;
// si rol === 'Apoyo'
case 'Apoyo':
console.log('Soporte: cura, protege y potencia a los aliados');
break;
// si ningún case coincidió (equivale al else final)
default:
console.log('Rol no reconocido');
}El break es obligatorio en cada caso salvo que quieras intencionadamente que la ejecución
“caiga” al siguiente. Sin él, JavaScript sigue ejecutando el código del case siguiente
aunque su condición no coincida —el llamado fall-through— y casi siempre es un bug.
El default actúa como el else: se ejecuta si ningún case coincide.
¿Cuándo usar switch y cuándo if?
- Comparar una variable contra varios valores concretos (
=== 'Tanque',=== 'Daño') →switch. - Comparar rangos (
>= 0.60,< 0.50) o condiciones compuestas →if/else if.
Bucles: repetir acciones#
for: cuando sabes cuántas veces#
La forma clásica. Tiene tres partes separadas por ;: la inicialización (let ronda = 1,
se ejecuta una sola vez al arrancar), la condición (ronda <= 5, se comprueba antes de
cada vuelta; cuando da false, el bucle termina) y la actualización (ronda++, se ejecuta
al final de cada vuelta). ronda++ es la forma corta de escribir ronda = ronda + 1: suma uno
a la variable. Así la cuenta avanza y el bucle no se queda dando vueltas para siempre.
// Un partido competitivo se juega al mejor de 5 rondas. Numeramos cada una.
// ronda va de 1 a 5; ronda++ pasa a la siguiente
for (let ronda = 1; ronda <= 5; ronda++) {
// cuerpo: se repite una vez por ronda
console.log('Ronda ' + ronda + ' de 5');
}Cuando lo que quieres es recorrer una lista, for...of es más limpio porque no necesitas
la variable índice:
// En el capítulo "Arrays y objetos" verás los arrays a fondo.
// Aquí usamos uno mínimo para que el bucle tenga algo que recorrer.
// lista de tres cadenas
const nombres = ['Tracer', 'Reinhardt', 'Mercy'];
// en cada iteración, nombre toma el valor del elemento actual
for (const nombre of nombres) {
console.log('Héroe: ' + nombre);
}La palabra clave of le dice al bucle que recorra los valores de la lista, uno a uno.
En cada vuelta, la variable (nombre) toma el siguiente elemento de forma automática: primero
'Tracer', luego 'Reinhardt', luego 'Mercy'. No hay índice que llevar, no hay condición
que mantener, no hay i++ que recordar. Compáralo con el for clásico:
// for clásico: tú controlas el índice manualmente.
// i = 0, 1, 2; .length es la cantidad de elementos
for (let i = 0; i < nombres.length; i++) {
// nombres[i] accede al elemento en la posición i
console.log('Héroe: ' + nombres[i]);
}
// for...of: el bucle lleva el índice por ti; solo describes qué hacer con cada valor.
// of recorre los valores directamente
for (const nombre of nombres) {
// nombre ya ES el elemento, sin indexar
console.log('Héroe: ' + nombre);
}Ambos producen el mismo resultado. for...of es preferible cuando no necesitas saber en qué
posición estás, que es la mayoría de las veces.
Arrays y objetos: lo justo para el ejercicio
El playground y el ejercicio usan una lista de héroes. Para no llegar ciegos, aquí tienes lo mínimo. El capítulo siguiente (“Arrays y objetos”) lo cubre todo a fondo.
Un array es una lista ordenada de valores. Se escribe con corchetes [], y sus elementos
van separados por comas:
// lista de tres strings; los corchetes abren y cierran el array
const nombres = ['Tracer', 'Reinhardt', 'Mercy'];Un objeto agrupa datos relacionados bajo un mismo nombre. Se escribe con llaves {}, y
cada dato es un par clave: valor:
// un objeto con cuatro propiedades: nombre, rol, partidas y victorias
const tracer = { nombre: 'Tracer', rol: 'Daño', partidas: 120, victorias: 78 };Para leer una propiedad de un objeto se usa el punto seguido del nombre de la propiedad:
// accede a la propiedad "nombre" del objeto tracer
const nombre = tracer.nombre;
// "Tracer"
console.log(nombre);
// accede a la propiedad "victorias"
const victorias = tracer.victorias;
// 78
console.log(victorias);Puedes meter objetos dentro de un array. Cuando el for...of recorre ese array, en cada
vuelta la variable de iteración es uno de esos objetos, y puedes leer sus propiedades con
el punto:
// array cuyos elementos son objetos (cada uno es un héroe)
const heroes = [
{ nombre: 'Tracer', rol: 'Daño', partidas: 120, victorias: 78 },
{ nombre: 'Mercy', rol: 'Apoyo', partidas: 200, victorias: 130 },
];
// en cada iteración, heroe ES uno de los objetos del array
for (const heroe of heroes) {
// heroe.nombre lee la propiedad nombre del héroe actual
console.log(heroe.nombre + ' — ' + heroe.rol);
}
// Tracer — Daño
// Mercy — ApoyoCon esto es suficiente para el ejercicio. Todo lo demás (métodos de array, desestructuración, cómo añadir o eliminar elementos) lo verás en el capítulo siguiente.
while: cuando no sabes cuántas veces de antemano#
while sigue ejecutando su cuerpo mientras la condición sea verdadera. Es útil cuando el
número de iteraciones depende de algo que no conoces al entrar al bucle.
Math.random()es una función de la biblioteca estándar de JavaScript.Mathes un objeto con herramientas matemáticas incorporadas;.random()devuelve un número decimal al azar entre 0 (incluido) y 1 (excluido): algo como0.4273o0.9981. No tienes que importarlo ni instalarlo: está disponible siempre. Lo usamos aquí para simular el resultado aleatorio de una partida.
// contador de partidas jugadas
let partidas = 0;
// contador de victorias acumuladas
let victorias = 0;
// Simula partidas hasta llegar a 5 victorias.
// No sabemos de antemano cuántas partidas hará falta: de ahí el while.
// sigue mientras no hayamos llegado a 5 victorias
while (victorias < 5) {
// cuenta esta partida (partidas = partidas + 1)
partidas++;
// Math.random() devuelve un decimal aleatorio entre 0 y 1.
// Si cae por debajo de 0.60, simulamos victoria (60% de probabilidad).
if (Math.random() < 0.60) {
// ganamos: sumamos una victoria
victorias++;
}
// Si perdemos, victorias no cambia y el bucle sigue
}
console.log('Victorias conseguidas en ' + partidas + ' partidas');Si la condición empieza siendo falsa, el cuerpo no se ejecuta ni una vez. Si nunca se hace falsa, tienes un bucle infinito —el programa se cuelga—. Por eso, asegúrate siempre de que algo dentro del bucle avanza hacia hacer la condición false.
break y continue: controlar el flujo dentro del bucle#
break sale del bucle por completo en cuanto se ejecuta. Útil cuando ya tienes lo que
buscabas y no tiene sentido seguir iterando.
continue salta el resto de la iteración actual y pasa a la siguiente. Útil para omitir
ciertos elementos sin salir del bucle.
const nombres = ['Tracer', 'Reinhardt', 'Mercy', 'Genji', 'Ana'];
for (const nombre of nombres) {
// Omitimos a Reinhardt en este recorrido.
if (nombre === 'Reinhardt') continue;
// Paramos cuando llegamos a Genji.
if (nombre === 'Genji') break;
// solo llega aquí si no era Reinhardt ni Genji
console.log('Procesando: ' + nombre);
}
// Imprime: "Procesando: Tracer" y "Procesando: Mercy".break también funciona dentro de switch, como viste arriba.
Pruébalo tú#
El playground muestra los tres conceptos principales en acción: clasificación con
if/else if, descripción del rol con switch y un resumen del equipo con for...of.
Edita los umbrales o el dataset y observa cómo cambia la consola.
Comprueba lo que sabes#
Pregunta 1 de 5
¿Qué imprime este código? const x = 0; if (x) { console.log("sí"); } else { console.log("no"); }
Tu turno#
Lee el enunciado, escribe tu solución en el editor y cuando la tengas (o si te atascas) despliega las soluciones. Lo valioso no es solo que funcione: fíjate en el salto de un nivel al siguiente y en el porqué de cada decisión.
Ejercicio · en esta página
Clasifica a los héroes por rendimiento
Recorre la lista de héroes con un bucle for...of. Para cada uno, calcula su winrate (victorias / partidas) y clasifícalo: winrate >= 0.60 → "Excelente", >= 0.50 → "Sólido", si no → "A revisar". Imprime un resumen por consola con nombre, rol, porcentaje y nivel.
Paso 1: Que funcione
- Recorre todos los héroes de la lista.
- La clasificación en tres niveles es correcta.
- Se imprime algo por consola para cada héroe.
Paso 2: Que esté pulido
- Usa for...of en vez de for con índice manual.
- === en todas las comparaciones.
- else if ordenado del umbral más alto al más bajo.
- El winrate se muestra como porcentaje (p.ej. 65.0%).
Paso 3: Que sea excelente
- Umbrales en const con nombre (UMBRAL_EXCELENTE, UMBRAL_SOLIDO).
- Sin lógica especial innecesaria: todos los héroes con el mismo formato.
- Un comentario corto que explica el porqué de las decisiones clave.
Ver soluciones
// ════════════════════════════════════════════════════════════════════════════
// NIVEL OK — "funciona"
//
// Enfoque base: el bucle recorre la lista y las condiciones clasifican
// el winrate. Produce el resultado correcto, que es el primer requisito.
//
// Sus límites (los que arregla el nivel "Mejor"):
// - Usa == en vez de === en alguna comparación (coerción de tipos, peligroso).
// - Los umbrales son números sueltos en el código (números mágicos).
// - El if interior podría estar peor ordenado (condición más laxa primero).
// - El formateo del winrate no existe: se muestra el decimal crudo (0.65).
// Aun así: clasifica bien y produce una línea por héroe. Eso vale como OK.
// ════════════════════════════════════════════════════════════════════════════
// lista con los seis héroes del equipo
const heroes = [
{ nombre: "Tracer", rol: "Daño", partidas: 120, victorias: 78 },
{ nombre: "Reinhardt", rol: "Tanque", partidas: 90, victorias: 51 },
{ nombre: "Mercy", rol: "Apoyo", partidas: 200, victorias: 130 },
{ nombre: "Genji", rol: "Daño", partidas: 150, victorias: 72 },
{ nombre: "Ana", rol: "Apoyo", partidas: 110, victorias: 66 },
{ nombre: "Winston", rol: "Tanque", partidas: 80, victorias: 38 },
];
// for clásico con índice: i empieza en 0, avanza de uno en uno, para antes de heroes.length
for (let i = 0; i < heroes.length; i++) {
// accede al héroe en la posición i
const heroe = heroes[i];
// decimal entre 0 y 1 (p.ej. 0.65)
const winrate = heroe.victorias / heroe.partidas;
// se asignará dentro del if según el winrate
let nivel;
// umbral más laxo primero (orden incorrecto)
if (winrate >= 0.5) {
nivel = "Sólido";
// nunca se llega aquí; eclipsado por el anterior
} else if (winrate >= 0.6) {
nivel = "Excelente";
} else {
nivel = "A revisar";
}
// == en vez de ===: funciona aquí porque rol siempre es string,
// pero es un hábito peligroso que conviene corregir.
// compara con == (sin comprobar el tipo)
if (heroe.rol == "Apoyo") {
// decimal crudo, no porcentaje
console.log(heroe.nombre + " [Apoyo] — " + winrate + " — " + nivel);
} else {
console.log(
heroe.nombre + " (" + heroe.rol + ") — " + winrate + " — " + nivel,
);
}
} Por qué este nivel
- Produce el resultado correcto: clasifica y imprime. Eso es lo primero y ya vale como OK.
- El orden de los else if está invertido: la condición >= 0.50 se comprueba antes que >= 0.60, así que "Excelente" nunca se alcanza. El bug es sutil pero real.
- Usa == en lugar de ===: en este caso no rompe nada, pero es un hábito que sí rompe cosas en código real.
- El winrate se imprime como decimal crudo (0.65), no como porcentaje.
// ════════════════════════════════════════════════════════════════════════════
// NIVEL MEJOR — "pulido"
//
// Misma lógica, pero con las decisiones de código correctas:
// - === en todas las comparaciones (sin coerción).
// - else if ordenado del umbral más alto al más bajo: la primera rama que
// se cumple es la más exigente, sin posibilidad de eclipsar condiciones.
// - Winrate formateado como porcentaje legible (65.0%).
// - for...of en vez de for con índice cuando no necesitas la posición:
// menos ruido, más intención.
//
// Sus límites (los que arregla el nivel "Excelente"):
// - 0.60 y 0.50 siguen siendo números mágicos en el cuerpo del bucle.
// - El rol se imprime de forma diferente para Apoyo, sin necesidad real.
// ════════════════════════════════════════════════════════════════════════════
// lista con los seis héroes del equipo
const heroes = [
{ nombre: "Tracer", rol: "Daño", partidas: 120, victorias: 78 },
{ nombre: "Reinhardt", rol: "Tanque", partidas: 90, victorias: 51 },
{ nombre: "Mercy", rol: "Apoyo", partidas: 200, victorias: 130 },
{ nombre: "Genji", rol: "Daño", partidas: 150, victorias: 72 },
{ nombre: "Ana", rol: "Apoyo", partidas: 110, victorias: 66 },
{ nombre: "Winston", rol: "Tanque", partidas: 80, victorias: 38 },
];
// for...of: sin variable índice, sin condición manual; el bucle dice lo que hace
for (const heroe of heroes) {
// decimal entre 0 y 1
const winrate = heroe.victorias / heroe.partidas;
// formateado: "65.0%"
const porcentaje = (winrate * 100).toFixed(1) + "%";
let nivel;
// umbral más exigente primero: sin riesgo de eclipsar ramas
if (winrate >= 0.6) {
nivel = "Excelente";
// solo llega aquí si winrate era < 0.60
} else if (winrate >= 0.5) {
nivel = "Sólido";
// winrate < 0.50
} else {
nivel = "A revisar";
}
console.log(`${heroe.nombre} (${heroe.rol}) — ${porcentaje} — ${nivel}`);
} Por qué es mejor que el anterior
- for...of: sin la variable i, sin el ruido de la condición y el incremento. El bucle dice lo que hace.
- === en todas las comparaciones: sin coerción, sin sorpresas.
- else if ordenado del umbral más alto al más bajo: la condición más exigente primero, sin riesgo de eclipsar ramas.
- El winrate se formatea con toFixed(1) y un símbolo de porcentaje: legible para una persona, no solo para el motor.
// ════════════════════════════════════════════════════════════════════════════
// NIVEL EXCELENTE — "óptimo"
//
// Los umbrales están en constantes con nombre: si el equipo decide cambiar
// los umbrales, hay un único sitio donde tocar, no que buscar números
// mágicos en el cuerpo del bucle.
//
// La clasificación y el formateo están como expresiones claras dentro del
// bucle, sin lógica duplicada. No hay funciones propias todavía (eso es el
// capítulo siguiente), pero la intención de cada línea se lee sola.
//
// Por qué mejora al nivel "Mejor":
// - Umbrales en const: UMBRAL_EXCELENTE y UMBRAL_SOLIDO. Un cambio de
// negocio se aplica en una línea, no en n sitios del bucle.
// - Sin lógica especial para el rol Apoyo: todos los héroes se imprimen
// igual. Si no necesitas distinción, no la introduzcas.
// - El porcentaje se calcula con la misma expresión compacta; sin variable
// intermedia si no aporta nombre.
// - Comentario corto que explica EL PORQUÉ de los umbrales, no el qué.
// ════════════════════════════════════════════════════════════════════════════
// lista con los seis héroes del equipo
const heroes = [
{ nombre: "Tracer", rol: "Daño", partidas: 120, victorias: 78 },
{ nombre: "Reinhardt", rol: "Tanque", partidas: 90, victorias: 51 },
{ nombre: "Mercy", rol: "Apoyo", partidas: 200, victorias: 130 },
{ nombre: "Genji", rol: "Daño", partidas: 150, victorias: 72 },
{ nombre: "Ana", rol: "Apoyo", partidas: 110, victorias: 66 },
{ nombre: "Winston", rol: "Tanque", partidas: 80, victorias: 38 },
];
// Los umbrales como constantes con nombre: si el criterio cambia,
// hay un solo sitio donde actualizar el código.
// winrate de 60% o más → Excelente
const UMBRAL_EXCELENTE = 0.6;
// winrate de 50% o más (pero < 60%) → Sólido
const UMBRAL_SOLIDO = 0.5;
// for...of: sin variable índice, sin condición manual; el bucle dice lo que hace
for (const heroe of heroes) {
// decimal entre 0 y 1
const winrate = heroe.victorias / heroe.partidas;
// Rango → if/else if: la estructura correcta para condiciones escalonadas.
// El umbral más alto primero: la primera rama que se cumple es la más exigente.
let nivel;
// 60% o más
if (winrate >= UMBRAL_EXCELENTE) {
nivel = "Excelente";
// entre 50% (incluido) y 60% (excluido)
} else if (winrate >= UMBRAL_SOLIDO) {
nivel = "Sólido";
// por debajo del 50%
} else {
nivel = "A revisar";
}
// toFixed(1) redondea a un decimal; el resultado es un string ("65.0%")
console.log(
`${heroe.nombre} (${heroe.rol}) — ${(winrate * 100).toFixed(1)}% — ${nivel}`,
);
} Por qué es mejor que el anterior
- Los umbrales en const con nombre: UMBRAL_EXCELENTE y UMBRAL_SOLIDO. Si el criterio cambia, hay un único sitio donde tocar. Los números mágicos escondidos en el cuerpo de un bucle son una fuente clásica de bugs.
- Sin tratamiento especial innecesario para ningún rol: todos los héroes pasan por el mismo formato. Si no necesitas distinción, no la introduzcas.
- El comentario explica el POR QUÉ de la estructura elegida (rango → if/else if; umbral más alto primero), no el qué. El qué ya lo dice el código.