De una aplicación Django a un sitio estático
Esta web comenzó como una aplicación Django tradicional. Django sigue siendo la herramienta con la que gestiono el contenido y genero el sitio, pero la versión publicada no necesita ejecutar Python ni una base de datos en producción.
La decisión fue generar una versión estática de la web y publicarla directamente en el servidor. De esta forma, en producción solo se sirven HTML, CSS, JavaScript e imágenes, manteniendo la sencillez del alojamiento y eliminando la necesidad de ejecutar un servidor de aplicaciones.
Arquitectura
Django
│
├── Modelos y contenidos
├── Django Admin
└── Templates
│
▼
Generador estático .py
│
▼
Versión estática
│
▼
GitHub Actions
│
▼
FTPS
│
▼
Mi proveedor de
alojamiento
│
▼
www.jlguerra.es
Generación del sitio
El proceso de construcción utiliza el propio proyecto Django para renderizar las páginas públicas. El script genera la estructura estática, adapta los enlaces para que funcionen desde cada directorio, recopila los archivos estáticos y copia las imágenes y demás contenidos necesarios.
El resultado incluye las páginas públicas, robots.txt y sitemap.xml para SEO, los archivos estáticos y los recursos multimedia.
Los contenidos no dependen de la base de datos publicada
La base de datos SQLite que utilizo durante el desarrollo no forma parte del repositorio ni del despliegue. El contenido que necesita el proyecto se puede reconstruir mediante las migraciones de Django y un fixture versionado en JSON.
Esto permite que el entorno de CI pueda crear una base de datos limpia, aplicar las migraciones, cargar el contenido y generar la web sin depender de mi base de datos local.
Control de versiones y despliegue
Aprovechando que el control de versiones del proyecto se gestiona con Git y el repositorio se aloja en GitHub, el propio historial de cambios sirve como punto de partida para el proceso de publicación.
El despliegue automático está deliberadamente protegido: hacer un push a la rama de despliegue no publica la web por sí solo. Para que GitHub Actions publique automáticamente, el mensaje del commit debe comenzar con un prefijo específico que actúa como autorización explícita para publicar.
Esto permite realizar cambios, pruebas y commits de desarrollo sin provocar una publicación accidental. Este mecanismo funciona como una confirmación explícita de que ese commit está preparado para llegar a producción.
git push
│
├── commit normal
│ │
│ └── no se publica
│
└── commit "my_prefix: ..."
│
▼
GitHub Actions
│
├── Django checks
├── Migraciones
├── Fixture
├── Tests
└── Build estático
│
▼
FTPS
│
▼
Mi proveedor de alojamiento
También es posible lanzar el workflow manualmente desde GitHub Actions. En ambos casos, antes de publicar se ejecutan las comprobaciones, migraciones, carga del contenido, tests y generación del sitio estático. Si todo es correcto, la web estática generada se publica mediante FTPS.
¿Por qué hacerlo así?
Porque la web no necesita funcionalidades dinámicas en producción. Django aporta una forma cómoda de estructurar el proyecto, gestionar los contenidos y reutilizar templates, mientras que el sitio estático permite publicar el resultado de una forma sencilla, rápida y con muy pocos requisitos en el servidor.
El proyecto es, en sí mismo, una pequeña muestra de una idea que aplico también en otros desarrollos: elegir la arquitectura en función de las necesidades reales del problema, en lugar de añadir complejidad que no aporta valor.
Chatbot con IA: jose.bot
La web incorpora jose.bot, un asistente virtual desarrollado específicamente para este proyecto. El chatbot permite hacer preguntas sobre mi perfil profesional y responder utilizando únicamente la información pública que se ha preparado para él.
La arquitectura está separada del sitio estático. Django sigue siendo la fuente de verdad: los contenidos se gestionan desde Django Admin y, durante la construcción de la web, se genera automáticamente un fichero JSON con la información que puede consultar el asistente.
Django Admin
│
▼
Contenido público
│
▼
Build de la web
│
├── HTML / CSS / JavaScript
│
└── Fichero JSON
│
▼
Sitio web estático
│
│ consulta
▼
Backend FastAPI
│
▼
Búsqueda de contexto
│
▼
OpenAI Responses API
│
▼
Respuesta de jose.bot
El backend del chatbot está construido con FastAPI y publicado como un servicio independiente. Cuando recibe una pregunta, carga el conocimiento público de la web, lo mantiene temporalmente en caché y realiza una búsqueda sencilla para localizar los documentos más relevantes.
Esta búsqueda se realiza antes de llamar a OpenAI. El backend no intenta generar por su cuenta una respuesta: primero comprueba si existe información relevante en el contenido disponible. Si la encuentra, solo ese contexto se envía al modelo para que genere una respuesta natural. Si no encuentra información suficiente, responde directamente indicando que no puede responder a la pregunta, sin realizar una llamada a OpenAI.
Las instrucciones del asistente le indican que responda en español, de forma natural y concisa, que utilice únicamente la información proporcionada y que no invente datos cuando no dispone de información suficiente.
Flujo de una pregunta
Usuario
│
▼
jose.bot
│
│ consulta
▼
FastAPI
│
├── 1. Comprueba límite de peticiones
│
├── 2. Carga el fichero JSON
│
├── 3. Busca documentos relevantes
│
└── 4. ¿Ha encontrado contexto?
│
┌─────────┴────────┐
│ │
NO SÍ
│ │
▼ ▼
"No encuentro Envía a OpenAI
información..." │
│
▼
Respuesta natural
Para evitar un uso abusivo del servicio, la API limita actualmente las consultas a 10 peticiones por IP cada 10 minutos. Si se supera ese límite, el backend devuelve una respuesta HTTP y no procesa una nueva consulta.
El servicio de FastAPI está desplegado de forma independiente. Esto permite mantener desacoplados el sitio estático y la parte dinámica necesaria para el asistente.
Una fuente de verdad para el contenido
Una decisión importante en el diseño del chatbot es evitar mantener una segunda copia manual de la información profesional. Los datos se editan en Django Admin, igual que el resto del contenido de la web, y el proceso de build genera el conocimiento que consume el chatbot.
De esta manera, cuando se actualiza un puesto de trabajo, una tecnología, una experiencia o cualquier otro dato publicado, el chatbot puede recibir esa información a partir del mismo contenido utilizado para generar la web.