Saltar al contenido
Volver al inicio 100 Días de Código en Python — Día 89: Proyecto de portafolio — Sitio web de lista de tareas (de la lista simple al tablero Kanban)

100 Días de Código en Python — Día 89: Proyecto de portafolio — Sitio web de lista de tareas (de la lista simple al tablero Kanban)

Nivel: Grado décimo Área: Tecnología e Informática Modalidad: Tutorial práctico Fuente: Curso "100 Days of Code: The Complete Python Pro Bootcamp" (Angela Yu, App Brewery). Contenido del Día 89, proyecto de portafolio profesional (categoría Web Development).

Este tutorial adapta pedagógicamente el Día 89. La sección 📝 Contenido original del curso proviene del único archivo de la carpeta: dia89-tarea8.md (enunciado y pregunta de reflexión, en inglés; aquí traducido). El curso no ofrece guía ni solución, y este tutorial no entrega el sitio terminado. Todo lo que no está en ese archivo (niveles de complejidad, modelo de datos con estados, esqueleto Flask verificado, escape de HTML, retos 🟢🟡🟠🔴, proyecto extra y la etapa 8 de Mi Portafolio Profesional) es creación de esta adaptación.

Nota sobre las verificaciones: el 28 de septiembre de 2026, con Python 3.14, Flask 3.1.3 y Flask-SQLAlchemy 3.1.1, se ejecutó el esqueleto de la sección 4 con el cliente de pruebas: crear una tarea redirige (302) y la guarda; un título de solo espacios no se guarda; mover una tarea a "hecha" la muestra en esa columna; un estado inventado responde 400; un id inexistente, 404; y un título con <script> se muestra escapado (&lt;script&gt;), sin ejecutarse.


Tabla de contenidos

  1. La tarea: una lista de tareas en la web
  2. Analizar el problema: elige tu nivel de ambición
  3. 📖 Modelar una tarea: de hecha: bool a estados
  4. 📖 Un esqueleto Kanban con Flask (para completar tú)
  5. 📖 CRUD completo y seguro
  6. 🚀 Proyecto del día: construcción por versiones
  7. 🌟 Proyecto extra: tablero del proyecto de grado del curso
  8. 🏗️ Mi Portafolio Profesional — Etapa 8
  9. 🧩 Comprueba lo aprendido
  10. 🤖 Programar con apoyo de IA
  11. 🛠️ Observaciones sobre el material original
  12. 📋 Matriz de trazabilidad
  13. 📦 Evidencias de aprendizaje del Día 89

1. La tarea: una lista de tareas en la web

(Fuente: dia89-tarea8.md)

📝 Contenido original del curso

Hoy vas a construir un sitio web de lista de tareas (todo list). Es un rito de iniciación para cualquier desarrollador.

Puedes elegir el tipo de lista que quieras construir: tan simple como un sitio donde anotas algunos elementos y los tachas, o tan complejo como una lista de tareas estilo Kanban como Trello. (El enunciado enlaza un sitio de inspiración: flask.io/new.)

Secreto: la mayoría de las entrevistas para desarrolladores las hace alguien que es gerente (manager). La principal tecnología que usa un gerente es un gestor de tareas como Trello o Jira. ¡Si puedes construir un buen gestor de tareas, seguro impresionarás a tu futuro entrevistador!

Pregunta de la tarea — Momento de reflexión: cómo abordaste el proyecto; qué fue difícil y qué fácil; cómo mejorarías; tu mayor aprendizaje; qué harías distinto.

Prerequisitos: Flask, Jinja y formularios HTML con POST (Días 54–60); SQLAlchemy (Día 63); CRUD en el sitio de películas (Día 64) y rutas REST (Día 66); autenticación (Día 68) para los niveles avanzados; Bootstrap (Día 58).


2. Analizar el problema: elige tu nivel de ambición

(Sección creada para esta adaptación.)

El enunciado deja el alcance abierto. Decidir el alcance es parte del trabajo profesional: mejor un proyecto sencillo terminado que uno ambicioso a medias.

