La lección anterior acabó con un programa que ya sabe decidir, pero que sigue siendo incapaz de hacer lo más natural del mundo: insistir. Cuando Marta escribe urgentisima donde iba una prioridad, TareaFácil interpreta lo que puede y, si no lo entiende, inventa un valor por defecto. Lo razonable sería volver a preguntar. Y para volver a preguntar hay que repetir.

Repetir es la segunda gran estructura de control, y la que convierte un programa en algo que trabaja de verdad: recorrer datos, insistir hasta obtener una respuesta válida, acumular totales, calcular medias. Sin bucles, un programa solo puede hacer tantas cosas como líneas tenga escritas; con bucles, diez líneas pueden procesar diez datos o diez mil.

En esta lección aprenderás las dos formas de repetir en Python —while y for—, cuándo usar cada una, los cuatro patrones que aparecen una y otra vez en el código real, y cómo cerrar por fin el cabo suelto que arrastramos desde el módulo 2.

Contenido

  1. Repetir sin repetir código
  2. El bucle while
  3. Bucles infinitos y cómo salir de ellos
  4. El bucle for y la función range()
  5. Recorrer una cadena carácter a carácter
  6. while frente a for: cuándo usar cada uno
  7. Los cuatro patrones clásicos
  8. TareaFácil v0.5.2: insistir hasta que el dato sea válido
  9. Errores comunes y consejos
  10. Ejercicios
  11. Conclusión

  1. Repetir sin repetir código

Imagina que Marta quiere ver la cuenta atrás de los tres días que faltan para entregar el cartel de la feria del libro. Con lo que sabías hasta ahora, la única salida era escribir tres print casi idénticos. Funciona para tres días; para treinta, son treinta líneas, y para un número que el usuario decide mientras el programa se ejecuta, es sencillamente imposible.

Un bucle (o iteración) resuelve esto: escribes el bloque una sola vez y le dices al programa cuántas veces, o hasta cuándo, debe ejecutarlo. Cada pasada del bloque se llama iteración.

Python ofrece dos bucles, y la diferencia entre ellos es solo esta: while repite mientras una condición sea verdadera, sin saber de antemano cuántas vueltas dará, y for repite para cada elemento de una secuencia, con el número de vueltas determinado antes de empezar.

  1. El bucle while

Su sintaxis es idéntica a la del if: palabra clave, condición, dos puntos y bloque indentado de 4 espacios. La diferencia es que, al terminar el bloque, el flujo vuelve a la condición en vez de seguir adelante.

dias_restantes = 3                          # 1. inicializacion

while dias_restantes > 0:                   # 2. condicion de continuacion
    print(f"Quedan {dias_restantes} dias")  # 3. cuerpo
    dias_restantes -= 1                     # 4. actualizacion

print("Plazo agotado.")

Se imprime Quedan 3 dias, Quedan 2 dias, Quedan 1 dias y, ya fuera del bucle, Plazo agotado.. Todo bucle while correcto tiene esas cuatro piezas, y conviene aprendérselas como una lista de comprobación:

Pieza Qué hace Si falta
Inicialización Da un valor inicial a la variable de control NameError: la variable no existe
Condición de continuación Se evalúa antes de cada vuelta SyntaxError
Cuerpo El trabajo que se repite IndentationError
Actualización Modifica la variable para acercarse a la salida Bucle infinito

Fíjate en un detalle importante: la condición se evalúa antes de la primera vuelta. Si es falsa desde el principio —por ejemplo, con dias_restantes = 0—, el cuerpo no se ejecuta ni una sola vez. Un while puede dar cero vueltas, y eso casi nunca es un error: suele ser exactamente lo que queremos.

flowchart TD
    A["Inicializar variable"] --> B{"Condicion cierta ?"}
    B -->|False| E["Continuar el programa"]
    B -->|True| C["Ejecutar el cuerpo"]
    C --> D["Actualizar variable"]
    D --> B

La flecha que vuelve de la actualización a la condición es lo que distingue un bucle de un if. Es literalmente un ciclo.

  1. Bucles infinitos y cómo salir de ellos

