Hasta ahora TareaFácil ha sido un guion de un solo acto: se lanza, hace preguntas, imprime una ficha y muere. Si Marta se equivoca al asignar la prioridad, no puede corregirla: tiene que volver a ejecutar el programa y teclearlo todo de nuevo. Si quiere volver a ver la ficha, lo mismo. Un programa así se demuestra, pero no se usa.

Lo que separa un script de una aplicación es un patrón sorprendentemente simple: el bucle principal. La aplicación muestra lo que sabe hacer, espera una orden, la ejecuta y vuelve a mostrarse, una y otra vez, hasta que el usuario decide marcharse. Es el esqueleto de casi todo el software interactivo que has usado nunca, desde un cajero automático hasta un editor de texto.

Lo mejor es que ya tienes todas las piezas: while True y break de la lección anterior, la validación insistente de 03-02, el despacho con if/elif o match de 03-01 y 03-03. Esta lección solo las monta juntas, y con ellas cierra el módulo.

Contenido

  1. Anatomía del bucle principal
  2. while True con break, y la variante con bandera
  3. Despachar la opción elegida
  4. Diseñar un menú que se entienda
  5. Validar la opción leída
  6. Confirmaciones para acciones destructivas
  7. TareaFácil v0.6: la aplicación completa
  8. Errores comunes y consejos
  9. Ejercicios
  10. Conclusión del módulo

  1. Anatomía del bucle principal

Un menú interactivo repite siempre el mismo ciclo de cinco pasos.

flowchart TD
    A["Mostrar el menu"] --> B["Leer la opcion"]
    B --> C{"Opcion valida ?"}
    C -->|False| B
    C -->|True| D{"Es la opcion de salir ?"}
    D -->|True| F["Despedirse y terminar"]
    D -->|False| E["Ejecutar la accion"]
    E --> G["Pausa"]
    G --> A

Los cinco pasos, con su traducción a código:

Paso Qué hace Con qué se implementa
Mostrar Pinta las opciones disponibles Varios print()
Leer Recoge la elección del usuario input()
Validar Rechaza lo que no es una opción while de validación (03-02)
Ejecutar Hace el trabajo pedido if/elif o match (03-01, 03-03)
Volver Regresa al principio El propio bucle

Y una regla de oro: el bucle no debe poder terminar por accidente. La única salida es la opción de salir; un error del usuario no puede cerrar la aplicación, porque en una aplicación real eso significa perder el trabajo.

  1. while True con break, y la variante con bandera

La forma más directa es el bucle infinito controlado que viste en 03-03: while True: da vueltas indefinidamente y la opción de salir ejecuta un break.

while True:
    print("1. Ver la tarea")
    print("2. Salir")
    opcion = input("Opcion: ").strip()

    if opcion == "1":
        print("Aqui iria la ficha de la tarea.")
    elif opcion == "2":
        print("Hasta luego.")
        break

Hay una segunda forma, con una bandera booleana que gobierna la condición: se inicializa en_ejecucion = True antes del bucle, la cabecera pasa a ser while en_ejecucion: y la opción de salir hace en_ejecucion = False en lugar del break. Las dos son correctas y verás ambas en código profesional.

while True + break Bandera en_ejecucion
Condición de salida Escondida en el cuerpo Visible en la cabecera
Salida inmediata Sí: corta en el acto No: termina la vuelta actual
Varios puntos de salida Un break por sitio Poner la bandera a False
Legibilidad Muy directa Más declarativa

La diferencia práctica importa: con break el bucle se corta en ese mismo instante; con la bandera, el resto del cuerpo de la vuelta actual sí se ejecuta antes de volver a comprobar la condición. Si después del despacho hay una pausa o un mensaje, con la bandera se verán también al salir. Usaremos break en TareaFácil, que es lo más habitual.

  1. Despachar la opción elegida

Despachar es dirigir la ejecución hacia el bloque que corresponde a la opción elegida. Con if/elif es inmediato: if opcion == "1":elif opcion == "2": … y un else final para lo no reconocido. Pero como estamos comparando un valor contra alternativas concretas, este es exactamente el escenario para el que se diseñó match:

match opcion:
    case "1":
        print("Registrar tarea")
    case "2":
        print("Ver ficha")
    case "3" | "c":
        print("Cambiar prioridad")
    case "5" | "s" | "salir":
        print("Hasta luego")
    case _:
        print("Opcion no reconocida")

Fíjate en la ventaja del patrón |: permite aceptar atajos con naturalidad, de modo que el usuario pueda teclear 5, s o salir para lo mismo. Con if/elif habría que escribir opcion in ("5", "s", "salir"), que también funciona pero se lee peor cuando hay muchas opciones.

