Skip to content

Setup Local

Cómo dejar el monorepo corriendo en tu máquina y cómo levantar cada app.

Requisitos

HerramientaVersiónNota
Node.js22.13.1 (.nvmrc / .node-version)engines exige >=22 <25. Node 25 da warning y puede romper el rango.
pnpm10.10.0Fijado en packageManager. Usa corepack enable para no instalarlo a mano.
Python3.12Solo para engine-backend (FastAPI + OR-Tools).
Railway CLIrecientePara deploy/logs de backends. railway login.
Wrangler (Cloudflare)viene por depPara frontends y clients-backend. Auth OAuth.
EAS CLI (Expo)>= 18.1.0Solo para publicar OTA de drivers-app.
bash
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 vez

No 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.

bash
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:

  1. Config no sensible (URLs, flags, intervalos, nombres de reporte/función): el .env.example ya trae un default razonable, o se copia del dashboard de Railway (backends) / del bloque vars del wrangler.jsonc (Workers).
  2. 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 proyecto rnb-logistics se 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 con wrangler secret put y 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

VariableQué es / proveedorDe dónde sacarla
DATABASE_URLPostgres compartida (Main Dashboard DB)Railway → Postgres del proyecto
SESSION_SECRETSecret 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_SECRETLlave/secret hacia clients-backendRailway vars · debe coincidir con el lado de clients-backend
DRIVER_APP_API_KEY, DRIVER_APP_WEBHOOK_KEYLlave/secret hacia drivers-backendRailway vars · debe coincidir con drivers-backend
TASK_EVENTS_WEBHOOK_SECRET, MOVIMIENTO_WEBHOOK_SECRETSecrets de webhooks entrantesRailway vars
ZOHO_CREATOR_API_KEYAPI key de Zoho CreatorRailway vars · origen: consola Zoho Creator
ZOHO_WEBHOOK_SECRET, ZOHO_EVENTS_WEBHOOK_SECRET, ZOHO_TASKS_WEBHOOK_SECRET, ZOHO_MOVIMIENTO_WEBHOOK_SECRETSecrets de webhooks de ZohoRailway vars · acordados con la app de Zoho
RESEND_API_KEY, RESEND_WEBHOOK_SECRETEmail transaccional (Resend)Railway vars · origen: dashboard de Resend
OPENAI_API_KEYOCR/IA (OpenAI)Railway vars · origen: platform.openai.com
GOOGLE_MAPS_API_KEYGoogle Cloud (Maps/Geocoding server-side)Railway vars · origen: Google Cloud Console
SATRACK_CLIENT_ID, SATRACK_CLIENT_SECRETGPS de flota (Satrack)Railway vars · origen: cuenta Satrack

drivers-backend · Railway rnb-drivers-api

VariableQué es / proveedorDe dónde sacarla
DATABASE_URLMisma Postgres que core (compartida)Railway → Postgres del proyecto
MAIN_APP_WEBHOOK_KEY, TASK_EVENTS_WEBHOOK_SECRETWebhooks con core-backendRailway vars · deben coincidir con core
RNB_ENGINE_API_KEY, ENGINE_WEBHOOK_SECRETHacia/desde engine-backendRailway vars · coinciden con el engine
ZOHO_API_KEYAPI key de ZohoRailway vars · origen: consola Zoho
GOOGLE_MAPS_KEY_DRIVER_APPGoogle Cloud (mapa server-rendered de la app)Railway vars · origen: Google Cloud Console
NOTIFICATIONS_API_KEYServicio de notificaciones pushRailway vars
SPARKCENTRAL_API_KEYMensajería (Sparkcentral, WhatsApp)Railway vars · origen: cuenta Sparkcentral
BUCKET_ACCESS_KEY_ID, BUCKET_SECRET_ACCESS_KEYCredenciales Cloudflare R2 (S3) para evidenciasRailway vars · origen: Cloudflare R2 → API Tokens
MAIN_DB_URL, DRIVERS_DB_URLSolo 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.