Nivel Qué hace Conceptos nuevos que exige
🟢 1. Lista simple Agregar tareas y tacharlas Un formulario, una tabla, un booleano
🟡 2. Lista completa + editar, eliminar, fechas límite, prioridad, ordenar CRUD completo, fechas
🟠 3. Kanban Columnas (pendiente, en progreso, hecha) y mover tareas entre ellas Estados, agrupar por columnas
🔴 4. Multiusuario Cada usuario ve sus tableros; tableros compartidos Autenticación, relaciones entre tablas, permisos

🔍 Antes de programar

  • ¿Quién la usará y para qué? (tareas del colegio, un trabajo en grupo, los oficios de la casa)
  • ¿Qué datos tiene una tarea? (título, descripción, fecha límite, prioridad, estado, responsable…)
  • ¿Qué acciones hay? Escribe la lista CRUD: crear, leer (listar/ver), actualizar (editar/mover/tachar), eliminar.
  • ¿La lista debe sobrevivir si cierro el navegador? ¿Si otra persona entra desde su celular?

3. 📖 Modelar una tarea: de hecha: bool a estados

(Sección creada para esta adaptación.)

🎯 Objetivo de aprendizaje

Elegir cómo representar el avance de una tarea y justificar la decisión.

📖 Explicación conceptual

Opción Ejemplo Sirve para Limitación
Booleano hecha = True Nivel 1: tachar Solo 2 estados
Texto con valores permitidos estado = "en_progreso" Nivel 3: columnas Hay que validar que el valor sea uno permitido
Tabla de columnas columna_id = 3 → tabla Columna Nivel 4: columnas personalizables por el usuario Más tablas y relaciones

En este tutorial usamos la opción del medio, con una lista de valores permitidos:

Python
ESTADOS = ["pendiente", "en_progreso", "hecha"]

Regla de oro: cualquier dato que llegue de un formulario puede ser manipulado (con las herramientas del navegador, cualquiera cambia un <option>). Por eso el servidor comprueba que el estado esté en ESTADOS antes de guardarlo.


4. 📖 Un esqueleto Kanban con Flask (para completar tú)

(Sección creada para esta adaptación. Verificado con el cliente de pruebas de Flask.)

Python
from datetime import date
from flask import Flask, render_template, request, redirect, url_for, abort
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import String

class Base(DeclarativeBase):
    pass

ESTADOS = ["pendiente", "en_progreso", "hecha"]

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///tareas.db"
db = SQLAlchemy(model_class=Base)
db.init_app(app)

class Tarea(db.Model):
    id: Mapped[int] = mapped_column(primary_key=True)
    titulo: Mapped[str] = mapped_column(String(200))
    estado: Mapped[str] = mapped_column(String(20), default="pendiente")
    fecha_limite: Mapped[date | None]

with app.app_context():
    db.create_all()

@app.route("/")
def tablero():
    tareas = db.session.execute(db.select(Tarea)).scalars().all()
    columnas = {estado: [t for t in tareas if t.estado == estado] for estado in ESTADOS}
    return render_template("tablero.html", columnas=columnas)

@app.route("/tareas", methods=["POST"])
def crear():
    titulo = request.form.get("titulo", "").strip()
    if titulo:
        db.session.add(Tarea(titulo=titulo))
        db.session.commit()
    return redirect(url_for("tablero"))

@app.route("/tareas/<int:tarea_id>/mover", methods=["POST"])
def mover(tarea_id):
    tarea = db.get_or_404(Tarea, tarea_id)
    nuevo = request.form.get("estado")
    if nuevo not in ESTADOS:
        abort(400)
    tarea.estado = nuevo
    db.session.commit()
    return redirect(url_for("tablero"))

