En la lección anterior, Papyrus aprendió a decidir: si hay stock, vende; si el cliente es socio, descuenta. Pero cerramos con un problema pendiente: Ana tiene un catálogo entero, y escribir un if por cada libro no escala. La solución son los bucles, estructuras que repiten un bloque de código tantas veces como haga falta. En esta lección dominarás los dos bucles de Python: for, para recorrer colecciones y secuencias de números, y while, para repetir mientras se cumpla una condición. Con ellos recorreremos el catálogo completo de Papyrus y sumaremos la caja del día en unas pocas líneas.

Contenido

  1. El bucle for y la función range()
  2. range() con inicio, fin y paso
  3. Recorrer cadenas y listas sencillas con for
  4. El bucle while: repetir mientras se cumpla una condición
  5. Contadores y acumuladores: la caja del día
  6. Bucles anidados
  7. enumerate(): índice y valor a la vez
  8. ¿for o while? Cuándo usar cada uno

El bucle for y la función range()

Un bucle for repite un bloque de código una vez por cada elemento de una secuencia. Su forma más básica usa range(), una función que genera una secuencia de números enteros:

for numero in range(5):
    print(f"Iteración número {numero}")

Salida:

Iteración número 0
Iteración número 1
Iteración número 2
Iteración número 3
Iteración número 4

Desglosemos la sintaxis pieza a pieza:

  • for y in son palabras reservadas: se leen "para cada numero en range(5)".
  • numero es la variable de bucle: en cada vuelta (cada iteración) toma el siguiente valor de la secuencia. Puedes llamarla como quieras, siguiendo snake_case.
  • range(5) genera los números 0, 1, 2, 3, 4. Sí: empieza en 0 y termina justo antes de 5. Genera exactamente 5 números, pero el 5 no está incluido. Esta convención (inicio incluido, fin excluido) es universal en Python y conviene interiorizarla cuanto antes.
  • Los dos puntos y el bloque indentado funcionan igual que en el if: todo lo indentado se repite; lo que va después, sin indentar, se ejecuta una sola vez al terminar el bucle.

Un uso inmediato en Papyrus: imprimir la línea separadora del ticket sin copiar y pegar.

NOMBRE_TIENDA = "Papyrus"

print(f"=== {NOMBRE_TIENDA} ===")
for _ in range(3):
    print("- Gracias por su compra -")

Cuando el valor de la variable de bucle no se usa (solo queremos repetir 3 veces), la convención en Python es llamarla _ (guion bajo). Es una señal para quien lee: "este valor no importa".

range() con inicio, fin y paso

range() acepta hasta tres argumentos, igual que el troceado de secuencias que verás más adelante:

Llamada Genera Significado
range(5) 0, 1, 2, 3, 4 Solo stop: desde 0 hasta stop−1
range(1, 6) 1, 2, 3, 4, 5 start y stop: desde start hasta stop−1
range(0, 10, 2) 0, 2, 4, 6, 8 start, stop y step: saltando de step en step
range(10, 0, -1) 10, 9, ..., 1 Paso negativo: cuenta hacia atrás

Ejemplo con las tres formas, numerando cosas de la librería:

# Numerar las estanterías de Papyrus, de la 1 a la 6
for estanteria in range(1, 7):
    print(f"Revisando estantería {estanteria}")

# Los días pares del mes, para los pedidos al distribuidor
for dia in range(2, 31, 2):
    print(f"Pedido programado el día {dia}")

# Cuenta atrás para la apertura
for segundos in range(3, 0, -1):
    print(f"Apertura en {segundos}...")
print("¡Papyrus abre sus puertas!")

Detalles a vigilar:

  • range(1, 7) llega hasta el 6, no hasta el 7 (fin excluido, siempre).
  • Con paso negativo, start debe ser mayor que stop, o el rango estará vacío y el bucle no ejecutará ninguna vuelta (sin error: simplemente no pasa nada).
  • range(0) o range(5, 5) también son rangos vacíos válidos.

Recorrer cadenas y listas sencillas con for

La verdadera potencia del for en Python es que no solo recorre números: recorre cualquier secuencia de elementos (lo que llamamos un iterable). Dos casos que ya podemos usar:

Recorrer una cadena

Una cadena es una secuencia de caracteres, así que se puede recorrer letra a letra:

NOMBRE_TIENDA = "Papyrus"