Si olvidas la actualización, la condición nunca cambia y el bucle no termina jamás:

dias_restantes = 3
while dias_restantes > 0:
    print(f"Quedan {dias_restantes} dias")   # falta dias_restantes -= 1

El programa imprime Quedan 3 dias para siempre, hasta llenar la pantalla. No es un error de sintaxis: Python no puede saber que te has equivocado, porque un bucle sin fin es perfectamente legal (de hecho, en la lección Menús interactivos lo usaremos a propósito).

Cómo detenerlo: pulsa Ctrl+C en la terminal. Eso interrumpe el programa y muestra un KeyboardInterrupt. No es un fallo de tu código: es la señal de que tú has cortado la ejecución.

Los tres descuidos que los producen son casi siempre los mismos: no actualizar la variable de control dentro del cuerpo, actualizarla en la dirección equivocada (dias_restantes += 1 cuando la condición es > 0), o actualizarla fuera del bucle, es decir, sin la sangría correcta, con lo que solo se ejecuta al terminar. Antes de ejecutar un while, hazte esta pregunta: ¿qué instrucción de este cuerpo hará que la condición acabe siendo falsa? Si no sabes señalarla con el dedo, tienes un bucle infinito.

  1. El bucle for y la función range()

El for repite un bloque para cada elemento de una secuencia. Cuando lo que quieres es simplemente repetir n veces, la secuencia la genera range(), que produce una sucesión de números enteros.

for numero in range(3):
    print(f"Vuelta numero {numero}")     # imprime 0, luego 1, luego 2

La variable numero es la variable de control: Python la crea, le asigna el primer valor de la secuencia, ejecuta el cuerpo, le asigna el siguiente, y así hasta agotarla. No hace falta inicializarla ni actualizarla: de eso se encarga el for, y por eso un for nunca produce un bucle infinito por descuido.

range() admite tres formas, y conviene dominar las tres:

Forma Genera Ejemplo Valores
range(n) De 0 a n−1 range(4) 0, 1, 2, 3
range(a, b) De a hasta b−1 range(1, 5) 1, 2, 3, 4
range(a, b, paso) De a a b−1 saltando de paso en paso range(0, 10, 3) 0, 3, 6, 9
range(a, b, -paso) Cuenta atrás range(3, 0, -1) 3, 2, 1

La regla que hay que grabarse: el límite superior nunca se incluye. range(5) da cinco números, pero el 5 no está entre ellos; es la fuente número uno de errores «de uno» (off-by-one) en principiantes.

for dia in range(1, 6):                  # los 5 dias laborables
    print(f"Dia {dia} de la planificacion")

for dias_restantes in range(3, 0, -1):   # cuenta atras: 3, 2, 1
    print(f"Quedan {dias_restantes} dias")
flowchart TD
    A["range genera la secuencia"] --> B{"Quedan elementos ?"}
    B -->|False| E["Continuar el programa"]
    B -->|True| C["Asignar el siguiente<br/>a la variable de control"]
    C --> D["Ejecutar el cuerpo"]
    D --> B

Compáralo con el diagrama del while: la estructura es la misma, pero aquí no hay actualización manual porque la propia secuencia decide cuándo se acaba.

  1. Recorrer una cadena carácter a carácter

range() no es la única secuencia que un for puede recorrer. Una cadena de texto también lo es: sus elementos son sus caracteres, de izquierda a derecha.

titulo = "Cartel"
for letra in titulo:
    print(letra, end=" ")       # C a r t e l

Esto abre la puerta a analizar texto sin conocer aún estructuras de datos. Por ejemplo, contar cuántos espacios tiene un título, que es una forma rudimentaria de contar palabras:

titulo = "Cartel de la feria del libro"
espacios = 0

for caracter in titulo:
    if caracter == " ":
        espacios += 1

print(f"El titulo tiene {espacios + 1} palabras")   # 6 palabras

