Saltar a contenido

1. Control de flujo: conceptos y diagrama

UD3 — Control de Flujo y Depuración · RA3 — Escribe y depura código, analizando y utilizando las estructuras de control del lenguaje.

IES Thiar · Curso 2026 - 2027

Aspecto Valor
Resultado de aprendizaje RA: 3 — Escribe y depura código, analizando y utilizando las estructuras de control del lenguaje.
Criterios de evaluación a los que contribuye esta sección CE 3.1 (identifica y clasifica las estructuras de control del lenguaje)
CE 3.2 (reconoce y construye expresiones booleanas válidas como base de toda decisión).
Marco normativo RD 405/2023 · RD 450/2010 · RD 659/2023
Tecnología base Java 25 LTS (OpenJDK Temurin 25) — soporte hasta 2030+
Horas estimadas ~4 h (de las 26 h de la UD3).

Requisitos previos (lo que ya sabes de UD1): variables, tipos primitivos (incluido boolean), operadores relacionales y lógicos, operador ternario ?:, String y text blocks, var, lectura con java.lang.IO (IO.readln, IO.println) y java.util.Scanner, conversión con Integer.parseInt / Double.parseDouble.


1.1 ¿De qué va esta sección? — Por qué necesitamos "controlar el flujo"

Antes de empezar, respira un segundo y date un aplauso: has llegado hasta aquí. Has pasado por la UD1, has escrito tus primeros programas en Java 25, has peleado con el punto y coma, has visto qué es una variable, has utilizado IO.readln para pedir datos y Integer.parseInt para convertir texto en número. Eso no es poco: ya sabes ejecutar un programa. Ahora vamos a aprender a decidir qué hace el programa en cada momento.

Hasta ahora, todos tus programas han hecho lo que hace una persona que sigue una receta de cocina al pie de la letra: empieza por el primer paso, sigue por el segundo, y termina en el último. Ese comportamiento se llama ejecución secuencial, y está bien para cosas sencillas. Pero la vida real, y desde luego un programa útil, no funciona así. A veces tienes que tomar decisiones ("si el usuario es mayor de edad, le muestro esta pantalla; si no, le muestro otra"), a veces tienes que repetir algo ("mientras el usuario no escriba 'salir', le sigo pidiendo órdenes") y a veces tienes que interrumpir lo que estabas haciendo ("si detecto un error, paro el bucle inmediatamente").

A ese "decidir qué pasa después" se le llama control de flujo, y es, sin exagerar, el salto más importante que das como programador. Sin control de flujo, un programa es poco más que una calculadora. Con control de flujo, un programa puede reaccionar al mundo, adaptarse a los datos, validar entradas, reintentar operaciones, ofrecer menús, procesar listas y, en última instancia, comportarse como una aplicación de verdad.

Analogía del GPS

Imagínate que tu programa es un coche que tiene que ir de Cartagena a Alicante. Sin control de flujo, solo sabes ir en línea recta: aceleras y avanzas. Con control de flujo, en cada cruce decides: "si hay atasco por la AP-7, tomo la N-332". Eso es exactamente lo que vamos a aprender a hacer con el código: poner carteles, bifurcaciones y "dar la vuelta" en nuestro programa cuando hace falta.

En esta sección no vamos a escribir todavía las palabras reservadas if, while o for. Eso será a partir de la sección 2. Aquí vamos a hacer algo más importante y que mucha gente se salta: entender qué es el control de flujo, qué tipos hay, cómo se representa visualmente y por qué el corazón de todo es la condición booleana. Cuando acabes esta sección, verás un algoritmo y sabrás "leerlo" aunque no conozcas todavía su sintaxis. Y cuando lleguemos a la sección 2, aprender if te parecerá natural, no un bulto nuevo.

Vamos a ello, con calma y con ejemplos del mundo real.


1.2 La ejecución secuencial: el camino por defecto

1.2.1 ¿Qué significa "secuencial"?

Cuando ejecutas un programa en Java, la máquina virtual (JVM) lee las instrucciones de arriba a abajo, una detrás de otra, en el orden en que las has escrito. No salta, no se salta líneas, no retrocede. Cada instrucción termina (con un punto y coma en el caso de una instrucción simple) y, automáticamente, pasa a la siguiente.

Esto es lo que has estado haciendo toda la UD1 sin nombrearlo. Cuando escribiste:

1
2
3
4
5
// Ejemplo: secuencia simple que saluda y se despide
IO.println("Hola, ¿cómo te llamas?");
String nombre = IO.readln();
IO.println("Encantado, " + nombre);
IO.println("Hasta pronto.");

La JVM ha ejecutado las cuatro sentencias exactamente en el orden en que están escritas. Ni una más, ni una menos, ni en otro orden. Ese es el flujo secuencial.

1.2.2 Analogía de la receta de cocina

Piensa en una receta de tortilla de patata:

  1. Pelar las patatas.
  2. Cortarlas en láminas finas.
  3. Freírlas en aceite suave.
  4. Batir los huevos en un bol.
  5. Mezclar las patatas con los huevos.
  6. Cuajar la tortilla en la sartén.

Cada paso depende del anterior: no puedes batir los huevos encima de las patatas crudas. Y cada paso se ejecuta una sola vez. Si tu vida fuera un programa secuencial, serías un cocinero muy riguroso pero también muy rígido: si no tienes patatas, igualmente intentarías pelarlas. Si los huevos están podridos, los batirías igualmente. El programa no "decide" nada.

1.2.3 Los límites de la secuencia