Un aviso importante: aunque el match de un menú se lea muy bien, el break que sale del bucle no se puede sustituir por nada del match. break sigue perteneciendo al while, y funciona igual dentro de un case.

  1. Diseñar un menú que se entienda

El menú es la interfaz de tu programa, y un menú mal presentado convierte una aplicación correcta en una aplicación inutilizable. Seis reglas bastan:

  • Numera las opciones y usa números consecutivos desde 1. Teclear un número es más rápido y menos propenso a error que escribir una palabra.
  • Describe acciones, no sustantivos. «Cambiar la prioridad» dice qué va a pasar; «Prioridad», no.
  • La opción de salir, siempre visible y siempre la última. El usuario debe poder marcharse sin adivinar cómo.
  • Separa visualmente el menú del resultado con líneas de guiones o iguales. Recuerda el truco de la lección 02-03: print("=" * ANCHO).
  • Pausa antes de repintar. Si el menú vuelve a dibujarse inmediatamente, el resultado de la acción desaparece de la vista antes de que dé tiempo a leerlo. La solución clásica es un input("Pulsa Intro para continuar...") cuyo valor se ignora: solo sirve para esperar.
  • Repite el rango en el mensaje: Elige una opcion (1-5): orienta mucho mejor que Opcion:.
ANCHO = 46

print("=" * ANCHO)
print(f"{'TareaFacil v0.6 - Estudio Alba':^{ANCHO}}")
print("=" * ANCHO)
print(" 1. Registrar / reemplazar la tarea")
print(" 2. Ver la ficha de la tarea")
print(" 3. Cambiar la prioridad")
print(" 4. Marcar como completada")
print(" 5. Salir")
print("-" * ANCHO)

  1. Validar la opción leída

La opción es un dato que viene de fuera, así que se le aplica el mismo trato que a cualquier otro: el marco de 02-04 —presencia, tipo, dominio— y el bucle insistente de 03-02.

OPCIONES = ("1", "2", "3", "4", "5")
opcion = input("Elige una opcion (1-5): ").strip()
while opcion not in OPCIONES:
    opcion = input("Opcion no valida. Elige 1-5: ").strip()

Tres decisiones de diseño que conviene entender:

  • Comparamos textos, no números. OPCIONES contiene "1" y no 1, así que no hace falta convertir. Si el usuario teclea hola, no hay ValueError que valga: simplemente no está en el catálogo. Convertir con int() sin comprobar antes rompería el programa.
  • El .strip() es imprescindible. Un espacio delante del número convierte " 1" en un dato inválido, y el usuario no vería por qué.
  • Intro sin escribir nada produce la cadena vacía, que tampoco está en OPCIONES, así que el bucle vuelve a preguntar sin necesidad de ningún caso especial. Eso sí: conviene que el mensaje de reproche sea claro, porque un menú que responde en silencio parece colgado.

Si además quieres aceptar atajos como s para salir, basta con normalizar con .lower() y ampliar el catálogo: OPCIONES = ("1", "2", "3", "4", "5", "s").

  1. Confirmaciones para acciones destructivas

Una acción es destructiva si pierde información que el usuario no puede recuperar: reemplazar la tarea registrada, borrarla, salir de la aplicación sin guardar. Ante ellas, un programa educado pregunta.

confirmar = input("Seguro que quieres reemplazar la tarea? (s/n): ").strip().lower()
if confirmar == "s":
    print("Reemplazando...")
else:
    print("Operacion cancelada.")

El detalle importante está en el else: comparamos con "s" y cualquier otra cosa cancela. Es deliberado. Ante la duda —un dedo torpe, un Intro accidental, una x sin querer—, la opción segura es no destruir nada; si hiciéramos if confirmar != "n":, cualquier tecla equivocada ejecutaría la acción irreversible. Y no abuses: si el programa pide confirmación para todo, el usuario aprende a teclear s sin leer y la confirmación deja de proteger. Confirma solo lo que de verdad no tiene vuelta atrás.

  1. TareaFácil v0.6: la aplicación completa

Ya está todo. TareaFácil deja de ser un guion y se convierte en una aplicación con cinco opciones sobre la única tarea que sabe gestionar. Fíjate en una variable nueva y decisiva: hay_tarea, la bandera que distingue «todavía no se ha registrado nada» de «hay una tarea que consultar o modificar».

# tareafacil.py - Estudio Alba
# Version 0.6: aplicacion con menu principal