Qué hace cada parte:

  • fecha_limite: Mapped[date | None]: con la notación moderna de SQLAlchemy 2, el tipo de Python define la columna; | None la hace opcional.
  • columnas = {estado: [...] for estado in ESTADOS}: una comprensión de diccionario (Día 26) que agrupa las tareas por estado. La plantilla recorre columnas.items() y dibuja una columna por estado.
  • .strip() quita espacios: un título de solo espacios no se guarda (verificado).
  • abort(400) responde "Bad Request" si alguien envía un estado no permitido.
  • Cada acción termina con redirect (patrón POST/Redirect/GET): si el usuario recarga la página, el navegador no reenvía el formulario ni duplica la tarea.

La plantilla (idea): tres columnas con Bootstrap (row y col-md-4), y en cada tarjeta un pequeño formulario con botones "→ En progreso" / "→ Hecha" que envían estado por POST.

🛡️ Escape automático: Jinja convierte < en &lt; al mostrar {{ tarea.titulo }} (verificado). Si alguien escribe <script>...</script> como título, se ve como texto y no se ejecuta. Nunca uses el filtro | safe con texto escrito por usuarios: desactivaría esa protección (ataque XSS).

Arrastrar y soltar: en Trello las tarjetas se arrastran. Eso requiere JavaScript (la API Drag and Drop del navegador y fetch para avisar al servidor). Es un reto excelente, pero no es obligatorio: los botones cumplen la misma función.


5. 📖 CRUD completo y seguro

(Sección creada para esta adaptación.)

Acción Método y ruta sugeridos Detalle
Listar GET / Tablero agrupado por estado
Crear POST /tareas Valida el título
Editar GET /tareas/<id>/editar (formulario) y POST /tareas/<id>/editar Reutiliza el formulario de crear (Día 64)
Mover POST /tareas/<id>/mover Valida el estado
Eliminar POST /tareas/<id>/eliminar Nunca por GET (Día 88); con confirmación

⚠️ Errores frecuentes

Error Qué ocurre Solución
Recargar después de crear duplica la tarea El navegador reenvía el POST Termina el POST con redirect
Fechas guardadas como texto "15/10/2026" No se pueden ordenar ni comparar Usa date: date.fromisoformat(request.form["fecha"]) con <input type="date">
ValueError con una fecha vacía El formulario envía "" Si está vacía, guarda None
Todas las tareas de todos los usuarios en un tablero Privacidad Nivel 4: columna usuario_id y filtrar por el usuario actual (Día 69)
| safe en títulos XSS No lo uses con datos de usuarios

6. 🚀 Proyecto del día: construcción por versiones

(Desarrolla la tarea dia89-tarea8.md. Las versiones son una propuesta de esta adaptación.)

Problema

Construir un sitio web de gestión de tareas.

Contexto

Tu grupo tiene un trabajo final de varias semanas. Quieren un tablero para repartir y seguir las tareas, sin depender de una aplicación comercial.

Objetivo

Un gestor de tareas web con persistencia en base de datos, al menos de nivel 2 (y, si puedes, nivel 3).

Requisitos funcionales

  1. Crear, editar, completar (o mover) y eliminar tareas.
  2. Fecha límite opcional, con las tareas vencidas resaltadas.
  3. Las tareas se conservan al cerrar el navegador.
  4. Diseño adaptable al celular.

Restricciones técnicas

  • Flask + SQLAlchemy.
  • Validación en el servidor de todo lo que llega en formularios.
  • Patrón POST/Redirect/GET; ninguna modificación por GET.

Planificación

  1. Comprender (usa Trello o un gestor similar unos minutos). 2. Entradas: formularios. 3. Procesos: CRUD. 4. Salidas: el tablero. 5. Dividir por rutas. 6. Bocetar. 7. Implementar. 8. Probar. 9. Corregir. 10. Mejorar.

Construcción paso a paso

  • Versión 1 — Nivel 1: agregar y tachar (booleano), sin base de datos (una lista en memoria). ¿Qué pasa al reiniciar el servidor?
  • Versión 2 — Persistencia: SQLAlchemy.
  • Versión 3 — Nivel 2: editar, eliminar, fecha límite y resaltado de vencidas.
  • Versión 4 — Nivel 3: estados y columnas Kanban.
  • Versión 5 — Pulido: Bootstrap, contador de tareas por columna, orden por fecha.
  • Versión final: README.md, capturas, diario.