La ejecución secuencial es perfecta para programas triviales, pero se queda corta en cuanto aparece cualquiera de estas situaciones reales:

Situación real ¿Qué necesitas? Ejemplo en Java (lo veremos luego)
Pedir la edad y mostrar un mensaje distinto según sea mayor o menor de edad. Decidir entre dos caminos alternativos. Estructura de selección (if/else).
Volver a pedir un número mientras el usuario escriba algo que no es un entero. Repetir un bloque de código hasta que se cumpla una condición. Estructura de repetición (while).
Recorrer los 30 días de un mes para calcular el gasto diario. Repetir un bloque un número conocido de veces. Estructura de repetición (for).
Salir de un bucle inmediatamente si el dato introducido es 0. Interrumpir el flujo normal. Sentencia de salto (break).
Si el usuario no escribe nada, mostrar un mensaje y terminar el programa. Decidir y salir a la vez. Combinación de selección y salto (return).

La trampa del programa secuencial

Un programa estrictamente secuencial no puede reaccionar a los datos del usuario. Hace lo mismo pase lo que pase. Y en el mundo real casi nunca queremos eso: una aplicación de banca no le permite sacar dinero igual a un cliente con saldo cero que a uno con 1000 €. Necesitamos que el programa observe y decida.

1.2.4 Representación visual: el diagrama secuencial

Antes de saltar a las estructuras de control, vamos a aprender a dibujar el flujo secuencial. Esto te va a servir para todo el resto del curso. Un diagrama de flujo secuencial es muy aburrido, precisamente porque la secuencia es el camino más simple:

flowchart TD
    Inicio([Inicio]) --> A["IO.println 'Hola'"]
    A --> B["String nombre = IO.readln()"]
    B --> C["IO.println 'Encantado, ' + nombre"]
    C --> Fin([Fin])

Observa lo que hemos hecho: cada instrucción se ha convertido en una caja y se ha unido con flechas. Las flechas son el "flujo": indican qué viene después. En un diagrama secuencial las flechas nunca se bifurcan ni se vuelven hacia atrás. Por eso, cuando veas un diagrama con flechas que se separan o que vuelven, sabrás inmediatamente que estás ante una estructura de control.

1.2.5 Buenas prácticas en la ejecución secuencial

Aunque la secuencia es lo más simple, ya desde ahora conviene coger buenas maneras:

  1. Una instrucción por línea. Aunque Java te permite escribir varias sentencias en la misma línea (int a = 1; int b = 2;), es ilegible. Una sentencia, una línea.
  2. Declarar variables cerca de donde se usan. No declares todo al principio del programa "por si acaso". Declara nombre justo antes de pedirlo. Eso hace el código más legible y más fácil de mantener.
  3. Comentarios que expliquen "por qué", no "qué". El "qué" ya lo dice el código; el comentario debe contar la intención.
  4. Nombres significativos. edad se entiende mejor que e. precioTotal mejor que x2. Esta regla vale para todo el curso, no solo para la UD3, pero conviene recordarla.
// Mal: declarar todo arriba "por si acaso"
int edad;
String nombre;
double precio;

// Bien: declarar cerca del uso
IO.print("¿Cómo te llamas? ");
String nombre = IO.readln();   // se declara y se inicializa a la vez
IO.print("¿Qué edad tienes? ");
int edad = Integer.parseInt(IO.readln());

La diferencia parece pequeña, pero cuando tu programa tenga cincuenta variables, agradecerás haber sido ordenado desde el primer día.


1.3 Los tres pilares del control de flujo

La programación estructurada, demostrada formalmente por Böhm y Jacopini en 1966, dice algo que parece magia: cualquier algoritmo imaginable se puede escribir combinando solo tres tipos de estructuras de control. Solo tres. Lo que vas a ver durante el resto de la UD3 —desde el if hasta el switch expression con pattern matching de Java 21, pasando por todos los bucles y el manejo de excepciones— son variaciones sobre estos tres tipos. Conocerlos por nombre y saber qué hace cada uno es, literalmente, tener las llaves del castillo.

1.3.1 Selección — "decidir entre caminos"

La selección permite que el programa escoja, en función de una condición, qué instrucciones ejecutar y cuáles saltarse. Es la base de cualquier programa que reacciona al usuario o al estado de los datos.

Analogías del mundo real:

  • El cajero automático que pregunta: "¿Quiere imprimir el ticket?". Si la respuesta es sí, imprime; si no, no.
  • El control de acceso de una discoteca: "Si eres mayor de 18, entras; si no, te quedas fuera".
  • El navegador GPS: "Si hay atasco en la ruta habitual, recalculo con otra carretera".

Forma genérica en diagrama de flujo:

flowchart TD
    Inicio([Inicio]) --> Condicion{"¿Condición\nbooleana?"}
    Condicion -->|Sí, verdadero| A["Bloque A"]
    Condicion -->|No, falso| B["Bloque B"]
    A --> Fin([Fin])
    B --> Fin

Ese rombo es el símbolo universal de decisión. Java tiene tres mecanismos para implementar la selección (los veremos en las secciones 2 y 3):

  • if / else if / else (selección por condiciones booleanas).
  • Operador ternario ?: (selección en una sola línea).
  • switch expression (selección por valor concreto de una variable).

1.3.2 Iteración — "repetir mientras haga falta"

La iteración (también llamada bucle o loop) permite ejecutar un bloque de instrucciones varias veces, ya sea un número fijo de veces o hasta que se cumpla una condición de salida.