Fíjate en la combinación: un for que recorre, un if que decide y un contador que acumula. Esa tríada es el 80% del código que escribirás. Recorrer colecciones de tareas con for —una lista con todas las tareas del estudio— es exactamente la misma idea, y llegará en Listas y arreglos.

  1. while frente a for: cuándo usar cada uno

Los dos bucles pueden simular al otro, pero cada problema tiene su forma natural.

while for
Se usa cuando No sabes cuántas vueltas darás Conoces el número de vueltas o la secuencia
Lo gobierna Una condición Una secuencia
Variable de control La inicializas y actualizas tú La gestiona Python
Riesgo de bucle infinito Sí, si olvidas actualizar Prácticamente ninguno
Ejemplo típico Pedir un dato hasta que sea válido Repetir un cálculo 12 veces

La pregunta que lo resuelve casi siempre: ¿puedo saber el número de repeticiones antes de empezar? Si la respuesta es sí, for. Si depende de algo que ocurrirá dentro del bucle —lo que teclee el usuario, un cálculo que converge—, while.

  1. Los cuatro patrones clásicos

Casi todo lo que se hace con bucles cae en uno de estos cuatro moldes. Los cuatro comparten la misma anatomía: una variable que se inicializa antes del bucle y se modifica dentro.

Contador: cuenta cuántas veces ocurre algo.

tareas_altas = 0
for i in range(4):
    prioridad = input("Prioridad de la tarea: ").strip().lower()
    if prioridad == "alta":
        tareas_altas += 1
print(f"Hay {tareas_altas} tareas de prioridad alta")

Acumulador: suma los valores que va viendo.

total_dias = 0
for i in range(3):
    total_dias += int(input("Dias estimados: "))
print(f"El lote suma {total_dias} jornadas")

Con el acumulador y el contador juntos sale la media, que es una división ordinaria: total_dias / numero_tareas. Recuerda de la lección 02-02 que / siempre devuelve un float.

Máximo y mínimo: busca el valor más grande o más pequeño.

mayor = 0                       # menor que cualquier estimacion valida
for i in range(3):
    dias = int(input("Dias estimados: "))
    if dias > mayor:
        mayor = dias
print(f"La tarea mas larga son {mayor} jornadas")

La clave está en la inicialización: mayor = 0 funciona porque ninguna estimación válida es menor o igual que cero. Para el mínimo el truco simétrico no vale (menor = 0 nunca sería superado), así que se inicializa con un valor deliberadamente alto, como menor = 9999, o con el primer dato leído.

Bandera booleana: recuerda si algo ha ocurrido al menos una vez.

hay_urgente = False
for i in range(3):
    prioridad = input("Prioridad de la tarea: ").strip().lower()
    if prioridad == "alta":
        hay_urgente = True
if hay_urgente:
    print("AVISO: el lote contiene urgencias.")

La bandera se inicializa a False y solo puede pasar a True; nunca vuelve atrás. Es el patrón que responde a preguntas del tipo «¿hay alguna…?».

  1. TareaFácil v0.5.2: insistir hasta que el dato sea válido

Ahora sí. El bucle de validación es el patrón que llevamos prometiendo desde la lección 02-04, y su forma es siempre la misma: leer el dato antes del bucle, y repetir mientras no sea válido.

prioridad = input("Prioridad (alta/media/baja): ").strip().lower()

while prioridad not in PRIORIDADES:
    print(f"'{prioridad}' no es una prioridad valida.")
    prioridad = input("Prioridad (alta/media/baja): ").strip().lower()

Léelo despacio, porque tiene una trampa clásica: el input() aparece dos veces, una antes del bucle y otra dentro. La primera lectura es necesaria para que la condición tenga algo que evaluar; la segunda es la actualización de la que hablábamos en el apartado 2. Si olvidas la de dentro, el valor nunca cambia y tienes un bucle infinito que te repite el mismo reproche para siempre. Con esta pieza, TareaFácil ya no inventa nada: pregunta hasta que le den un dato correcto. Esta es la versión 0.5.2 completa, donde el mensaje de error va incorporado al propio prompt de la segunda lectura para no alargar el código.