ANCHO = 46
PRIORIDADES = ("alta", "media", "baja")
EQUIPO = ("Marta", "Luis", "Nuria")
OPCIONES = ("1", "2", "3", "4", "5")

# --- Estado de la aplicacion: los datos de LA tarea ---
hay_tarea = False
titulo = ""
responsable = ""
prioridad = ""
dias = 0
completada = False

while True:
    print("\n" + "=" * ANCHO)
    print(f"{'TareaFacil v0.6 - Estudio Alba':^{ANCHO}}")
    print("=" * ANCHO)
    print(" 1. Registrar / reemplazar la tarea")
    print(" 2. Ver la ficha de la tarea")
    print(" 3. Cambiar la prioridad")
    print(" 4. Marcar como completada")
    print(" 5. Salir")
    print("-" * ANCHO)

    opcion = input("Elige una opcion (1-5): ").strip()
    while opcion not in OPCIONES:
        opcion = input("Opcion no valida. Elige 1-5: ").strip()

    if opcion == "1":
        if hay_tarea:
            confirmar = input(f"Ya existe '{titulo}'. Reemplazarla? (s/n): ").strip().lower()
            if confirmar != "s":
                print("Operacion cancelada.")
                continue
        titulo = input("Titulo        : ").strip()
        while titulo == "":
            titulo = input("Vacio. Titulo : ").strip()
        responsable = input("Responsable   : ").strip().capitalize()
        while responsable not in EQUIPO:
            responsable = input("No es del equipo. Responsable: ").strip().capitalize()
        prioridad = input("Prioridad     : ").strip().lower()
        while prioridad not in PRIORIDADES:
            prioridad = input("No valida. Prioridad: ").strip().lower()
        dias_texto = input("Dias (1-365)  : ").strip()
        while not dias_texto.isdigit() or not 1 <= int(dias_texto) <= 365:
            dias_texto = input("Entero 1-365. Dias: ").strip()
        dias = int(dias_texto)
        completada = False
        hay_tarea = True
        print("Tarea registrada correctamente.")
    elif opcion == "2":
        if not hay_tarea:
            print("Todavia no hay ninguna tarea registrada.")
        else:
            estado = "Completada" if completada else "Pendiente"
            print("-" * ANCHO)
            print(f"{'Titulo':<14}{titulo:>{ANCHO - 14}}")
            print(f"{'Responsable':<14}{f'{responsable} ({prioridad})':>{ANCHO - 14}}")
            print(f"{'Dias / estado':<14}{f'{dias} / {estado}':>{ANCHO - 14}}")
    elif opcion == "3":
        if not hay_tarea:
            print("Todavia no hay ninguna tarea registrada.")
        else:
            nueva = input("Nueva prioridad (alta/media/baja): ").strip().lower()
            while nueva not in PRIORIDADES:
                nueva = input("No valida. Prioridad: ").strip().lower()
            print(f"Prioridad de '{titulo}': {prioridad} -> {nueva}")
            prioridad = nueva
    elif opcion == "4":
        if not hay_tarea:
            print("Todavia no hay ninguna tarea registrada.")
        elif completada:
            print("La tarea ya estaba completada.")
        else:
            confirmar = input(f"Marcar '{titulo}' como completada? (s/n): ").strip().lower()
            if confirmar == "s":
                completada = True
                print("Tarea completada. Buen trabajo.")
            else:
                print("Operacion cancelada.")
    elif opcion == "5":
        confirmar = input("Seguro que quieres salir? (s/n): ").strip().lower()
        if confirmar == "s":
            print("Hasta luego. TareaFacil se cierra.")
            break

    input("\nPulsa Intro para volver al menu...")

Cuatro detalles del código merecen comentario:

  • El estado vive fuera del bucle. Las variables titulo, prioridad, hay_tarea… se declaran antes del while y sobreviven a todas las vueltas. Si estuvieran dentro, cada vuelta las reiniciaría y la aplicación olvidaría la tarea nada más pintar el menú. Es el mismo principio que el acumulador de 03-02.
  • hay_tarea protege las opciones 2, 3 y 4. Sin esa comprobación, elegir «Ver la ficha» antes de registrar nada mostraría una ficha vacía y absurda. Una aplicación seria nunca ofrece un dato que no tiene.
  • El continue de la opción 1 cancela el reemplazo y vuelve al menú saltándose el resto de la vuelta, incluida la pausa final. Es exactamente el continue de 03-03 aplicado al bucle principal.
  • Salir también se confirma, porque en la versión actual cerrar el programa pierde la tarea. Y solo "s" sale: cualquier otra tecla sigue en la aplicación.

