Local development guide

Run the MercoraHub monorepo on your machine with Docker or native Node.

Source: docs/development/DOC-011-local-development-guide.md

DOC-011: Local Development Guide

Estado

Approved

Audiencia

Desarrolladores humanos y agentes IA que implementan features en el monorepo MercoraHub.

Objetivo

Un solo documento con qué instalar, qué comandos ejecutar y cómo verificar que el entorno funciona antes de escribir código.

Referencias: ADR-010 (Docker), ADR-041 (Makefile/ESLint), ADR-002 (pnpm), ADR-009 (OpenSpec).


1. Prerrequisitos

HerramientaVersión mínimaVerificar
Node.js20+node -v
pnpm9.15+pnpm -v
Docker Desktop / EngineCompose v2docker compose version
makecualquieramake -v
Git2.xgit --version
OpenSpec CLIopcionalopenspec --version

Activar pnpm (una vez por máquina):

corepack enable
corepack prepare [email protected] --activate

2. Primer arranque (clone nuevo)

Desde la raíz del repositorio:

# 1. Dependencias del monorepo
pnpm install

# 2. Variables Docker (stack ADR-010)
cp devops/docker/.env.docker.example devops/docker/.env.docker

# 3. Variables nativas opcionales (dev en host)
cp .env.example .env.local

# 4. Diagnóstico
make doctor

# 5. (Opcional) Referencia Metronic admin — gitignored, spike 003
make metronic-reference

Si make doctor falla, corregir lo indicado antes de continuar.


3. Modos de desarrollo

Hay dos flujos válidos. Elige según tu máquina y tarea.

Modo A — Stack completo en Docker (canónico ADR-010)

Recomendado para onboarding, agentes IA y entorno idéntico entre devs.

Primera vez (o cambiaste Dockerfile / compose)

make local-up-build    # construye imágenes + levanta (logs en consola)
# o en background:
make local-up-d-build

Día a día (sin cambios en Docker)

make local-up          # reutiliza imágenes existentes — sin rebuild
# o:
make local-up-d        # detached

make local-up no pasa --build: arranca contenedores ya creados en segundos. Solo reconstruye si usas *-build o cambias Dockerfile.dev / docker-compose.yml.

En otra terminal (mientras local-up corre en foreground):

make local-status
ServicioURL
API live / readyhttp://localhost:3000/api/v1/health/live · /api/v1/health/ready (alias /api/v1/health)
API metricshttp://localhost:3000/api/v1/metrics (Bearer opcional METRICS_BEARER_TOKEN)
Webhttp://localhost:3001
Helphttp://localhost:3002/en (también /es; selector bandera ↔ URL)
Developer Docshttp://localhost:3003/en (también /es; incluido en make local-up — servicio docs)
API Referencehttp://localhost:3003/en/docs/api (OpenAPI desde packages/api-contract/openapi.yaml)
Mailpit UIhttp://localhost:8025
PostgreSQLlocalhost:5432 (user/pass/db: foodhub)
Redislocalhost:6379
Grafana (ops)http://localhost:3003 (make observability-up) · Prometheus :9090 · Loki :3100

Observability stack (opcional)

make observability-up    # Loki :3100 · Prometheus :9090 · Grafana :3003 (admin/admin; anónimo Viewer)
make observability-down

Grafana home = MercoraHub Ops Home (KPIs Prometheus + links). Logs: dashboard API Logs (safe) — ventana default 15m, max 100 líneas (evitar Explore con rangos largos). Promtail solo scrapea foodhub-api y dropea backlog >2h.

Guía completa (conceptos + /api/v1/metrics + recetas): docs/ops/go-live/observability-stack.md.
Smoke/validación: specs/033-go-live-hardening/quickstart.md. Estrategia: ADR-018. Sin Sentry en este slice.

Background (sin bloquear la terminal — como antes):

make local-up-d      # detached (-d)
make local-logs        # seguir logs api + web + help + docs

Detener:

# Si usaste local-up en foreground: Ctrl+C, luego:
make local-down

# Si usaste local-up-d:
make local-down
# o solo infra si levantaste con make infra-up:
make infra-down

Logs (solo si levantaste con local-up-d):

make local-logs      # api + web + help + docs en vivo

Modo B — Infra Docker + apps en el host (HMR más rápido en Mac)

make infra-up
pnpm dev             # Turborepo: api :3000 + web :3001 + help :3002 + docs :3003

Usar .env.local con hosts localhost (ver .env.example).


4. Comandos del día a día

