Lab guiado: Integración — mensajes flash y API REST mínima¶
- Unidad: UT4 — Interfaz web con Flask
- Sesión: Fase 6 — Integración funcional de la app web: mensajes flash tras redirect y API REST mínima en JSON.
- Agrupamiento: Individual o en parejas conductor/navegante.
- Recursos:
- Ficheros de trabajo:
expendedora/presentation/app.pyexpendedora/presentation/templates/CHANGELOG.md,README.md
Punto de partida¶
Parte del zip que entregaste al final del lab a5. Antes de empezar:
- Activa el entorno virtual del proyecto.
- Sitúate en la carpeta padre de
expendedora/. - Si la BD
expendedora.dbno existe (porque partes del zip y no traes el fichero), créala conpython expendedora/crear_bd.pydesde la raíz del proyecto. Si ya la tenías de a5, sáltate este paso. - Arranca el servidor:
python -m expendedora.presentation.app. - Comprueba que los formularios de a5 siguen funcionando: insertar saldo, seleccionar un producto que exista en tu BD, confirmar la compra (verás
mensaje.htmlcon el cambio — ahí es donde quedó a medio el patrón PRG), agregar un producto nuevo y eliminar un producto existente.
Sobre este lab
Es la fase de integración funcional de la app web. Tiene dos bloques: mensajes flash para dar feedback al usuario tras un redirect (cierra el patrón PRG que dejaste a medio en a5) y una API REST mínima en JSON que comparte el servicio con la interfaz web (demuestra que la capa de presentación es realmente intercambiable). El cierre arquitectónico de la unidad —recorrer el proyecto y comprobar qué se ha tocado y qué no— vendrá en la Fase 8 (auditoría).
Antes de empezar: repasemos los conceptos
Mensajes flash
Flask permite "guardar un mensaje" en la sesión del usuario para mostrarlo en la siguiente petición. Útil tras un POST con redirect: el mensaje sobrevive al redirect y aparece una vez en la página de destino. Resuelve el problema de "redirigí a /saldo y ya no muestro el cambio que devolvió /comprar".
API REST
En vez de devolver HTML, una ruta puede devolver datos en formato JSON. Útil para que otros programas (apps móviles, scripts) consuman la misma lógica de negocio sin parsear HTML. Flask convierte automáticamente diccionarios y listas a JSON con jsonify.
Paso 1 — Mensajes flash para feedback tras redirect app.py, base.html¶
Conceptos
Un mensaje flash necesita una clave secreta en la app (app.secret_key) porque Flask los guarda firmados en una cookie. Es la primera vez que la app necesita "estado entre peticiones" más allá de la base de datos.
Objetivo¶
Mostrar mensajes informativos tras acciones que terminan en redirect.
Tarea¶
En app.py, añade una clave secreta tras crear app:
La clave secreta es secreta
En producción esta clave no se escribe literalmente en el código: viene de una variable de entorno o de un fichero .env que va al .gitignore. Para este lab vale el string literal — el alumno lo aprenderá en el módulo de despliegue.
Importa flash junto a las demás importaciones de Flask:
Modifica el route /comprar para que, tras éxito, redirija a / con un mensaje flash en lugar de renderizar mensaje.html:
Haz lo mismo con /cancelar:
Y el route /eliminar/<codigo> tras éxito. Solo se cambia la línea resaltada dentro del bloque if request.method == 'POST':: la rama GET del route (la que renderiza la página de confirmación con eliminar.html) se queda intacta.
Modifica presentation/templates/base.html para mostrar los mensajes flash en todas las páginas. Añade el bloque dentro de <main>:
<main>
{% with mensajes = get_flashed_messages(with_categories=true) %}
{% if mensajes %}
<ul class="flashes">
{% for categoria, msg in mensajes %}
<li class="flash flash-{{ categoria }}">{{ msg }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block contenido %}{% endblock %}
</main>
{% with %} en Jinja2
{% with nombre = expresión %}…{% endwith %} crea una variable local que solo existe dentro del bloque. Aquí guardamos en mensajes el resultado de get_flashed_messages(with_categories=true) para no llamar a la función dos veces (una en el {% if %} y otra en el {% for %}). Es la primera vez que aparece esta etiqueta en UT4: las que usaste hasta a5 eran {% for %}, {% if %}, {% block %} y {% extends %}.
Puedes borrar también mensaje.html ya que comprar y cancelar no lo usan (eliminar tampoco lo usaba).
Pregunta¶
Antes de este paso, tras
/comprarveías el cambio en una página dedicada. Ahora vas a/y el mensaje aparece en la cabecera. ¿Qué ganas y qué pierdes con el cambio?
Respuesta
Ganas: una sola página de inicio que centraliza la información, mensajes consistentes en estilo, y la URL final del flujo (/) es siempre la misma — recargar es seguro. Pierdes: el cambio era el dato más importante de la operación de compra y ahora compite visualmente con el resto de la página de inicio. En aplicaciones reales esto se compensa con estilos llamativos para los flash o pantallas dedicadas para operaciones críticas; aquí lo dejamos simple.
Comprobación¶
- Flujo completo:
/seleccionar→ A1,/insertar→ 2.0,/comprar→ confirmar. Aterrizas en/con un mensaje flash arriba. - Recarga
/: el mensaje desaparece (los flash son de un solo uso). /cancelar(con saldo) → flash con el saldo devuelto en/./eliminar/A1→ confirma → flash en/productos.
Reflexión¶
Respuestas
- Los flash viven en la cookie del navegador (firmada con la clave secreta). Solo este navegador los ve.
- Son mensajes "de un uso": los lee
get_flashed_messagesy se vacían. - La separación se mantiene: el route guarda mensaje + redirige; el template los muestra.
Paso 2 — API REST mínima en JSON app.py¶
Conceptos
Una API REST expone los datos del dominio en formato JSON, sin HTML. Misma capa de aplicación, distinta capa de presentación: en lugar de render_template, devolvemos jsonify. Esto demuestra hasta qué punto es real la separación de capas que llevamos toda la unidad defendiendo.
Objetivo¶
Añadir un par de endpoints /api/productos y /api/producto/<codigo> que devuelvan JSON.
Tarea¶
Importa jsonify junto a las demás funciones de Flask:
Añade los routes al final del fichero:
El servicio no cambia
api_productos llama a la misma servicio.listar_productos() que /productos. La capa de aplicación no se entera de si el cliente quiere HTML o JSON; eso lo decide la presentación. Esto es exactamente la promesa de la arquitectura por capas.
Pregunta¶
¿Qué cambiaría en
application/,domain/oinfrastructure/para añadir esta API REST?
Respuesta
Nada. Ni una línea. La API REST es un nuevo módulo en la capa de presentación — exactamente como Flask fue un nuevo módulo en la capa de presentación cuando llegamos en a1. La arquitectura permite añadir interfaces sin tocar el resto. En la Fase 8 lo comprobarás fichero a fichero en la auditoría.
Comprobación¶
Desde otra terminal:
curl http://localhost:5000/api/productos
curl http://localhost:5000/api/producto/A1
curl -i http://localhost:5000/api/producto/ZZ
- El primer comando devuelve un array JSON con todos los productos.
- El segundo, un objeto JSON con el producto A1.
- El tercero, un JSON
{"error": "..."}con código404.
Reflexión¶
Respuestas
jsonifyserializa diccionarios y listas a JSON con cabeceraContent-Type: application/json.- Los routes API y los routes web son hermanos de la misma capa de presentación. Ninguno sabe del otro.
- En proyectos grandes los routes API se ponen en un fichero distinto (
presentation/api.py) y se registran como blueprint. Aquí los dejamos juntos por simplicidad.
Paso 3 — Actualizar la documentación¶
Tarea¶
CHANGELOG.md¶
## [0.10.0] - (Fase 6: integración — mensajes flash y API REST mínima)
### Added
- `presentation/app.py`: clave secreta para mensajes flash.
- `presentation/app.py`: routes `/api/productos` y `/api/producto/<codigo>` que devuelven JSON.
- `presentation/templates/base.html`: bloque que muestra los mensajes flash de la sesión.
### Changed
- `presentation/app.py`: routes `/comprar`, `/cancelar`, `/eliminar/<codigo>` usan `flash` + `redirect` tras éxito en lugar de `render_template('mensaje.html', ...)`.
### Removed
- `presentation/templates/mensaje.html`: sustituido por mensajes flash en `base.html`.
README.md¶
Añade al final de la sección Uso (interfaz web):
**API REST (JSON):**
- `GET /api/productos` — lista de productos en JSON.
- `GET /api/producto/<codigo>` — detalle de un producto en JSON (404 + JSON `{"error": ...}` si no existe).
Comprobación¶
curl http://localhost:5000/api/productos | python -m json.tool— JSON formateado.- Recorrido visual: arranca el servidor, ejecuta el flujo de compra completo y observa los mensajes flash.
Checklist de entrega¶
-
app.secret_keydeclarada (con la advertencia de que no es production-grade). - Mensajes flash funcionando tras
/comprar,/cancelary/eliminar/<codigo>. -
base.htmlmuestra los flash con categoría. -
mensaje.htmleliminado. - API REST:
/api/productosy/api/producto/<codigo>devuelven JSON correcto. - Todos los tests del proyecto siguen pasando.
-
CHANGELOG.mdcon entrada0.10.0yREADME.mdactualizados.
Problemas comunes¶
RuntimeError: The session is unavailable because no secret key was set
Olvidaste declarar app.secret_key. Los flash necesitan sesión, y la sesión necesita clave para firmarse.
El mensaje flash no aparece tras el redirect
Asegúrate de que base.html lleva el bloque get_flashed_messages antes del {% block contenido %}. Si solo está en una página específica, los flash que aparezcan tras un redirect a otra página no se mostrarán.
La API JSON devuelve null o algo raro
Comprueba que devuelves jsonify(...) y no json.dumps(...). jsonify añade la cabecera Content-Type: application/json correcta; json.dumps solo serializa.