Analogías del mundo real:

  • Subir una escalera: "mientras no haya llegado al último escalón, subo uno más".
  • Escuchar música en bucle: "reproduce esta playlist hasta que yo pulse stop".
  • Revisar correos en la bandeja de entrada: "para cada mensaje sin leer, márcalo como leído".

Forma genérica en diagrama de flujo (bucle con precondición):

flowchart TD
    Inicio([Inicio]) --> Condicion{"¿Condición\npara repetir?"}
    Condicion -->|Sí| Cuerpo["Bloque que se repite"]
    Cuerpo --> Condicion
    Condicion -->|No| Fin([Fin])

Fíjate bien: la flecha vuelve hacia atrás. Esa flecha que regresa es la marca visual de un bucle. Cuando veas una flecha retrocediendo, ya sabes que estás ante una iteración.

Java tiene cuatro mecanismos de iteración (los veremos en las secciones 4 a 6):

  • while (precondición: comprueba antes de entrar).
  • do-while (poscondición: comprueba después de ejecutar al menos una vez).
  • for clásico (cuando sabes cuántas iteraciones necesitas).
  • for-each (para recorrer colecciones elemento a elemento, sin índice).

1.3.3 Salto — "interrumpir el flujo normal"

Las sentencias de salto modifican el flujo saliéndose del camino por defecto de la secuencia o del bucle. No son nuevas estructuras, sino "interruptores" que se insertan dentro de las estructuras anteriores.

Analogías del mundo real:

  • Estás viendo una película en el sofá y suena el teléfono: "pauso la película, atiendo, y luego continúo".
  • Estás rellenando un impreso y descubres que falta una página: "paro inmediatamente, no sigo rellenando".
  • Estás buscando las llaves por los bolsillos: "en cuanto las encuentre, dejo de buscar".

Java tiene cuatro sentencias de salto (las veremos en la sección 7):

Sentencia Qué hace Dónde se usa
break Sale inmediatamente del bucle o del switch más cercano. Bucles y switch.
continue Salta a la siguiente iteración del bucle sin ejecutar lo que queda. Bucles.
return Sale del método completo (devolviendo o no un valor). Cualquier método.
break etiqueta Sale de un bucle externo etiquetado (uso avanzado). Bucles anidados.

El mito de 'goto es malo'

Quizás oigas que "el salto es pecado" o que "las sentencias de salto rompen la estructuración". En Java no existe goto (es palabra reservada pero no se usa), y break/continue/return son saltos estructurados y limitados que no permiten saltar a cualquier punto del programa. Son perfectamente legítimos cuando se usan bien; en la sección 7 verás cómo.

1.3.4 Mapa global: ¿qué verás en cada sección?

Para que no te pierdas, esta tabla te dice en qué sección de la UD3 se desarrolla cada mecanismo concreto de los tres pilares:

Pilar Mecanismo Sección de UD3
Selección if / else if / else y operador ternario aplicado Sección 2
Selección switch expression (flecha, multitipo, con tipos, guards, exhaustividad) Sección 3
Iteración while y do-while Sección 4
Iteración for clásico Sección 5
Iteración for-each (enhanced for) Sección 6
Salto + iteración Bucles anidados, break, break etiquetado, continue, return Sección 7

Y, por si te lo preguntas: el control de excepciones (try/catch/finally), que verás en la sección 8, también es una forma de control de flujo, pero de un tipo especial: salta a un bloque de manejo de errores cuando algo falla. Lo verás a fondo más adelante; por ahora quédate con que existe.

1.3.5 Composición de estructuras: el verdadero poder

La magia de los tres pilares no es que cada uno sea potente por separado, sino que se pueden combinar dentro unos de otros sin límite. Puedes meter una selección dentro de una iteración (un bucle que, en cada vuelta, decide qué hacer según el dato leído), o una iteración dentro de una selección (si el usuario eligió "ver listado", recorro una lista; si eligió "salir", no). Y a su vez meter selecciones dentro de selecciones, bucles dentro de bucles (tablas, matrices) y saltos en cualquier punto.

mindmap
  root((Estructuras de control))
    Selección
      if / else if / else
      Operador ternario
      switch expression
    Iteración
      while
      do-while
      for clásico
      for-each
    Salto
      break
      continue
      return
      break etiquetado
    Composición
      Selección dentro de iteración
      Iteración dentro de selección
      Bucles anidados
      Excepciones "sección 8"

Esta composición es lo que permite escribir cualquier algoritmo, desde una calculadora hasta el control de un cohete. Si te aprendes bien estos tres pilares y cómo combinarlos, tendrás una base sólida para todo el ciclo de DAM y para tu carrera profesional.


1.4 Diagramas de flujo: tu mapa antes del viaje

1.4.1 ¿Por qué dibujar antes de programar?

Cuando vas a hacer un viaje largo, lo normal es que mires el mapa antes de arrancar. No te sientas al volante y empiezas a girar a ciegas. Pues con la programación pasa igual: dibujar el flujo antes de escribir código te ahorra horas de debug y muchos dolores de cabeza. Es una de las habilidades más infravaloradas entre quienes empiezan, y de las que más agradeces cuando llevas cinco años programando.

El diagrama de flujo es un lenguaje visual universal: no depende de Java, ni de Python, ni de nadie. Cualquier programador del mundo, sea del lenguaje que sea, entiende un diagrama de flujo bien hecho. Por eso se usa para:

  • Documentar cómo funciona un algoritmo (CE 3.7).
  • Diseñar una solución antes de programarla, para detectar casos límite.
  • Comunicarte con compañeros, jefes o clientes no técnicos.
  • Aprender a pensar como un programador, separando el qué hace el programa del cómo lo escribimos en Java.