Una sesión de trabajo típica de Marta:

Elige una opcion (1-5): 9
Opcion no valida. Elige 1-5: 1
Titulo        : Cartel feria del libro
Responsable   : nuria
Prioridad     : urgentisima
No valida. Prioridad: alta
Dias (1-365)  : 4
Tarea registrada correctamente.

Pulsa Intro para volver al menu...

Después, la opción 3 cambia la prioridad a media, la 2 muestra la ficha actualizada y la 5 pide confirmación antes de cerrar. La misma ejecución del programa hace todo eso, que es justo lo que no podíamos hacer en ninguna versión anterior.

Errores Comunes y Consejos

Declarar el estado dentro del bucle. El error número uno de los menús: la aplicación «olvida» los datos en cada vuelta. Todo lo que deba sobrevivir va antes del while.

Olvidar el break en la opción de salir. El menú se vuelve inescapable y solo queda Ctrl+C. Compruébalo siempre lo primero.

Convertir la opción con int() sin validar. Si el usuario teclea una letra, ValueError y programa muerto. Trabaja con la opción como texto y compárala contra un catálogo.

No pausar antes de repintar. El resultado aparece y desaparece en el mismo parpadeo; un input("Pulsa Intro...") lo resuelve. Y no ofrezcas opciones imposibles, como consultar una tarea que aún no existe: una bandera de estado como hay_tarea y un mensaje claro lo evitan.

Confirmar con != "n". Cualquier tecla despistada ejecuta la acción destructiva. Confirma siempre con == "s" y deja que todo lo demás cancele.

Consejo: guarda el número de opciones en una constante. Con OPCIONES arriba del fichero, añadir una opción es tocar dos sitios (la constante y el menú), no rebuscar por todo el programa.

Consejo: prueba tu menú como un usuario hostil. Teclea letras, espacios, Intro a secas, números fuera de rango, y elige la opción 2 nada más arrancar. Cada cosa que rompa el programa es una validación que falta.

Ejercicios

Ejercicio 1: Detectar los fallos

Este menú tiene tres defectos graves. Encuéntralos y explica qué le pasa al usuario en cada caso.

while True:
    contador_usos = 0
    print("1. Ver  2. Salir")
    opcion = int(input("Opcion: "))
    if opcion == 1:
        contador_usos += 1
        print(f"Consultas: {contador_usos}")
    elif opcion == 2:
        print("Adios")

Ejercicio 2: Menú de la agenda del equipo

Escribe agenda_equipo.py, una aplicación con bucle principal y menú de cuatro opciones que gestione una sola nota de agenda: 1) escribir/reemplazar la nota (texto no vacío y día de la semana entre 1 y 5), 2) ver la nota, 3) borrar la nota (con confirmación), 4) salir. Usa una bandera hay_nota, valida la opción con un while, despacha con match y pausa antes de repintar.

Ejercicio 3: Contador de operaciones

Amplía TareaFácil v0.6 para que lleve la cuenta de cuántas veces se ha usado cada opción durante la sesión y muestre un resumen al salir, con el patrón contador de 03-02. Indica dónde deben declararse los contadores y por qué.

Soluciones

Solución 1.

Defecto Qué le pasa al usuario
contador_usos = 0 está dentro del bucle Se reinicia en cada vuelta: el contador siempre muestra 1
int(input(...)) sin validar Si teclea una letra o pulsa Intro, el programa muere con ValueError
La opción 2 no tiene break Imprime «Adios» y vuelve al menú: es imposible salir

Los tres tienen la misma raíz: el bucle se escribió pensando solo en el usuario que hace lo previsto. La versión corregida saca el contador fuera del while, lee la opción como texto validado contra un catálogo y añade el break.

Solución 2.

# agenda_equipo.py - Estudio Alba
OPCIONES = ("1", "2", "3", "4")
DIAS = ("1", "2", "3", "4", "5")
hay_nota = False
nota = ""
dia = ""