# tareafacil.py - Estudio Alba
# Version 0.5.2: entrada insistente y resumen del lote

ANCHO = 46
HORAS_POR_JORNADA = 8
PRIORIDADES = ("alta", "media", "baja")
EQUIPO = ("Marta", "Luis", "Nuria")
DIAS_MIN = 1
DIAS_MAX = 365

# --- 1. Titulo: insistir mientras este vacio ---
titulo = input("Titulo de la tarea          : ").strip()
while titulo == "":
    titulo = input("Vacio. Titulo de la tarea   : ").strip()

# --- 2. Responsable: insistir mientras no sea del equipo ---
responsable = input("Responsable (Marta/Luis/Nuria): ").strip().capitalize()
while responsable not in EQUIPO:
    responsable = input("No es del equipo. Responsable: ").strip().capitalize()

# --- 3. Prioridad: insistir mientras no este en el catalogo ---
prioridad = input("Prioridad (alta/media/baja) : ").strip().lower()
while prioridad not in PRIORIDADES:
    prioridad = input("No valida. Prioridad        : ").strip().lower()

# --- 4. Dias: insistir mientras no sea un numero dentro de rango ---
dias_texto = input("Dias estimados              : ").strip()
while not dias_texto.isdigit() or not DIAS_MIN <= int(dias_texto) <= DIAS_MAX:
    dias_texto = input(f"Entero de {DIAS_MIN} a {DIAS_MAX}. Dias  : ").strip()
dias_estimados = int(dias_texto)

# --- Ficha de la tarea ---
horas_estimadas = dias_estimados * HORAS_POR_JORNADA
print("=" * ANCHO)
print(f"{'Titulo':<16}{titulo:>{ANCHO - 16}}")
print(f"{'Responsable':<16}{f'{responsable} ({prioridad})':>{ANCHO - 16}}")
print(f"{'Dias / horas':<16}{f'{dias_estimados} / {horas_estimadas}':>{ANCHO - 16}}")
print("=" * ANCHO)

Observa la condición del cuarto bucle, que reúne todo lo del módulo: not dias_texto.isdigit() or not DIAS_MIN <= int(dias_texto) <= DIAS_MAX. Gracias al cortocircuito del or, si el texto no es un número la segunda parte no se evalúa y el int() nunca llega a ejecutarse con basura. Una sola condición valida tipo y rango a la vez.

Ninguna de las cuatro entradas puede ya provocar un valor inventado. Compara con la versión 0.5.1: allí el programa corregía; aquí exige. Un fragmento de sesión:

Prioridad (alta/media/baja) : urgentisima
No valida. Prioridad        : ALTA
Dias estimados              : cuatro
Entero de 1 a 365. Dias     : 4

El resumen del lote

Marta suele planificar varias tareas de golpe: sabe cuántas son y quiere saber cuántas jornadas suman y cuál es la media. Como conocemos el número de vueltas antes de empezar, este es territorio del for, con tres de los patrones del apartado 7 a la vez.

# --- Resumen de planificacion del lote ---
numero_tareas = int(input("Cuantas tareas vas a planificar? "))
total_jornadas = 0
mayor = 0

for i in range(numero_tareas):
    dias = int(input(f"Jornadas de la tarea {i + 1}: "))
    total_jornadas += dias
    if dias > mayor:
        mayor = dias

media = total_jornadas / numero_tareas
print(f"Total del lote  : {total_jornadas} jornadas")
print(f"Media por tarea : {media:.1f} jornadas")
print(f"La mas larga    : {mayor} jornadas")

Con las jornadas 4, 10 y 1, el resumen sale Total del lote: 15 jornadas, Media por tarea: 5.0 jornadas y La mas larga: 10 jornadas.

Tres detalles que merecen atención. El i + 1 del mensaje existe porque range() empieza en 0 y las personas contamos desde 1. El :.1f es el especificador de f-string de la lección 02-03, necesario porque / devuelve un float con demasiados decimales. Y lo más importante: los datos de cada tarea se pierden en cuanto se lee la siguiente. Podemos sumarlos y compararlos al vuelo, pero no guardarlos: TareaFácil sigue teniendo sitio para una sola tarea. Para almacenar varias hacen falta listas.