Quiero…Comando
Ver ayudamake help
Verificar entornomake doctor
Levantar solo DB/Redis/Mailpitmake infra-up
Levantar todo (logs, sin rebuild)make local-up
Levantar todo (background, sin rebuild)make local-up-d
Rebuild imágenes + levantarmake local-up-build o make local-up-d-build
Limpiar cache Next (web+help+docs)make local-ui-reset
Reset deps Docker (módulos + Next)make local-reset-deps → luego local-up-*-build
Estado / healthmake local-status
Logs (tras local-up-d)make local-logs
Lintmake lint o pnpm lint
Typecheckmake typecheck
Buildmake build
Testsmake test o pnpm test
Calidad pre-PR (igual que CI)make check
Formatomake format
Instalar depsmake install

4.0 Tras git pull / switch a master

El código fuente viaja en git. La cache Next (.next), los node_modules dentro de volúmenes Docker y la DB local no. Un pull actualiza el bind mount, pero web_next_cache / help_next_cache / docs_next_cache pueden seguir sirviendo bundles viejos.

git pull --ff-only   # o checkout master + pull
pnpm install         # si cambió pnpm-lock.yaml
make local-up-d      # o ya estaba corriendo — HMR suele alcanzar
# Si la UI se ve rara (404 de rutas nuevas, chrome viejo, versión inconsistente):
make local-ui-reset
# Si `Cannot find module` / lockfile cambió fuerte:
make local-reset-deps && make local-up-d-build
# Schema Prisma nuevo:
make local-db-migrate   # o dejar que el watcher del api lo haga
make local-status

local-ui-reset no borra Postgres ni volúmenes node_modules. La primera página tras el reset puede tardar ~10–30 s (recompile).

Equivalentes pnpm (sin Make):

pnpm docker:up          # solo infra
pnpm docker:local         # stack dev, foreground, sin rebuild
pnpm docker:local:d       # detached, sin rebuild
pnpm docker:local:build   # foreground + --build
pnpm docker:local:down

4.1 Git hooks (Husky)

Tras pnpm install, Husky instala hooks automáticamente:

HookQué hace
pre-commitESLint + Prettier en archivos staged (lint-staged)
pre-pushtypecheck + test

Antes de abrir PR, puedes correr la suite completa (incluye build):

make check    # lint → typecheck → test → build (mismo orden que CI)

CI en GitHub es la fuente de verdad para merge. Los hooks dan feedback rápido local.

Saltar hooks (solo emergencias): git commit --no-verify / git push --no-verify. Configura branch protection en GitHub — ver .github/BRANCH_PROTECTION.md.


5. Trabajar en una feature

MercoraHub es spec-first (ADR-009). Flujo completo: DOC-012.

Features de dominio (auth, tenant, menu, orders, payments):

/fh-init-session
/fh-feature-brief FEATURE-001    # → brief.md + gate APROBADO
/opsx:propose auth-foundation    # lee el brief aprobado
/opsx:apply
make check
/opsx:archive
/fh-wrap

Infra / DX (CI, docker, monorepo): OpenSpec directo — sin brief.

/fh-init-session
/opsx:propose mi-change
/opsx:apply
make check
/fh-wrap

Fuentes de verdad al codificar:

TemaDónde
Flujo desarrolloDOC-012
Brief de featuredocs/product/features/*/brief.md
Patrones de código.cursor/rules/foodhub-code-patterns.mdc
Dominio.cursor/rules/foodhub-domain.mdc, DOC-002
APIDOC-005, ADR-014
BacklogDOC-009

6. Mobile (Expo)

Expo no corre en Docker Compose (simulador/dispositivo en el host).

pnpm --filter @foodhub/mobile dev

Requiere infra levantada si la app consume API local (make infra-up o stack completo).


7. Prisma y base de datos (auth-foundation)

Requiere Postgres levantado (make infra-up o make local-up).

# Desde la raíz del monorepo
export DATABASE_URL="postgresql://foodhub:foodhub@localhost:5432/foodhub"

pnpm --filter @foodhub/api db:generate   # generar cliente Prisma
pnpm --filter @foodhub/api db:migrate      # migraciones (dev)
pnpm --filter @foodhub/api db:seed         # datos demo
pnpm --filter @foodhub/api db:validate     # validar schema (CI)

Credenciales demo (seed): ver catálogo completo en DOC-016 — Local demo credentials.

Resumen:

Rol / usotenantemailpassword
Platform Admin[email protected]Alex1234!
OWNER — dashboard fulldemo[email protected]Demo1234!
KITCHEN — dashboard orders-onlydemo[email protected]Demo1234!
Courier network (Casey)[email protected]Demo1234!
Courier fleet (activo)demo[email protected]Demo1234!
Customer (checkout / order history)[email protected]Customer123!

Los formularios de login no rellenan estas credenciales automáticamente.

El seed también puebla S-057 Dispatch Board (/t/demo/dispatch) con 7 deliveries activas (UNASSIGNED / OFFERED / ASSIGNED / PICKED_UP / IN_TRANSIT / FAILED) y un roster de couriers online con carga vs capacidad.

Probar login (API en puerto 3000 o el que uses con API_PORT):

curl -X POST http://localhost:3000/api/v1/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"tenantSlug":"demo","email":"[email protected]","password":"Demo1234!"}'

Variables de autenticación (Turnstile + email verification)

VariableDóndeDescripción
JWT_ACCESS_SECRETAPISecreto para firmar JWT (tenant, customer, platform). Generar valor fuerte; mismo valor en .env.local y devops/docker/.env.docker.
EMAIL_VERIFICATION_REQUIREDAPItrue por defecto. false omite verify en registro/login (dev/CI).
TURNSTILE_SECRET_KEYAPISecret Cloudflare Turnstile (ADR-043). Vacío o TURNSTILE_ENABLED=false → bypass.
TURNSTILE_ENABLEDAPItrue por defecto.
NEXT_PUBLIC_TURNSTILE_SITE_KEYWebSite key pública del widget Turnstile.
WEB_URLAPIBase URL web para links en emails (reset, verify). Ej. http://localhost:3001.
MAILPIT_UI_URLUI Mailpit local: http://localhost:8025
STRIPE_SECRET_KEYAPIStripe test secret (sk_test_…). Requerido para Connect, checkout y Stripe Tax.
STRIPE_WEBHOOK_SECRETAPISigning secret (whsec_…) de stripe listen o Dashboard webhook.
STRIPE_PRICE_LITE_MONTHLYAPIPrice ID Stripe (price_…) plan LITE — seed → plans.stripe_price_id_monthly (ADR-021).
STRIPE_PRICE_PLUS_MONTHLYAPIPrice ID plan PLUS.
STRIPE_PRICE_PREMIUM_MONTHLYAPIPrice ID plan PREMIUM.
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEYWebPublishable key (pk_test_…). Requerido para Payment Element de tips post-pedido (S-065). Checkout de pedido sigue hosted sin Elements.

La cuenta Connect (acct_…) no va en env — se persiste en tenants.stripe_connect_account_id tras onboarding S-032. Ver DOC-014 §3.0.

Referencia completa: .env.example (nativo) y devops/docker/.env.docker.example (Docker).

Stripe Connect + checkout local (FEAT-016 / ADR-040)

Guía operativa completa (onboarding S-032, Stripe Tax, datos test, disconnect, troubleshooting): DOC-014 — Stripe Connect Onboarding Guide.

Para probar pagos de pedidos (POST .../orders/checkout + webhook):

  1. Configura en devops/docker/.env.docker:
    • STRIPE_SECRET_KEY=sk_test_… (obligatorio para pagos)
    • STRIPE_WEBHOOK_SECRETno hace falta en Docker; el servicio foodhub-stripe-cli escribe el whsec_… en un volumen compartido y la API lo lee en cada webhook (STRIPE_WEBHOOK_SECRET_FILE).
  2. Levanta el stack: make local-up o make local-up-d — incluye automáticamente foodhub-stripe-cli (perfil dev).
  3. Verifica: make local-status✓ Stripe CLI (webhook secret synced).
  4. Onboarding obligatorio: login [email protected]/t/demo/settings/paymentsConnect with Stripe → completar KYC test → badge Active
  5. Stripe Tax (tax-preview / checkout): configurar en la cuenta Connect del tenant — ver DOC-014 §3.4
  6. Smoke: menú → carrito → /checkout → tarjeta test 4242 4242 4242 4242 → confirmación.
  7. Verifica:
    • Tenant admin /t/demo/orderspayment_status = PAID
    • Mailpit http://localhost:8025 → email S-034 si el checkout incluyó email

Modo nativo (pnpm dev en host, sin contenedor API): en otra terminal, make stripe-listen y copia el whsec_… a STRIPE_WEBHOOK_SECRET en .env.local.

Sin STRIPE_SECRET_KEY: el contenedor foodhub-stripe-cli queda idle (sleep) — el resto del stack funciona; checkout devuelve STRIPE_NOT_CONFIGURED o STRIPE_CONNECT_NOT_READY.

<details> <summary>Manual `stripe listen` (legacy / troubleshooting)</summary>

Si preferís el CLI en el host en lugar del contenedor:

stripe listen --forward-to localhost:3000/api/v1/webhooks/stripe --api-key "$STRIPE_SECRET_KEY"

Copia el whsec_… en STRIPE_WEBHOOK_SECRET y reinicia la API. Cada reinicio del CLI genera un secret nuevo.

</details>

Stripe Billing SaaS (FEAT-018 / ADR-021 / S-016)

Suscripción de plataforma (distinto de pagos de pedidos ADR-040). Webhooks discriminan metadata.type: platform_billing.

Prerrequisitos

  1. STRIPE_SECRET_KEY en devops/docker/.env.docker + stack con foodhub-stripe-cli (make local-status → webhook secret synced).
  2. Price IDs en env (seed los copia a plans.stripe_price_id_monthly):
VariablePlanMonto seed
STRIPE_PRICE_LITE_MONTHLYLITE$49/mo
STRIPE_PRICE_PLUS_MONTHLYPLUS$99/mo
STRIPE_PRICE_PREMIUM_MONTHLYPREMIUM$199/mo

Crear precios en Stripe test (Dashboard → Products o CLI stripe prices create con recurring[interval]=month). Tras editar env, re-seed:

./devops/docker/compose exec api pnpm --filter @foodhub/api db:seed
  1. Customer Portal activo en Stripe Dashboard (test mode) — Settings → Billing → Customer portal.

Smoke manual

  1. Login [email protected] / Demo1234!/t/demo/billing
  2. Subscribe en un plan (ej. Lite) → Stripe Checkout → tarjeta test 4242 4242 4242 4242
  3. Retorno a /t/demo/billing?session=complete → plan Lite + CTA Manage billing
  4. Manage billing → Stripe Customer Portal (tarjeta, facturas, cancelar)
  5. Verificar webhook: make local-logscheckout.session.completed y customer.subscription.* con HTTP 200
  6. RBAC: ADMIN puede leer suscripción; checkout/portal → 403 (cubierto en tenant-saas-billing.e2e-spec.ts)

Sin Price IDs: POST /tenant/billing/checkout devuelve 422 BILLING_PLAN_NOT_AVAILABLE.

Playwright E2E — flujo consumidor (SLICE-007-08)

Smoke Playwright del journey S-020 → S-021 → S-030 → post-pago → receipt → S-031 track. Spec: apps/web/e2e/consumer-order-flow.spec.ts.

Prerrequisitos

  1. Stack levantado: make local-up (API + web + DB seed).
  2. Para smoke pre-checkout (sin Stripe): basta con el stack.
  3. Para flujo completo con pago:
    • Connect Active en /t/demo/settings/payments (DOC-014 §5.5)
    • STRIPE_SECRET_KEY en .env.docker + stack Docker (foodhub-stripe-cli auto)
    • E2E_STRIPE_CHECKOUT=1

Instalación (una vez)

pnpm --filter @foodhub/web test:e2e:install

Comandos

# Smoke pre-checkout (menú → cart tax → checkout validation)
E2E_SKIP_WEB_SERVER=1 pnpm --filter @foodhub/web test:e2e e2e/consumer-order-flow.spec.ts

# Flujo completo con Stripe test card 4242…
E2E_SKIP_WEB_SERVER=1 E2E_STRIPE_CHECKOUT=1 pnpm --filter @foodhub/web test:e2e e2e/consumer-order-flow.spec.ts
VariableDefaultDescripción
E2E_BASE_URLhttp://localhost:3001URL base web
E2E_SKIP_WEB_SERVERunset1 = no levantar pnpm dev (usar Docker)
E2E_PUBLIC_LOCATION_SLUGbbq-smoke-pitLocation demo
E2E_DEMO_PRODUCT_NAMEChef SpecialProducto a añadir
E2E_STRIPE_CHECKOUTunset1 habilita test de pago Stripe

Los tests usan test.skip() si el stack no responde — no fallan en entornos sin Docker.

CI (GitHub Actions)

Workflow .github/workflows/e2e-web.yml — job e2e-web en PR a master (paths: apps/web, apps/api, packages, devops):

  1. Service containers Postgres + Redis (sin Docker dev stack — arranque más rápido en CI)
  2. prisma migrate deploy + db seed en el runner
  3. API + Web nativos en background (devops/scripts/start-native-e2e-stack.sh)
  4. Playwright: tenant-orders.spec.ts + customer-order-history.spec.ts

Turnstile y email verification desactivados en .env.e2e / devops/docker/.env.e2e (no mutar .env.docker de dev).

Reproducir localmente:

cp .env.e2e.example .env.e2e
cp devops/docker/.env.e2e.example devops/docker/.env.e2e
pnpm --filter @foodhub/web exec playwright install chromium

make e2e-web-ci          # Docker stack con .env.e2e + seed + Playwright (recomendado)
# o por pasos:
make e2e-stack-up        # solo stack E2E
make e2e-web             # Playwright (carga .env.e2e)

Specs /platform: requieren PLATFORM_ADMIN_EMAIL / PLATFORM_ADMIN_PASSWORD en .env.e2e (ya vienen en el .example con las credenciales del seed). Sin ellas los tests se saltan.

Dev normal: .env.local — Turnstile y verify email como prefieras.
No mezclar: make local-up usa .env.docker; E2E usa .env.e2e.

Tras cambiar variables en Docker dev (no E2E):

./devops/docker/compose --profile dev up -d --force-recreate api web

Swagger UI (desarrollo)

Con la API levantada y SWAGGER_ENABLED=true (Docker dev lo incluye por defecto):

Postman: Import → Link → http://localhost:3000/api/docs-json

Try it out: abre /api/docs, expande POST /api/v1/auth/login, usa credenciales demo de la tabla anterior.

El contrato fuente de verdad sigue en packages/api-contract/openapi.yaml. Swagger UI lo carga en runtime (contract-first). En NODE_ENV=production Swagger está deshabilitado salvo SWAGGER_ENABLED=true.

Nota arquitectura: apps/api/prisma/ es capa DDL (migraciones). La lógica de negocio vive en apps/api/src/modules/* (hexagonal). Prisma 6.x en Release 1 — ver spike 001.


8. Comandos dentro de contenedores

Cuando existan tests e2e, etc. (ADR-010):

# Ejemplos futuros — ejecutar desde raíz
devops/docker/compose exec api pnpm prisma migrate dev
devops/docker/compose exec api pnpm test

9. Troubleshooting

SíntomaAcción
Missing .env.dockercp devops/docker/.env.docker.example devops/docker/.env.docker
Puerto 5432 ocupadoCambiar POSTGRES_PORT en .env.docker o parar otro Postgres
Cambiaste package.json / depspnpm install en host; en Docker el entrypoint reinstala al arrancar si hace falta
Cambiaste Dockerfile o composemake local-up-build
API no responde tras local-upmake local-logs; esperar arranque Nest/Next; docker ps
Tras pull: rutas 404 / UI o chrome desactualizadomake local-ui-reset (cache .next stale en volúmenes Docker o host)
Cannot find module en API Dockermake local-reset-deps && make local-up-d-build (volumen node_modules desactualizado)
pnpm install fallanode -v >= 20; borrar node_modules y reinstalar
Lint fallapnpm lint en el package afectado
HMR lento en Docker (Mac)Usar Modo B (infra Docker + pnpm dev)
Checkout 422 STRIPE_CONNECT_NOT_READYTenant sin Connect active — completar onboarding S-032 (/t/demo/settings/payments)
tax-preview 422 STRIPE_TAX_CALCULATION_FAILEDStripe Tax no activo en cuenta Connect — DOC-014 §3.4 (head office + registration)
Orden queda UNPAID tras pagarmake local-status → Stripe CLI synced; STRIPE_SECRET_KEY en .env.docker; logs: make local-logs
stripe listen manual / hostPreferir Docker foodhub-stripe-cli; nativo: make stripe-listen + STRIPE_WEBHOOK_SECRET en .env.local

10. Referencia Metronic (admin UI, opcional)

Spike 003-metronic-admin-shell: Metronic v9.5 es benchmark UX, no código de producción.

ComandoResultado
make metronic-referenceCopia paquete completo Metronic a docs/ux/references/external/metronic-v9.5.0/ (gitignored, ~1 GB)
make doctorConfirma perfil full + manifest
METRONIC_REFERENCE_PROFILE=admin make metronic-referenceSubset ligero (~450 MB) solo si falta espacio

Documentación: docs/ux/references/metronic/README.md · Catálogo flujos: REFERENCE-CATALOG.md

Si el zip no está en ~/Downloads/MercoraHub/metronic-v9.5.0:

export METRONIC_SOURCE="/ruta/extraida/metronic-v9.5.0"
make metronic-reference

11. Checklist rápido “¿listo para trabajar?”

  • make doctor → Environment OK
  • make local-up o make infra-up + pnpm dev (primera vez: make local-up-build)
  • make local-status → API y Web OK (si usas Modo A)
  • Change OpenSpec activo o story DOC-009 clara
  • Leíste ADRs relacionados a la tarea
  • Admin UI: make metronic-reference + wireframe S-018 aprobado

Referencias