for letra in NOMBRE_TIENDA:
    print(letra, end=" ")

Salida:

P a p y r u s

En cada iteración, letra vale un carácter de la cadena, en orden. (Recuerda el parámetro end de print() del módulo 1: aquí sustituye el salto de línea por un espacio.)

Recorrer una lista sencilla

Para recorrer el catálogo de Papyrus necesitamos agrupar los títulos en una sola variable. Para eso Python tiene las listas: una colección ordenada de valores, escrita entre corchetes y separada por comas. Las estudiaremos a fondo en el módulo 4; de momento nos basta con saber crearlas y recorrerlas:

catalogo = ["La Odisea", "Hamlet", "El Quijote"]

for titulo in catalogo:
    print(f"- {titulo}")

Salida:

- La Odisea
- Hamlet
- El Quijote

Lee ese bucle en voz alta: "para cada titulo en catalogo, imprímelo". El for de Python está diseñado para sonar a lenguaje natural. En cada vuelta, titulo apunta al siguiente elemento de la lista, del primero al último.

Y por supuesto, dentro del bucle podemos usar todo lo aprendido en la lección anterior. Combinemos dos listas paralelas (misma posición = mismo libro) con un condicional:

catalogo = ["La Odisea", "Hamlet", "El Quijote"]
stocks = [4, 6, 8]

for i in range(3):
    titulo = catalogo[i]
    stock = stocks[i]
    disponibilidad = "disponible" if stock > 0 else "AGOTADO"
    print(f"{titulo:<12} | {disponibilidad}")

Aquí catalogo[i] accede al elemento en la posición i de la lista (las posiciones también empiezan en 0, como range). Este patrón de "índice manual" funciona, pero enseguida veremos con enumerate() una forma más elegante.

El bucle while: repetir mientras se cumpla una condición

El for es ideal cuando sabes qué recorrer o cuántas veces repetir. Pero a veces no lo sabes de antemano: "repite mientras quede stock", "pregunta hasta que el usuario escriba algo válido". Para eso está while:

while condicion:
    # se repite MIENTRAS la condición sea True
    instrucciones

El flujo es: evaluar la condición → si es True, ejecutar el bloque y volver a evaluar → si es False, salir del bucle.

flowchart TD
    A[Inicio] --> B{¿condición?}
    B -- True --> C[Ejecutar bloque]
    C --> B
    B -- False --> D[Continuar tras el bucle]

Ejemplo en Papyrus: simular ventas de La Odisea hasta agotar el stock.

titulo = "La Odisea"
stock = 4

while stock > 0:
    print(f"Vendido un ejemplar de '{titulo}'. Quedan {stock - 1}.")
    stock = stock - 1

print(f"'{titulo}' agotado. Ana debe reponer.")

Salida:

Vendido un ejemplar de 'La Odisea'. Quedan 3.
Vendido un ejemplar de 'La Odisea'. Quedan 2.
Vendido un ejemplar de 'La Odisea'. Quedan 1.
Vendido un ejemplar de 'La Odisea'. Quedan 0.
'La Odisea' agotado. Ana debe reponer.

La línea stock = stock - 1 es crucial: algo dentro del bucle debe acercar la condición a False. Si la olvidas, el stock nunca baja, la condición siempre es cierta y tienes un bucle infinito: el programa no termina jamás (si te ocurre, corta la ejecución con Ctrl+C en la terminal). Es el error clásico del while.

Por cierto, stock = stock - 1 es tan frecuente que Python ofrece una abreviatura: stock -= 1 ("resta 1 a stock"). Existen las equivalentes +=, *=, /=, etc. A partir de ahora las usaremos.

Otro uso típico del while: repetir una pregunta hasta obtener respuesta válida, una de las mejoras pendientes de ficha_libro.py:

respuesta = ""
while respuesta != "s" and respuesta != "n":
    respuesta = input("¿El cliente es socio? (s/n): ")

es_socio = respuesta == "s"
print(f"Socio: {es_socio}")

Mientras el usuario no escriba exactamente s o n, la pregunta se repite. Fíjate en el truco de inicializar respuesta = "" antes del bucle para que la primera evaluación de la condición sea posible (y dé True).

Contadores y acumuladores: la caja del día

Dos patrones aparecen una y otra vez con bucles, y merecen nombre propio:

  • Contador: una variable que empieza en 0 y suma 1 cada vez que ocurre algo. Responde a "¿cuántas veces...?".
  • Acumulador: una variable que empieza en 0 (o en "" para textos) y va sumando cantidades. Responde a "¿cuánto en total...?".

