Setup Local
Cómo dejar el monorepo corriendo en tu máquina y cómo levantar cada app.
Requisitos
| Herramienta | Versión | Nota |
|---|---|---|
| Node.js | 22.13.1 (.nvmrc / .node-version) | engines exige >=22 <25. Node 25 da warning y puede romper el rango. |
| pnpm | 10.10.0 | Fijado en packageManager. Usa corepack enable para no instalarlo a mano. |
| Python | 3.12 | Solo para engine-backend (FastAPI + OR-Tools). |
| Railway CLI | reciente | Para deploy/logs de backends. railway login. |
| Wrangler (Cloudflare) | viene por dep | Para frontends y clients-backend. Auth OAuth. |
| EAS CLI (Expo) | >= 18.1.0 | Solo para publicar OTA de drivers-app. |
nvm install # toma 22.13.1 del .nvmrc
nvm use
corepack enable # habilita pnpm@10.10.0
pnpm install # instala TODO el workspace de una vezNo uses npm ni yarn
El repo es pnpm puro. Introducir un package-lock.json o yarn.lock rompe el workspace y el cacheo de Turbo.
Variables De Entorno y Secrets
Cada app trae su plantilla: .env.example (la mayoría) o .dev.vars.example (clients-backend, porque es Worker). Copia la plantilla a .env / .dev.vars y completa los valores.
cp apps/core-backend/.env.example apps/core-backend/.env
cp apps/drivers-backend/.env.example apps/drivers-backend/.env
cp apps/clients-backend/.dev.vars.example apps/clients-backend/.dev.vars
# ...etc por app¿De dónde salen los valores? Hay dos clases de variable:
- Config no sensible (URLs, flags, intervalos, nombres de reporte/función): el
.env.exampleya trae un default razonable, o se copia del dashboard de Railway (backends) / del bloquevarsdelwrangler.jsonc(Workers). - Secrets (credenciales, claves de terceros, secrets de firma/webhook): no van en el repo y varios no se ven en los dashboards. El inventario de abajo dice, para cada uno, a qué servicio pertenece y de dónde sacarlo / a quién pedírselo.
Responsable de secrets: los secrets de cuentas de proveedor y los secrets de Worker no legibles los tienen Luis Leiva o Rodney Arauz. Pídeles cualquier valor que no esté visible en los dashboards de Railway/Cloudflare (es a quien apuntan los "pedir al responsable" de las tablas).
Reglas de fuente:
- Backends en Railway (
core-backend,rnb-drivers-api,engine-backend): los valores de producción están en Railway → servicio → Variables. Con acceso al proyectornb-logisticsse copian de ahí. La fuente original de las claves de terceros es la consola del proveedor. - Workers en Cloudflare (
clients-backend,client-tracking-view): los secrets se cargan conwrangler secret puty no se pueden volver a leer (ni en el dashboard de Cloudflare). Si no los tienes, hay que regenerarlos en el proveedor o pedírselos al responsable. - Los frontends (
dash-web,clients-web,drivers-app) no tienen secrets ocultos: sus variables son públicas (VITE_*,NEXT_PUBLIC_*,EXPO_PUBLIC_*) y se embeben en el bundle. La clave de mapas del navegador (NEXT_PUBLIC_GOOGLE_MAPS_API_KEY) es una credencial pública restringida por dominio, no un secret a ocultar.
core-backend · Railway core-backend
| Variable | Qué es / proveedor | De dónde sacarla |
|---|---|---|
DATABASE_URL | Postgres compartida (Main Dashboard DB) | Railway → Postgres del proyecto |
SESSION_SECRET | Secret de sesión (interno) | Railway vars · si no, generar y rotar |
OAUTH_SIGNING_SECRET (+ _PREVIOUS) | Firma de tokens OAuth (interno) | Railway vars |
CLIENTS_RNB_API_KEY, CLIENTS_RNB_WEBHOOK_SECRET | Llave/secret hacia clients-backend | Railway vars · debe coincidir con el lado de clients-backend |
DRIVER_APP_API_KEY, DRIVER_APP_WEBHOOK_KEY | Llave/secret hacia drivers-backend | Railway vars · debe coincidir con drivers-backend |
TASK_EVENTS_WEBHOOK_SECRET, MOVIMIENTO_WEBHOOK_SECRET | Secrets de webhooks entrantes | Railway vars |
ZOHO_CREATOR_API_KEY | API key de Zoho Creator | Railway vars · origen: consola Zoho Creator |
ZOHO_WEBHOOK_SECRET, ZOHO_EVENTS_WEBHOOK_SECRET, ZOHO_TASKS_WEBHOOK_SECRET, ZOHO_MOVIMIENTO_WEBHOOK_SECRET | Secrets de webhooks de Zoho | Railway vars · acordados con la app de Zoho |
RESEND_API_KEY, RESEND_WEBHOOK_SECRET | Email transaccional (Resend) | Railway vars · origen: dashboard de Resend |
OPENAI_API_KEY | OCR/IA (OpenAI) | Railway vars · origen: platform.openai.com |
GOOGLE_MAPS_API_KEY | Google Cloud (Maps/Geocoding server-side) | Railway vars · origen: Google Cloud Console |
SATRACK_CLIENT_ID, SATRACK_CLIENT_SECRET | GPS de flota (Satrack) | Railway vars · origen: cuenta Satrack |
drivers-backend · Railway rnb-drivers-api
| Variable | Qué es / proveedor | De dónde sacarla |
|---|---|---|
DATABASE_URL | Misma Postgres que core (compartida) | Railway → Postgres del proyecto |
MAIN_APP_WEBHOOK_KEY, TASK_EVENTS_WEBHOOK_SECRET | Webhooks con core-backend | Railway vars · deben coincidir con core |
RNB_ENGINE_API_KEY, ENGINE_WEBHOOK_SECRET | Hacia/desde engine-backend | Railway vars · coinciden con el engine |
ZOHO_API_KEY | API key de Zoho | Railway vars · origen: consola Zoho |
GOOGLE_MAPS_KEY_DRIVER_APP | Google Cloud (mapa server-rendered de la app) | Railway vars · origen: Google Cloud Console |
NOTIFICATIONS_API_KEY | Servicio de notificaciones push | Railway vars |
SPARKCENTRAL_API_KEY | Mensajería (Sparkcentral, WhatsApp) | Railway vars · origen: cuenta Sparkcentral |
BUCKET_ACCESS_KEY_ID, BUCKET_SECRET_ACCESS_KEY | Credenciales Cloudflare R2 (S3) para evidencias | Railway vars · origen: Cloudflare R2 → API Tokens |
MAIN_DB_URL, DRIVERS_DB_URL | Solo el script migrate-to-main-db.ts (no runtime) | No se necesitan para correr la app |
clients-backend · Cloudflare Worker backend-clients-rnb-logistics (secrets de Worker)
Estos no se leen del dashboard; se cargan con wrangler secret put <VAR> --env <dev|prod>. La lista oficial está comentada en apps/clients-backend/wrangler.jsonc.
| Variable | Qué es / proveedor | De dónde sacarla |
|---|---|---|
JWT_SECRET | Firma de JWT de sesión (interno) | wrangler secret put · pedir/regenerar |
ADMIN_API_KEY | Llave de endpoints admin | wrangler secret put · pedir al responsable |
ZOHO_CREATOR_API_KEY, ZOHO_CREATOR_API_URL, ZOHO_EMAIL_FUNCTION_URL | Zoho Creator | origen: consola Zoho · cargar como secret |
ZOHO_WEBHOOK_SECRET | Secret de webhook de Zoho | acordado con Zoho |
MAIN_RNB_WEBHOOK_URL, MAIN_RNB_WEBHOOK_SECRET | Webhook hacia core-backend | debe coincidir con core (CLIENTS_RNB_*) |
GOOGLE_MAPS_API_KEY | Google Cloud | origen: Google Cloud Console |
CLOUDFLARE_D1_API_TOKEN (en .env, solo si DRIZZLE_REMOTE=true) | Token para migrar D1 remota | Cloudflare → My Profile → API Tokens |
client-tracking-view · Cloudflare Worker frontend-tracking-view-rnb-logistics
| Variable | Qué es / proveedor | De dónde sacarla |
|---|---|---|
GOOGLE_DIRECTIONS_API_KEY | Google Cloud (Directions) — secret de Worker | wrangler secret put GOOGLE_DIRECTIONS_API_KEY --env <dev|prod> · origen: Google Cloud Console |
DRIVERS_APP_API_URLno es secret: está comovarsenwrangler.jsonc.
engine-backend · Railway engine-backend
| Variable | Qué es / proveedor | De dónde sacarla |
|---|---|---|
ROS_DATABASE_URL (+ ROS_DATABASE_READ_URL) | Postgres propia del engine (separada de la principal) | Railway → Postgres del engine |
ROS_REDIS_URL | Redis del engine | Railway → Redis |
ROS_JWT_SECRET_KEY | Firma JWT del engine (si ROS_AUTH_ENABLED=true) | Railway vars |
ROS_INITIAL_ADMIN_PASSWORD (+ ROS_INITIAL_ADMIN_USER) | Admin inicial del engine | Railway vars |
ROS_TRAFFIC_API_KEY | Proveedor de tráfico (si ROS_TRAFFIC_API_ENABLED=true) | Railway vars · origen: proveedor configurado |
El .env local apaga cosas a propósito — no asumas que refleja prod
Los defaults del .env.example están pensados para desarrollo local, y varios comportamientos pesados vienen apagados a propósito. Ejemplo real: ROUTE_ENGINE_SESSIONS_ENABLED=false en local es intencional — evita que la optimización de ruta se regenere cada vez que cambian las tareas mientras pruebas; en producción está true y es quien gobierna el orden de ruta. Por eso, el valor productivo de un flag vive en Railway/Cloudflare, no en el .env: verifícalo ahí antes de concluir que algo está apagado, y si quieres reproducir el comportamiento de prod en local, actívalo a mano. Más casos en Gotchas.
Los secrets de Cloudflare Workers se cargan así (no van en wrangler.jsonc):
# Ejemplo: client-tracking-view usa GOOGLE_DIRECTIONS_API_KEY como secret
cd apps/client-tracking-view
npx wrangler secret put GOOGLE_DIRECTIONS_API_KEY --env prodBase De Datos
Hoy se trabaja contra la DB de producción
No existe (todavía) una base de datos de desarrollo. En la práctica, al correr core-backend / drivers-backend en local, el DATABASE_URL apunta a la Postgres de producción y se trabaja con cuidado de no escribir. Esto es un riesgo conocido.
🔧 Mejora pendiente: montar una DB de dev (dump de prod o Postgres efímera) para que el desarrollo local no toque producción. Ver propuesta en Gotchas → DB compartida.
Hechos clave de la base de datos:
core-backendydrivers-backendcomparten la MISMA Postgres (la "Main Dashboard DB"). Ambos conectan porDATABASE_URL(server/db.tsen cada uno).- El schema lo posee
core-backendvíapackages/core-shared/src/schema.ts.drivers-backendtiene su propioshared/schema.tscomo espejo;core-backendcorrescripts/check-schema-mirror.ts(dentro depnpm --filter @rnb/core-backend check) para detectar drift entre ambos. db:pushestá DESHABILITADO endrivers-backenda propósito (sale conexit 1y un mensaje). Cualquier cambio de schema se gestiona solo desdecore-backend. Atención: el flujo real de migraciones no esdb:generate/db:migrate(esos scripts existen pero están en desuso) — ver Migraciones: deuda técnica conocida abajo.MAIN_DB_URL/DRIVERS_DB_URLque ves enapps/drivers-backend/.env.exampleno son de runtime: solo los usa el script históricoscripts/migrate-to-main-db.ts, que en su momento consolidó la DB separada de drivers dentro de la principal. Ignóralos para correr la app.clients-backendes aparte: usa Cloudflare D1 (SQLite), con su propio schema Drizzle (src/db/schema.ts) y sus migraciones (db:migrate-local/db:migrate-dev/db:migrate-prod). No toca la Postgres principal.
Migraciones: deuda técnica conocida
El flujo de migraciones versionadas estándar está en desuso
Las migraciones Drizzle versionadas (migrations/0000_*.sql … 0005_*.sql + meta/_journal.json) están congeladas (el journal no avanza desde 0005), y db:generate / db:migrate no son el flujo vivo. Hoy los cambios de schema llegan a producción por dos carriles, según el tipo de cambio. El schema fuente de verdad es packages/core-shared/src/schema.ts; check-schema-mirror.ts y check-drift.ts (dentro de ... check) avisan cuando la DB y el schema divergen.
Carril 1 — Auto-migrator (aditivo, automático en cada deploy)
core-backend corre en el arranque una lista de sentencias idempotentes en server/routes.ts (ALTER TABLE … ADD COLUMN IF NOT EXISTS, CREATE INDEX IF NOT EXISTS, y algunos UPDATE de backfill). Es additive-only por diseño: agrega lo que falta, nunca altera ni borra.
→ Para un cambio aditivo (columna o índice nuevo): agrega la sentencia idempotente a esa lista en server/routes.ts. Llega a prod sola, en el próximo deploy — no hay paso manual ni db:push.
Carril 2 — SQL manual fechado (no-aditivo, aplicado a mano)
Para lo que el auto-migrator no puede hacer (ALTER COLUMN TYPE, DROP, constraints únicos con dedup, backfills/limpiezas no triviales — lo dice check-drift.ts): se escribe un archivo en apps/core-backend/migrations/manual/<YYYY-MM-DD>-<topic>.sql y se aplica a mano a la Postgres de prod con railway run (que inyecta el DATABASE_URL del servicio):
# Desde apps/core-backend, con el servicio/entorno de PROD seleccionado
railway run -- psql "$DATABASE_URL" -f migrations/manual/<YYYY-MM-DD>-<topic>.sqlEsto corre DDL directo contra producción
No hay DB de dev: railway run conecta a la Postgres de prod. Antes de ejecutar:
- Confirma el servicio/entorno con
railway status— que sea prod, no otro. - Envuelve el SQL en transacción (
BEGIN; … COMMIT;) cuando se pueda, y deja losALTER/UPDATEidempotentes o con guardas (IF NOT EXISTS,WHERE … IS NULL), como los archivos existentes. - Verifica después con
pnpm --filter @rnb/core-backend check(mirror + drift).
Cómo hacer un cambio de schema hoy (resumen)
- Edita el schema en
packages/core-shared/src/schema.ts. - Refleja el mismo cambio en
apps/drivers-backend/shared/schema.ts(para que pasecheck-schema-mirror). - Elige el carril:
- Aditivo → sentencia idempotente en el auto-migrator (
server/routes.ts); aplica en el deploy. - No-aditivo →
migrations/manual/<fecha>-<topic>.sql+railway run -- psql … -f.
- Aditivo → sentencia idempotente en el auto-migrator (
pnpm --filter @rnb/core-backend check(mirror + drift).
🔧 Mejora pendiente: retomar un flujo de migraciones versionadas estándar (que
db:generate/db:migratevuelvan a ser la fuente canónica). Esta sección documenta el estado actual, no el deseado.
Correr Cada App En Local
Hay atajos en la raíz (dev:<app>) y siempre puedes usar pnpm --filter.
# Frontends y backends Node/TS
pnpm dev:dash-web # Vite (dashboard de operaciones)
pnpm dev:core-backend # Express, NODE_ENV=development, lee .env
pnpm dev:drivers-backend # Express (API de la app de conductores)
pnpm dev:clients-web # Next.js (portal de clientes)
pnpm dev:clients-backend # wrangler dev --env dev (Worker Hono + D1 local)
# App móvil
pnpm dev:drivers-app # expo start
# Build nativo (cuando cambia código nativo, no JS):
pnpm --filter @rnb/drivers-app exec expo run:ios
# Engine (Python) — requiere venv con deps de pyproject.toml
pnpm dev:engine-backend # uvicorn ros.main:app --reload --app-dir src --port 8000
# Sitio de docs (este manual)
pnpm dev:logistics-manual # vitepress devPara el engine, primero instala las dependencias Python:
cd apps/engine-backend
python3.12 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]" # deps + extras de test (pytest, mypy)Verificar Que Todo Compila
pnpm typecheck # tsc en todos los workspaces configurados
pnpm lint
pnpm test # vitest / pytest según la app
pnpm build # build de los workspaces deployables (excluye drivers-app)Deuda conocida — no te asustes si fallan estos
drivers-backend typecheckarrastra deuda estricta de TypeScript: falla aunque build y runtime estén bien.clients-backend lintarrastra deuda de Biome/estilo: falla aunque tests, typecheck y elwrangler dry-runpasen.- Lo que sí se espera que pase siempre: el
pnpm buildde la raíz.
Detalle en Flujo de trabajo → Checks y deuda conocida.