VariableQué es / proveedorDe dónde sacarla
JWT_SECRETFirma de JWT de sesión (interno)wrangler secret put · pedir/regenerar
ADMIN_API_KEYLlave de endpoints adminwrangler secret put · pedir al responsable
ZOHO_CREATOR_API_KEY, ZOHO_CREATOR_API_URL, ZOHO_EMAIL_FUNCTION_URLZoho Creatororigen: consola Zoho · cargar como secret
ZOHO_WEBHOOK_SECRETSecret de webhook de Zohoacordado con Zoho
MAIN_RNB_WEBHOOK_URL, MAIN_RNB_WEBHOOK_SECRETWebhook hacia core-backenddebe coincidir con core (CLIENTS_RNB_*)
GOOGLE_MAPS_API_KEYGoogle Cloudorigen: Google Cloud Console
CLOUDFLARE_D1_API_TOKEN (en .env, solo si DRIZZLE_REMOTE=true)Token para migrar D1 remotaCloudflare → My Profile → API Tokens

client-tracking-view · Cloudflare Worker frontend-tracking-view-rnb-logistics

VariableQué es / proveedorDe dónde sacarla
GOOGLE_DIRECTIONS_API_KEYGoogle Cloud (Directions) — secret de Workerwrangler secret put GOOGLE_DIRECTIONS_API_KEY --env <dev|prod> · origen: Google Cloud Console

DRIVERS_APP_API_URL no es secret: está como vars en wrangler.jsonc.

engine-backend · Railway engine-backend

VariableQué es / proveedorDe dónde sacarla
ROS_DATABASE_URL (+ ROS_DATABASE_READ_URL)Postgres propia del engine (separada de la principal)Railway → Postgres del engine
ROS_REDIS_URLRedis del engineRailway → Redis
ROS_JWT_SECRET_KEYFirma JWT del engine (si ROS_AUTH_ENABLED=true)Railway vars
ROS_INITIAL_ADMIN_PASSWORD (+ ROS_INITIAL_ADMIN_USER)Admin inicial del engineRailway vars
ROS_TRAFFIC_API_KEYProveedor 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):

bash
# 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 prod

Base 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-backend y drivers-backend comparten la MISMA Postgres (la "Main Dashboard DB"). Ambos conectan por DATABASE_URL (server/db.ts en cada uno).
  • El schema lo posee core-backend vía packages/core-shared/src/schema.ts. drivers-backend tiene su propio shared/schema.ts como espejo; core-backend corre scripts/check-schema-mirror.ts (dentro de pnpm --filter @rnb/core-backend check) para detectar drift entre ambos.
  • db:push está DESHABILITADO en drivers-backend a propósito (sale con exit 1 y un mensaje). Cualquier cambio de schema se gestiona solo desde core-backend. Atención: el flujo real de migraciones no es db:generate / db:migrate (esos scripts existen pero están en desuso) — ver Migraciones: deuda técnica conocida abajo.
  • MAIN_DB_URL / DRIVERS_DB_URL que ves en apps/drivers-backend/.env.example no son de runtime: solo los usa el script histórico scripts/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-backend es 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):

bash
# Desde apps/core-backend, con el servicio/entorno de PROD seleccionado
railway run -- psql "$DATABASE_URL" -f migrations/manual/<YYYY-MM-DD>-<topic>.sql

Esto 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 los ALTER/UPDATE idempotentes 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)

  1. Edita el schema en packages/core-shared/src/schema.ts.
  2. Refleja el mismo cambio en apps/drivers-backend/shared/schema.ts (para que pase check-schema-mirror).
  3. Elige el carril:
    • Aditivo → sentencia idempotente en el auto-migrator (server/routes.ts); aplica en el deploy.
    • No-aditivomigrations/manual/<fecha>-<topic>.sql + railway run -- psql … -f.
  4. pnpm --filter @rnb/core-backend check (mirror + drift).

🔧 Mejora pendiente: retomar un flujo de migraciones versionadas estándar (que db:generate / db:migrate vuelvan 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.

bash
# 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 dev

Para el engine, primero instala las dependencias Python:

bash
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

bash
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 typecheck arrastra deuda estricta de TypeScript: falla aunque build y runtime estén bien.
  • clients-backend lint arrastra deuda de Biome/estilo: falla aunque tests, typecheck y el wrangler dry-run pasen.
  • Lo que se espera que pase siempre: el pnpm build de la raíz.

Detalle en Flujo de trabajo → Checks y deuda conocida.

Borrador vivo basado en codigo. Validar con operaciones antes de convertir en procedimiento oficial.