El ejemplo perfecto es la caja del día de Papyrus: al cerrar, Ana quiere saber cuánto ha ingresado y cuántas ventas ha hecho.

NOMBRE_TIENDA = "Papyrus"
ventas_del_dia = [12.50, 9.95, 15.90, 12.50, 9.95]  # importes cobrados hoy

total_caja = 0.0     # acumulador: suma de importes
num_ventas = 0       # contador: número de operaciones

for importe in ventas_del_dia:
    total_caja += importe
    num_ventas += 1
    print(f"Venta {num_ventas}: {importe:.2f} € (acumulado: {total_caja:.2f} €)")

print("-" * 40)
print(f"{NOMBRE_TIENDA} — cierre de caja")
print(f"Ventas realizadas: {num_ventas}")
print(f"Total del día:     {total_caja:.2f} €")
print(f"Ticket medio:      {total_caja / num_ventas:.2f} €")

Salida:

Venta 1: 12.50 € (acumulado: 12.50 €)
Venta 2: 9.95 € (acumulado: 22.45 €)
Venta 3: 15.90 € (acumulado: 38.35 €)
Venta 4: 12.50 € (acumulado: 50.85 €)
Venta 5: 9.95 € (acumulado: 60.80 €)
----------------------------------------
Papyrus — cierre de caja
Ventas realizadas: 5
Total del día:     60.80 €
Ticket medio:      60.80 €...

(El ticket medio real es 12.16 €; compruébalo al ejecutar.)

La anatomía del patrón, que repetirás cientos de veces:

  1. Inicializar el contador/acumulador antes del bucle (si lo inicializas dentro, se pondría a cero en cada vuelta y no acumularía nada).
  2. Actualizar dentro del bucle (+=).
  3. Usar el resultado después del bucle.

Los contadores también se combinan con condicionales: contar solo lo que cumple algo.

stocks = [4, 6, 8, 0, 2]
titulos_bajo_minimo = 0

for stock in stocks:
    if stock <= 3:
        titulos_bajo_minimo += 1

print(f"Títulos que hay que reponer: {titulos_bajo_minimo}")   # 2

Bucles anidados

Igual que los if, los bucles pueden anidarse: un bucle dentro de otro. El bucle interior se ejecuta completo por cada vuelta del exterior.

Ana quiere etiquetas para todos los ejemplares físicos: una etiqueta por unidad en stock de cada título.

catalogo = ["La Odisea", "Hamlet", "El Quijote"]
stocks = [3, 2, 2]   # stock reducido para abreviar la salida

for i in range(3):                      # bucle exterior: cada título
    titulo = catalogo[i]
    for unidad in range(1, stocks[i] + 1):   # bucle interior: cada ejemplar
        print(f"Etiqueta: {titulo} — ejemplar {unidad}/{stocks[i]}")

Salida:

Etiqueta: La Odisea — ejemplar 1/3
Etiqueta: La Odisea — ejemplar 2/3
Etiqueta: La Odisea — ejemplar 3/3
Etiqueta: Hamlet — ejemplar 1/2
Etiqueta: Hamlet — ejemplar 2/2
Etiqueta: El Quijote — ejemplar 1/2
Etiqueta: El Quijote — ejemplar 2/2

Cuenta las iteraciones: el exterior da 3 vueltas y el interior 3+2+2 = 7 en total. Con bucles anidados el número de ejecuciones se multiplica rápido: dos bucles de 1000 vueltas son un millón de ejecuciones del bloque interior. Tenlo presente cuando trabajes con datos grandes.

Un clásico visual para fijar la idea, la tabla de multiplicar:

for fila in range(1, 4):
    for columna in range(1, 4):
        print(f"{fila * columna:>3}", end="")
    print()   # salto de línea al terminar cada fila

Salida:

  1  2  3
  2  4  6
  3  6  9

Observa el print() vacío: está indentado al nivel del bucle exterior, así que se ejecuta una vez por fila, no una vez por celda. Con bucles anidados, el nivel de indentación de cada línea determina "a qué vuelta pertenece" — revísalo siempre dos veces.

enumerate(): índice y valor a la vez

