Lab guiado: Flask como nueva capa de presentación¶
- Unidad: UT4 — Interfaz web con Flask
- Sesión: 1 — Flask como nueva capa de presentación
- Agrupamiento: Individual o en parejas conductor/navegante
- Recursos:
- Ficheros que crearás o modificarás:
- Código:
presentation/app.py,requirements.txt,application/servicios.py,domain/maquina.py - Documentación:
README.md,CHANGELOG.md,docs/ARQUITECTURA_POR_CAPAS.md,docs/EJECUCION.md
- Código:
Antes de empezar¶
Descarga expendedora.zip del aula virtual y descomprímelo. Es el resultado de las fases anteriores del proyecto: arquitectura por capas completa, base de datos SQLite funcionando y menú de consola operativo.
Sobre ese código añadirás Flask como nueva capa de presentación. No vas a reescribir nada — vas a añadir una nueva forma de interactuar con el proyecto.
Entrega
Al terminar el lab entregarás un zip con todo el proyecto resultante (expendedora + los ficheros nuevos y modificados). Ese zip será la base de la siguiente sesión, así que asegúrate de que todo funciona antes de entregarlo.
Cómo trabajar¶
Puedes hacer este lab de forma individual o en parejas. Si eliges pareja, sigue el procedimiento conductor/navegante descrito a continuación. Si trabajas en solitario, salta a la sección "Antes de empezar: repasemos los conceptos".
Trabajo en parejas (opcional)¶
| Rol | Qué hace |
|---|---|
| Conductor | Escribe el código y ejecuta los pasos. Solo el conductor toca el teclado. |
| Navegante | Lee el enunciado en voz alta, propone soluciones, detecta errores y formula las preguntas. |
- Cambiad de rol al final de cada paso.
- Al rotar, el navegante ocupa el teclado y el conductor se convierte en navegante.
- Ambos debéis poder explicar cualquier parte del trabajo al terminar el lab.
Antes de empezar: repasemos los conceptos
Arquitectura por capas
La expendedora está dividida en presentación, aplicación, dominio e infraestructura. Cada capa tiene una responsabilidad clara y no debe conocer los detalles internos de las demás.
Capa de presentación
Es la que interactúa con el usuario: recibe su input y le muestra resultados. Hasta ahora era menu.py con print() e input(). Flask será una nueva forma de escribir esta misma capa.
HTTP y URLs
Cuando escribes una URL en el navegador, envías una petición HTTP a un servidor. Flask es el código Python que recibe esa petición y decide qué responder.
Route
En Flask, una route asocia una URL con una función Python. Cuando el navegador pide /productos, Flask ejecuta la función que hayas asociado a esa ruta.
Entorno virtual
Carpeta aislada donde se instalan las dependencias del proyecto. Evita conflictos entre paquetes de distintos proyectos.
Paso 1 — Preparar el entorno requirements.txt¶
Conceptos
Flask se instala como cualquier paquete Python con pip. Usaremos un entorno virtual para no contaminar el sistema. El fichero requirements.txt guarda las dependencias del proyecto.
Objetivo¶
Crear el entorno virtual e instalar Flask en el proyecto de la expendedora.
Tarea¶
Descomprime expendedora.zip y entra en la carpeta expendedora/ (la que contiene presentation/, application/, domain/...).
Empezamos declarando flask como dependencia: abre requirements.txt y añade una línea nueva con flask. El fichero ya contenía coverage de las fases anteriores; ahora declara también Flask. Debe quedar así:
Luego crea el entorno virtual e instala las dependencias. Elige la línea de activación según tu sistema operativo (deja la otra comentada con #):
¿Tenías otro venv activo?
Si al abrir el terminal ves el prompt de otro entorno virtual (por ejemplo (otro_proyecto)), desactívalo con el comando deactivate antes de crear el nuevo.
Ubicación del entorno virtual
El .venv/ y el requirements.txt viven dentro de expendedora/, como ya se hacía en las fases anteriores del proyecto (ver docs/EJECUCION.md).
Pregunta¶
¿Por qué guardamos las dependencias en
requirements.txty no simplemente las instalamos?
Respuesta
Para que cualquier persona que clone el proyecto pueda instalar exactamente las mismas versiones con pip install -r requirements.txt, sin tener que adivinar qué librerías hacen falta.
Comprobación¶
Debe mostrar la versión de Flask instalada.Reflexión¶
Respuestas
- El entorno virtual aísla las dependencias del proyecto del resto del sistema.
requirements.txthace el proyecto reproducible en cualquier máquina.- Flask se instala como cualquier otro paquete Python.
Paso 2 — Primera app Flask expendedora/presentation/app.py¶
Conceptos
Una app Flask se crea instanciando la clase Flask. Los routes se definen con el decorador @app.route(url). La función decorada es la que se ejecuta cuando el navegador pide esa URL.
Objetivo¶
Crear app.py con el primer route, y arrancarlo como módulo igual que se hace con el menú.
Tarea¶
Crea el fichero expendedora/presentation/app.py con este contenido:
¿Por qué en presentation/?
Flask es una nueva capa de presentación que convivirá con menu.py. Si menu.py vive en presentation/ porque es la presentación de consola, app.py vive en el mismo sitio porque es la presentación web. La estructura de carpetas refleja la arquitectura.
Sitúate en la carpeta padre de expendedora/ (la misma desde la que se lanza el menú). Si estás dentro de expendedora/, sube un nivel con cd ... Luego ejecuta:
Luego abre http://localhost:5000 en el navegador.
El servidor no termina
El comando no devuelve el prompt — el servidor queda escuchando peticiones. Esto es normal. Para detenerlo pulsa Ctrl+C. Para seguir trabajando mientras el servidor corre, abre otra terminal (y actívale el entorno virtual si vas a ejecutar pip o python).
Pista
Ejecutamos con python -m expendedora.presentation.app por la misma razón que el menú usa python -m expendedora.presentation.menu: para que Python trate expendedora/ como un paquete y los imports internos (from expendedora.infrastructure...) funcionen correctamente cuando los añadamos en el paso siguiente. Fíjate en la simetría: un módulo por capa de presentación. debug=True activa el modo de depuración: el servidor se reinicia automáticamente cuando cambias el código y muestra errores detallados en el navegador. Si ves errores raros al guardar un archivo, detén el servidor con Ctrl+C y reinícialo.
Comprobación¶
El navegador debe mostrar: Expendedora — Interfaz web
La terminal debe mostrar Running on http://127.0.0.1:5000
Reflexión¶
Respuestas
- Flask es una clase Python. Crear la app es instanciarla.
@app.route('/')es un decorador que registra la función como manejadora de esa URL.debug=Truees solo para desarrollo, nunca para producción.
Paso 3 — Conectar Flask con la expendedora app.py¶
Conceptos
Flask es la nueva capa de presentación. Igual que menu.py, necesita un ServicioExpendedora para acceder a los datos. En lugar de construir la cadena repo → maquina → servicio a mano, reutilizamos crear_servicio_sqlite(), la función de inicio que ya usa menu.py.
¿Por qué se llama bootstrap?
En informática, bootstrap (arranque) es el proceso que monta todas las piezas de una aplicación antes de que empiece a funcionar — igual que un ordenador que se "autoinicia" al encenderse. crear_servicio_sqlite() es la función de inicio (bootstrap) del proyecto: abre la base de datos, crea el repositorio, lo pasa a la máquina, y esta a su vez al servicio. Con una sola llamada obtienes el servicio listo para usar.
Objetivo¶
Conectar app.py con la capa de aplicación usando la misma función de inicio que menu.py.
Tarea¶
Pista
crear_servicio_sqlite() devuelve un ServicioExpendedora completamente listo: abre la base de datos, instancia la máquina y monta el servicio. No modifiques nada en application/, domain/ ni infrastructure/ en este paso.
Pregunta¶
Abre
presentation/menu.pyy mira la funciónmain(). ¿Qué línea crea el servicio? Compárala con la deapp.py. ¿Qué conclusión sacas sobre qué sabe y qué no sabe la capa de presentación?
Respuesta
Ambos archivos llaman a crear_servicio_sqlite() y reciben un ServicioExpendedora. Ninguno importa sqlite3 directamente ni clases de dominio como Item. La presentación solo conoce la capa de aplicación (el servicio) — el dominio y la infraestructura quedan detrás del servicio.
Comprobación¶
El servidor debe seguir arrancando sin errores tras añadir el import y la línea del servicio.
Si aparece ModuleNotFoundError: No module named 'expendedora', revisa que estás ejecutando python -m expendedora.presentation.app desde la carpeta padre (la que contiene la carpeta expendedora/), no desde dentro del paquete (en la carpeta expendedora/).
Reflexión¶
Respuestas
app.pyes la nuevamenu.py: mismo punto de entrada a la capa de aplicación.- El dominio y la infraestructura no necesitan saber que ahora existe Flask.
- La arquitectura por capas permite añadir una nueva presentación sin tocar nada más.
Paso 4 — Route /productos con datos reales app.py¶
Conceptos
Un route puede llamar a métodos del servicio y devolver los resultados al navegador. Es lo mismo que hacía los if/elif/else en menu.py. Por ahora devolveremos texto plano. En la siguiente sesión usaremos plantillas para generar HTML de respuesta.
Objetivo¶
Crear un route /productos que devuelva la lista de productos de la base de datos.
Tarea¶
Añade el siguiente route debajo del route /:
Abre http://localhost:5000/productos en el navegador.
Pista
servicio.listar_productos() devuelve una lista de tuplas con el formato (codigo, nombre, precio_base, precio_final, cantidad, porcentaje_descuento). Lo mismo que ya usaba opcion_mostrar() en menu.py. El zip ya incluye una expendedora.db con datos de prueba. Si por algún motivo la BD está vacía, ejecuta python crear_bd.py desde la carpeta expendedora/ para recrearla.
Dos detalles del código
- Guion bajo delante del nombre (
_precio_base,_descuento): convención de Python para indicar que esa variable no se usa en el cuerpo del bucle. Si más adelante la necesitas, le quitas el guion bajo. '<br>'.join(lineas): el navegador ignora los saltos de línea\nde Python al renderizar HTML. Para separar líneas visualmente se usa<br>, que es el salto de línea HTML.
Pregunta¶
En
menu.py, la funciónopcion_mostrar()usaprint()para mostrar cada producto. En el route, ¿qué sustituye aprint()?
Respuesta
El return. En Flask, lo que la función devuelve es lo que el navegador recibe. print() escribe en la consola; return escribe el cuerpo de la respuesta HTTP que es enviada usando el protocolo del mismo nombre al navegador.
Comprobación¶
El navegador en /productos debe mostrar los productos de tu base de datos, uno por línea.
Reflexión¶
Respuestas
returnen un route Flask equivale aprint()en el menú de consola.- El servicio se usa exactamente igual que en
menu.py— es la misma llamada, mismo resultado. - La base de datos no cambió, solo la forma de mostrar los datos.
Paso 5 — Preparar el servicio para consultar un producto servicios.py + maquina.py¶
Conceptos
Para consultar un producto concreto en Flask necesitamos un método que devuelva su información. seleccionar() no sirve — solo guarda la selección internamente sin devolver nada. Vamos a añadir obtener_producto(codigo) a la capa de aplicación y a la capa de dominio.
Objetivo¶
Añadir el método obtener_producto(codigo) al servicio y a la máquina.
Tarea¶
En expendedora/domain/maquina.py, añade este método inmediatamente después de seleccionar():
En expendedora/application/servicios.py, añade el método equivalente inmediatamente después de seleccionar():
Pista
self._repo.obtener(codigo) ya lanza ProductoNoEncontradoError si el código no existe. No hace falta ningún if — la excepción llegará hasta el route.
Pregunta¶
Acabas de tocar
domain/yapplication/. ¿Contradice esto lo que vimos en la presentación sobre qué capas "no deberían cambiar" al añadir Flask?
Respuesta
No. Lo que no cambia por culpa de Flask es todo el código existente. Aquí estás añadiendo un caso de uso nuevo (consultar un producto por código) que también podría existir desde el menú. El cambio lo pide el dominio, no la interfaz web.
Comprobación¶
Guarda los dos ficheros (maquina.py y servicios.py) antes de probar.
Necesitas una terminal libre
Si dejaste el servidor Flask corriendo del Paso 2, su terminal está ocupada. Abre otra terminal, sitúate en la carpeta padre de expendedora/ y activa el entorno virtual (source .venv/bin/activate o equivalente). Sabrás que está activo cuando veas (.venv) al inicio del prompt. Si prefieres usar la misma terminal, detén el servidor con Ctrl+C antes.
Desde esa terminal abre el intérprete:
Y ejecuta línea por línea:
from expendedora.infrastructure.datos_iniciales import crear_servicio_sqlite
servicio = crear_servicio_sqlite()
print(servicio.obtener_producto('A1')) # tupla con los datos
print(servicio.obtener_producto('ZZZ')) # lanza ProductoNoEncontradoError
Reflexión¶
Respuestas
- Añadir un caso de uso nuevo puede requerir tocar dominio y aplicación.
- La excepción sube desde infraestructura hasta el route sin que ninguna capa intermedia la capture.
- El repositorio ya hacía el trabajo; solo hay que dejar pasar el resultado.
Paso 6 — Route con parámetro app.py¶
Conceptos
Flask permite capturar partes de la URL como parámetros. @app.route('/producto/<codigo>') captura el segmento de la URL y lo pasa como argumento a la función. Las excepciones de dominio se capturan en el route, igual que menu.py las capturaba en su bucle main().
Objetivo¶
Crear un route /producto/<codigo> que devuelva los datos de un producto concreto y gestione el caso de producto no encontrado.
Tarea¶
Añade el import de la excepción al inicio del archivo, junto a los otros imports (no dentro del route):
Dónde va cada cosa
El import se añade arriba, al lado de los demás. El @app.route y la función ver_producto van después del route /productos.
Añade el route debajo del anterior:
Pista
El segundo valor del return (404) es el código HTTP de respuesta. Cuando el producto no existe, el navegador recibe un error 404 para indicar dicho error, igual que en cualquier web cuando una URL no existe.
Pregunta¶
¿Qué URL escribirías para ver el producto con código
A1? ¿Y para uno que no existe?
Antes de probarlo, predice qué verás en cada caso.
Respuesta
http://localhost:5000/producto/A1→ datos del producto A1.http://localhost:5000/producto/ZZZ→ "Error: No existe ningún producto con código 'ZZZ'" con código HTTP 404.
Comprobación¶
Prueba con un código que existe y con uno que no. Verifica que el manejo de la excepción funciona sin que el servidor se caiga.
Reflexión¶
Respuestas
<codigo>en la URL captura ese segmento y lo pasa a la función como argumento.- Las excepciones de dominio se capturan en el route, igual que en
menu.py. - El route no sabe nada de SQLite — solo ve
ProductoNoEncontradoError.
Paso 7 — Reflexión arquitectónica (sin teclado)¶
Conceptos
Antes de entregar, es momento de comprobar si la arquitectura por capas ha cumplido su promesa.
Objetivo¶
Escribir por escrito las conclusiones del lab.
Tarea¶
Responde por escrito en un comentario al inicio de app.py a las siguientes tres preguntas. Si trabajas en pareja, responded juntos.
Orientación de respuesta
- Has creado
presentation/app.pyy actualizadorequirements.txt, y has añadido el métodoobtener_productoenapplication/servicios.pyydomain/maquina.py. No has tocadoinfrastructure/,item.py,repositorio_productos.py,repositorio_sqlite.py,repositorio_memoria.pynierrores.py. Fíjate en que la nueva capa de presentación (app.py) convive con la anterior (menu.py) en la misma carpeta — exactamente lo que prometía la arquitectura por capas. - Navegador → petición HTTP → Flask (
@app.route('/productos')) →listar_productos()del servicio (aplicación) →mostrar_productos()de la máquina (dominio) →listar()del repositorio (infraestructura) → SQLite → y vuelta. - Porque el route es la nueva capa de presentación. La capa de aplicación no debe saber nada sobre cómo se le informa al usuario (si es un
print, un HTML o un JSON). Esa decisión vive en la capa de presentación, igual que enmenu.py.
Paso 8 — Actualizar la documentación del proyecto¶
Conceptos
Un proyecto bien mantenido tiene su documentación sincronizada con el código. En este paso actualizarás los ficheros afectados por los cambios de este lab.
Objetivo¶
Dejar README.md, CHANGELOG.md y docs/ en sintonía con la nueva capa de presentación web.
Tarea¶
CHANGELOG.md¶
Añade una entrada nueva al inicio del fichero:
## [0.5.0] - (Fase 05: interfaz web con Flask)
### Added
- `presentation/app.py`: interfaz web con Flask. Routes `/`, `/productos` y `/producto/<codigo>`.
### Changed
- `domain/maquina.py`: añadido `obtener_producto(codigo)`.
- `application/servicios.py`: añadido `obtener_producto(codigo)`.
- `requirements.txt`: añadida dependencia `flask`.
README.md¶
- En Requisitos, menciona que
requirements.txtahora incluye también Flask (quien clone el proyecto lo instalará junto al resto conpip install -r requirements.txt). - En Quickstart y Uso, añade la sección de la interfaz web:
## Uso (interfaz web)
Ejecuta desde la carpeta padre que contiene el paquete `expendedora/`:
```bash
python -m expendedora.presentation.app
```
Abre `http://localhost:5000` en el navegador. Rutas disponibles:
- `/` — mensaje de bienvenida.
- `/productos` — lista todos los productos.
- `/producto/<codigo>` — detalle de un producto (404 si no existe).
- En Estructura del proyecto, añade
app.pyenpresentation/:
docs/ARQUITECTURA_POR_CAPAS.md¶
-
En Capas y responsabilidades, actualiza la línea de Presentation:
Presentation: entrada/salida por consola (menu.py) o web (app.py). No contiene reglas de negocio. -
En Mapa de archivos, añade:
presentation/app.py: interfaz web con Flask.
docs/EJECUCION.md¶
Añade una sección nueva tras "Ejecutar menu":
## Ejecutar interfaz web
```bash
python -m expendedora.presentation.app
```
Abre `http://localhost:5000` en el navegador.
Comprobación¶
Abre README.md y comprueba que alguien que no conozca el proyecto puede saber, solo leyéndolo, que ahora tiene dos interfaces: consola y web.
Problemas comunes¶
ModuleNotFoundError: No module named 'expendedora'
Estás ejecutando python -m expendedora.presentation.app desde dentro de la carpeta expendedora/. Debes lanzarlo desde la carpeta padre (la que contiene expendedora/). Usa cd .. para subir un nivel.
ModuleNotFoundError: No module named 'flask'
Has olvidado activar el entorno virtual. Actívalo con source .venv/bin/activate (Linux/Mac) o source .venv/Scripts/activate (Windows Git Bash). Sabrás que está activo cuando veas (.venv) al inicio del prompt.
Address already in use o Port 5000 is in use
Ya hay otro servidor Flask corriendo en el puerto 5000. Busca la terminal donde lo lanzaste y detenlo con Ctrl+C. Si no la encuentras, cierra todos los procesos python o reinicia el equipo.
Checklist de entrega¶
-
requirements.txtactualizado dentro deexpendedora/conflaskdeclarado. -
app.pycreado enexpendedora/presentation/(al lado demenu.py), conservicio = crear_servicio_sqlite()arriba del archivo y usado en los routes. Ejecutable conpython -m expendedora.presentation.app. - Método
obtener_producto(codigo)añadido aapplication/servicios.pyy adomain/maquina.py. - Route
/con mensaje de bienvenida. - Route
/productosque lista los productos usandoservicio.listar_productos(). - Route
/producto/<codigo>con manejo deProductoNoEncontradoErrory respuesta 404. - Las capas
infrastructure/eitem.pyno se han tocado. - El menú de consola (
menu.py) sigue funcionando sin cambios. - Reflexión arquitectónica escrita al inicio de
app.py. -
CHANGELOG.md,README.md,docs/ARQUITECTURA_POR_CAPAS.mdydocs/EJECUCION.mdactualizados. - Entregado un zip con todo el proyecto — será la base del siguiente lab.