1.4.2 Símbolos estándar de los diagramas de flujo

Los diagramas de flujo utilizan una serie de figuras geométricas con significado normalizado. Reconocerlas te permitirá leer cualquier diagrama, no solo los tuyos.

Símbolo Forma Significado
Terminal Óvalo o círculo alargado Inicio o fin del programa.
Proceso Rectángulo Instrucción o bloque de instrucciones.
Decisión Rombo Pregunta booleana: se separa en Sí/No.
Entrada / Salida Paralelogramo Lectura o escritura de datos.
Conector Líneas de flechas Salto a otra parte del diagrama.
Subprograma Rectángulo con líneas verticales Llamada a una función o subrutina.

1.4.3 Ejemplo completo: pedir edad y decir si es mayor de edad

Aún no sabemos escribir if en Java, pero sí sabemos dibujar el flujo. Vamos a representar el algoritmo que pide una edad y responde si la persona es mayor de edad:

flowchart TD
    Inicio([Inicio]) --> Entrada[/Pedir edad/]
    Entrada --> Condicion{"¿Edad ≥ 18?"}
    Condicion -->|Sí| Mayor["Mostrar: Eres mayor de edad"]
    Condicion -->|No| Menor["Mostrar: Eres menor de edad"]
    Mayor --> Fin([Fin])
    Menor --> Fin([Fin])

Este diagrama describe el algoritmo sin escribir Java. Si te lo dan dibujado y te dicen "ahora prográmalo en Java", en la sección 2 sabrás exactamente qué escribir. Esa es la gracia: el diagrama es el puente entre el problema (en español) y la solución (en Java).

1.4.4 Ejemplo con iteración: bucle "pedir nombre hasta que no esté vacío"

Ahora uno con repetición. El algoritmo pide al usuario su nombre y, si deja la entrada vacía, le vuelve a preguntar. Cuando el nombre es válido, lo saluda y termina.

flowchart TD
    Inicio([Inicio]) --> Inicializa["Inicializar nombre vacío"]
    Inicializa --> Condicion{"¿Nombre está vacío?"}
    Condicion -->|Sí| Pide[/Pedir nombre/]
    Pide --> Guarda["Guardar nombre"]
    Guarda --> Condicion
    Condicion -->|No| Saluda["Mostrar: Hola, + nombre"]
    Saluda --> Fin([Fin])

Fíjate en la flecha que vuelve desde Guarda hacia Condicion. Esa flecha de retroceso es la firma visual de un bucle. Cuando la veas, ya sabes que necesitas una estructura de iteración (while, do-while o for, según el caso).

1.4.5 Ejemplo con salto: salir de un bucle si el dato es cero

Tercer patrón muy típico: pedir números y acumularlos, pero si el usuario escribe 0, se termina el bucle inmediatamente.

flowchart TD
    Inicio([Inicio]) --> Ini["Inicializar suma a 0"]
    Ini --> Pide[/Pedir un número/]
    Pide --> Lee["Guardar número"]
    Lee --> Cero{"¿El número es 0?"}
    Cero -->|Sí| Fin([Fin])
    Cero -->|No| Acum["Añadir número a la suma"]
    Acum --> Pide

Aquí el flujo se sale del bucle sin volver a la condición: hay un break implícito. En la sección 7 verás cómo se implementa en Java. De momento, quédate con el patrón visual: una flecha que sale del bucle hacia el final indica una salida anticipada por salto.

1.4.6 Buenas prácticas al dibujar diagramas de flujo

  1. Un diagrama, un propósito. No intentes dibujar un programa entero de 200 líneas en un solo diagrama: se vuelve ilegible. Divide el problema en partes y dibuja cada una por separado.
  2. Empieza por el camino feliz. Primero dibuja el flujo "cuando todo va bien". Luego añade los casos de error.
  3. Nombra las decisiones como preguntas booleanas completas. Mejor "¿edad >= 18?" que "edad". La decisión tiene que ser legible.
  4. Las flechas se leen hacia delente. Etiqueta las flechas importantes (Sí/No, casos de un switch) para que el diagrama sea autoexplicativo.
  5. Evita cruces de flechas. Si dos flechas se cruzan, redibuja: probablemente hay una forma más clara de organizar el flujo.
  6. No metas sintaxis de Java dentro de los nodos. Un diagrama describe el qué, no el cómo. En vez de ["for (int i=0; i<10; i++)"], escribe ["Repetir 10 veces"] o ["Para i desde 0 hasta 9"]. Cuando llegues a programar, sabrás qué bucle elegir.

Diagrama como herramienta de depuración

Cuando un programa no te funcione (y te pasará, a todos nos pasa), a veces el error no está en el código sino en la lógica. Dibujar el flujo en papel te ayuda a ver dónde has pensado mal, antes de tocar una sola línea de Java. Es, literalmente, una técnica de depuración (CE 3.6), y te sorprenderá lo rápido que resuelve problemas que llevas una hora persiguiendo en el IDE.


1.5 Condiciones booleanas: el corazón del control de flujo

1.5.1 ¿Qué es una condición booleana?

Todas las estructuras de control que veremos —desde el if más simple hasta el switch con guards de Java 21— comparten un mismo corazón: una condición booleana, es decir, una expresión que se evalúa a true o a false. Sin condiciones booleanas no hay decisiones, y sin decisiones no hay control de flujo. Por eso necesitamos dominarlas antes de tocar if.