Volvamos al patrón del índice manual que usamos con las listas paralelas. Cuando recorres una lista y necesitas también la posición de cada elemento, Python ofrece algo mejor que range() + corchetes: la función enumerate(), que en cada vuelta entrega la pareja índice-valor.

catalogo = ["La Odisea", "Hamlet", "El Quijote"]

for posicion, titulo in enumerate(catalogo, start=1):
    print(f"{posicion}. {titulo}")

Salida:

1. La Odisea
2. Hamlet
3. El Quijote

Cómo funciona:

  • enumerate(catalogo) va generando parejas: (0, "La Odisea"), (1, "Hamlet"), (2, "El Quijote").
  • En el for escribimos dos variables separadas por coma (posicion, titulo), y Python desempaqueta cada pareja en ellas — es la misma asignación múltiple que viste en el módulo 1.
  • El argumento opcional start=1 hace que la numeración empiece en 1, perfecto para listados de cara al público (por defecto empieza en 0).

Compara las dos versiones del mismo listado numerado:

# Con índice manual: funciona, pero más ruido
for i in range(len(catalogo)):
    print(f"{i + 1}. {catalogo[i]}")

# Con enumerate: más claro y más pythónico
for posicion, titulo in enumerate(catalogo, start=1):
    print(f"{posicion}. {titulo}")

(len() devuelve la longitud de una secuencia: len(catalogo) es 3. La usaremos constantemente.) Regla práctica: si solo necesitas los valores, for x in lista; si también necesitas la posición, enumerate(); reserva range(len(...)) para casos muy concretos, como recorrer dos listas paralelas por índice.

¿for o while? Cuándo usar cada uno

Pregunta que te haces Bucle adecuado Ejemplo en Papyrus
"Para cada elemento de esta colección..." for Recorrer el catálogo
"Exactamente N veces" for con range(N) Imprimir 3 líneas de ticket
"Mientras se cumpla esta condición..." while Vender mientras quede stock
"Hasta que el usuario haga X" while Repetir pregunta hasta respuesta válida
"No sé cuántas vueltas harán falta" while Esperar una entrada correcta

Regla de oro: si conoces de antemano la colección o el número de repeticiones, usa for; si la parada depende de algo que ocurre durante la ejecución, usa while. En la práctica, el for es mucho más frecuente en Python.

Errores Comunes y Consejos

  • Esperar que range(5) incluya el 5: genera del 0 al 4. Si quieres del 1 al 5, escribe range(1, 6). El clásico "error de uno de más/de menos" (off-by-one) nace casi siempre aquí.
  • El bucle infinito en while: olvidar actualizar la variable de la condición (stock -= 1, leer un nuevo input()...). Si tu programa "se queda colgado", es esto; interrumpe con Ctrl+C y revisa qué debería cambiar en cada vuelta.
  • Inicializar el acumulador dentro del bucle: total = 0 debe ir antes del for; dentro, se reinicia en cada vuelta y el total final sería solo el último importe.
  • Modificar la variable de bucle de un for esperando alterar el recorrido: hacer numero += 10 dentro de for numero in range(5) no salta iteraciones; en la siguiente vuelta numero toma el siguiente valor del rango igualmente. Para controlar los saltos usa el paso de range() o un while.
  • Indentación en bucles anidados: una línea indentada un nivel de más o de menos cambia cuántas veces se ejecuta. Si un print sale repetido más veces de la cuenta, comprueba su nivel.
  • Consejo: nombra la variable de bucle en singular y la colección en plural (for titulo in titulos). El código se lee solo.
  • Consejo: antes de escribir un bucle, di en voz alta qué quieres: si la frase empieza por "para cada..." es un for; si empieza por "mientras..." es un while. Funciona sorprendentemente bien.

Ejercicios

Ejercicio 1: catálogo alineado con numeración

Retoma la tabla alineada de catalogo_inicial.py (módulo 1) y genérala ahora con un bucle. Con las listas titulos = ["La Odisea", "Hamlet", "El Quijote"] y precios = [12.50, 9.95, 15.90], usa enumerate() para imprimir:

1. La Odisea    12.50 €
2. Hamlet        9.95 €
3. El Quijote   15.90 €

(Título alineado a la izquierda en 12 caracteres, precio a la derecha en 6 con dos decimales.)

Ejercicio 2: caja del día con estadísticas

