100 Días de Código en Python — Día 71: Publicar tu aplicación web en Internet
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 71 (nivel avanzado).
21 min de lectura
18 secciones
Este tutorial adapta pedagógicamente el Día 71, que cierra el bloque de desarrollo web publicando el blog en Internet. Las secciones 📝 Contenido original del curso provienen de las lecciones 511 a 518; los archivos de inicio (day-71-starting-files-blog-for-deployment.zip) están en los recursos del curso. Los retos 🟢🟡🟠🔴, las secciones sobre probar en Windows, costos y límites de los planes gratuitos y publicar con responsabilidad, el proyecto extra y la etapa final de la Plataforma del Club 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, SQLAlchemy 2.1.1, psycopg 3.3.6, gunicorn 26.2.0 y waitress 3.0.2, se comprobó: que SQLAlchemy 2.1 usa psycopg 3 como controlador por defecto para postgresql://; que el paquete psycopg-binary que lista el requirements.txt del curso no instala el módulo psycopg (ModuleNotFoundError: No module named 'psycopg') y que la solución es psycopg[binary]; que postgres:// provoca NoSuchModuleError; y que gunicorn no funciona en Windows (ModuleNotFoundError: No module named 'fcntl'). También se revisaron los proveedores de la tabla del curso: la documentación de Render indica que los servicios gratuitos se detienen tras 15 minutos sin tráfico y que las bases PostgreSQL gratuitas caducan a los 30 días; Glitch redirige su página de precios a su blog (dejó de alojar proyectos) y Cyclic no respondió. No se creó ninguna cuenta ni se publicó ningún sitio.
🏫 Para el aula: publicar un sitio lo hace accesible a todo Internet. Antes de hacerlo, revisa con tu docente: ¿qué datos contiene?, ¿quién podrá registrarse?, ¿qué pasa si alguien publica algo inapropiado? Los proveedores exigen una edad mínima y, a menudo, una tarjeta de crédito para los planes pagos: la cuenta de publicación debe ser del docente o de la institución.
Hoy es "un momento crucial": el blog en Flask se publica en Internet, con una URL que cualquier persona del mundo puede abrir, incluso desde el celular. Para eso se combinan varias herramientas: Git y GitHub, un proveedor de hosting, gunicorn y el paso de la base de datos de desarrollo (SQLite) a una de producción (PostgreSQL).
Prerequisitos: el blog completo del Día 69, Git y GitHub (Día 70), variables de entorno (Días 35 y 61), SQLAlchemy (Día 63).
Puedes seguir con tu código del Día 69 o descargar los archivos de inicio. Si usas el tuyo, haz dos cambios:
Agrega el .gitignore de los archivos de inicio, para no subir a GitHub archivos innecesarios, como posts.db, la carpeta venv o la carpeta oculta .idea de PyCharm.
Reemplaza requirements.txt por el de los archivos de inicio, para que las versiones coincidan con las usadas para el despliegue y evitar errores (por ejemplo, la última versión de Flask no estaba disponible en render.com). Incluye además gunicorn y psycopg-binary, que se usarán más adelante. La información está actualizada al 13 de marzo de 2026, con Python 3.14:
Por qué fijar versiones (==). En tu computador instalaste ciertas versiones; el servidor instalará exactamente las del archivo. Si no las fijas, podría instalar otras más nuevas con cambios que rompan tu código.
🛠️ Nota técnica (verificado): con SQLAlchemy 2.1, una URL postgresql://... usa el controlador psycopg 3, que se importa como import psycopg. El paquete psycopg-binary solo contiene la parte compilada y no instala ese módulo: al conectarse aparece ModuleNotFoundError: No module named 'psycopg'. La línea correcta es:
Texto
1
psycopg[binary]==3.3.6
que instala psycopgy su versión compilada (verificado: la implementación queda en binary).
🛠️ Nota técnica:SQLAlchemy==2.1.0b1 es una versión beta (de prueba). En producción conviene una versión estable (en esta verificación, 2.1.1). Además, si agregaste email_validator o nh3 en los Días 61 y 67, deben estar en requirements.txt, o el sitio fallará en el servidor aunque funcione en tu computador.
Cómo generar tu propio requirements.txt con las versiones que usas de verdad (con el entorno virtual activo): python -m pip freeze > requirements.txt. Revísalo y quita lo que no sea del proyecto.
Usa la versión final de tu blog o la copia de los recursos (instala los paquetes). La copia trae un administrador ya registrado: correo admin@email.com, contraseña asdf.
Pon el proyecto bajo control de versiones, con la terminal o con la interfaz gráfica.
En PyCharm: VCS → Enable Version Control Integration y elige Git: equivale a git init. Aparece un panel de Git con la consola y el registro.
Los archivos se ponen rojos (sin seguimiento). Con la herramienta Commit, agrega los archivos sin versionar, escribe el mensaje y confirma. Lo que está en .gitignore (como .idea) queda excluido. Los archivos dejan de estar en rojo y el primer commit aparece en el registro.
La interfaz de PyCharm hace lo mismo que los comandos del Día 70 (git init, git add, git commit). Si usas VS Code, su panel "Control de código fuente" es equivalente.
🛠️ Nota técnica (seguridad): la base de datos de ejemplo tiene un administrador con la contraseña asdf. Sirve para probar en tu computador, pero nunca publiques un sitio con esa cuenta: cualquiera que haya hecho el curso la conoce. En el servidor empezarás con una base de datos vacía (sección 7) y registrarás tu administrador con una contraseña larga.
os.environ.get("DB_URI", "sqlite:///posts.db") significa: "usa la variable DB_URI si existe; si no, usa SQLite". Así el mismo código funciona en tu computador (SQLite) y en el servidor (PostgreSQL), cambiando solo la configuración.
🛠️ Nota técnica (verificado):os.environ.get('FLASK_KEY') devuelve None si la variable no existe. En tu computador, sin la variable, los formularios fallarán con RuntimeError: A secret key is required to use CSRF. (Día 61). Para desarrollo, guarda las variables en un .env (que no se sube, Día 70) y cárgalas con python-dotenv, o usa un valor por defecto solo para desarrollo y haz que en producción sea obligatoria:
Python
1
app.config["SECRET_KEY"]= os.environ["FLASK_KEY"]# si falta, el servidor no arranca: mejor que arrancar inseguro
Cada vez que ejecutabas la aplicación aparecía un aviso: el servidor de Flask es de desarrollo; en producción hay que usar un servidor WSGI. WSGI (Web Server Gateway Interface, PEP 3333) es el estándar que conecta una aplicación Python con el servidor web: "los servidores web normales no pueden ejecutar aplicaciones Python". Se usará el más popular: gunicorn.
Verifica que gunicorn esté en requirements.txt (con tu código, agrega gunicorn==21.2.0).
Crea en la carpeta principal un archivo llamado exactamente Procfile (con P mayúscula y sin extensión) con:
Texto
1
web: gunicorn main:app
web: un proceso que recibe solicitudes HTTP.
gunicorn: el servidor que se usará.
main:app: el objeto app está en main.py (si tu archivo se llama distinto, cambia main).
Guarda los cambios con un commit.
📖 ¿Qué hace un servidor de producción? (adaptación)#
El servidor de desarrollo de Flask atiende una solicitud a la vez y está pensado para depurar. Un servidor de producción ejecuta varios procesos (workers), reinicia los que fallan y soporta muchas visitas simultáneas.
🛠️ Nota técnica (verificado): gunicorn no funciona en Windows: al ejecutarlo aparece ModuleNotFoundError: No module named 'fcntl' (usa funciones propias de Linux y Mac). No es un error tuyo: el servidor del proveedor usa Linux, así que allí funcionará. Para probar en Windows un servidor de producción, usa waitress, que sí es compatible:
y abre http://127.0.0.1:8000. Si funciona con waitress y sindebug, es muy probable que funcione con gunicorn en el servidor.
🛠️ Nota técnica: el Procfile lo usaba Heroku. En Render (el proveedor del video) lo que importa es el campo Start Command del panel (sección 6), donde se escribe el mismo gunicorn main:app. Tener el Procfile no hace daño y documenta cómo arrancar la aplicación.
En la configuración de PyCharm, busca "github" y vincula tu cuenta.
Con Git activo, el menú VCS se llama Git: Git → GitHub → Share Project on GitHub, elige un nombre de repositorio y pulsa Share. El código aparece en GitHub.
Hay muchos proveedores, con precios y características que cambian. La tabla del curso:
Proveedor
Costo aproximado al mes
Plan
Heroku
5 dólares
Eco y Basic
Render
0
Individual
Cyclic
0
Free Forever
Glitch
0
Starter
Vercel
0
Hobby
PythonAnywhere
0
Beginner
La mayoría despliega directamente desde un repositorio de GitHub. Pasos:
Crear una cuenta (en Render, con GitHub: Authorize y confirmar el correo).
New Web Service: elegir y conectar el repositorio del blog.
Dejar casi todo por defecto; poner un nombre y cambiar el Start Command a gunicorn main:app.
En Advanced, agregar la primera variable de entorno (FLASK_KEY).
Crear el servicio. Todavía no funciona: falta la base de datos.
"Heroku eliminó su plan gratuito, pero otros proveedores todavía lo ofrecen".
📖 La tabla, hoy (adaptación, verificado el 27/09/2026)#
🛠️ Nota técnica: los planes gratuitos cambian mucho, y la tabla ya no está al día:
Glitch: su página de precios redirige a su blog; dejó de alojar proyectos.
Cyclic: su sitio no respondió.
Vercel: está pensado para sitios estáticos y funciones, no para ejecutar gunicorn; publicar Flask allí requiere otra configuración.
PythonAnywhere: en la tabla del curso, su enlace apunta por error a la página de Vercel. Su dirección es pythonanywhere.com; tiene un plan gratuito para principiantes, pero no usa GitHub ni gunicorn de la misma forma (se configura desde su propio panel).
Render: según su documentación, los servicios gratuitos se detienen tras 15 minutos sin tráfico (la siguiente visita tarda en "despertar") y los cambios en su disco se pierden al detenerse; las bases PostgreSQL gratuitas caducan a los 30 días (hay 14 días de gracia para pasar a un plan pago).
Conclusión: antes de elegir, lee la página de precios actual del proveedor. Un plan gratuito sirve para aprender y mostrar un proyecto; no para un sitio del que dependan otras personas.
SQLite es un archivo: es su fortaleza (puedes abrirlo con DB Browser mientras programas) y su debilidad: en proveedores como Heroku o Render, los archivos del servidor se reemplazan periódicamente (Heroku lo explicaba: más o menos cada 24 horas), así que la base de datos podría borrarse. Hay que pasar a PostgreSQL, que maneja millones de registros de forma confiable.
Como usamos SQLAlchemy, no hay que cambiar el código: solo crear la base PostgreSQL.
En el menú del proveedor, crear una base Postgres con un nombre.
Copiar la Internal Database URL (puede tardar un poco en estar lista).
En la configuración del servicio web (Environment), crear la variable con el nombre que usa main.py (DB_URI) y pegar la URL.
Cambiar el comienzo de postgres:// a postgresql://: SQLAlchemy lo exige.
Funciona porque el paquete psycopg es el adaptador de PostgreSQL para Python y trabaja junto con SQLAlchemy. Con la base vacía, registra el primer usuario (el administrador) y escribe la primera entrada. Ejemplo publicado por el curso: blog-deployment.onrender.com.
Igual, pero indicando el controlador de forma explícita
¿Por qué "no hay que cambiar el código"? SQLAlchemy es un ORM (Día 63): tú escribes db.select(...) y él traduce al "dialecto" de SQL de cada base de datos. Las tablas se crean en PostgreSQL con el mismo db.create_all().
🛠️ Nota técnica: en lugar de corregir a mano postgres://, puedes hacerlo en el código, por si el proveedor entrega el formato antiguo:
🛠️ Nota técnica: la URL de la base de datos incluye el usuario y la contraseña. Es un secreto: solo va en las variables de entorno del proveedor, nunca en el código ni en capturas de pantalla.
Comprender la diferencia entre desarrollo y producción. 2. Entradas: el proyecto y las credenciales. 3. Procesos: preparar → versionar → subir → configurar → conectar. 4. Salida: una URL pública. 5. Dividir en las 7 lecciones. 6. Diseñar la lista de variables de entorno. 7. Implementar. 8. Probar desde otro dispositivo. 9. Corregir con los registros. 10. Documentar en el README.
Encuentra cinco problemas: la clave secreta y la URL de la base de datos están en el código; la URL empieza por postgres://; debug=True; las versiones no están fijadas; y psycopg-binary no instala el módulo psycopg.
Cada vez que alguien del grupo publica un proyecto se le olvida algo distinto (la clave en el código, un paquete, la depuración encendida). Quieren una herramienta que lo revise antes de publicar.
Un proyecto con app.run(debug=True) → ❌ en esa revisión.
Un requirements.txt con flask sin versión → ❌.
El proyecto "bueno" → todo ✅.
13
11. 🏗️ La Plataforma del Club — Etapa 13: publicación#
(Proyecto integrador del bloque avanzado, descrito completo en el Día 70. Esta es su etapa adicional.)
Prepara la Plataforma para producción: variables de entorno, debug=False, requirements.txt con versiones y psycopg[binary].
Pásala por tu revisar_despliegue.py.
Con autorización del docente, publícala con una cuenta de la institución y PostgreSQL, solo con datos ficticios, y compártela con el grupo durante una semana.
Recoge comentarios de los usuarios y registra en el README qué cambió entre la versión local y la publicada.
Al terminar el periodo de prueba, exporta los datos y da de baja el servicio si no se va a mantener.
Escribe una ruta /salud que devuelva {"estado": "ok", "base_de_datos": "..."} con el nombre del motor de base de datos en uso (pista: db.engine.dialect.name), útil para comprobar que el servidor usa PostgreSQL.
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 — El requirements.txt de la IA. Pide a una IA un requirements.txt para desplegar Flask con PostgreSQL. ¿Usa psycopg2-binary, psycopg-binary o psycopg[binary]? Compruébalo contra la documentación de psycopg y la de dialectos de SQLAlchemy.
Actividad 2 — ¿Gratis para siempre? Pregunta a una IA qué proveedores ofrecen hosting gratuito para Flask. Verifica cada respuesta en la página actual de precios del proveedor. ¿Alguno ya no existe?
Actividad 3 — Leer registros. Copia (sin datos sensibles) un error de los registros de tu servicio y pide a una IA que lo explique. Comprueba su hipótesis antes de cambiar el código.
16
14. 🛠️ Correcciones técnicas al material original#
Archivo
Afirmación o comportamiento del curso
Observación
Tratamiento
512, 518
psycopg-binary==3.3.3 como adaptador de PostgreSQL
No instala el módulo psycopg; con SQLAlchemy 2.1 falla con ModuleNotFoundError (verificado). Se usa psycopg[binary]
Nota técnica de la sección 1
512
SQLAlchemy==2.1.0b1
Es una versión beta; en producción, una estable
Nota técnica de la sección 1
515
Usar gunicorn
No funciona en Windows (fcntl, verificado); se prueba con waitress
Sección 4
515
Procfile
Lo usa Heroku; en Render cuenta el Start Command
Nota técnica de la sección 4
514
os.environ.get('FLASK_KEY')
Devuelve None sin la variable y los formularios fallan (verificado)
Nota técnica de la sección 3
517
Tabla de proveedores gratuitos
Glitch ya no aloja proyectos; Cyclic no respondió; el enlace de PythonAnywhere apunta a Vercel; Render detiene servicios gratuitos y su PostgreSQL gratuito caduca a los 30 días (verificado)
Sección 6
513
Administrador admin@email.com / asdf
Contraseña débil y conocida; no publicar con esa cuenta
Nota técnica de la sección 2
518
Cambiar postgres:// por postgresql://
Correcto (verificado: NoSuchModuleError); se puede automatizar en el código
Sección 7
511
"Bienvenidos al día 70" y mención de Heroku
Es el Día 71; el despliegue del curso se hace en Render
La URL del blog publicado (o, si no se publicó, la captura de la prueba local con waitress y sin depuración).
Tu requirements.txt final y la lista de variables de entorno (solo los nombres).
La prueba de reinicio: los datos siguen ahí.
revisar_despliegue.py con su informe sobre dos proyectos (proyecto extra).
La etapa 13 de la Plataforma del Club y la reflexión sobre la semana de prueba.
Cuaderno de errores:psycopg-binary sin psycopg, postgres://, gunicorn en Windows, Procfile.txt, SECRET_KEY en None, paquetes que faltan en el servidor, SQLite en producción, base gratuita caducada, debug=True publicado.