while True:
    print(" 1. Escribir / reemplazar la nota   2. Ver la nota")
    print(" 3. Borrar la nota                  4. Salir")
    opcion = input("Elige una opcion (1-4): ").strip()
    while opcion not in OPCIONES:
        opcion = input("Opcion no valida. Elige 1-4: ").strip()

    match opcion:
        case "1":
            nota = input("Texto de la nota: ").strip()
            while nota == "":
                nota = input("No puede estar vacia. Nota: ").strip()
            dia = input("Dia laborable (1-5): ").strip()
            while dia not in DIAS:
                dia = input("Debe ser de 1 a 5. Dia: ").strip()
            hay_nota = True
            print("Nota guardada.")
        case "2":
            if hay_nota:
                print(f"Dia {dia}: {nota}")
            else:
                print("No hay ninguna nota escrita.")
        case "3":
            if not hay_nota:
                print("No hay nada que borrar.")
            else:
                confirmar = input(f"Borrar '{nota}'? (s/n): ").strip().lower()
                if confirmar == "s":
                    hay_nota = False
                    nota = ""
                    print("Nota borrada.")
                else:
                    print("Operacion cancelada.")
        case "4":
            print("Hasta luego.")
            break

    input("\nPulsa Intro para continuar...")

Observa que el break de case "4" funciona con normalidad: pertenece al while, no al match. Y que borrar no consiste solo en poner hay_nota = False: conviene limpiar también nota, para que ningún camino del programa pueda mostrar un dato que el usuario cree eliminado.

Solución 3.

Los contadores —uno por opción— deben declararse antes del while, junto al resto del estado, exactamente por la misma razón que hay_tarea: si estuvieran dentro, se reiniciarían en cada vuelta y siempre valdrían 1. Se incrementan al principio del bloque de cada opción, y el resumen se imprime justo antes del break:

usos_registrar = 0
usos_ver = 0
usos_prioridad = 0
usos_completar = 0

while True:
    # ... menu y validacion de la opcion ...
    if opcion == "1":
        usos_registrar += 1
        # ... registro de la tarea ...
    elif opcion == "5":
        confirmar = input("Seguro que quieres salir? (s/n): ").strip().lower()
        if confirmar == "s":
            print(f"Resumen: {usos_registrar} registros, {usos_ver} consultas,")
            print(f"{usos_prioridad} cambios de prioridad, {usos_completar} completadas.")
            break

Y aquí asoma un problema que ya no es de bucles: cuatro variables casi idénticas para contar cuatro cosas casi idénticas. Es el mismo síntoma que veremos en la conclusión, y su solución está en el módulo 5.

Conclusión del módulo

TareaFácil ha llegado a la versión 0.6 y ya es una aplicación de verdad. Muestra un menú, espera órdenes, valida lo que recibe, ejecuta la acción pedida, avisa cuando algo no se puede hacer, confirma lo irreversible y solo se cierra cuando el usuario lo dice. Marta puede registrar la tarea, consultarla, cambiarle la prioridad y marcarla como completada sin volver a lanzar el programa.

Y con ello se cierra el módulo 3. Sabes decidir con if, elif y else, con condiciones compuestas, anidamiento y cláusulas de guarda (03-01). Sabes repetir con while y for, con range() y sus tres formas, y con los cuatro patrones clásicos —contador, acumulador, máximo y bandera— (03-02). Sabes controlar el flujo con precisión mediante break, continue, la cláusula else de los bucles, los bucles anidados y match (03-03). Y sabes montar todo eso en un bucle de aplicación con menú, validación, despacho, pausa y confirmaciones (03-04). Con estas tres estructuras —secuencia, decisión y repetición— puedes expresar cualquier algoritmo que exista: es un resultado clásico de la informática, y ya lo tienes entero.

Pero mira la versión 0.6 con ojos críticos, porque sus dos límites son el mapa de lo que viene. El primero: el código se ha vuelto largo y repetitivo. Casi cien líneas en un solo bloque, con if not hay_tarea: escrito tres veces, cuatro bucles de validación que se parecen como gotas de agua y un menú que crecerá cada vez que añadamos una función. Todo eso pide a gritos poder dar nombre a un trozo de código y reutilizarlo: escribir una vez pedir_prioridad() y llamarla desde donde haga falta. Eso son las funciones, y son el módulo 4 entero.

El segundo: solo cabe una tarea. Toda la aplicación gira alrededor de un único juego de variables —titulo, responsable, prioridad, dias—, y registrar una tarea nueva significa destruir la anterior. Por eso la opción 1 pide confirmación. Pero el estudio no tiene una tarea: tiene decenas, y Marta necesita verlas todas, filtrarlas por responsable y ordenarlas por prioridad. Para eso hacen falta estructuras capaces de guardar muchos valores bajo un solo nombre: las listas, que llegan en Listas y arreglos, y con ellas el for que recorre una colección de tareas de verdad.

Dicho de otro modo: sabes construir la maquinaria de un programa, y ahora toca aprender a organizarla y a darle datos con los que valga la pena trabajar. Nos vemos en el módulo 4, con las funciones.

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