Dada ventas = [12.50, 9.95, 15.90, 15.90, 12.50, 9.95, 15.90], escribe caja_dia.py que, con un solo bucle (sin usar sum(), max() ni min(), que verás más adelante), calcule e imprima: el total de la caja, el número de ventas, la venta más alta y cuántas ventas superaron los 10 €.

Ejercicio 3: hucha de reposición

Ana aparta dinero para reponer El Quijote (15.90 € el ejemplar). Cada día puede ahorrar 4 €. Escribe un while que simule los días: acumula 4 € diarios e imprime Día N: M.MM € ahorrados hasta tener suficiente para comprar 2 ejemplares. Al final indica cuántos días hicieron falta.

Soluciones

Solución 1:

titulos = ["La Odisea", "Hamlet", "El Quijote"]
precios = [12.50, 9.95, 15.90]

for i, titulo in enumerate(titulos, start=1):
    precio = precios[i - 1]
    print(f"{i}. {titulo:<12}{precio:>6.2f} €")

Como enumerate empieza en 1 pero las posiciones de la lista empiezan en 0, accedemos al precio con precios[i - 1]. Alternativa igual de válida: enumerate(titulos) sin start, usar precios[i] e imprimir {i + 1}.

Solución 2:

ventas = [12.50, 9.95, 15.90, 15.90, 12.50, 9.95, 15.90]

total = 0.0
num_ventas = 0
venta_maxima = 0.0
ventas_grandes = 0

for importe in ventas:
    total += importe
    num_ventas += 1
    if importe > venta_maxima:
        venta_maxima = importe
    if importe > 10:
        ventas_grandes += 1

print(f"Total caja:        {total:.2f} €")
print(f"Número de ventas:  {num_ventas}")
print(f"Venta más alta:    {venta_maxima:.2f} €")
print(f"Ventas > 10 €:     {ventas_grandes}")

Cuatro variables, cuatro patrones: dos acumuladores/contadores incondicionales (total, num_ventas), un máximo (se actualiza solo si el importe supera al mejor visto hasta ahora) y un contador condicional. El total debe dar 92.60 € y el máximo 15.90 €.

Solución 3:

PRECIO_QUIJOTE = 15.90
AHORRO_DIARIO = 4.0
objetivo = PRECIO_QUIJOTE * 2   # 31.80 €

ahorrado = 0.0
dias = 0

while ahorrado < objetivo:
    dias += 1
    ahorrado += AHORRO_DIARIO
    print(f"Día {dias}: {ahorrado:.2f} € ahorrados")

print(f"¡Objetivo alcanzado en {dias} días! ({ahorrado:.2f} € para {objetivo:.2f} €)")

La condición ahorrado < objetivo garantiza que el bucle para en cuanto se alcanza o supera la meta: con 4 € al día se llega a 32 € el día 8. Fíjate en que incrementamos dias al principio de cada vuelta para que el mensaje muestre el día correcto.

Conclusión

Con esta lección, Papyrus ha dado un salto de escala: ya no trabajamos libro a libro, sino sobre el catálogo entero. Has aprendido el for con range() (inicio incluido, fin excluido, paso opcional), a recorrer cadenas y listas, el while para repetir mientras se cumpla una condición, los patrones de contador y acumulador con los que cerramos la caja del día, los bucles anidados y la elegante enumerate() para tener posición y valor a la vez.

Aun así, nuestros bucles son todavía algo rígidos: siempre recorren la secuencia completa, de principio a fin. ¿Y si encontramos el libro que buscábamos a mitad del catálogo y queremos parar ahí? ¿Y si queremos saltarnos los títulos agotados y seguir con el resto? ¿Y si necesitamos un menú que se repita indefinidamente hasta que Ana elija "salir"? Para todo eso existen las herramientas de control de flujo — break, continue, else en bucles y la moderna sentencia match — que son exactamente el tema de la próxima lección, donde construiremos el primer menú interactivo de Papyrus.

Curso de Programación en Python

Módulo 1: Introducción a Python

Módulo 2: Estructuras de Control

Módulo 3: Funciones y Módulos

Módulo 4: Estructuras de Datos

Módulo 5: Programación Orientada a Objetos

Módulo 6: Manejo de Archivos

Módulo 7: Manejo de Errores y Excepciones

Módulo 8: Temas Avanzados

Módulo 9: Pruebas y Depuración

Módulo 10: Desarrollo Web con Python

Módulo 11: Ciencia de Datos con Python

Módulo 12: Proyecto Final

© Copyright 2026. Todos los derechos reservados