Errores Comunes y Consejos

Olvidar la actualización en un while. El error clásico y el que produce bucles infinitos. Antes de ejecutar, señala con el dedo la línea que hará falsa la condición.

Olvidar el segundo input() en un bucle de validación. Si solo lees antes del while, el valor nunca cambia y el programa repite el mismo aviso eternamente. Ctrl+C y a revisar la sangría.

Contar mal con range(). range(5) da 0, 1, 2, 3, 4: el límite superior no se incluye. Si quieres del 1 al 5, escribe range(1, 6).

Inicializar el acumulador dentro del bucle. Poner total = 0 dentro del cuerpo lo reinicia en cada vuelta, y al final vale lo mismo que el último dato: la inicialización va siempre antes del bucle. Y ojo con la variable de control de un for: tras for i in range(3):, i vale 2, no 3, y modificarla dentro del cuerpo no afecta a las vueltas siguientes.

Consejo: escribe primero la condición de salida. Antes de teclear el cuerpo, pregúntate cuándo debe terminar el bucle. Un bucle sin una idea clara de su final es un bucle infinito en potencia.

Consejo: prueba los bucles con cero vueltas. ¿Qué pasa si el usuario dice que va a planificar 0 tareas? El for no da ninguna vuelta y la media provoca una división por cero. Los casos límite (0 vueltas, 1 vuelta) descubren la mayoría de los fallos.

Ejercicios

Ejercicio 1: Predecir la salida

Sin ejecutar nada, indica qué imprime cada fragmento y cuántas vueltas da.

# a)
for i in range(2, 8, 2):
    print(i, end=" ")
# b)
n = 5
while n > 5:
    n -= 1
print("fin", n)
# c)
total = 0
for i in range(1, 5):
    total += i
print(total)

Ejercicio 2: Contador de vocales del título

Escribe contar_vocales.py que pida un título de tarea y, recorriéndolo carácter a carácter con un for, cuente cuántas vocales tiene (normaliza a minúsculas antes con .lower() y usa in "aeiou"). Muestra el total y el porcentaje de vocales sobre la longitud del título, con un decimal. Pista: len(titulo) devuelve el número de caracteres.

Ejercicio 3: Reparto de jornadas del equipo

Escribe reparto_equipo.py que pregunte a Marta cuántas tareas quiere repartir y, para cada una, pida las jornadas estimadas (validando con un while que sea un entero entre 1 y 30) y el responsable (validando con otro while que esté en EQUIPO). Al terminar debe mostrar el total de jornadas del lote, la media por tarea y si hay alguna tarea de más de 15 jornadas, usando una bandera booleana.

Soluciones

Solución 1.

Salida Vueltas
a 2 4 6 3 — range(2, 8, 2) genera 2, 4 y 6; el 8 queda fuera
b fin 5 0 — la condición 5 > 5 es falsa desde el principio, así que el cuerpo no se ejecuta nunca
c 10 4 — acumula 1+2+3+4; el 5 no entra en range(1, 5)

El caso b es el que más sorprende: un while puede dar cero vueltas. Y el c recuerda que el límite superior de range() siempre queda excluido.

Solución 2.

# contar_vocales.py - Estudio Alba
titulo = input("Titulo de la tarea: ").strip()
while titulo == "":
    titulo = input("El titulo no puede estar vacio. Titulo: ").strip()

vocales = 0
for caracter in titulo.lower():
    if caracter in "aeiou":
        vocales += 1

porcentaje = vocales / len(titulo) * 100
print(f"'{titulo}' tiene {len(titulo)} caracteres y {vocales} vocales ({porcentaje:.1f}%)")

Dos apuntes. El .lower() se aplica sobre la cadena que recorre el for, no sobre cada carácter: normalizar una vez es más barato y más claro que hacerlo en cada vuelta. Y caracter in "aeiou" usa el operador in de la lección 02-02, que sobre cadenas comprueba si un texto está contenido en otro; con un solo carácter equivale a preguntar si es una de esas cinco letras.