Ya conoces el tipo boolean de UD1. Recuerda: tiene solo dos valores literales, true y false, ocupa 1 bit lógico (aunque la JVM lo almacena en un byte) y no se convierte implícitamente a ningún otro tipo. Lo que hacemos ahora es darle vida: combinarlo con operadores para construir expresiones booleanas capaces de modelar preguntas reales.

1.5.2 Los operadores relacionales (repaso desde la óptica del control de flujo)

En UD1 los vimos como un operador más. Aquí los reinterpretamos como constructores de condiciones. La tabla completa:

Operador Significado Ejemplo Resultado
== Igual a 5 == 5 true
!= Distinto de 5 != 3 true
< Menor que 3 < 7 true
> Mayor que 9 > 2 true
<= Menor o igual que 5 <= 5 true
>= Mayor o igual que 4 >= 9 false

Estos operadores funcionan con cualquier tipo numérico primitivo (int, long, double, char...) y, con cuidado, también con String (donde no debes usar == para comparar contenidos, sino .equals(), como ya vimos en UD1).

Igualdad en String: la trampa clásica

Para String, == compara identidad (¿es el mismo objeto en memoria?), no contenido. Como Java tiene un pool de strings, a veces == "parece funcionar", pero falla en casos reales. Usa siempre .equals():

1
2
3
4
String a = "hola";
String b = "hola";
boolean mismoContenido = a.equals(b);  // true
boolean mismoObjeto   = (a == b);     // true aquí, PERO no fiable

1.5.3 Los operadores lógicos: combinar condiciones

Las condiciones rara vez son simples. Para combinar varias preguntas en una sola usamos los operadores lógicos:

Operador Significado Ejemplo Resultado
&& AND (y) — cortocircuito (edad >= 18) && (edad <= 65) true si ambas son ciertas
\|\| OR (o) — cortocircuito (nota < 0) \|\| (nota > 10) true si al menos una es cierta
! NOT (no) — negación !(edad < 18) true si edad >= 18

Java tiene también las versiones sin cortocircuito (& y |), pero en el 99 % de los casos querrás usar && y ||. La diferencia es importante: con cortocircuito, si el primer operando ya decide el resultado, el segundo no se evalúa. Eso es útil para evitar errores:

// Si x es null, no se evalúa lo de la derecha y no hay NullPointerException
if (x != null && x.length() > 5) { /* ... */ }

Si usaras & aquí, Java evaluaría x.length() incluso con x nulo, y el programa reventaría. Siempre && y || salvo que tengas una razón muy específica para no cortocircuitar.

1.5.4 Tablas de verdad

Para pensar bien como programador, tienes que tener interiorizadas las tablas de verdad de AND, OR y NOT. Son como las tablas de multiplicar: básicas y necesarias.

Tabla de AND (&&)

A B A && B
true true true
true false false
false true false
false false false

Solo es cierto si ambas son ciertas.

Tabla de OR (||)

A B A || B
true true true
true false true
false true true
false false false

Es cierto si al menos una es cierta.

Tabla de NOT (!)

A !A
true false
false true

Invierte el valor.

1.5.5 Precedencia: ¿qué se evalúa primero?

Cuando combinas operadores, Java decide el orden según una tabla de precedencia. No hace falta memorizarla entera, pero sí las tres reglas que más se usan en condiciones:

  1. Los operadores relacionales (<, >, <=, >=, ==, !=) se evalúan antes que los lógicos (&&, ||).
  2. && se evalúa antes que ||.
  3. Los paréntesis siempre ganan; úsalos sin miedo para dejar tu intención clara.

Ejemplo clásico de trampa:

// ¿Qué significa esto?
boolean r = edad >= 18 && tieneDNI || esTutor;

Puede que pienses "mayor de edad con DNI, o tutor". Pero la precedencia dice: (edad >= 18 && tieneDNI) || esTutor. Eso significa: si es tutor, da igual la edad y el DNI, entra. ¿Es eso lo que querías? Quizás sí, quizás no. Si querías "tutor mayor de edad con DNI", tenías que escribir edad >= 18 && (tieneDNI || esTutor) o (edad >= 18 && tieneDNI) || (edad >= 18 && esTutor).

Regla de oro: cuando una condición tiene más de un operador lógico, usa paréntesis siempre. Aunque la precedencia sea la que quieres, los paréntesis hacen que tu intención sea evidente para el que lea tu código (incluido tú dentro de tres meses).

// Mejor: paréntesis que dejan clara la intención
boolean puedeEntrar = (edad >= 18 && tieneDNI) || esTutor;

1.5.6 Variables booleanas: tu primera "bandera" (flag)

Una práctica que te va a ahorrar muchos quebraderos de cabeza es dar nombre a las condiciones complejas mediante una variable booleana. En vez de escribir una condición enorme inline, la descompones en variables con nombre significativo. Esto es documentación viva: el nombre explica la intención.

// Evita: condición larga e ilegible
if (edad >= 18 && edad <= 65 && tieneDNI && !estaSancionado) {
    // ...
}

// Mejor: condiciones con nombre
boolean esEdadValida = edad >= 18 && edad <= 65;
boolean estaAptoParaTrabajar = esEdadValida && tieneDNI && !estaSancionado;

if (estaAptoParaTrabajar) {
    // ...
}

El nombre estaAptoParaTrabajar ya es, por sí solo, documentación. Cuando dentro de tres meses abras el código, sabrás qué significa esa condición sin tener que descifrarla.

1.5.7 La ley de De Morgan: cómo simplificar negaciones