Pruebas

# Caso Resultado esperado
1 Crear "Estudiar para el quiz" Aparece en "pendiente"
2 Crear con título de espacios No se crea
3 Mover a "hecha" Aparece en "hecha"
4 Mover a "borrada" (manipulando el formulario) 400
5 Mover la tarea 99 (no existe) 404
6 Título <script>alert(1)</script> Se ve como texto
7 Recargar tras crear No se duplica
8 Tarea con fecha de ayer Se ve resaltada como vencida
Python
cliente = app.test_client()
cliente.post("/tareas", data={"titulo": "Estudiar para el quiz"})
assert cliente.post("/tareas/1/mover", data={"estado": "borrada"}).status_code == 400

🧪 Prueba y depuración

Python
@app.route("/completar/<id>")
def completar(id):
    tarea = db.session.get(Tarea, id)
    tarea.estado = request.args.get("estado")
    return render_template("tablero.html")

Encuentra cinco problemas: modifica por GET; id sin int: (y id oculta la función incorporada de Python); no comprueba que la tarea exista; acepta cualquier estado sin validar; no hace commit; y renderiza sin pasar las columnas en lugar de redirigir.

⭐ Extensión del proyecto

  • 🟢 Reto 1 — Aplicación: prioridad (alta, media, baja) con colores y orden por prioridad dentro de cada columna.
  • 🟡 Reto 2 — Ampliación: etiquetas (una tarea puede tener varias; tabla intermedia, relación muchos a muchos).
  • 🟠 Reto 3 — Desafío: usuarios con login; cada uno ve solo sus tareas (Días 68–69).
  • 🔴 Reto 4 — Pensamiento computacional: tableros compartidos con roles (dueño, editor, lector). Diseña las tablas y escribe una función puede(usuario, accion, tablero) con su tabla de casos de prueba antes de programar las rutas.

7. 🌟 Proyecto extra: tablero del proyecto de grado del curso

(Proyecto creado para esta adaptación; no existe en el curso original.)

Situación

El curso organiza la feria de fin de año: cada comisión (logística, publicidad, montaje, presentación) tiene tareas con responsables y fechas. El docente quiere ver el avance de todas las comisiones.

Requisitos

  1. Tareas con comisión, responsable (alias), fecha límite y estado.
  2. Vista por comisión y vista general con el porcentaje de avance de cada comisión.
  3. Resumen de tareas vencidas por comisión.

Decisiones que debes tomar (y justificar)

  • ¿Cómo calculas el avance: por número de tareas o ponderado por tamaño?
  • ¿Quién puede marcar una tarea como hecha: cualquiera o solo su responsable?

Casos de prueba

  • Una comisión sin tareas muestra 0 % (sin error de división por cero).
  • Una tarea vencida y ya hecha no cuenta como vencida.

8. 🏗️ Mi Portafolio Profesional — Etapa 8

(Proyecto integrador creado para esta adaptación; etapas anteriores en los Días 82 a 88.)

Etapa 8 (hoy):

  1. Repositorio del gestor de tareas con README.md que indique qué nivel (sección 2) alcanzaste y qué dejarías para después (una "hoja de ruta").
  2. Usa tu propio tablero para gestionar las tareas de este proyecto: captura del tablero con tus tareas reales del desarrollo.
  3. DIARIO.md del Día 89 y ficha en proyectos.json.
  4. Siguiendo el "secreto" del enunciado: prepara una demostración de 2 minutos del gestor, como si se la mostraras a un entrevistador.

9. 🧩 Comprueba lo aprendido