Solución 3.

# reparto_equipo.py - Estudio Alba
EQUIPO = ("Marta", "Luis", "Nuria")
numero_tareas = int(input("Cuantas tareas vas a repartir? "))
total_jornadas = 0
hay_tarea_larga = False

for i in range(numero_tareas):
    print(f"--- Tarea {i + 1} ---")
    dias_texto = input("Jornadas estimadas (1-30): ").strip()
    while not dias_texto.isdigit() or not 1 <= int(dias_texto) <= 30:
        dias_texto = input("Debe ser un entero de 1 a 30. Jornadas: ").strip()
    responsable = input("Responsable (Marta/Luis/Nuria): ").strip().capitalize()
    while responsable not in EQUIPO:
        responsable = input("No es del equipo. Responsable: ").strip().capitalize()
    total_jornadas += int(dias_texto)
    if int(dias_texto) > 15:
        hay_tarea_larga = True

media = total_jornadas / numero_tareas
print(f"Total: {total_jornadas} jornadas | Media: {media:.1f} por tarea")
if hay_tarea_larga:
    print("AVISO: hay tareas de mas de 15 jornadas. Conviene partirlas.")

Fíjate en la estructura: un for que sabe cuántas vueltas dará contiene dos while que no lo saben. Esa combinación —for para lo contable, while para lo insistente— es la que aparece en el código real. Y la bandera hay_tarea_larga se declara antes del for, porque debe sobrevivir a todas las vueltas; si estuviera dentro, se reiniciaría en cada tarea y al final solo reflejaría la última.

Conclusión

Ya sabes repetir. Conoces el bucle while con sus cuatro piezas —inicialización, condición de continuación, cuerpo y actualización—, sabes que la condición se evalúa antes de cada vuelta (y que por eso un while puede dar cero vueltas), y sabes qué es un bucle infinito, por qué ocurre y que se sale con Ctrl+C. Conoces el bucle for con las tres formas de range() —recordando que el límite superior nunca se incluye— y su capacidad de recorrer una cadena carácter a carácter. Y tienes el criterio para elegir: si puedes contar las vueltas antes de empezar, for; si dependen de lo que ocurra dentro, while.

Tienes además los cuatro patrones que resuelven la mayoría de los problemas con bucles: contador, acumulador, máximo/mínimo y bandera booleana, todos con la misma anatomía de variable inicializada fuera y modificada dentro. Y TareaFácil, en su versión 0.5.2, ha cerrado por fin el cabo suelto que arrastrábamos desde el módulo 2: ya no corrige ni inventa datos, insiste hasta que Marta escribe algo válido, con un bucle de validación por cada campo; además sabe resumir un lote de tareas —total, media y máximo— aunque no pueda guardarlas.

Quedan cabos, y son los que abren la próxima lección. Si Marta se equivoca cinco veces seguidas, el programa la interroga cinco veces sin piedad: no hay forma de rendirse tras un número razonable de intentos, ni de saltarse una vuelta concreta, ni de cortar un bucle en mitad del cuerpo cuando ya se sabe la respuesta. Tampoco sabemos aún combinar dos bucles, uno dentro de otro, para recorrer una parrilla de días por persona. Todo eso es control fino del flujo, y es lo que aprenderás en Estructuras de control avanzadas: break, continue, la sorprendente cláusula else de los bucles, los bucles anidados y la sentencia match para clasificar valores con más elegancia que una cadena de elif.

Fundamentos de la Programación

Módulo 1: Introducción a la Programación

Módulo 2: Conceptos Básicos

Módulo 3: Estructuras de Control

Módulo 4: Funciones y Procedimientos

Módulo 5: Estructuras de Datos

Módulo 6: Algoritmos Básicos

Módulo 7: Objetos y Organización del Código

Módulo 8: Buenas Prácticas y Herramientas

Módulo 9: Proyecto Final y Cierre del Curso

© Copyright 2026. Todos los derechos reservados