El módulo 9 cerró con una constatación incómoda: Papyrus funciona, está probado y tiene red de seguridad, pero vive en un solo ordenador. Si Luis quiere saber cuántos ejemplares de Fausto quedan, tiene que llamar a Ana; si Júlia quiere reservar Hamlet, tiene que acercarse a la tienda. La solución tiene nombre desde hace treinta años: convertir Papyrus en una aplicación web, un programa que corre en un servidor y al que cualquiera accede desde un navegador o un móvil. Antes de escribir la primera línea de Flask (eso llega en la próxima lección), necesitas entender el terreno de juego: cómo conversan un navegador y un servidor, qué es HTTP, qué pinta el HTML en todo esto y qué te regala un framework. Esta lección es el mapa; las cuatro siguientes, el viaje.
Contenido
- El modelo cliente-servidor: quién pide y quién responde
- HTTP: el protocolo de la conversación
- Métodos HTTP: los verbos de la web
- Códigos de estado: el semáforo de las respuestas
- Anatomía de una URL
- HTML: lo mínimo imprescindible para este módulo
- Frontend y backend: dos mundos, una frontera
- JSON: el idioma de las APIs (viejo conocido)
- Qué hace un framework por ti
- El panorama Python: Flask, Django y FastAPI
El modelo cliente-servidor: quién pide y quién responde
Toda la web se sostiene sobre una idea simple: hay programas que piden (clientes) y programas que responden (servidores). El navegador de Luis es un cliente; el ordenador donde correrá Papyrus será un servidor. La conversación siempre la inicia el cliente — el servidor nunca llama a nadie por iniciativa propia, solo espera peticiones y las contesta.
sequenceDiagram
participant N as Navegador de Luis (cliente)
participant S as Servidor de Papyrus
N->>S: GET /catalogo (petición HTTP)
Note over S: Ejecuta código Python:<br/>cargar_catalogo(), montar la página
S->>N: 200 OK + HTML del catálogo (respuesta)
Note over N: El navegador dibuja la página
N->>S: GET /libro/Fausto
S->>N: 200 OK + ficha de Fausto (21.00 €, stock 10)
Fíjate en el reparto de papeles:
- El cliente (navegador, app móvil, o incluso un script Python con
requests) construye una petición, la envía y espera. - El servidor recibe la petición, ejecuta código — en nuestro caso, código Python que ya tienes escrito:
buscar_libro(),vender(),cargar_catalogo()— y devuelve una respuesta. - Cada intercambio es independiente: petición → respuesta, y la conexión conceptualmente termina. Esta amnesia del servidor (se llama sin estado, y volveremos a ella en 10-03) es lo que permite que miles de clientes hablen con el mismo servidor sin que este tenga que recordar a cada uno.
La consecuencia práctica para Papyrus: el paquete papyrus/ que construiste en M3–M9 no cambia. Lo que vas a añadir es una capa por encima que traduce peticiones HTTP en llamadas a tus funciones, y los valores de retorno (o las excepciones) en respuestas HTTP.
HTTP: el protocolo de la conversación
HTTP (HyperText Transfer Protocol) es el formato acordado de esas peticiones y respuestas. Es texto, y es legible. Una petición real tiene tres partes — línea inicial, cabeceras y cuerpo opcional:
Y la respuesta, la misma estructura:
HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 1274 <!DOCTYPE html> <html>...la ficha de Hamlet...</html>
- La línea inicial de la petición dice el método (
GET), la ruta (/libro/Hamlet) y la versión del protocolo. - Las cabeceras (headers) son metadatos en formato
Clave: valor— qué formato acepta el cliente, qué formato envía el servidor, cuánto ocupa el cuerpo... - El cuerpo (body) lleva el contenido: HTML en una página web, JSON en una API, nada en muchas peticiones GET.
No necesitas escribir HTTP a mano jamás — el navegador y el framework lo hacen por ti —, pero saber leerlo te convertirá en alguien que depura problemas web en minutos en lugar de horas.
Métodos HTTP: los verbos de la web
El método declara la intención de la petición. Cuatro cubren casi todo, y encajan de forma natural con las operaciones que Papyrus ya sabe hacer:
| Método | Intención | Ejemplo en Papyrus | ¿Modifica datos? |
|---|---|---|---|
GET |
Leer, consultar | Ver el catálogo, buscar La Odisea | No |
POST |
Crear algo nuevo | Dar de alta un libro nuevo en el catálogo | Sí |
PUT |
Actualizar algo existente | Cambiar el stock de Hamlet de 6 a 9 | Sí |
DELETE |
Eliminar | Retirar un libro descatalogado | Sí |
Dos reglas de oro que los frameworks respetan y tú también deberás respetar:
GETnunca debe modificar datos. Un buscador que indexe tu web seguirá todos los enlaces GET que encuentre; si/vender/Hamletfuera un GET, un robot podría vaciarte el stock sin querer.POST,PUTyDELETEllevan la información sensible en el cuerpo de la petición, no en la URL (las URLs acaban en logs e historiales).
Códigos de estado: el semáforo de las respuestas
Cada respuesta HTTP empieza con un código de tres cifras que resume cómo fue la cosa. La primera cifra es la familia: 2xx éxito, 4xx el cliente se equivocó, 5xx el servidor se rompió. Estos cinco son los que usarás constantemente:
| Código | Nombre | Cuándo lo devolverá Papyrus |
|---|---|---|
200 OK |
Éxito | GET /libro/Hamlet y Hamlet existe: aquí tienes su ficha |
201 Created |
Creado | POST de un libro nuevo aceptado: Fausto II ya está en el catálogo |
400 Bad Request |
Petición mal formada | POST de un libro sin titulo o con precio: "gratis" |
404 Not Found |
No existe | GET /libro/Rayuela — no lo tenemos (¿te suena LibroNoEncontradoError?) |
500 Internal Server Error |
Error del servidor | Una excepción no capturada en nuestro código Python |
La fila del 404 es un adelanto importante: en M7 diseñaste LibroNoEncontradoError para señalar exactamente esta situación dentro de Python. En 10-03 verás que la traducción excepción → código de estado es literal: capturas LibroNoEncontradoError y respondes 404. Tu jerarquía de excepciones de M7 ya estaba pensando en la web sin saberlo.
Y un matiz de actitud profesional: un 500 siempre es culpa tuya (o de tu código). Si un usuario puede provocar un 500 escribiendo algo raro en un formulario, te falta validación — debería ser un 400 con un mensaje útil.
Anatomía de una URL
La URL es la dirección completa de un recurso. Diseccionemos una futura URL de Papyrus:
https://papyrus.example:443/libros/buscar?titulo=odisea&max=10#resultados └─┬─┘ └──────┬───────┘└┬┘└─────┬──────┘└──────────┬─────────┘└───┬────┘ esquema host puerto ruta query string fragmento
- Esquema: el protocolo,
httpohttps(lases HTTP cifrado; hoy, siemprehttpsen producción). - Host: el nombre del servidor. En desarrollo será
localhosto127.0.0.1— tu propia máquina. - Puerto: la "puerta" del servidor. Se omite si es el estándar (80 para http, 443 para https). El servidor de desarrollo de Flask usa el 5000 y el de Django el 8000; los verás mil veces.
- Ruta (path): qué recurso se pide. Es lo que tus rutas de Flask/Django van a mapear a funciones.
- Query string: parámetros opcionales tras
?, en paresclave=valorseparados por&. Es donde viajan los términos de búsqueda de un formulario GET. - Fragmento: tras
#, una posición dentro de la página; no llega al servidor.
HTML: lo mínimo imprescindible para este módulo
Seamos honestos: esto no es un curso de HTML, y no intentará serlo. Pero las plantillas de Flask y Django generan HTML, así que necesitas leerlo y escribir el básico. HTML es un lenguaje de marcado: envuelves contenido entre etiquetas que declaran qué es cada cosa.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Papyrus — Librería</title>
</head>
<body>
<h1>Catálogo de Papyrus</h1>
<p>Los clásicos de siempre, en tu barrio.</p>
<ul>
<li>La Odisea — 12.50 €</li>
<li>Hamlet — 9.95 €</li>
</ul>
<a href="/libro/Fausto">Ver ficha de Fausto</a>
</body>
</html>Línea a línea: <!DOCTYPE html> declara HTML moderno; <head> contiene metadatos que no se pintan (codificación, título de la pestaña); <body> es lo visible; <h1> un encabezado, <p> un párrafo, <ul>/<li> una lista, <a href="..."> un enlace. Las etiquetas se abren <p> y se cierran </p>.
La pieza que más usarás es el formulario, porque es como el usuario envía datos al servidor:
<form action="/buscar" method="get">
<label for="titulo">Buscar libro:</label>
<input type="text" id="titulo" name="titulo">
<button type="submit">Buscar</button>
</form>actiones la ruta a la que se envía;methodel verbo HTTP (getpara búsquedas,postpara crear/modificar).- Cada
<input>connamese convierte en un parámetro: al buscar "odisea", el navegador pideGET /buscar?titulo=odisea. Ahí está la query string de antes. <button type="submit">dispara el envío.
Con encabezados, párrafos, listas, tablas (<table>, <tr>, <td>), enlaces y formularios tienes el 95 % de lo que este módulo necesita.
Frontend y backend: dos mundos, una frontera
| Frontend | Backend | |
|---|---|---|
| Dónde se ejecuta | En el navegador del usuario | En el servidor |
| Lenguajes típicos | HTML, CSS, JavaScript | Python (¡lo nuestro!), y muchos otros |
| Responsabilidad | Presentación e interacción | Lógica de negocio, datos, seguridad |
| En Papyrus | Las páginas que verán Luis y Júlia | buscar_libro(), vender(), el catálogo |
La frontera entre ambos es exactamente HTTP: el frontend pide, el backend responde. Este curso es de Python, así que viviremos en el backend; el frontend que escribiremos será HTML modesto generado por plantillas, suficiente para que Papyrus sea usable. Una regla que nunca debes olvidar: toda validación del frontend es cortesía, no seguridad. Cualquiera puede enviar peticiones HTTP sin pasar por tu formulario (lo harás tú mismo con curl en 10-03), así que el backend debe validar siempre, se fíe o no de la página que sirvió.
JSON: el idioma de las APIs (viejo conocido)
Cuando el cliente no es un navegador que quiere HTML sino un programa que quiere datos — la futura app móvil de Júlia, un script de Luis —, la respuesta viaja en JSON. Y aquí no hay nada nuevo que aprender: es exactamente el formato de M6, el mismo de datos/catalogo.json:
json.dumps() convertía diccionarios en texto JSON; asdict() (M5) convertía tus dataclasses Libro en diccionarios. La cadena Libro → asdict() → JSON → respuesta HTTP es la columna vertebral de la lección 10-03, y ya conoces dos de sus tres eslabones. Una API (Application Programming Interface) web no es más que un conjunto de URLs pensadas para programas: mismas peticiones HTTP, misma estructura, pero JSON en lugar de HTML en el cuerpo.
Qué hace un framework por ti
Podrías escribir un servidor web con la librería estándar de Python (http.server existe), igual que podrías escribir tus propios tests sin pytest. Nadie lo hace, por la misma razón: un framework web resuelve de serie los problemas que toda aplicación web comparte.
- Enrutado: mapear
GET /libro/Hamleta tu función Python que sabe responder. Sin framework, sería unif/elifgigante sobre la ruta. - Plantillas: generar HTML mezclando una estructura fija con datos variables (el catálogo, el resultado de una búsqueda), sin concatenar cadenas a mano.
- Formularios: leer los datos enviados, convertir tipos, validar.
- Seguridad: protección contra ataques clásicos (inyección de HTML malicioso, falsificación de peticiones) que ni siquiera sabías que existían — y de las que hablaremos en 10-05.
- Errores: convertir excepciones en respuestas 404/500 razonables en lugar de dejar caer el servidor.
El framework se ocupa de la fontanería HTTP; tú escribes solo lo que hace única a tu aplicación. En Papyrus, esa parte única ya está escrita y probada: es el paquete papyrus/.
El panorama Python: Flask, Django y FastAPI
Python tiene tres frameworks dominantes, y este módulo te enseñará los dos primeros:
| Flask | Django | FastAPI | |
|---|---|---|---|
| Filosofía | Micro: núcleo mínimo, añades lo que necesitas | Baterías incluidas: trae de todo | Moderno, especializado en APIs |
| Curva de aprendizaje | Suave: app en 5 líneas | Más empinada: estructura y convenciones | Suave si dominas type hints (M8) |
| Trae de serie | Rutas, plantillas, servidor de desarrollo | Además: ORM (base de datos), admin, formularios, usuarios | Validación automática, documentación automática |
| Brilla en | Proyectos pequeños/medianos, APIs, aprender | Aplicaciones grandes con muchas piezas estándar | APIs de alto rendimiento (async, M8) |
| En este módulo | 10-02 y 10-03 | 10-04 y 10-05 | Solo esta mención honesta |
Los tres se instalan como cualquier paquete: pip install flask, pip install django — dentro de un entorno virtual, como aprendiste en M1. FastAPI merece la mención porque es el tercero en discordia y usa intensivamente los type hints de M8 y asyncio para servidores asíncronos; si algún día construyes una API seria, míralo. Pero para aprender los fundamentos, Flask primero (verás cada pieza por separado) y Django después (verás lo que es tenerlas todas integradas) es el camino clásico, y es el nuestro.
Errores Comunes y Consejos
- Confundir la URL con la página. La URL identifica el recurso; la página es la respuesta. Dos peticiones a la misma URL pueden devolver cosas distintas (el stock cambia). Piensa en URLs como preguntas, no como ficheros.
- Usar GET para operaciones que modifican datos. Es el error conceptual más peligroso de los principiantes. Si modifica algo: POST, PUT o DELETE. Siempre.
- Devolver 200 para todo. Una API que responde
200 OKcon el cuerpo{"error": "no encontrado"}es una API mentirosa: los clientes automáticos miran primero el código. Códigos correctos desde el primer día. - Creer que hay que dominar HTML/CSS/JavaScript antes de seguir. No. Con el HTML de esta lección basta para todo el módulo. El backend es una especialidad completa por sí misma.
- Olvidar que el servidor no recuerda nada entre peticiones. Cada petición llega huérfana. Los mecanismos para "recordar" usuarios (sesiones, cookies) existen y los frameworks los dan hechos, pero el protocolo base es amnésico — y eso es una virtud, como verás en 10-03.
Ejercicios
Ejercicio 1: verbos y códigos
Para cada operación de Papyrus, indica el método HTTP adecuado y el código de estado que debería devolver el servidor en el escenario descrito:
- Júlia consulta la ficha de El Quijote (existe).
- Ana da de alta Fausto II con título, precio y stock válidos.
- Luis consulta la ficha de Rayuela (no está en el catálogo).
- Un script envía un alta de libro con
"precio": "veinte euros". - Ana actualiza el stock de Hamlet a 9 unidades.
Ejercicio 2: disección de URL
Descompón en sus partes (esquema, host, puerto, ruta, query string) la URL:
¿Qué pareja clave=valor recibirá el servidor de un formulario cuyo único <input> tiene name="titulo" si el usuario escribe fausto?
Ejercicio 3: el formulario de reserva
Escribe el HTML de un formulario para que Júlia reserve un libro: un campo de texto para el título, un campo numérico (type="number") llamado unidades, y un botón "Reservar". La reserva modifica datos (aparta stock): elige action y method en consecuencia y justifica la elección en un comentario HTML (<!-- ... -->).
Soluciones
Ejercicio 1:
GET→200 OK(lectura con éxito).POST→201 Created(recurso nuevo creado).GET→404 Not Found(la petición está bien formada; el recurso no existe — el casoLibroNoEncontradoError).POST→400 Bad Request(el cliente envió datos inválidos; validamos y explicamos el motivo, nunca dejamos que estalle en un 500).PUT→200 OK(actualización de un recurso existente).
Ejercicio 2: esquema http, host localhost, puerto 5000 (el de desarrollo de Flask), ruta /buscar, query string titulo=quijote&socio=true (dos parámetros: titulo=quijote y socio=true). Con el input name="titulo" y el valor fausto, el servidor recibe titulo=fausto — la URL resultante sería /buscar?titulo=fausto.
Ejercicio 3:
<!-- method="post" porque reservar modifica el stock: las operaciones
con efectos nunca deben viajar en GET (los robots siguen enlaces GET
y los parámetros GET acaban en historiales y logs). -->
<form action="/reservar" method="post">
<label for="titulo">Título:</label>
<input type="text" id="titulo" name="titulo">
<label for="unidades">Unidades:</label>
<input type="number" id="unidades" name="unidades" min="1">
<button type="submit">Reservar</button>
</form>Conclusión
Ya tienes el mapa. La web es un diálogo de peticiones y respuestas HTTP entre clientes que piden y un servidor que ejecuta código — pronto, tu código. Los métodos (GET lee, POST crea, PUT actualiza, DELETE borra) declaran la intención; los códigos de estado (200, 201, 400, 404, 500) resumen el resultado, y ya viste que LibroNoEncontradoError y el 404 están destinados a encontrarse. Sabes leer una URL pieza a pieza, escribir el HTML mínimo con formularios incluidos, y distinguir el frontend (presentación, en el navegador) del backend (lógica y datos, en Python). Y sabes qué framework elegirás para cada cosa: Flask para empezar ligero, Django cuando quieras las baterías incluidas, FastAPI como asignatura optativa futura. En la próxima lección la teoría se acaba: instalarás Flask en el venv, escribirás una aplicación completa en cinco líneas, descubrirás que @app.route es exactamente uno de aquellos decoradores de 08-02, y pondrás el catálogo de Papyrus — La Odisea, Hamlet, El Quijote y Fausto — en un navegador por primera vez.
Curso de Programación en Python
Módulo 1: Introducción a Python
- Introducción a Python
- Configuración del Entorno de Desarrollo
- Sintaxis de Python y Tipos de Datos Básicos
- Variables y Constantes
- Entrada y Salida Básica
- Entornos Virtuales y Gestión de Paquetes
Módulo 2: Estructuras de Control
- Sentencias Condicionales
- Bucles: for y while
- Herramientas de Control de Flujo
- Comprensiones de Listas
Módulo 3: Funciones y Módulos
- Definición de Funciones
- Argumentos de Función
- Funciones Lambda
- Módulos y Paquetes
- Visión General de la Biblioteca Estándar
Módulo 4: Estructuras de Datos
Módulo 5: Programación Orientada a Objetos
Módulo 6: Manejo de Archivos
- Lectura y Escritura de Archivos
- Trabajo con Archivos CSV
- Manejo de Datos JSON
- Operaciones con Archivos y Directorios
Módulo 7: Manejo de Errores y Excepciones
- Introducción a las Excepciones
- Manejo de Excepciones
- Lanzamiento de Excepciones
- Excepciones Personalizadas
- Buenas Prácticas y Registro de Errores con logging
Módulo 8: Temas Avanzados
- Anotaciones de Tipos (type hints)
- Decoradores
- Generadores
- Administradores de Contexto
- Concurrencia: Hilos y Procesos
- Asyncio para Programación Asíncrona
Módulo 9: Pruebas y Depuración
- Introducción a las Pruebas
- Pruebas Unitarias con unittest
- Pruebas con pytest
- Desarrollo Guiado por Pruebas
- Técnicas de Depuración
- Uso de pdb para Depuración
Módulo 10: Desarrollo Web con Python
- Introducción al Desarrollo Web
- Fundamentos del Framework Flask
- Construcción de APIs REST con Flask
- Introducción a Django
- Construcción de Aplicaciones Web con Django
Módulo 11: Ciencia de Datos con Python
- Introducción a la Ciencia de Datos
- NumPy para Computación Numérica
- Pandas para Manipulación de Datos
- Matplotlib para Visualización de Datos
- Introducción al Aprendizaje Automático con scikit-learn