Conceptuales

  1. ¿Qué significa CRUD? Da un ejemplo de cada operación en tu gestor.
  2. ¿Por qué el servidor debe validar el estado si el formulario solo ofrece opciones correctas?
  3. ¿Qué problema resuelve el patrón POST/Redirect/GET?
  4. ¿Qué es un ataque XSS y cómo te protege Jinja?

Análisis de código

Python
{estado: [t for t in tareas if t.estado == estado] for estado in ESTADOS}

Si hay 3 tareas pendientes y 1 hecha, ¿qué diccionario resulta (en cantidades)?

Predicción

Con la versión 1 (lista en memoria), agregas 5 tareas y reinicias el servidor. ¿Qué ves?

Depuración

"Cuando recargo la página después de agregar una tarea, se agrega otra vez." ¿Qué falta?

Modificación

Agrega un cuarto estado, "bloqueada". ¿Cuántas líneas tuviste que cambiar? (Si fueron muchas, revisa si tu código depende de ESTADOS.)

Creación

Escribe esta_vencida(tarea, hoy) que devuelva True si la tarea tiene fecha límite anterior a hoy y no está hecha. Pruébala con 4 casos.


10. 🤖 Programar con apoyo de IA

Ciclo de trabajo: formula el problema → inténtalo tú primero → consulta a la IA sobre un punto concreto → lee y entiende → prueba → compara con la documentación oficial → modifica → explica con tus palabras.

Actividad 1 — Diseño de datos. Describe tu nivel elegido y pide a una IA que proponga el modelo de datos. Compara con la sección 3 y justifica qué adoptas.

Actividad 2 — Seguridad comprobable. Pregunta "¿Jinja escapa el HTML automáticamente?". Compruébalo tú con la prueba 6 y con la documentación de Jinja (autoescaping).

Actividad 3 — JavaScript con cuidado. Si haces arrastrar y soltar, pide a una IA el código de drag and drop, pero léelo con la documentación de MDN y explica cada evento (dragstart, dragover, drop) en tu diario.

Declara el uso: sección "Uso de IA" en el README.md.


11. 🛠️ Observaciones sobre el material original

Archivo Observación Tratamiento
dia89-tarea8.md El alcance es totalmente abierto Sección 2: cuatro niveles para decidir
dia89-tarea8.md El sitio de inspiración (flask.io/new) responde, pero su contenido puede haber cambiado desde 2020 Se sugiere también probar un gestor conocido (Trello) para comprender el problema
dia89-tarea8.md "Secreto" sobre entrevistas con gerentes Se convierte en la demostración de 2 minutos de la etapa 8
dia89-tarea8.md No menciona seguridad Secciones 3 a 5: validación en el servidor, POST, escape de HTML

12. 📋 Matriz de trazabilidad

Contenido original Archivo de origen Concepto desarrollado Ejercicio original Reto adicional Proyecto relacionado
Sitio de lista de tareas, "rito de iniciación" dia89-tarea8.md Secciones 3 a 5 Todo list Retos 1 y 2 Secciones 6 y 8
Desde tachar elementos hasta Kanban tipo Trello dia89-tarea8.md Sección 2 (niveles) y 4 (Kanban) Todo list Retos 3 y 4 Sección 6
Los gerentes usan Trello o Jira: impresiona en entrevistas dia89-tarea8.md Sección 8 — — Demostración de la etapa 8
Momento de reflexión dia89-tarea8.md Sección 6 (versión final) Pregunta de reflexión — Sección 8
(No existe en el curso original) — Estados validados, PRG, XSS — Tablero de la feria Sección 7

13. 📦 Evidencias de aprendizaje del Día 89

  • El gestor funcionando: capturas del tablero y de la edición de una tarea.
  • Las pruebas del cliente de Flask superadas.
  • La hoja de ruta en el README.md y el tablero con las tareas reales del proyecto.
  • Guion o video de la demostración de 2 minutos.
  • DIARIO.md del Día 89 y ficha en el portafolio.
  • Cuaderno de errores: duplicados al recargar, fechas como texto, fecha vacía, estado sin validar, modificación por GET, | safe con datos de usuarios.