Listo es el proyecto base de la asignatura. Es un gestor de tareas pequeño, hecho con Flask y MariaDB, pensado para ser vistoso y muy fácil de entender. Sobre él se hacen las prácticas (instalación del sistema base, gestión del código fuente, integración continua, contenedores y aislamiento, JavaScript y Flask) y sirve de punto de partida para los proyectos.
Los usuarios se registran, inician sesión y gestionan su propia lista de tareas. Es poca funcionalidad a propósito. Lo importante es cómo está organizado el código.
listo/
├── app/
│ ├── __init__.py ← create_app(), la fábrica de la aplicación; registra los blueprints
│ ├── config.py ← configuración (lee el fichero .env)
│ ├── extensions.py ← db (SQLAlchemy), login_manager (Flask-Login), csrf (Flask-WTF)
│ ├── commands.py ← comandos flask init-db, seed-db y reset-db
│ ├── templates/ ← base.html, macros y páginas de error (comunes a todas las features)
│ ├── static/ ← CSS y JavaScript
│ └── features/ ← UNA CARPETA POR FUNCIONALIDAD
│ ├── home/ ← página de inicio
│ ├── auth/ ← registro, login y logout
│ └── tasks/ ← gestor de tareas
│ ├── __init__.py ← define el blueprint (agrupa las rutas de la feature)
│ ├── models.py ← tabla tasks (SQLAlchemy)
│ ├── forms.py ← formulario (WTForms)
│ ├── services.py ← lógica de negocio; las rutas llaman aquí y aquí se usa la base de datos
│ ├── routes.py ← vistas HTML
│ ├── api.py ← endpoints JSON (los usa el JavaScript)
│ ├── seeders.py ← datos de ejemplo
│ ├── templates/ ← plantillas Jinja de la feature
│ └── tests/ ← tests de la feature
├── conftest.py ← fixtures compartidas por los tests (app, client, user...)
├── requirements.txt ← dependencias de Python
├── pyproject.toml ← configuración de ruff (linter) y pytest
├── .env.example ← plantilla de configuración (copiar a .env)
├── .flaskenv ← variables del comando flask (FLASK_APP y FLASK_DEBUG)
├── Dockerfile ← imagen de la aplicación
├── docker-compose.yml ← aplicación + MariaDB con un solo comando
└── .github/workflows/ci.yml ← integración continua (GitHub Actions)
navegador ──► routes.py (vista) ──► services.py (lógica) ──► models.py (base de datos)
│
└──► templates/ (HTML) o jsonify() (API)
- El
__init__.pyde cada feature crea el blueprint e importaroutes.pyal final del fichero. app/__init__.pyimporta los blueprints de todas las features y los registra encreate_app().- Las rutas son cortas. Validan el formulario, llaman a un servicio y devuelven una plantilla o una redirección.
- Los servicios contienen la lógica y son los únicos que tocan la base de datos. Por eso se pueden reutilizar desde los seeders, desde los tests y desde otras features.
Antes de empezar necesitas tener hecha la Práctica 0 (Ubuntu 24.04 LTS, Python 3.13 con entornos virtuales, git, Visual Studio Code y tu clave SSH en GitHub). Todos los comandos son para la terminal de Ubuntu.
MariaDB es la base de datos donde Listo guarda los usuarios y las tareas. Solo hay que instalarlo una vez.
sudo apt update
sudo apt install -y mariadb-server
sudo systemctl status mariadb # debe poner active (running); sal con q-
Haz un fork y clónalo. En GitHub, haz un fork de este repositorio y llámalo
listo_practicas. Después clónalo con la dirección SSH de tu fork (botón Code, pestaña SSH) y entra en la carpeta.git clone <dirección SSH de tu fork> cd listo_practicas
-
Entorno virtual y dependencias. El entorno virtual aísla las librerías de este proyecto de las del resto del sistema. Cada vez que abras una terminal nueva, vuelve a activarlo.
python3.13 -m venv .venv # crea el entorno (solo la primera vez) source .venv/bin/activate # lo activa; verás (.venv) delante pip install -r requirements.txt # instala las dependencias
-
Base de datos y usuario. Solo hay que hacerlo una vez. Entra en la consola de MariaDB con
sudo mariadb, ejecuta estas órdenes y sal conEXIT;.CREATE DATABASE listo_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'listo_user'@'localhost' IDENTIFIED BY 'listo_password'; GRANT ALL PRIVILEGES ON listo_db.* TO 'listo_user'@'localhost'; FLUSH PRIVILEGES;
-
Configuración. Copia la plantilla con
cp .env.example .envy comprueba que los valoresMARIADB_*del fichero.envcoinciden con lo que has creado en el paso anterior. El.envno se sube al repositorio porque contiene contraseñas (está en.gitignore). -
Tablas y datos de ejemplo.
flask init-db # crea las tablas flask seed-db # añade los datos de ejemplo
-
Arrancar. Ejecuta
flask runy abre en el navegador la dirección que aparece en la terminal (localhost, puerto 5000). Puedes entrar con el usuariodemo@listo.appy la contraseñademo1234(solo existe en tu máquina; en producción no se cargan los datos de ejemplo). Para pararlo, pulsa Ctrl+C en la terminal. Si el puerto 5000 está ocupado, arranca conflask run --port 5001.
- No encuentra
flask. Activa el entorno virtual (paso 2). Permission denied (publickey)al clonar. Revisa tu clave SSH en GitHub (Práctica 0).- Error de conexión a MariaDB. Comprueba que está en marcha y que el
.envcoincide con el paso 3.
Con Docker no hace falta instalar Python ni MariaDB. Solo necesitas Docker y Docker Compose.
cp .env.example .env
docker compose up --buildSe construye la imagen de la aplicación, se arranca MariaDB en otro contenedor, se crean las tablas y se cargan los datos de ejemplo. La aplicación queda en localhost, puerto 5000. El código está montado dentro del contenedor, así que los cambios se ven al recargar la página.
Para pararlo, docker compose down. Si además quieres borrar la base de datos, docker compose down -v.
Si ya tienes algo escuchando en el puerto 3306 o en el 5000, cambia MARIADB_PORT o WEB_PORT en .env.
WEB_PORT solo lo usa Docker, pero MARIADB_PORT también lo usan flask run y los demás comandos flask
para conectarse a MariaDB, así que afecta también a la puesta en marcha sin Docker.
Los ficheros que crea el contenedor dentro del proyecto (por ejemplo .pytest_cache al ejecutar
docker compose exec web pytest) pertenecen a root. Si después pytest o ruff fallan en tu máquina con
un error de permisos, borra esas carpetas con sudo rm -rf .pytest_cache .ruff_cache.
| Comando | Qué hace |
|---|---|
flask run |
Arranca el servidor de desarrollo |
flask init-db |
Crea las tablas (si no existen) |
flask seed-db |
Inserta los datos de ejemplo (si no están ya) |
flask reset-db |
Borra todo, crea las tablas e inserta los datos de ejemplo |
flask routes |
Lista todas las rutas de la aplicación |
flask shell |
Consola Python con la aplicación cargada |
pytest |
Ejecuta los tests |
ruff check . |
Pasa el linter (ruff check --fix . corrige lo que puede) |
ruff format . |
Formatea el código |
Con Docker, antepón docker compose exec web (por ejemplo, docker compose exec web pytest).
Los tests viven dentro de cada feature (app/features/<feature>/tests/) y usan una base de datos
SQLite en memoria, así que no necesitan MariaDB ni Docker. Las fixtures comunes (app, client,
user y logged_client) están en conftest.py. En cada feature, test_services.py prueba la lógica
sin pasar por HTTP y test_routes.py prueba las vistas con el cliente de pruebas de Flask.
pytest # todos
pytest -v # con el nombre de cada test
pytest app/features/tasks # solo una featureEn cada push y pull request, GitHub Actions (.github/workflows/ci.yml)
instala las dependencias, pasa el linter (ruff check .), comprueba el formato (ruff format --check .)
y ejecuta los tests (pytest). Antes de subir cambios, ejecuta esos tres comandos en tu máquina.
En un fork, GitHub Actions viene desactivado. Actívalo una vez en la pestaña Actions de tu fork (botón
I understand my workflows, go ahead and enable them). Hasta entonces, los push no lanzan la CI.
Supongamos que quieres añadir notes.
-
Crea
app/features/notes/copiando la estructura detasks(__init__.py,models.py,forms.py,services.py,routes.py,templates/notes/ytests/). La carpetatests/necesita un__init__.pyvacío, como en las demás features. Sin él, dos features con untest_routes.pychocan al ejecutarpytest. -
En
app/features/notes/__init__.pydefine el blueprint e importa las rutas al final del fichero, igual que hacetasks. El comentarionoqaevita que el linter se queje de que ese import no está al principio del fichero.from flask import Blueprint notes_bp = Blueprint("notes", __name__, url_prefix="/notes", template_folder="templates") from app.features.notes import routes # noqa
-
En
app/__init__.pyimpórtalo y regístralo enregister_blueprints(). -
Si tiene modelos nuevos, ejecuta
flask init-dbpara crear las tablas. Ojo, solo se crean las tablas de los modelos que la aplicación importa al arrancar; en cada feature eso ocurre porqueroutes.pyimportaservices.pyy este importamodels.py, así que escribe esos ficheros antes de ejecutar el comando. Si cambias una tabla que ya existe,flask reset-dbla vuelve a crear desde cero. -
Si quieres datos de ejemplo, añade
seeders.pyy llámalo desdeseed_all()enapp/commands.py. -
Escribe sus tests en
app/features/notes/tests/.
- Flask, con blueprints y application factory
- Flask-SQLAlchemy y PyMySQL para MariaDB
- Flask-Login (sesiones) y Flask-WTF (formularios y CSRF)
- Bootstrap 5 e iconos de Bootstrap, cargados desde CDN
- pytest y ruff
- Docker y GitHub Actions