Una herramienta mental muy útil: las leyes de De Morgan, que dicen cómo transformar negaciones de condiciones compuestas:

Original Equivalente según De Morgan
!(A && B) !A || !B
!(A || B) !A && !B

Es decir: la negación de "A y B" es "no A o no B"; y la negación de "A o B" es "no A y no B". En la práctica, esto te ayuda a reformular condiciones para que se lean mejor:

1
2
3
4
5
// Negación de "mayor de edad y con DNI"
boolean rechazado = !(edad >= 18 && tieneDNI);

// Equivalente con De Morgan: "menor de edad o sin DNI"
boolean rechazado = edad < 18 || !tieneDNI;

La segunda versión suele leerse más natural. Aprende a aplicar De Morgan mentalmente: te ayudará a evitar dobles negaciones, que son una fuente constante de bugs.

1.5.8 Errores típicos con condiciones booleanas

Error Ejemplo Por qué falla Cómo corregirlo
Usar = en vez de == if (x = 5) = es asignación, no comparación. En Java da error de compilación porque = no devuelve boolean. Usar ==: if (x == 5).
Comparar String con == if (nombre == "admin") Compara identidad, no contenido. Funciona a veces por el pool. Usar .equals(): if (nombre.equals("admin")).
Olvidar paréntesis if (a < b && c > d || e) La precedencia decide por ti, quizás no como querías. Poner paréntesis: if ((a < b && c > d) || e).
Doble negación if (!!(edad < 18)) Confuso y redundante. Simplificar: if (edad < 18).
Comparar boolean con == true if (esValido == true) Redundante. esValido ya es boolean. Directo: if (esValido).
Comparar boolean con == false if (esValido == false) Mejor usar !. if (!esValido).
Rango encadenado if (18 <= edad <= 65) En Java no existe esa sintaxis. if (edad >= 18 && edad <= 65).

La última fila es el error más típico entre quien viene de matemáticas. En Python 18 <= edad <= 65 funciona; en Java no compila. Tienes que descomponerlo en dos comparaciones unidas por &&.

1.5.9 Ejemplos resueltos paso a paso

Para afianzar, vamos a construir varias condiciones booleanas a partir de frases, paso a paso.

Ejemplo 1: "El usuario puede conducir legalmente un coche: debe ser mayor de 18 años y tener carnet de conducir válido."

boolean puedeConducir = (edad >= 18) && tieneCarnet;

Ejemplo 2: "Un empleado cobra bonificación si ha trabajado más de 5 años o si ha superado los objetivos del trimestre."

boolean cobraBonificacion = (antiguedad > 5) || objetivosSuperados;

Ejemplo 3: "Una nota es válida si está entre 0 y 10, ambos incluidos."

boolean notaValida = (nota >= 0) && (nota <= 10);

Ejemplo 4: "Un carácter es una vocal minúscula." (Aquí usamos || porque son alternativas.)

boolean esVocalMinuscula = (c == 'a') || (c == 'e') || (c == 'i') || (c == 'o') || (c == 'u');

Ejemplo 5: "El número entero n está fuera del rango [10, 20]."

boolean fueraDeRango = (n < 10) || (n > 20);
// Equivalente por De Morgan de !(n >= 10 && n <= 20)

Ejemplo 6: "Una contraseña es robusta: tiene al menos 8 caracteres y contiene al menos un número y al menos una mayúscula."

1
2
3
4
boolean longitudOk = (password.length() >= 8);
boolean tieneNumero = /* comprobación con un bucle en sección 7 */;
boolean tieneMayuscula = /* comprobación con un bucle en sección 7 */;
boolean robusta = longitudOk && tieneNumero && tieneMayuscula;

Aquí ya ves que necesitamos un bucle para comprobar "hay algún número en la cadena". Esa parte la dejamos aparcada hasta la sección 4, cuando veamos while y for. Pero el esqueleto booleano ya está claro: esa es la gracia de pensar con condiciones antes que con bucles.


1.6 Errores comunes y buenas prácticas desde el primer día

Aunque en esta sección aún no hemos escrito if ni while, ya podemos sentar buenas prácticas que te van a acompañar todo el curso y que afectan al control de flujo en general. Empezar con buen pie evita tener que desaprender después.

1.6.1 Siete errores que verás una y otra vez

  1. Olvidar las llaves del bloque. Aunque Java permite omitirlas cuando el bloque tiene una sola sentencia, esa flexibilidad provoca bugs clásicos. En la sección 2 verás esto con detalle, pero adopta ya la regla: siempre llaves, aunque el bloque tenga una línea.
  2. Confundir = con ==. En Java lo detecta el compilador en condiciones booleanas (porque = no devuelve boolean), pero es el primer error mental. Cuando escribas condiciones, piensa "¿comparo o asigno?".
  3. Anidar más de tres niveles. Si tienes un if dentro de un if dentro de un if dentro de un while, el código es difícil de leer. Lo verás en la sección 2 con la técnica del early return.
  4. Condiciones negadas. if (!(!listo || error)) es un infierno de lectura. Aplica De Morgan y simplifica.
  5. Bucles sin condición de salida clara. Un while cuya condición nunca se vuelve false es un bucle infinito. Antes de escribirlo, pregúntate: "¿qué variable cambia dentro del bucle que pueda hacer que la condición sea false?".
  6. Comparar boolean con true/false. Como vimos: if (esValido == true)if (esValido).
  7. Magic numbers en condiciones. if (edad >= 18) no se entiende tan bien como if (edad >= MAYORIA_EDAD) con una constante final int MAYORIA_EDAD = 18;.

