Portafolio profesional
De archivos estáticos a contenedores: La evolución de una landing page
La historia de cómo una simple página web de clase se transformó en una aplicación contenedorizada con backend, y los trade-offs de intentar hacerlo todo.
Artículo
# De archivos estáticos a contenedores: La evolución de una landing page
## El problema
Todo empezó como un ejercicio académico: crear una landing page básica con HTML y CSS. Tenía archivos sueltos (`index.html`, `styles.css`), imágenes en carpetas y cero consistencia entre entornos. Si quería mostrársela a alguien, tenía que decir "abrí el `index.html` en tu navegador, pero asegurate de tener todas las imágenes en la carpeta `clase2/images`...". Funcionaba en mi máquina, pero era frágil. Cada vez que movía archivos, algo se rompía. Los paths relativos eran una pesadilla. Necesitaba una forma de empaquetar todo -el frontend, el backend que luego añadí, y sus dependencias- en algo que funcionara igual en cualquier lado, desde mi laptop hasta un servidor de producción.
## Por qué Docker Compose
Podría haber subido los archivos HTML/CSS/JS a un hosting estático y listo. Pero el proyecto creció: añadí un backend Node.js con autenticación (`authController.js`, `User.js`). Ahora tenía dos servicios que necesitaban comunicarse y un entorno específico (Node, paquetes NPM). Configurar manualmente Node, instalar dependencias, y asegurar que el frontend apuntara al puerto correcto del backend era propenso a errores. Docker Compose me permitió definir ambos servicios (frontend y backend) y su red en un solo archivo `docker-compose.yml`. Con un comando, todo se levantaba: un contenedor sirviendo los archivos estáticos (usando un servidor web simple como `nginx` o sirviéndolos con Node) y otro con la aplicación Node.js. La consistencia estaba garantizada.
## Arquitectura
Mirando la evolución del código, desde las carpetas `clase2`/`clase3` (solo frontend) hasta la estructura final en `corte1`, se puede ver el salto arquitectónico.
```mermaid
flowchart LR
Usuario["Usuario
Navegador Web"] -- HTTP Request --> ServidorWeb["Servidor Web (estático)
Sirve HTML/CSS/JS"]
ServidorWeb -- Sirve --> Frontend["Frontend Contenedorizado
HTML, CSS, JS (corte1/frontend)"]
Frontend -- Llamadas API / Fetch --> Backend["Backend Contenedorizado
Node.js, Express (corte1/backend/src)"]
Backend -- Conexión --> BDD["Base de Datos
No evidenciada en estructura"]
NOTE["Evidence gap:
No se evidencia DB (ej. PostgreSQL)
en archivos o Dockerfile"]
Docker["Orquestador
Docker Compose (corte1/docker-compose.yml)"] -- build & up --> Frontend
Docker -- build & up --> Backend
```
El diagrama muestra el flujo final. El usuario accede al servidor web (que podría estar dentro del mismo contenedor del frontend o ser un `nginx` aparte). Ese servidor sirve los archivos estáticos del frontend. Luego, el JavaScript del frontend (`script.js`) hace llamadas a la API del backend Node.js, que está en otro contenedor. Docker Compose orquesta la creación de ambos contenedores y la red que los une.
**Decisiones arquitectónicas clave:**
1. **Separación Frontend/Backend en contenedores distintos:** En lugar de un monolito, separé los servicios. Esto me permitió escalar, depurar y desarrollar cada parte de forma independiente. El frontend es solo assets, el backend es lógica y API.
2. **`Dockerfile` por servicio:** Tanto `corte1/frontend/Dockerfile` como `corte1/backend/Dockerfile` definen entornos optimizados para cada uno. El del frontend probablemente copia los archivos HTML/CSS/JS a una imagen ligera con un servidor web. El del backend instala dependencias de Node con `npm install` basado en su `package.json`.
3. **Orquestación con `docker-compose.yml`:** Este es el corazón. Define la red, los volúmenes (si los hay), las variables de entorno (como la conexión a la DB, aunque no se vea) y los comandos para iniciar ambos servicios con una sola instrucción: `docker-compose up`.
4. **Estructura de código modular en el backend:** Dentro de `corte1/backend/src`, separé configuración (`config`), controladores (`controllers`), modelos (`models`) y rutas (`routes`). Esto no es solo por Docker, pero el contenedor me obligó a pensar en dependencias y puntos de entrada de forma clara (`server.js` como punto de inicio).
## El trade-off que nadie te cuenta
El mayor trade-off fue la **complejidad operativa temprana**. Para un proyecto que inicialmente era una landing page estática, introducir Docker y un backend fue como usar un martillo hidráulico para clavar un clavo. El tiempo de desarrollo inicial se triplicó: en lugar de solo escribir HTML y CSS, ahora tenía que escribir `Dockerfile`s, un `docker-compose.yml`, manejar variables de entorno entre contenedores, y asegurarme de que las dependencias del backend estuvieran bien definidas. Un error en el `Dockerfile` del backend (como copiar archivos en el orden incorrecto) invalidaba la caché de construcción y hacía que builds simples tomaran minutos.
La solución fue iterativa y un poco dolorosa. Empecé por containerizar solo el frontend, asegurándome de que la landing page se sirviera correctamente desde un contenedor. Luego, containericé el backend por separado y lo probé de forma aislada. Solo al final integré ambos con Compose. Aprendí que, a veces, la consistencia del entorno (el beneficio de Docker) tiene un costo de configuración inicial alto, y no siempre vale la pena para proyectos ultra-simples. Pero una vez superada esa curva, la capacidad de replicar el entorno en cualquier máquina con Docker instalado fue invaluable.
## Resultado
* **Entorno reproducible 100%:** Cualquier desarrollador (o servidor de CI/CD) con Docker puede clonar el repo y ejecutar `docker-compose up` para tener una copia exacta de la aplicación funcionando, sin instalar Node, sin preocuparse por versiones de paquetes.
* **Aislamiento y limpieza:** Eliminé el "funciona en mi máquina". Los contenedores encapsulan las dependencias. Cuando termino de trabajar, `docker-compose down` elimina todo, dejando mi sistema host limpio.
* **Base para despliegue en la nube:** La configuración con Compose es un paso muy cercano a configurar los mismos servicios en una plataforma como AWS ECS o Google Cloud Run, facilitando un futuro despliegue en producción.
* **Estructura de proyecto profesional:** La evolución desde carpetas desorganizadas (`clase15`, `clase2`) a una estructura `corte1/frontend` y `corte1/backend` dentro de un sistema contenedorizado refleja un salto en madurez de desarrollo.
## Lo que haría diferente
Empezaría con un servidor de desarrollo más simple para el frontend en el contenedor. Inicialmente, probablemente usé `nginx` en modo producción, lo que hacía que cada cambio en CSS requiriera reconstruir la imagen. Si empezara de nuevo, configuraría el `Dockerfile` del frontend para usar un servidor con recarga en caliente (como servir los archivos con `live-server` o montar un volumen para desarrollo) durante la fase de desarrollo, y solo usaría la imagen optimizada de `nginx` para la construcción final de producción. Separaría claramente el `Dockerfile` de desarrollo del de producción para agilizar los ciclos de feedback.
---
¿Querés profundizar en algún componente? Contactame o revisá el código en https://github.com/Albarracin-sg/paginaWeb.