Flask es un framework web ligero para Python. Proporciona rutas, manejo de solicitudes, respuestas, plantillas y extensiones sin imponer una estructura excesiva. Es una buena opción para aprender cómo funciona una aplicación web, crear APIs pequeñas, paneles internos y servicios que pueden crecer de manera organizada. “Ligero” no significa incompleto: significa que eliges los componentes adicionales según las necesidades del proyecto.
Antes de comenzar conviene revisar la guía de entornos virtuales, la instalación de paquetes con pip, el uso de funciones en Python, el trabajo con JSON y el manejo de excepciones.
Las fuentes externas principales son la guía rápida oficial de Flask y la documentación oficial de plantillas Jinja.
Crear el entorno del proyecto
mkdir mi_flask_app
cd mi_flask_app
python -m venv .venv
Activa el entorno. En Windows:
.venv\Scripts\activate
En macOS o Linux:
source .venv/bin/activate
Instala Flask:
python -m pip install Flask
Comprueba que el paquete pertenece al intérprete correcto:
python -m pip show Flask
Crear la primera aplicación
Crea un archivo llamado app.py:
from flask import Flask
app = Flask(__name__)
@app.get("/")
def inicio():
return "Hola desde Flask"
Ejecuta el servidor de desarrollo:
flask --app app run --debug
Abre la dirección indicada en la terminal. El modo debug recarga el código y muestra información detallada de errores. No debe utilizarse en producción porque expone herramientas peligrosas si se publica incorrectamente.
Qué significa __name__
Flask(__name__) ayuda al framework a localizar recursos asociados al módulo, como plantillas y archivos estáticos. No reemplaces este valor por una cadena arbitraria sin comprender cómo afecta las rutas internas.
Crear rutas
@app.get("/acerca")
def acerca():
return "Aplicación creada con Python y Flask"
Cada decorador asocia una URL y un método HTTP con una función. Las funciones deben devolver una respuesta válida, como texto, HTML, JSON o un objeto de respuesta.
Rutas con parámetros
from markupsafe import escape
@app.get("/usuario/<nombre>")
def usuario(nombre):
return f"Hola, {escape(nombre)}"
escape evita interpretar como HTML el contenido recibido. En plantillas Jinja, el autoescape suele proteger el HTML, pero no debes desactivarlo sin una razón clara.
Convertidores de ruta
@app.get("/producto/<int:producto_id>")
def producto(producto_id):
return {"id": producto_id}
El convertidor int valida que el segmento sea entero. Flask devolverá una respuesta de ruta no encontrada si el valor no coincide.
Devolver JSON
@app.get("/api/estado")
def estado():
return {
"servicio": "academia",
"activo": True,
}
Flask convierte diccionarios en JSON. Para respuestas más explícitas puedes usar jsonify y definir el código de estado:
from flask import jsonify
@app.get("/api/usuarios/<int:usuario_id>")
def obtener_usuario(usuario_id):
if usuario_id != 1:
return jsonify({"error": "No encontrado"}), 404
return jsonify({"id": 1, "nombre": "Ana"})
Recibir parámetros de consulta
from flask import request
@app.get("/buscar")
def buscar():
termino = request.args.get("q", "").strip()
limite = request.args.get("limite", default=10, type=int)
if not termino:
return {"error": "Falta el parámetro q"}, 400
return {"consulta": termino, "limite": limite}
No confíes en la entrada del usuario. Valida longitud, tipo y formato antes de usarla en consultas o archivos.
Procesar formularios POST
@app.post("/contacto")
def contacto():
nombre = request.form.get("nombre", "").strip()
email = request.form.get("email", "").strip()
if not nombre or not email:
return "Datos incompletos", 400
return "Mensaje recibido", 201
Para formularios reales añade protección CSRF mediante una extensión adecuada. El método POST no protege por sí mismo contra solicitudes maliciosas.
Recibir JSON
@app.post("/api/tareas")
def crear_tarea():
datos = request.get_json(silent=True)
if not isinstance(datos, dict):
return {"error": "JSON inválido"}, 400
titulo = str(datos.get("titulo", "")).strip()
if not titulo:
return {"error": "El título es obligatorio"}, 400
return {"id": 1, "titulo": titulo}, 201
silent=True evita una excepción automática, pero debes responder explícitamente cuando el cuerpo no sea válido.
Usar plantillas HTML
Crea una carpeta templates y un archivo inicio.html:
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>{{ titulo }}</title>
</head>
<body>
<h1>{{ titulo }}</h1>
<p>Bienvenido, {{ usuario }}.</p>
</body>
</html>
En Python:
from flask import render_template
@app.get("/")
def inicio():
return render_template(
"inicio.html",
titulo="Mi aplicación Flask",
usuario="Ana",
)
Jinja escapa variables en plantillas HTML. Evita utilizar el filtro safe con contenido suministrado por usuarios.
Condiciones y bucles en Jinja
<ul>
{% for tarea in tareas %}
<li>{{ tarea }}</li>
{% else %}
<li>No hay tareas</li>
{% endfor %}
</ul>
La lógica compleja debe permanecer en Python. Las plantillas deberían concentrarse en presentar datos.
Archivos estáticos
Coloca CSS, JavaScript e imágenes propias en la carpeta static:
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/estilos.css') }}"
>
url_for genera rutas correctas y evita escribir direcciones rígidas.
Redirecciones y URLs
from flask import redirect, url_for
@app.post("/guardar")
def guardar():
return redirect(url_for("inicio"))
Utiliza el nombre de la función de vista, no una URL escrita manualmente. Esto facilita cambiar rutas sin romper enlaces internos.
Manejar errores
@app.errorhandler(404)
def no_encontrado(error):
return render_template("404.html"), 404
@app.errorhandler(500)
def error_interno(error):
return render_template("500.html"), 500
No muestres el traceback al visitante. Registra el error de forma privada y devuelve un mensaje seguro.
Configuración y secretos
import os
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY")
if not app.config["SECRET_KEY"]:
raise RuntimeError("Falta SECRET_KEY")
No incluyas una clave real en el código. La clave secreta protege firmas de sesión y debe ser larga, aleatoria y diferente por entorno.
Organizar un proyecto mayor
mi_flask_app/
├── app/
│ ├── __init__.py
│ ├── routes.py
│ ├── templates/
│ └── static/
├── tests/
├── config.py
└── requirements.txt
Una factoría de aplicación facilita pruebas:
from flask import Flask
def create_app(test_config=None):
app = Flask(__name__)
app.config.from_mapping(SECRET_KEY="desarrollo")
if test_config:
app.config.update(test_config)
@app.get("/")
def inicio():
return "OK"
return app
La clave de desarrollo del ejemplo nunca debe llegar a producción.
Probar la aplicación
def test_inicio():
app = create_app({"TESTING": True})
cliente = app.test_client()
respuesta = cliente.get("/")
assert respuesta.status_code == 200
assert respuesta.data == b"OK"
El cliente de pruebas realiza solicitudes sin iniciar un servidor real. Añade pruebas para errores, JSON inválido, permisos y límites.
Base de datos
Para un proyecto pequeño puedes comenzar con SQLite. Utiliza consultas parametrizadas o un ORM; nunca construyas SQL concatenando entrada del usuario. Gestiona transacciones y cierra conexiones incluso cuando ocurre una excepción.
Despliegue
El servidor incluido es solo para desarrollo. En producción usa un servidor WSGI compatible, un proxy cuando corresponda, HTTPS, variables de entorno y logging. Define límites de tamaño, timeouts y políticas de actualización. No publiques el depurador.
Errores frecuentes
Los problemas habituales son ejecutar con debug en producción, guardar secretos en el repositorio, confiar en formularios sin validar, concatenar SQL, desactivar el autoescape y colocar toda la aplicación en un solo archivo. También es frecuente instalar Flask en un entorno y ejecutar otro intérprete.
Conclusión
Flask permite aprender desarrollo web construyendo rutas, respuestas JSON, formularios y plantillas con una base pequeña. Empieza con un entorno virtual y una aplicación mínima, valida toda entrada y separa configuración, vistas y lógica. Cuando el proyecto crezca, utiliza factorías, blueprints, pruebas y un servidor de producción adecuado.






