100 Días de Código en Python — Día 54: Tu primer servidor web con Flask (y los decoradores que lo hacen posible)
Nivel: Grado décimo
Área: Tecnología e Informática
Modalidad: Tutorial práctico
Fuente: Curso "100 Days of Code: The Complete Python Pro Bootcamp" (Angela Yu, App Brewery). Contenido del Día 54 (nivel intermedio+).
26 min de lectura
15 secciones
Este tutorial adapta pedagógicamente el Día 54, que abre el bloque de desarrollo web con Flask (Días 54 a 58). Las secciones 📝 Contenido original del curso provienen de las transcripciones de las lecciones 388 a 393. El ejercicio del decorador que mide el tiempo se menciona al final de la lección 393, pero su enunciado no está entre los archivos suministrados; aquí se reconstruye a partir de lo que dice el video y se marca como tal. Los retos 🟢🟡🟠🔴, el proyecto extra y el proyecto integrador del bloque son creaciones de esta adaptación. Las correcciones al material original aparecen como 🛠️ Nota técnica.
Nota sobre las verificaciones: el 27 de septiembre de 2026, con Python 3.14, Flask 3.1.3 y Werkzeug 3.1.8, se ejecutaron todos los ejemplos de código de este tutorial. En particular se comprobó el comando flask --app hello routes, el valor de __name__ en distintos casos y el mensaje de error que aparece al llamar requests.py a un archivo propio.
En los Días 41 a 44 hiciste sitios con HTML y CSS: eso es el frontend, lo que el usuario ve. Una aplicación web necesita además un backend: la parte que hace los cálculos, aplica las reglas del negocio y maneja los datos. Full stack significa dominar ambos.
Frontend: HTML (estructura), CSS (estilo) y JavaScript (interactividad). Frameworks populares: Angular, React.
Backend: puede escribirse en JavaScript, Java, Python, Ruby… Frameworks de Python: Flask, Django, Bottle, CherryPy, Pyramid. Los más pedidos en ofertas de empleo son Flask (mejor para principiantes y proyectos pequeños) y Django (proyectos comerciales grandes).
Los tres componentes del backend:
Componente
Qué es
En el restaurante
Cliente
El navegador del usuario
El comedor, donde se sienta el cliente
Servidor
Un computador conectado a Internet, siempre encendido, que responde solicitudes
La cocina, donde se preparan los platos
Base de datos
Una "hoja de cálculo con esteroides" donde se guarda la información
La despensa con los ingredientes
Cuando escribes google.com y presionas Enter, el navegador envía una solicitud (request) al servidor, que responde con archivos HTML, CSS y JavaScript. Si la página necesita datos (tus boletas en Eventbrite, tus listas de Spotify), el servidor los saca de la base de datos, los pone dentro del HTML y envía el "plato terminado". El cliente nunca entra a la cocina ni a la despensa.
Desde el Día 33 has sido cliente muchas veces: requests.get(url) envía una solicitud y recibe una respuesta con un código de estado (200, 404…). Hoy cambias de lado: vas a escribir el programa que recibe esas solicitudes y decide qué responder.
Texto
12
Días 33–53: tu programa ──request──▶ servidor ajeno ──response──▶ tu programa
Día 54+: navegador ──request──▶ TU servidor ──response──▶ navegador
Biblioteca contra framework. Con una biblioteca (como requests o Beautiful Soup), tú la llamas cuando la necesitas: requests.get(...). Con un framework, él te llama a ti: tú escribes funciones con la forma que el framework exige y él decide cuándo ejecutarlas. Solo tienes que planear: "si pasa esta situación, ¿qué código debe correr?".
Lo primero es la documentación. El Quickstart de Flask muestra una aplicación mínima. La profesora crea un proyecto hello_flask con un archivo hello.py:
Python
12345678
from flask import Flask
app = Flask(__name__)@app.route("/")defhello_world():return"<p>Hello, World!</p>"
Cuidado con el nombre del archivo. Si llamas a tu archivo igual que una biblioteca (por ejemplo requests.py o flask.py), el import carga tu archivo en lugar de la biblioteca y aparece un error confuso como "requests no tiene el atributo get".
Instalar Flask: con la bombilla roja de PyCharm, desde la configuración del intérprete, o en la terminal con pip install Flask. Cualquier paquete de pypi.org se instala con pip.
Ejecutarlo: ejecutar hello.py "normalmente" termina con código de salida 0 y no pasa nada: definir la aplicación no la pone a funcionar. Según el Quickstart de esa época, hay que indicar a Flask qué archivo contiene la aplicación con una variable de entorno y luego ejecutar flask run:
Terminal
123
# MacexportFLASK_APP=hello.py
flask run
Batch
123
:: Windows (símbolo del sistema)setFLASK_APP=hello.pyflask run
Aparece una dirección como http://127.0.0.1:5000: es tu sitio, servido por tu propio computador. Flask advierte que es un servidor de desarrollo y que no debe usarse en producción. En las herramientas de desarrollo de Chrome se ve que Flask envolvió tu texto en un documento HTML completo. Para detener el servidor: Ctrl + C. Si no lo detienes e intentas ejecutar otra aplicación en la misma dirección, la segunda no arrancará.
🛠️ Nota técnica (Flask 3.1, verificado): hoy la forma recomendada por la documentación es indicar la aplicación en el mismo comando, sin variables de entorno:
Terminal
12
flask --app hello run # funciona igual en Windows, Mac y Linux
flask --app hello run --debug# con recarga automática y depurador (Día 55)
Además, si el archivo se llama app.py o wsgi.py, basta con flask run. Y si usas PowerShell (la terminal por defecto de VS Code en Windows), set FLASK_APP=...no funciona; la sintaxis es $env:FLASK_APP = "hello.py".
🛠️ Nota técnica (Python 3.14, verificado): las versiones recientes de Python ya ayudan con el error del nombre del archivo:
Texto
12
AttributeError: module 'requests' has no attribute 'get' (consider renaming '...\requests.py'
if it has the same name as a library you intended to import)
Si ves "consider renaming", ya sabes qué pasó. Borra también la carpeta __pycache__ que se haya creado junto a tu archivo.
🟢 Reto 1 — Aplicación: cambia el mensaje de la ruta / por un saludo al salón y agrega una ruta /bye que diga "¡Hasta luego!".
🟡 Reto 2 — Ampliación: agrega tres rutas más (/horario, /materias, /contacto) y comprueba cuáles existen con flask --app hello routes.
🟠 Reto 3 — Desafío: visita una ruta que no existe. ¿Qué código de estado devuelve? Compruébalo con requests.get("http://127.0.0.1:5000/no-existe").status_code desde otro programa mientras el servidor corre.
🔴 Reto 4 — Pensamiento computacional: dibuja el recorrido completo de una solicitud a /horario: desde que presionas Enter en el navegador hasta que ves el texto. Marca qué parte hace el navegador, cuál hace Flask y cuál hace tu función.
Kernel y shell. Si el sistema operativo fuera un pistacho, la semilla sería el kernel (el programa que habla con el hardware) y la cáscara sería el shell (la interfaz para que los humanos hablen con el kernel). Hay shells gráficos (el Explorador de archivos, el Finder) y de línea de comandos. ¿Por qué usar la línea de comandos? Más control y, con práctica, más rapidez.
En Mac, la aplicación es Terminal (con zsh desde macOS Catalina); en Windows, el Símbolo del sistema. La terminal de PyCharm es una de esas mismas terminales dentro del editor.
Comando
Qué hace
pwd
Print working directory: muestra en qué carpeta estás
ls
Lista los archivos y carpetas de la carpeta actual
cd Desktop
Change directory: entra a una carpeta (tecla Tab para autocompletar)
cd ..
Sube a la carpeta padre
mkdir Prueba
Crea una carpeta
touch main.py
Crea un archivo vacío (cambia la extensión para otros tipos)
rm main.py
Borra un archivo
rm -rf Prueba
Borra una carpeta y todo su contenido, recursivamente y sin preguntar
La profesora insiste: rm -rfno pide confirmación ni manda nada a la papelera. "Un gran poder conlleva una gran responsabilidad": verifica en qué carpeta estás y qué vas a borrar. Para aprender más comandos, busca una "cheat sheet" de la terminal o del símbolo del sistema.
🛠️ Nota técnica: el video afirma que "en su mayor parte" los mismos comandos sirven en Windows y en Mac. Eso es cierto en PowerShell (que tiene alias como ls, pwd y rm), pero no en el Símbolo del sistema (cmd), donde pwd, ls, touch y rm no existen. Equivalencias:
Trabaja siempre dentro de una carpeta de práctica (por ejemplo Documentos\python_decimo). Nunca ejecutes un borrado recursivo desde C:\ ni desde tu carpeta de usuario.
Antes de borrar, ejecuta ls (o dir) y lee lo que vas a eliminar.
Rutas con espacios van entre comillas: cd "Mis proyectos".
🟢 Reto 1: solo con la terminal, crea sitio_salon/templates y sitio_salon/static (las necesitarás el Día 56) y un archivo sitio_salon/server.py.
🟡 Reto 2: muévete entre esas carpetas usando solo rutas relativas (.., nombres) y luego una absoluta. ¿Cuál es más cómoda y cuál es más segura?
🟠 Reto 3: crea tres archivos de prueba, bórralos uno por uno y luego borra la carpeta que los contenía. Antes de cada borrado, ejecuta el comando que lista el contenido.
🔴 Reto 4: escribe una "cheat sheet" de 10 comandos para tu sistema operativo y tu terminal, con un ejemplo probado de cada uno.
Flask(__name__) recibe un único dato obligatorio, el nombre de importación. Al imprimir __name__ en el archivo que se está ejecutando, aparece __main__: significa que el código se ejecuta como programa principal, no como un módulo importado por otro. En cambio, random.__name__ es "random".
Esto permite una forma común de arrancar Flask:
Python
12
if __name__ =="__main__":
app.run()
app.run() hace lo mismo que flask run, pero sin variable de entorno y usando los botones normales de Ejecutar y Detener del editor.
__name__ es una variable que Python crea automáticamente en cada módulo (cada archivo .py):
Cómo se usa el archivo hello.py
Valor de __name__ dentro de hello.py (verificado)
python hello.py o el botón Ejecutar
"__main__"
Otro archivo hace import hello
"hello"
flask --app hello run
"hello" (Flask lo importa)
Por eso el bloque if __name__ == "__main__": es un "solo si me ejecutan directamente". Con flask run, ese bloque no se ejecuta, y no pasa nada malo: Flask arranca el servidor por su cuenta. Así el mismo archivo sirve de las dos formas.
🛠️ Nota técnica: la transcripción dice que __name__ sirve para averiguar "el nombre de la clase, función, método o descriptor actual". Funciones y clases tienen su propio atributo __name__ (print.__name__ es "print"), pero la variable __name__ que usas suelta en un archivo es el nombre del módulo. Es la que recibe Flask(__name__) para saber dónde buscar las carpetas templates y static (Día 56).
La línea @app.route("/") es un decorador. Para entenderlo, primero hay que saber tres cosas sobre las funciones de Python. La profesora usa cuatro funciones de calculadora:
La profesora recomienda recorrer este código paso a paso en Python Tutor, que dibuja cómo la variable inner_function termina apuntando a nested_function.
📖 La clave: con paréntesis o sin paréntesis (adaptación)#
Escribes
Significa
Ejemplo
multiply
La función (la receta)
calculate(multiply, 2, 3)
multiply(2, 3)
El resultado de ejecutarla (el plato)
6
Ya lo usaste sin notarlo: en Turtle, screen.onkey(key="Up", fun=snake.up) (Día 20) y en Tkinter, Button(command=calcular) (Día 27). Pasabas la función sin paréntesis para que otro la llamara después. Eso es exactamente lo que hace Flask con tus rutas.
🟢 Reto 1: agrega una función power(n1, n2) y úsala con calculate.
🟡 Reto 2: crea un diccionario operaciones = {"+": add, "-": subtract, ...} y úsalo para calcular a partir de un símbolo que escribe el usuario (como la calculadora del Día 10).
🟠 Reto 3: escribe aplicar_a_todos(funcion, lista) que devuelva una lista con funcion(x) para cada elemento. Pruébala con abs, str y una función propia.
🔴 Reto 4: escribe crear_multiplicador(n) que devuelva una función que multiplica por n. Con ella crea doble y triple. Explica por qué doble "recuerda" el 2.
Un decorador es una función que envuelve a otra para darle funcionalidad adicional (antes, después o modificándola) sin cambiar su código. Usa las cuatro ideas de la sección 4: recibe una función, define otra adentro, la llama y la devuelve sin paréntesis.
Python
1234567891011121314151617181920212223
import time
defdelay_decorator(function):defwrapper_function():
time.sleep(2)# hacer algo antes
function()
function()# hacer algo despuésreturn wrapper_function
@delay_decoratordefsay_hello():print("Hello")defsay_greeting():print("How are you?")
say_hello()# espera 2 s e imprime "Hello" dos veces
El problema que resuelve: si quisieras un retraso en say_hello, say_bye y say_greeting, tendrías que copiar time.sleep(2) en las tres. Con el decorador, lo escribes una vez y lo aplicas donde quieras.
@ es azúcar sintáctico. Estas dos formas son equivalentes:
Python
123
@delay_decoratordefsay_hello():print("Hello")
Python
12345
defsay_greeting():print("How are you?")
decorated_function = delay_decorator(say_greeting)
decorated_function()
La primera es más fácil de leer: al ver la función, ves de inmediato qué "filtro" tiene encima.
De vuelta a Flask:@app.route("/") es un decorador que le dice a Flask "cuando alguien pida la ruta /, ejecuta esta función". Si agregas
Python
123
@app.route("/bye")defsay_bye():return"Bye"
visitar /bye ejecuta say_bye. Es el framework quien decide qué función llamar, según la ruta.
📝 Ejercicio original del curso: el decorador que mide el tiempo#
(Reconstruido a partir del final de la lección 393: "un ejercicio de codificación para crear sus propias funciones de decorador… medirá la cantidad de tiempo que tarda una función en ejecutarse". El enunciado completo no está entre los archivos suministrados.)
Crea un decorador que, al aplicarse a una función, la ejecute e imprima cuánto tiempo tardó.
¿Qué necesitas medir? El momento antes y el momento después de llamar a la función.
¿Dónde va esa medición: en el decorador o en el wrapper? (Pista: ¿cuál se ejecuta cada vez que llamas a la función?)
¿Cómo sabes el nombre de la función que estás midiendo? (Pista: las funciones tienen el atributo __name__.)
Una posible estructura, para que la completes:
Python
12345678910111213141516171819202122232425
import time
defspeed_calc_decorator(function):defwrapper():
inicio =...
function()
fin =...print(f"{function.__name__} tardó {...:.3f} segundos")return wrapper
@speed_calc_decoratordeffast_function():for i inrange(1_000_000):
i * i
@speed_calc_decoratordefslow_function():for i inrange(10_000_000):
i * i
fast_function()
slow_function()
💡 Para medir tiempos cortos, time.perf_counter() es más preciso que time.time().
🟢 Reto 1 — Aplicación: un decorador @repetir_tres que ejecute la función tres veces.
🟡 Reto 2 — Ampliación: un decorador @anunciar que imprima "Empezando…" antes y "Listo." después.
🟠 Reto 3 — Desafío: aplica dos decoradores a la misma función (@anunciar y @speed_calc_decorator). Cambia el orden. ¿Cambia lo que se imprime? Explica por qué.
🔴 Reto 4 — Pensamiento computacional: en el Día 52 el bot necesitaba reintentar un clic cuando fallaba. Diseña (en papel o en código) un decorador @reintentar que vuelva a ejecutar una función si lanza una excepción, hasta 3 veces. ¿Qué pasa si la función necesita argumentos? (Lo resolverás el Día 55 con *args y **kwargs.)
El salón necesita una "cartelera" en la red local: una página con el horario, los anuncios y un saludo, que el representante pueda actualizar editando un archivo Python.
Hasta ahora tus páginas eran archivos HTML que se abrían con doble clic. Un servidor puede calcular la respuesta en el momento: la fecha de hoy, el día de la semana, el anuncio vigente.
Comprender. 2. Entradas: la ruta que pide el navegador y la fecha del sistema. 3. Procesos: elegir la función según la ruta, calcular, construir el HTML. 4. Salidas: texto HTML. 5. Dividir: una función por ruta y un decorador. 6. Diseñar el diccionario del horario. 7. Implementar. 8. Probar. 9. Corregir. 10. Mejorar.
Versión 4: el decorador @registrar_visita (escríbelo primero en un archivo aparte, sin Flask, y pruébalo con funciones normales).
Versión final: aplica el decorador a las rutas.
⚠️ Aviso para la versión final: al aplicar tu decorador a dos o más rutas aparecerá un error nuevo (AssertionError: View function mapping is overwriting an existing endpoint function: wrapper). Es esperado: lo entenderás y lo resolverás el Día 55 (sección 4). Mientras tanto, aplícalo a una sola ruta. (Si pruebas a poner @registrar_visitaencima de @app.route(...), el error desaparece… pero las visitas dejan de registrarse, porque Flask guardó la función original antes de que tu decorador la envolviera. Compruébalo y explica por qué.)
from flask import Flask
app = Flask("__name__")@app.route("/")definicio():return"Hola"@app.route("/")defhorario():return"Horario"if __name__ =="__main__":
app.run
Encuentra tres problemas (ninguno produce un mensaje de error, lo que los hace más traicioneros):
"__name__" entre comillas: debe ser la variable. Con el texto, Flask no sabe dónde está tu archivo y busca las carpetas templates y static en la carpeta desde donde ejecutas (verificado); el Día 56 eso rompe las plantillas.
Dos funciones en la misma ruta /: Flask atiende con la primera y horario() nunca se ejecuta (verificado).
app.run sin paréntesis: nombra la función pero no la llama; el programa termina sin arrancar el servidor.
Tu grupo va a mantener varios programas del bloque de automatización (Días 45 a 53) y quiere funciones "comodín" reutilizables para medir, registrar y proteger funciones.
Un módulo herramientas.py con al menos tres decoradores sin argumentos (por ejemplo: @cronometrar, @registrar que escriba en registro.txt la fecha y el nombre de la función, y @silenciar_errores que atrape cualquier excepción, la escriba en el registro y no detenga el programa).
Un archivo demo.py que importe el módulo y lo use con al menos cuatro funciones.
En herramientas.py, un bloque if __name__ == "__main__": con pruebas que solo se ejecuten al correr ese archivo directamente.
Importar herramientas desde demo.pyno ejecuta las pruebas internas.
Una función decorada con @silenciar_errores que hace 1 / 0 deja una línea en registro.txt y el programa continúa.
10
8. 🏗️ Proyecto integrador del bloque Flask — Etapa 1#
(Proyecto creado para esta adaptación. Se construye por etapas entre los Días 54 y 58; la descripción completa está en el tutorial del Día 58.)
El Portal del Grupo. En los Días 41 a 44 construiste el sitio web de un grupo del colegio y en los Días 45 a 51 lo automatizaste. Ahora lo convertirás en una aplicación web servida por Python.
Etapa 1 (hoy):
Crea la carpeta portal_grupo desde la terminal, con server.py adentro.
Una aplicación Flask con las rutas / (bienvenida), /integrantes (lista de nombres ficticios) y /actividades.
Un decorador @registrar_visita aplicado a una ruta (mira el aviso de la sección 6).
Un README.md con los comandos exactos para ejecutarlo en tu sistema (flask --app server run).
Desde la terminal (sin usar el Explorador de archivos), crea una carpeta mi_api, un archivo app.py con una ruta /ping que responda "pong" y ejecútalo solo con flask run. ¿Por qué aquí no necesitas --app?
Ciclo de trabajo: formula el problema → inténtalo tú → consulta a la IA → lee y entiende → prueba → compara con la documentación oficial → modifica → explica con tus palabras.
Actividad 1 — Comandos para tu terminal. Pide a una IA "cómo crear un archivo vacío desde la terminal". Si responde touch, pregúntate: ¿funciona en tu terminal? Pruébalo y corrígelo con la tabla de la sección 2.
Actividad 2 — Cómo arrancar Flask. Pregunta a una IA cómo ejecutar una aplicación Flask. Compara su respuesta con el Quickstartactual de la documentación oficial de Flask. ¿Usa set FLASK_APP o flask --app? ¿Cuál corresponde a la versión instalada (pip show flask)?
Actividad 3 — Explicar un decorador. Pide a una IA que explique @app.route("/"). Luego explícalo tú sin mirar su respuesta, usando las palabras "función", "envolver", "ruta" y "Flask la llama". Compara ambas explicaciones.
13
11. 🛠️ Correcciones técnicas al material original#
Archivo
Afirmación o comportamiento del curso
Observación
Tratamiento
389
Ejecutar con export FLASK_APP=hello.py (Mac) o set FLASK_APP=hello.py (Windows) y luego flask run
Sigue funcionando, pero la documentación actual de Flask 3 usa flask --app hello run; set no funciona en PowerShell ($env:FLASK_APP = "...") (verificado)
Nota técnica de la sección 1
389
Error confuso al llamar requests.py a un archivo
Python 3.13+ sugiere renombrarlo (verificado con 3.14)
Nota técnica de la sección 1
390
"En su mayor parte, los mismos comandos sirven en Windows y en Mac"
En el Símbolo del sistema no existen pwd, ls, touch ni rm -rf
Tabla de equivalencias de la sección 2
390
Mostrar pwd en Windows
En cmd, la carpeta actual se ve con cd sin argumentos; pwd existe en PowerShell
Tabla de la sección 2
391
__name__ da "el nombre de la clase, función, método o descriptor actual"
La variable __name__ de un archivo es el nombre del módulo; con flask run vale "hello", no "__main__" (verificado)
Nota técnica de la sección 3
393
Ejercicio del decorador que mide el tiempo
El enunciado no está entre los archivos suministrados
Frontend y backend, full stack, lenguajes y frameworks, Flask contra Django, cliente/servidor/base de datos, analogía del restaurante
388
Sección 0
—
Cliente y servidor desde los dos lados
Secciones 6 y 8
Biblioteca contra framework, Quickstart, hello.py, conflicto de nombres, instalar con pip, FLASK_APP, flask run, servidor de desarrollo, HTML generado, Ctrl + C
389
Sección 1
Crear el servidor mínimo
Retos 1–4 de la sección 1
Secciones 6 y 8
Kernel y shell, Terminal/zsh y Símbolo del sistema, pwd, ls, cd, Tab, mkdir, touch, rm, cd .., rm -rf y sus riesgos, cheat sheets
390
Sección 2
Crear y borrar carpetas y archivos
Retos 1–4 de la sección 2; tabla de equivalencias
Sección 8
Flask(__name__), __main__, random.__name__, if __name__ == "__main__": app.run()
391
Sección 3
—
Tabla de valores de __name__
Secciones 6 y 7
Funciones como objetos de primera clase, calculate, funciones anidadas, devolver funciones, Python Tutor
392
Sección 4
Ejemplos de la calculadora
Retos 1–4 de la sección 4
Sección 7
Decoradores, delay_decorator, wrapper_function, @ como azúcar sintáctico, @app.route, ruta /bye, ejercicio del decorador que mide el tiempo
El servidor del salón funcionando, con la captura de la terminal mostrando las visitas registradas por el decorador.
La salida de flask --app server routes.
Tu "cheat sheet" de la terminal (Reto 4 de la sección 2) con comandos probados.
El decorador que mide el tiempo, aplicado a una función rápida y a una lenta.
La caja de herramientas de decoradores (proyecto extra).
La etapa 1 del Portal del Grupo con su README.md.
Cuaderno de errores: archivo con nombre de biblioteca, set en PowerShell, puerto ocupado, touch en cmd, "__main__" mal escrito, función con y sin paréntesis, return wrapper(), dos funciones en la misma ruta.