# 🚀 Deploy — klaUS-aicalc-roi
## 🤔 ¿Qué hago? ¿Cómo lo hago? ¿Y para qué lo hago?
**¿Qué hago?**
Gestiono el despliegue a producción de klaUS-aicalc-roi en dos plataformas complementarias: la API FastAPI en Railway y el frontend Next.js en Vercel. Cada componente se despliega de forma independiente y se comunica a través de una variable de entorno.
**¿Cómo lo hago?**
Railway despliega la API usando el `Dockerfile` del repositorio — detecta la imagen automáticamente al conectar el repo GitHub. Vercel despliega el frontend usando el `vercel.json` que apunta al directorio `frontend/` — Next.js se detecta de forma nativa. Ambas plataformas se conectan al repositorio GitHub y despliegan automáticamente en cada merge a `main`.
**¿Y para qué lo hago?**
Sin un entorno de producción accesible, klaUS-aicalc-roi es solo código en un repositorio. El deploy en producción es el punto de inflexión que convierte el MVP en un producto real — con una URL pública, usuarios reales y métricas reales de uso.
---
## 🗺️ Arquitecturas de despliegue
### 🖥️ Local — Podman (desarrollo y validación)
```mermaid
flowchart TD
A[👤 Usuario\nnavegador] --> B[🌐 HOST:3000]
B --> C[🐳 frontend container\nnode:22-slim]
C -- NEXT_PUBLIC_API_URL\n${HOST}:8000 --> D[🐳 api container\npython:3.12-slim]
D --> E[⚡ FastAPI + Uvicorn\n:8000]
E --> F[🧠 TCO Engine]
E --> G[📦 Catálogo estático\nbackend/data/]
ENV[📄 .env\nNEXT_PUBLIC_API_URL=...] -.configura.-> C
subgraph Podman [🐳 podman compose]
C
D
end
```
### 🌐 Producción — Railway + Vercel
```mermaid
flowchart TD
A[👤 Usuario] --> B[🌐 Vercel CDN]
B --> C[🖥️ Frontend Next.js\nvercel.app domain]
C -- NEXT_PUBLIC_API_URL --> D[🚂 Railway\nrailway.app domain]
D --> E[🐳 Docker Container\npython:3.12-slim]
E --> F[⚡ FastAPI + Uvicorn\n:PORT]
F --> G[🧠 TCO Engine]
F --> H[📦 Catálogo estático\nbackend/data/]
subgraph CI_CD [⚙️ CI/CD — GitHub Actions]
I[Push a main] --> J[lint + test + frontend]
J -- ✅ CI verde --> K[Vercel auto-deploy]
J -- ✅ CI verde --> L[Railway auto-deploy]
end
I --> CI_CD
```
---
## 🖥️ Entorno local — Podman
### Prerrequisitos
```bash
# Instalar Podman + podman-compose en macOS M1
brew install podman podman-compose
# Inicializar la máquina virtual de Podman (solo la primera vez)
podman machine init
podman machine start
```
### ⚙️ Configurar la URL de la API (`.env`)
`NEXT_PUBLIC_API_URL` se embebe en el bundle del frontend en build time. Si el navegador accede desde una IP distinta a `localhost` (red local, otro dispositivo, VM), hay que configurarla antes de construir.
```bash
# Copiar la plantilla y editar
cp .env.example .env
# Editar .env con tu IP local
# NEXT_PUBLIC_API_URL=http://192.168.1.127:8000
```
| Escenario | Valor de `NEXT_PUBLIC_API_URL` |
| --- | --- |
| Acceso solo desde el mismo Mac | `http://localhost:8000` (defecto) |
| Acceso desde otro dispositivo en la red | `http://192.168.X.X:8000` |
| Acceso desde VM en el mismo host | `http://host.containers.internal:8000` |
> ⚠️ `.env` está en `.gitignore` — nunca se commitea. Usa `.env.example` como plantilla compartida.
---
### Levantar el stack completo
```bash
# Desde la raíz del repo
podman compose up --build
# La primera vez tarda ~3-5 minutos (descarga imágenes base + npm ci + build)
# Las siguientes arranca en ~30 segundos (capas cacheadas)
```
Cuando veas `frontend_1 | Ready on http://localhost:3000`, abre el navegador:
| URL | Qué es |
| --- | --- |
| `http://localhost:3000` | 🖥️ Interfaz web — formulario TCO |
| `http://localhost:8000/health` | 💚 Healthcheck de la API |
| `http://localhost:8000/docs` | 📄 Swagger UI de la API |
### Parar el stack
```bash
podman compose down
```
### Reconstruir tras cambios de código
```bash
# Si cambias backend/ — solo reconstruye la imagen API
podman compose up --build api
# Si cambias frontend/ — reconstruye la imagen frontend
# (necesario porque NEXT_PUBLIC_API_URL se embebe en build time)
podman compose up --build frontend
```
### Ficheros implicados
| Fichero | Rol |
| --- | --- |
| `compose.yml` | Orquesta API (:8000) + frontend (:3000) |
| `Dockerfile` | Imagen API — `python:3.12-slim` + FastAPI + uvicorn |
| `frontend/Dockerfile` | Imagen frontend — multi-stage: build Next.js + runner `node:22-slim` |
| `.dockerignore` | Excluye `.venv`, `node_modules`, tests, artefactos locales y secretos |
> ⚠️ **Por qué `NEXT_PUBLIC_API_URL` apunta a la IP del host y no a `http://api:8000`**
> Los `NEXT_PUBLIC_*` se embeben en el JavaScript del cliente en tiempo de build — los ejecuta el navegador del usuario, no el servidor. El navegador ve los puertos del host (`localhost` o la IP de red), no la red interna de Podman (`api`). Configura la URL correcta en `.env` antes de `podman compose up --build`.
---
## 🏗️ Componentes de deploy
### 🐳 Docker — API (Railway)
| Fichero | Propósito |
| --- | --- |
| `Dockerfile` | Imagen Python 3.12-slim con FastAPI + uvicorn |
| `.dockerignore` | Excluye frontend, tests, .venv, artefactos locales |
| `railway.toml` | Builder dockerfile, healthcheck `/health`, política de restart |
**Variables de entorno Railway:**
| Variable | Valor | Descripción |
| --- | --- | --- |
| `PORT` | Auto (Railway lo inyecta) | Puerto en el que escucha uvicorn |
El endpoint `/health` devuelve `{"status": "ok"}` — Railway lo usa como liveness probe con timeout 30s.
---
### ⚡ Vercel — Frontend (Next.js)
| Fichero | Propósito |
| --- | --- |
| `vercel.json` | `rootDirectory: frontend`, Next.js framework, `npm ci` + `npm run build` |
**Variables de entorno Vercel (configurar en dashboard):**
| Variable | Valor | Descripción |
| --- | --- | --- |
| `NEXT_PUBLIC_API_URL` | `https://<nombre>.railway.app` | URL pública de la API en Railway |
> ⚠️ `NEXT_PUBLIC_API_URL` se embebe en el bundle del cliente en build time. Configurar antes del primer deploy de Vercel.
---
## 🔧 Setup inicial — paso a paso
### 1️⃣ Deploy de la API en Railway
```bash
# Prerrequisito: cuenta Railway + CLI instalada
npm install -g @railway/cli
railway login
# Crear proyecto desde el repo GitHub
railway init
# → Seleccionar "Empty Project" → nombre: klaUS-api
# Conectar el repo
railway connect
# Primer deploy (Railway detecta el Dockerfile automáticamente)
railway up
# Obtener la URL pública
railway domain
# → Copiar este dominio para configurar Vercel
```
O bien desde la web: railway.app → New Project → Deploy from GitHub repo → seleccionar `Ka0s-Klaus/klaUS-aicalc-roi`.
---
### 2️⃣ Deploy del Frontend en Vercel
```bash
# Prerrequisito: cuenta Vercel + CLI instalada
npm install -g vercel
vercel login
# Desde la raíz del repo
vercel
# Configurar variable de entorno con la URL de Railway
vercel env add NEXT_PUBLIC_API_URL production
# → Introducir: https://<nombre>.railway.app
# Redeploy para que tome la variable
vercel --prod
```
O bien desde la web: vercel.com → New Project → Import `Ka0s-Klaus/klaUS-aicalc-roi` → Vercel detecta `vercel.json` automáticamente.
---
### 3️⃣ Verificar el deploy
```bash
# Healthcheck API
curl https://<nombre>.railway.app/health
# → {"status": "ok", "version": "0.1.0"}
# Catálogo de modelos
curl https://<nombre>.railway.app/v1/models | jq '.total'
# → 30
# Frontend accesible
open https://<nombre>.vercel.app
```
---
## 🔁 Flujo de deploys automáticos
Una vez conectados ambos servicios al repo GitHub:
```mermaid
sequenceDiagram
participant Dev as 👨💻 Developer
participant GH as GitHub
participant CI as GitHub Actions
participant RW as Railway
participant VR as Vercel
Dev->>GH: git push main (merge PR)
GH->>CI: trigger CI workflow
CI->>CI: lint + test + frontend (paralelo)
CI-->>GH: ✅ CI verde
GH->>RW: webhook → build Dockerfile → deploy API
GH->>VR: webhook → npm ci + npm build → deploy frontend
RW-->>Dev: 🚂 API live en railway.app
VR-->>Dev: ⚡ Frontend live en vercel.app
```
> 📝 Railway y Vercel despliegan de forma independiente tras cada push a `main`. CI no bloquea el deploy de las plataformas — ambas leen el mismo trigger de GitHub. Si CI falla, el equipo debe revisar antes de que el deploy llegue a usuarios.
---
## 🔒 Seguridad
| Control | Estado | Detalle |
| --- | --- | --- |
| Secretos en plataforma | ✅ | Env vars en Railway/Vercel dashboard — nunca en el repo |
| HTTPS forzado | ✅ | Ambas plataformas sirven solo HTTPS por defecto |
| Sin credenciales en Dockerfile | ✅ | Solo deps públicos de PyPI |
| `.dockerignore` | ✅ | Excluye `.env`, `.claude/`, `CLAUDE.md`, `uv.lock` |
| CORS | ✅ | `CORSMiddleware` activo con `allow_origins=["*"]` en Fase 1 — restringir al dominio Vercel en Fase 2 |
| Auth (Fase 2) | ❌ | Sin autenticación en Fase 1 — API pública de solo lectura |
---
## 📊 Costes estimados (free tier)
| Plataforma | Plan | Límite free | Coste si se supera |
| --- | --- | --- | --- |
| Railway | Hobby ($5/mes) | 500h ejecución / 5GB egress | $0.000463/min CPU + $0.000231/min RAM |
| Vercel | Free | 100GB bandwidth / 100h build | $20/mes Pro |
Para el MVP con tráfico bajo, ambos planes free/hobby son suficientes.
---
## 🔗 Documentos relacionados
- [CI/CD](ci-cd.md) — pipeline de GitHub Actions que valida antes de cada deploy
- [API REST](api.md) — endpoints que expone la API desplegada en Railway
- [Frontend](frontend.md) — aplicación Next.js desplegada en Vercel
- [TCO Engine](tco-engine.md) — motor de cálculo que corre dentro del contenedor Railway