Despliegue
Guía detallada para llevar Nexlo a producción. Backend (API) en Railway, frontend (web) y wiki en Vercel, base de datos PostgreSQL gestionada por Railway.
Topología
| App | Plataforma | Qué desplegar | Base de datos |
|---|---|---|---|
apps/api | Railway | Servicio Node (Fastify) sobre la raíz del repo | Plugin PostgreSQL de Railway |
apps/web | Vercel | Proyecto Next.js con root apps/web | — |
apps/wiki | Vercel | Proyecto Next.js (Nextra) con root apps/wiki | — |
Orden recomendado: 1) Railway (API + DB) → 2) Vercel (web) → 3) Vercel (wiki). La web necesita la URL del API, y la wiki comparte secreto con el API.
1. API + PostgreSQL en Railway
1.1 Crear el servicio
-
railway.app → New Project → Deploy from GitHub repo.
-
Selecciona el repo y deja el Root Directory en la raíz (
/). Es obligatorio: los workspaces de pnpm necesitan instalarse desde la raíz. -
Railway detecta
railway.toml, que ya define todo:[build] builder = "NIXPACKS" buildCommand = "pnpm install --frozen-lockfile" [deploy] startCommand = "pnpm start:api" # prisma db push + arranque del servidor healthcheckPath = "/health" healthcheckTimeout = 120 restartPolicyType = "ON_FAILURE"pnpm start:api→pnpm --filter @nexlo/api deploy:start→prisma db push --skip-generate && tsx src/index.ts.- El
postinstallde la raíz ejecutaprisma generateautomáticamente.
1.2 Añadir PostgreSQL
- Dentro del proyecto: New → Database → Add PostgreSQL.
- Railway inyecta
DATABASE_URLen el servicio del API automáticamente. Si no se enlaza solo, en el servicio del API añade la referencia:DATABASE_URL = ${{Postgres.DATABASE_URL}}.
1.3 Variables de entorno
En el servicio del API → Variables:
# Secretos (genera valores fuertes, p. ej. `openssl rand -hex 32`)
JWT_SECRET=<aleatorio-fuerte>
WIKI_JWT_SECRET=<aleatorio-fuerte> # EXACTAMENTE el mismo en la wiki
# URLs públicas (rellénalas tras desplegar web/wiki; controlan CORS y los links)
WEB_URL=https://tu-web.vercel.app
WIKI_URL=https://tu-wiki.vercel.app
# Cashela
CASHELA_WEBHOOK_SECRET=<secreto-compartido-con-cashela>
# Cuando tengas credenciales reales (sin ellas se usa el provider mock):
# CASHELA_API_URL=https://api.cashela.com
# CASHELA_API_KEY=ck_live_...
# Producción: desactiva el modo demo (endpoints /dev y simulador de webhooks)
DEMO_MODE=falseSin
DATABASE_URLel API arranca en modo memoria con datos demo. En Railway, con el plugin de Postgres, siempre habráDATABASE_URL, así que corre contra la base real. DefineDEMO_MODE=falseexplícitamente en prod.
1.4 Verificar
- Logs: debe aparecer
[nexlo-api] escuchando en :4000y eldb pushsin errores. - Healthcheck:
https://<api>.up.railway.app/health→200. - (Opcional) Sembrar datos demo una vez: en Settings → Deploy → Custom Start
Command temporal, o desde la consola del servicio:
pnpm --filter @nexlo/api db:seed.
2. Web en Vercel
2.1 Crear el proyecto
-
vercel.com → Add New → Project → importa el repo.
-
Root Directory:
apps/web. Vercel detecta el monorepo pnpm y Next.js automáticamente (Framework Preset: Next.js). -
Build & Output (normalmente autodetectado, no hace falta tocar):
- Install Command:
pnpm install(Vercel lo ejecuta desde la raíz del workspace). - Build Command:
next build(opnpm build). - Output Directory:
.next.
Si el build no encuentra
@nexlo/shared, fuerza el Install Command apnpm install --frozen-lockfiley activa Include source files outside of the Root Directory (Settings → Build & Development). - Install Command:
2.2 Variables de entorno
NEXT_PUBLIC_API_URL=https://<api>.up.railway.app # URL del API en Railway
NEXT_PUBLIC_WHATSAPP_NUMBER=34600000000 # tu número de WhatsApp Business
NEXT_PUBLIC_*se inyecta en build: si la cambias, hay que redeplegar.
2.3 Multidioma (i18n) — importante
La web usa next-intl sin routing por URL: el idioma se guarda en la cookie
NEXT_LOCALE y el idioma por defecto es inglés. Implicaciones en deploy:
- No hay rutas
/en,/es… — todas las URLs son únicas y el contenido se renderiza según la cookie. Por eso todas las páginas son dinámicas (SSR), no estáticas (lo verás comoƒen el output del build). Es lo esperado. - No requiere ninguna variable de entorno adicional.
- El selector de idioma (🌐 en la navbar) cubre 10 idiomas: en, es, zh, hi, fr, pt, ru, de, ja, id. Las cadenas sin traducir caen a inglés automáticamente.
3. Wiki en Vercel
-
Add New → Project → mismo repo, Root Directory:
apps/wiki. -
Framework: Next.js (autodetectado). El
postbuildindexa la búsqueda con Pagefind automáticamente. -
Variable de entorno:
WIKI_JWT_SECRET=<el-MISMO-valor-que-en-el-API> -
La wiki está cerrada por
proxy.ts: sin token JWT válido no se entra. La única puerta es el botón «Wiki interna» del backoffice de la web, que pide un token de 15 min al API y lo cambia por una cookie de sesión de 8 h.
Para que el botón del backoffice funcione,
WEB_URL/WIKI_URLen el API yWIKI_JWT_SECRET(API ↔ wiki) deben ser coherentes entre los tres servicios.
4. Webhooks de Cashela
En el panel de Cashela, configura el endpoint de webhooks:
https://<api>.up.railway.app/webhooks/cashelacon el secreto de CASHELA_WEBHOOK_SECRET. En local, el modo demo permite
simular cada paso (payin/payout) desde la UI sin Cashela real.
5. Checklist post-despliegue
-
GET https://<api>/healthresponde200. - La web carga y la calculadora muestra una cotización (usa
@nexlo/shared). - El selector de idioma cambia el idioma y persiste al recargar (cookie).
- Registro + login funcionan (la web habla con el API → CORS con
WEB_URLok). - Crear un envío demo recorre los estados (o, en prod, llega el webhook de Cashela).
- El botón «Wiki interna» del backoffice abre la wiki (secreto compartido ok).
-
DEMO_MODE=falseen el API de producción. - Dominios propios configurados en Vercel y
WEB_URL/WIKI_URLactualizados en Railway.
6. Dominios propios (opcional)
- Vercel (web y wiki): Settings → Domains → añade
nexlo.appywiki.nexlo.app, configura los DNS que indique Vercel. - Railway (API): Settings → Networking → Custom Domain →
api.nexlo.app. - Actualiza en Railway:
WEB_URL=https://nexlo.app,WIKI_URL=https://wiki.nexlo.app; y en Vercel (web):NEXT_PUBLIC_API_URL=https://api.nexlo.app. Redespliega web y API.
7. Desarrollo local
pnpm install
pnpm dev:api # :4000 (sin DATABASE_URL → modo memoria + datos demo)
pnpm dev:web # :3000
pnpm dev:wiki # :3100
pnpm test # shared + api + webTodo-en-uno con Docker (hot-reload)
pnpm docker:dev # PostgreSQL + API + web + wiki con recarga en caliente
pnpm docker:dev:build # reconstruye la imagen tras cambiar dependencias
pnpm docker:stop # vuelca la DB a ./backups y detiene el stackCopias de seguridad de la base (fuera de Docker, en ./backups):
pnpm db:dump # backups/nexlo-<fecha>.sql.gz (+ nexlo-latest.sql.gz)
pnpm db:restore # restaura el último dump (en caliente)
pnpm db:restore backups/nexlo-XXXX.sql.gzDetalle del flujo de desarrollo y backups en README.md.