1.6.2 Buenas prácticas para el control de flujo

Buena práctica Por qué ayuda
Nombra tus condiciones con variables booleanas. Documentación viva; el código se lee como prosa.
Usa paréntesis en condiciones compuestas. Elimina ambigüedad de precedencia.
Prefiere condiciones positivas. if (esValido) se lee mejor que if (!esNoValido).
Dibuja el flujo antes de programar. Detectas errores de lógica antes de escribir Java.
Una sola salidad por método, cuando sea posible. Más fácil de razonar y de testear.
Comenta el por qué, no el qué. El código ya dice "qué"; el comentario explica la intención.
Identación consistente (4 espacios en Java). IntelliJ lo hace por ti; no pelees con el formateador.

1.6.3 El "code smell": complejidad ciclomática

Oirás hablar de complejidad ciclomática, un número que indica cuántos caminos distintos puede tomar la ejecución de un método. Cuantos más if, while, for, case, &&, || tenga tu método, mayor será la complejidad. Como regla práctica:

Complejidad Estado del método
1–5 Sencillo, fácil de leer y probar.
6–10 Aceptable, pero ya piensa en refactorizar.
> 10 Complicado; hay que dividirlo.
> 20 Inmantenible; refactoriza urgentemente.

No te asustes por el concepto: solo es una forma de medir "cuánto control de flujo hay en un trozo de código". Cuanto menos, mejor.


1.7 Resumen y mapa conceptual

Vamos a cerrar la sección consolidando lo aprendido. Si tuvieras que explicar a un compañero qué has visto en esta sección, debería sonar algo así:

"El control de flujo es lo que permite a un programa decidir, repetir e interrumpir. Hay tres pilares: selección, iteración y salto. Cualquier algoritmo se construye combinándolos. Para representarlos visualmente usamos diagramas de flujo. Y el corazón de todas las estructuras de control es la condición booleana, que se construye con operadores relacionales y lógicos, se combina con paréntesis y se puede simplificar con De Morgan."

1.7.1 Mapa conceptual

mindmap
  root((Sección 1 - Conceptos))
    Ejecución secuencial
      Definición
      Analogía receta de cocina
      Límites
    Tres pilares
      Selección
        if / else
        ternario
        switch
      Iteración
        while
        do-while
        for
        for-each
      Salto
        break
        continue
        return
    Diagramas de flujo
      Símbolos estándar
      Mermaid flowchart
      Buenas prácticas
    Condiciones booleanas
      Operadores relacionales
      Operadores lógicos
      Tablas de verdad
      Precedencia
      De Morgan
      Variables booleanas
    Errores comunes
      = vs ==
      Strings con ==
      Rangos encadenados
      Doble negación

1.7.2 Las 10 ideas que no debes olvidar

  1. El flujo por defecto es secuencial. Lo demás son "interrupciones" de esa secuencia.
  2. Solo hay tres tipos de control de flujo: selección, iteración y salto. Todo lo demás son variaciones.
  3. Cualquier algoritmo se puede escribir combinando los tres. Lo demostraron Böhm y Jacopini.
  4. El diagrama de flujo es un lenguaje universal que conviene dominar antes de programar.
  5. La flecha que retrocede es la marca visual de un bucle.
  6. La flecha que se bifurca en un rombo es la marca visual de una selección.
  7. La flecha que sale de un bucle hacia el final indica un salto (break o return).
  8. Una condición booleana siempre se evalúa a true o false. Sin eso no hay control de flujo.
  9. Usa paréntesis y variables booleanas con nombre para que tus condiciones sean legibles.
  10. De Morgan te ayuda a simplificar negaciones. Aprende a aplicarlo mentalmente.

1.7.3 Glosario rápido

Término Significado
Control de flujo Mecanismo que decide qué instrucción se ejecuta a continuación.
Ejecución secuencial Flujo por defecto: una instrucción detrás de otra, de arriba a abajo.
Selección Estructura que decide entre dos o más caminos según una condición.
Iteración Estructura que repite un bloque de instrucciones.
Salto Sentencia que interrumpe el flujo normal de la secuencia o del bucle.
Bucle Sinónimo de iteración.
Condición booleana Expresión que se evalúa a true o false.
Operador relacional Compara dos valores (==, !=, <, >, <=, >=).
Operador lógico Combina condiciones (&&, \|\|, !).
Cortocircuito Si el primer operando decide el resultado, no se evalúa el segundo.
Diagrama de flujo Representación visual de un algoritmo.
Mermaid Lenguaje de diagramas basado en texto.
De Morgan Leyes para transformar negaciones de condiciones compuestas.
Complejidad ciclomática Métrica que cuenta los caminos de ejecución de un método.

1.8 Actividades propuestas

Para que esta sección no se quede en teoría, aquí tienes cinco actividades graduadas. Hazlas en orden: cada una pisa la anterior. Las soluciones y comentarios los encontrarás en el aula virtual (Aules) cuando el profesor los publique; antes de mirar, inténtalas tú.

1.8.1 Actividad 1 — Dibuja tu rutina matutina

Objetivo: practicar la representación de un algoritmo real en un diagrama de flujo.

Enunciado: Dibuja en Mermaid un diagrama de flujo de tu rutina matutina de un día de clase, desde que suena el despertador hasta que llegas al aula. Debes incluir:

  • Un inicio y un fin.
  • Al menos una decisión ("¿Ha sonado el despertador?").
  • Al menos una iteración ("Mientras no estoy vestido del todo, sigo poniéndome prendas" o "Mientras no he desayunado todo, sigo comiendo").
  • Al menos una entrada de datos ("Leo la hora en el móvil").
  • Al menos una salida ("Muestro 'Ya estoy listo' por pantalla").

