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; unidinexistente, 404; y un título con<script>se muestra escapado (<script>), sin ejecutarse.
Tabla de contenidos
- La tarea: una lista de tareas en la web
- Analizar el problema: elige tu nivel de ambición
- 📖 Modelar una tarea: de
hecha: boola estados - 📖 Un esqueleto Kanban con Flask (para completar tú)
- 📖 CRUD completo y seguro
- 🚀 Proyecto del día: construcción por versiones
- 🌟 Proyecto extra: tablero del proyecto de grado del curso
- 🏗️ Mi Portafolio Profesional — Etapa 8
- 🧩 Comprueba lo aprendido
- 🤖 Programar con apoyo de IA
- 🛠️ Observaciones sobre el material original
- 📋 Matriz de trazabilidad
- 📦 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:
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.)
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;| Nonela hace opcional.columnas = {estado: [...] for estado in ESTADOS}: una comprensión de diccionario (Día 26) que agrupa las tareas por estado. La plantilla recorrecolumnas.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<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| safecon 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
fetchpara 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
- Crear, editar, completar (o mover) y eliminar tareas.
- Fecha límite opcional, con las tareas vencidas resaltadas.
- Las tareas se conservan al cerrar el navegador.
- 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
- 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 |
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
@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
- Tareas con comisión, responsable (alias), fecha límite y estado.
- Vista por comisión y vista general con el porcentaje de avance de cada comisión.
- 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):
- Repositorio del gestor de tareas con
README.mdque indique qué nivel (sección 2) alcanzaste y qué dejarías para después (una "hoja de ruta"). - Usa tu propio tablero para gestionar las tareas de este proyecto: captura del tablero con tus tareas reales del desarrollo.
DIARIO.mddel Día 89 y ficha enproyectos.json.- 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
- ¿Qué significa CRUD? Da un ejemplo de cada operación en tu gestor.
- ¿Por qué el servidor debe validar el estado si el formulario solo ofrece opciones correctas?
- ¿Qué problema resuelve el patrón POST/Redirect/GET?
- ¿Qué es un ataque XSS y cómo te protege Jinja?
Análisis de código
{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.mdy el tablero con las tareas reales del proyecto. - Guion o video de la demostración de 2 minutos.
DIARIO.mddel 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,
| safecon datos de usuarios.