Lección 42 de 45 · Proyectos y cierre

Proyecto: lista de tareas en CLI

Tu primer proyecto no necesita muchas funciones; necesita límites claros. Construiremos una lista de tareas por capas pequeñas que puedas probar una a una.

Paso a pasoCheckpoints acumulativosAvanza a tu ritmo
Índice · Lección 42/45
Proyecto 1 · objetivo real

Vas a construir la lógica de una pequeña lista de tareas para terminal. No empezaremos por el menú. Primero haremos que el programa sepa representar, añadir y completar tareas.

Tu versión mínima solo necesita tres acciones: añadir, listar y marcar una tarea como terminada. Todo lo demás es opcional.

Evidencia de aprendizaje

Construir una aplicación mínima por checkpoints: representar, añadir/listar, completar y persistir.

Paso 1: decide qué información necesita una tarea

Antes de escribir funciones, define el dato. Para este proyecto una tarea puede ser simplemente un diccionario con dos piezas: el texto y si está terminada.

El estado mínimo del proyecto
tarea = {
    "texto": "Repasar funciones",
    "hecha": False,
}

print(tarea["texto"])
print(tarea["hecha"])

Este modelo es deliberadamente pequeño. No añadas fecha, prioridad, etiquetas o identificadores hasta que exista una necesidad concreta. Cada campo extra añade decisiones y posibles errores.

¿Por qué un diccionario y no una clase?

Porque ahora mismo solo necesitamos guardar dos datos. Una clase podría ser una buena evolución más adelante, pero no mejora automáticamente esta primera versión.

Paso 2: construye el corazón antes de construir el menú

El programa debería poder trabajar aunque todavía no exista input(). Eso permite probar las reglas con datos controlados.

Tres operaciones, tres responsabilidades
def agregar(tareas, texto):
    tareas.append({"texto": texto, "hecha": False})


def completar(tareas, posicion):
    tareas[posicion]["hecha"] = True


def pendientes(tareas):
    return [t for t in tareas if not t["hecha"]]


tareas = []
agregar(tareas, "Practicar listas")
agregar(tareas, "Leer 10 minutos")
completar(tareas, 0)
print(len(pendientes(tareas)))

Observa la ventaja: si completar() falla, no necesitas investigar un menú, un archivo y cinco opciones. El error está en una pieza pequeña.

Paso 3: deja que la interfaz traduzca lo que escribe la persona

La terminal produce texto. Tu lógica trabaja mejor con valores ya preparados. Por eso conviene que la interfaz haga la traducción y luego llame a las funciones del proyecto.

La interfaz coordina; no concentra las reglas
def procesar_opcion(opcion, tareas):
    if opcion == "1":
        return "Añadir"
    if opcion == "2":
        return f"Hay {len(tareas)} tareas"
    return "Opción no válida"

print(procesar_opcion("2", [{"texto": "Estudiar", "hecha": False}]))

Cuando añadas input(): recuerda que siempre devuelve texto. Convierte a entero solo aquello que realmente necesite ser un número, como una posición elegida por el usuario.

Paso 4: añade persistencia cuando el núcleo ya funciona

Guardar en JSON es una capa nueva, no una razón para reescribir el proyecto. Dos funciones pequeñas pueden encargarse de leer y guardar la lista completa.

Persistencia aislada
import json
from pathlib import Path

RUTA = Path("tareas.json")

def guardar(tareas):
    RUTA.write_text(json.dumps(tareas, ensure_ascii=False), encoding="utf-8")

def cargar():
    if not RUTA.exists():
        return []
    return json.loads(RUTA.read_text(encoding="utf-8"))

No necesitas ejecutar este bloque para entender la idea: guardar() transforma la lista en texto JSON y cargar() recupera una lista o devuelve una vacía si todavía no existe archivo.

Construye el MVP en cuatro checkpoints

Cada checkpoint conserva lo anterior. No añadas colores, prioridades, fechas ni usuarios hasta que estos cuatro funcionen.

Checkpoint 1 · representar una tarea

Completa el diccionario mínimo de una tarea: texto + estado.

Salida esperada
Comprar pan
False
cli_1.py
La salida aparecerá aquí.

Necesito una pista
  • Añade la clave "hecha" con valor booleano False.
Ver una solución razonada
tarea = {"texto": "Comprar pan", "hecha": False}
print(tarea["texto"])
print(tarea["hecha"])

Checkpoint 2 · añadir y listar

Implementa agregar() y deja que el listado recorra la colección.

Salida esperada
Comprar pan
Estudiar Python
cli_2.py
La salida aparecerá aquí.

Necesito una pista
  • Añade a la lista un diccionario con texto y hecha=False.
Ver una solución razonada
def agregar(tareas, texto):
    tareas.append({"texto": texto, "hecha": False})

tareas = []
agregar(tareas, "Comprar pan")
agregar(tareas, "Estudiar Python")
for tarea in tareas:
    print(tarea["texto"])

Checkpoint 3 · completar sin destruir datos

Marca una tarea como hecha conservando su texto.

Salida esperada
Comprar pan | True
cli_3.py
La salida aparecerá aquí.

Necesito una pista
  • Accede a la tarea por posición y cambia solo la clave "hecha".
Ver una solución razonada
def completar(tareas, posicion):
    tareas[posicion]["hecha"] = True

tareas = [{"texto": "Comprar pan", "hecha": False}]
completar(tareas, 0)
print(tareas[0]["texto"], "|", tareas[0]["hecha"])

Checkpoint 4 · persistir y recuperar

Serializa la lista a JSON y vuelve a cargarla. Así compruebas la persistencia sin depender de un menú.

Salida esperada
[{'texto': 'Comprar pan', 'hecha': True}]
cli_4.py
La salida aparecerá aquí.

Necesito una pista
  • Usa json.dumps(tareas) y después json.loads(texto).
Ver una solución razonada
import json

tareas = [{"texto": "Comprar pan", "hecha": True}]
texto = json.dumps(tareas)
recuperadas = json.loads(texto)
print(recuperadas)
MVP terminado cuando: puedes añadir, listar, completar y recuperar las tareas guardadas. Todo lo demás es una extensión opcional.

Si el proyecto se rompe, localiza primero la capa

  • Los datos están mal: inspecciona la lista y un diccionario concreto.
  • La regla está mal: llama a la función directamente sin menú.
  • La entrada falla: imprime temporalmente lo que devuelve input().
  • No se conserva al reiniciar: revisa solo cargar() y guardar().

Esta división evita el pensamiento “todo está roto”. Casi siempre hay una capa concreta que está produciendo el resultado inesperado.

La versión 1 está terminada mucho antes de ser perfecta

Criterio de finalización

Da el proyecto por terminado cuando puedas añadir dos tareas, listarlas, marcar una como hecha y volver a obtener el mismo estado después de guardarlo y cargarlo.

Después, si te apetece, añade una sola mejora: borrar tareas, prioridades o búsqueda. No intentes convertir el ejercicio en una aplicación profesional.