1.8.2 Actividad 2 — Clasifica el control

Objetivo: distinguir selección, iteración y salto a partir de frases en español.

Enunciado: Para cada una de estas frases, indica a qué tipo de estructura de control corresponde (selección, iteración o salto) y dibuja su diagrama de flujo en Mermaid:

  1. "Si el usuario escribe 'admin', le doy acceso de administrador; si no, le doy acceso normal."
  2. "Repetir el proceso para cada uno de los 30 alumnos de la clase."
  3. "Si el saldo es negativo, detener el bucle de cálculo inmediatamente."
  4. "Mientras la contraseña no tenga 8 caracteres, volver a pedirla."
  5. "Para cada asignatura, si la nota es mayor o igual que 5, marcar como aprobada; si no, suspender."
  6. "Si detecto un fallo en la red, salir del método de sincronización."

Entrega: los seis diagramas.

1.8.3 Actividad 3 — Construye condiciones booleanas

Objetivo: traducir frases del español a expresiones booleanas válidas en Java, usando paréntesis y variables intermedias.

Enunciado: Para cada frase, escribe en Java la condición booleana correspondiente, declarando las variables necesarias. Usa variables booleanas con nombre significativo cuando la condición tenga más de un operador lógico.

Frase Variable resultado esperada
"Un alumno aprueba si la nota es mayor o igual que 5 y no ha faltado más de 10 veces." boolean aprobado
"Un cliente tiene derecho a descuento si es mayor de 65 años o tiene la tarjeta VIP." boolean tieneDescuento
"Un número n es par y positivo." boolean esParPositivo
"Un año es bisiesto si es divisible por 4 pero no por 100, salvo que también sea divisible por 400." boolean esBisiesto
"Una contraseña es débil si tiene menos de 6 caracteres o no tiene ninguna letra." boolean esDebil
"Una persona puede donar sangre si pesa más de 50 kg, tiene entre 18 y 65 años y no está embarazada." boolean puedeDonar

Entrega: un archivo Condiciones.java con todas las variables declaradas e inicializadas con valores de prueba. Comenta cada una con el enunciado que resuelve.

1.8.4 Actividad 4 — Aplica De Morgan

Objetivo: simplificar condiciones mediante las leyes de De Morgan.

Enunciado: Reescribe cada una de estas condiciones aplicando De Morgan para que no tengan negación externa. Indica cuál te parece más legible y por qué.

  1. !(edad >= 18)
  2. !(tieneDNI && tienePasaporte)
  3. !(nota < 0 || nota > 10)
  4. !(precio < 100 && cantidad < 5)
  5. !(estaLogueado && !estaBloqueado)

Entrega: tabla con la versión original, la versión transformada y tu justificación de legibilidad (una o dos líneas por caso).

1.8.5 Actividad 5 — Cuestionario de repaso

Objetivo: afianzar el vocabulario y los conceptos clave antes de pasar a la sección 2.

Responde por escrito (no hace falta compilar nada, es teoría):

  1. ¿Qué tres tipos de estructuras de control existen y en qué se diferencian?
  2. ¿Por qué decimos que el operador ternario ya es "selección"?
  3. ¿Qué símbolo de Mermaid se usa para representar una decisión?
  4. ¿Por qué es peligroso comparar String con ==?
  5. ¿Qué significa que && es "cortocircuito"? Pon un ejemplo donde sea útil.
  6. En la condición a || b && c, ¿qué se evalúa primero? ¿Y si pones (a || b) && c?
  7. ¿Por qué if (18 <= edad <= 65) no compila en Java?
  8. ¿Qué es la complejidad ciclomática y por qué importa?
  9. ¿Cuál es la diferencia entre una flecha que retrocede y una flecha que se bifurca?
  10. Nombra dos buenas prácticas al dibujar diagramas de flujo.

Entrega: un archivo cuestionario_seccion1 con tus respuestas.


1.9 Lo que viene en la sección 2

Has sentado las bases: ya sabes qué es el control de flujo, cuántos tipos hay, cómo se representa y con qué se construyen las decisiones (condiciones booleanas). En la sección 2 vamos a vestir esos conceptos con la sintaxis de Java:

  • La sentencia if simple, con else y encadenada con else if.
  • if anidados y la técnica del early return para evitar anidamiento excesivo.
  • El operador ternario revisado en su rol dentro de las asignaciones (cuándo usarlo y cuándo evitarlo).
  • Los errores clásicos del if: = vs ==, bloques sin llaves, dangling else.
  • Validación de entrada con Scanner y if.

Cuando llegues al final de la sección 2, serás capaz de escribir programas que deciden entre varios caminos según los datos del usuario. Y desde ahí, las iteraciones de las secciones 4 a 6 y el resto de la UD3 serán un camino natural.


Cierre del profesor. Si esta sección te ha parecido teórica, no te preocupes: lo es. Pero esta teoría es la que separa al programador que "funciona por casualidad" del que "funciona por convicción". La sintaxis la olvidas si no usas Java un par de meses; los conceptos —los tres pilares, el diagrama, la condición booleana— los llevas contigo a cualquier lenguaje, en cualquier empresa, durante toda tu carrera. Vale la pena invertirles una tarde. Nos vemos en la sección 2, donde por fin escribimos if.