Una landing page es una página enfocada en una acción concreta: registrar usuarios, presentar un producto, captar contactos o promocionar un servicio. Python puede encargarse del servidor, la validación y el procesamiento del formulario, mientras Tailwind CSS facilita construir una interfaz moderna y adaptable. Con Flask, el proyecto puede empezar pequeño y crecer después con base de datos, correo, pruebas y despliegue.
Para seguir esta guía conviene conocer los fundamentos de Flask en Python, la creación de entornos virtuales con venv, el uso de variables de entorno y el manejo de errores con try y except. La página será sencilla, pero tendrá una estructura adecuada para continuar desarrollándola.
Crear el proyecto
mkdir landing-python
cd landing-python
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux o macOS
source .venv/bin/activate
python -m pip install Flask python-dotenvLa estructura inicial puede ser:
landing-python/
├── app.py
├── .env
├── templates/
│ ├── base.html
│ └── index.html
└── static/
└── app.jsLa documentación oficial de Flask explica plantillas, rutas, formularios, configuración y despliegue.
Crear la aplicación Flask
import os
from flask import Flask, render_template, request, redirect, url_for, flash
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", "cambiar-en-desarrollo")
@app.get("/")
def inicio():
return render_template("index.html")
if __name__ == "__main__":
app.run(debug=True)En producción, SECRET_KEY debe ser larga, aleatoria y almacenada fuera del código. El modo debug tampoco debe permanecer activado públicamente.
Crear la plantilla base
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Producto Simple</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-950 text-white">
{% block contenido %}{% endblock %}
</body>
</html>El CDN de Tailwind es conveniente para prototipos. La documentación oficial del Play CDN advierte que no es la opción recomendada para producción. Para un sitio publicado, instala Tailwind mediante su proceso de compilación y genera un CSS optimizado.
Construir la sección principal
{% extends "base.html" %}
{% block contenido %}
<main class="min-h-screen">
<section class="mx-auto grid max-w-6xl gap-12 px-6 py-24 lg:grid-cols-2 lg:items-center">
<div>
<span class="rounded-full bg-cyan-400/10 px-3 py-1 text-sm text-cyan-300">
Automatiza tu trabajo
</span>
<h1 class="mt-6 text-4xl font-bold tracking-tight sm:text-6xl">
Convierte tareas repetitivas en procesos simples
</h1>
<p class="mt-6 text-lg text-slate-300">
Una herramienta creada con Python para organizar datos y ahorrar tiempo.
</p>
<a href="#contacto" class="mt-8 inline-block rounded-lg bg-cyan-400 px-6 py-3 font-semibold text-slate-950">
Solicitar acceso
</a>
</div>
<div class="rounded-3xl border border-white/10 bg-white/5 p-8 shadow-2xl">
<p class="text-sm text-slate-400">Resultado semanal</p>
<p class="mt-2 text-5xl font-bold">12 horas</p>
<p class="mt-3 text-slate-300">de trabajo manual ahorradas</p>
</div>
</section>
</main>
{% endblock %}Las clases responsivas como lg:grid-cols-2 cambian el diseño en pantallas grandes. Mantén un orden visual claro y un único llamado principal a la acción.
Añadir una sección de beneficios
<section class="mx-auto max-w-6xl px-6 py-16">
<h2 class="text-3xl font-bold">Qué puedes conseguir</h2>
<div class="mt-10 grid gap-6 md:grid-cols-3">
{% for beneficio in beneficios %}
<article class="rounded-2xl border border-white/10 bg-slate-900 p-6">
<h3 class="text-xl font-semibold">{{ beneficio.titulo }}</h3>
<p class="mt-3 text-slate-400">{{ beneficio.texto }}</p>
</article>
{% endfor %}
</div>
</section>En la ruta, envía los datos:
@app.get("/")
def inicio():
beneficios = [
{"titulo": "Menos errores", "texto": "Procesos repetibles y verificables."},
{"titulo": "Más tiempo", "texto": "Automatiza tareas operativas."},
{"titulo": "Datos claros", "texto": "Genera resultados listos para analizar."},
]
return render_template("index.html", beneficios=beneficios)Jinja escapa el texto por defecto, lo que ayuda a evitar inyección de HTML. No marques contenido externo como seguro sin validarlo.
Crear un formulario de contacto
<section id="contacto" class="mx-auto max-w-3xl px-6 py-20">
<form method="post" action="{{ url_for('contacto') }}" class="space-y-5 rounded-3xl bg-white p-8 text-slate-900">
<div>
<label for="nombre" class="font-medium">Nombre</label>
<input id="nombre" name="nombre" required maxlength="100"
class="mt-2 w-full rounded-lg border px-4 py-3">
</div>
<div>
<label for="email" class="font-medium">Correo</label>
<input id="email" name="email" type="email" required maxlength="254"
class="mt-2 w-full rounded-lg border px-4 py-3">
</div>
<button class="w-full rounded-lg bg-slate-950 px-6 py-3 font-semibold text-white">
Enviar solicitud
</button>
</form>
</section>Procesar el formulario
import re
EMAIL = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
@app.post("/contacto")
def contacto():
nombre = request.form.get("nombre", "").strip()
email = request.form.get("email", "").strip().lower()
if not nombre or len(nombre) > 100:
flash("Escribe un nombre válido.", "error")
return redirect(url_for("inicio") + "#contacto")
if not EMAIL.fullmatch(email):
flash("Escribe un correo válido.", "error")
return redirect(url_for("inicio") + "#contacto")
# guardar en base de datos o enviar a un servicio
flash("Solicitud recibida.", "success")
return redirect(url_for("inicio") + "#contacto")La validación del navegador mejora la experiencia, pero el servidor debe validar nuevamente. Un patrón sencillo detecta errores obvios, aunque no confirma que el correo exista.
Mostrar mensajes flash
{% with mensajes = get_flashed_messages(with_categories=true) %}
{% for categoria, mensaje in mensajes %}
<div class="mx-auto mt-4 max-w-3xl rounded-lg px-4 py-3
{{ 'bg-emerald-100 text-emerald-900' if categoria == 'success' else 'bg-red-100 text-red-900' }}">
{{ mensaje }}
</div>
{% endfor %}
{% endwith %}Proteger el formulario
Una landing page pública recibe bots. Añade límites de frecuencia, un campo trampa oculto o CAPTCHA cuando sea necesario. Si el formulario modifica datos sensibles o autentica usuarios, implementa protección CSRF mediante una biblioteca como Flask-WTF.
No registres mensajes completos con datos personales sin necesidad. Define una política de retención y explica para qué se utilizará la información.
Guardar contactos en SQLite
import sqlite3
def guardar_contacto(nombre, email):
with sqlite3.connect("contactos.db") as conexion:
conexion.execute(
"INSERT INTO contactos (nombre, email) VALUES (?, ?)",
(nombre, email)
)Utiliza parámetros, nunca concatenes valores dentro del SQL. Crea una restricción única si no quieres correos duplicados y maneja los conflictos.
Mejorar SEO y accesibilidad
Incluye un título descriptivo, una meta descripción, encabezados en orden y texto alternativo en imágenes. Los botones y campos necesitan etiquetas visibles. Comprueba contraste, navegación con teclado y mensajes de error claros. Una página rápida y accesible suele convertir mejor que una página llena de animaciones.
Optimizar Tailwind para producción
En producción, instala Tailwind y configura las rutas de plantillas para eliminar clases no utilizadas. Mantén los estilos repetidos dentro de componentes o plantillas parciales. Evita construir nombres de clases dinámicamente de una forma que el compilador no pueda detectar.
Desplegar la aplicación
No utilices el servidor de desarrollo de Flask en producción. Ejecuta la aplicación con un servidor WSGI como Gunicorn o Waitress detrás de HTTPS. Configura variables de entorno, registros, límites de carga y una página de error. Antes de publicar, prueba el formulario, los mensajes, los enlaces y el diseño en móvil.
Errores frecuentes
Los fallos más comunes son dejar debug activo, confiar solo en la validación del navegador, guardar secretos en Git, usar el CDN de Tailwind como solución definitiva, no proteger el formulario contra bots y no ofrecer una política de privacidad. También debes evitar enviar correos directamente en la solicitud si el proveedor puede tardar; una cola de tareas mejora la respuesta.
Conclusión
Flask y Tailwind CSS permiten construir una landing page limpia con poco código. Flask gestiona rutas, plantillas y formularios, mientras Tailwind organiza el diseño responsivo. Para convertir el prototipo en una página confiable, valida datos en el servidor, protege secretos, controla bots, mejora accesibilidad y utiliza un despliegue apropiado. Con esta base puedes añadir una base de datos, analítica, correos y pruebas sin rehacer todo el proyecto.






