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
| Herramienta | Versión mínima | Verificar |
|---|---|---|
| Node.js | 20+ | node -v |
| pnpm | 9.15+ | pnpm -v |
| Docker Desktop / Engine | Compose v2 | docker compose version |
| make | cualquiera | make -v |
| Git | 2.x | git --version |
| OpenSpec CLI | opcional | openspec --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
| Servicio | URL |
|---|---|
| API live / ready | http://localhost:3000/api/v1/health/live · /api/v1/health/ready (alias /api/v1/health) |
| API metrics | http://localhost:3000/api/v1/metrics (Bearer opcional METRICS_BEARER_TOKEN) |
| Web | http://localhost:3001 |
| Help | http://localhost:3002/en (también /es; selector bandera ↔ URL) |
| Developer Docs | http://localhost:3003/en (también /es; incluido en make local-up — servicio docs) |
| API Reference | http://localhost:3003/en/docs/api (OpenAPI desde packages/api-contract/openapi.yaml) |
| Mailpit UI | http://localhost:8025 |
| PostgreSQL | localhost:5432 (user/pass/db: foodhub) |
| Redis | localhost: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 ayuda | make help |
| Verificar entorno | make doctor |
| Levantar solo DB/Redis/Mailpit | make infra-up |
| Levantar todo (logs, sin rebuild) | make local-up |
| Levantar todo (background, sin rebuild) | make local-up-d |
| Rebuild imágenes + levantar | make 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 / health | make local-status |
Logs (tras local-up-d) | make local-logs |
| Lint | make lint o pnpm lint |
| Typecheck | make typecheck |
| Build | make build |
| Tests | make test o pnpm test |
| Calidad pre-PR (igual que CI) | make check |
| Formato | make format |
| Instalar deps | make 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:
| Hook | Qué hace |
|---|---|
| pre-commit | ESLint + Prettier en archivos staged (lint-staged) |
| pre-push | typecheck + 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:
| Tema | Dónde |
|---|---|
| Flujo desarrollo | DOC-012 |
| Brief de feature | docs/product/features/*/brief.md |
| Patrones de código | .cursor/rules/foodhub-code-patterns.mdc |
| Dominio | .cursor/rules/foodhub-domain.mdc, DOC-002 |
| API | DOC-005, ADR-014 |
| Backlog | DOC-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 / uso | tenant | password | |
|---|---|---|---|
| Platform Admin | — | [email protected] | Alex1234! |
| OWNER — dashboard full | demo | [email protected] | Demo1234! |
| KITCHEN — dashboard orders-only | demo | [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)
| Variable | Dónde | Descripción |
|---|---|---|
JWT_ACCESS_SECRET | API | Secreto para firmar JWT (tenant, customer, platform). Generar valor fuerte; mismo valor en .env.local y devops/docker/.env.docker. |
EMAIL_VERIFICATION_REQUIRED | API | true por defecto. false omite verify en registro/login (dev/CI). |
TURNSTILE_SECRET_KEY | API | Secret Cloudflare Turnstile (ADR-043). Vacío o TURNSTILE_ENABLED=false → bypass. |
TURNSTILE_ENABLED | API | true por defecto. |
NEXT_PUBLIC_TURNSTILE_SITE_KEY | Web | Site key pública del widget Turnstile. |
WEB_URL | API | Base URL web para links en emails (reset, verify). Ej. http://localhost:3001. |
MAILPIT_UI_URL | — | UI Mailpit local: http://localhost:8025 |
STRIPE_SECRET_KEY | API | Stripe test secret (sk_test_…). Requerido para Connect, checkout y Stripe Tax. |
STRIPE_WEBHOOK_SECRET | API | Signing secret (whsec_…) de stripe listen o Dashboard webhook. |
STRIPE_PRICE_LITE_MONTHLY | API | Price ID Stripe (price_…) plan LITE — seed → plans.stripe_price_id_monthly (ADR-021). |
STRIPE_PRICE_PLUS_MONTHLY | API | Price ID plan PLUS. |
STRIPE_PRICE_PREMIUM_MONTHLY | API | Price ID plan PREMIUM. |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY | Web | Publishable 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):
- Configura en
devops/docker/.env.docker:STRIPE_SECRET_KEY=sk_test_…(obligatorio para pagos)STRIPE_WEBHOOK_SECRET— no hace falta en Docker; el serviciofoodhub-stripe-cliescribe elwhsec_…en un volumen compartido y la API lo lee en cada webhook (STRIPE_WEBHOOK_SECRET_FILE).
- Levanta el stack:
make local-upomake local-up-d— incluye automáticamentefoodhub-stripe-cli(perfildev). - Verifica:
make local-status→✓ Stripe CLI (webhook secret synced). - Onboarding obligatorio: login
[email protected]→/t/demo/settings/payments→ Connect with Stripe → completar KYC test → badge Active - Stripe Tax (tax-preview / checkout): configurar en la cuenta Connect del tenant — ver DOC-014 §3.4
- Smoke: menú → carrito →
/checkout→ tarjeta test4242 4242 4242 4242→ confirmación. - Verifica:
- Tenant admin
/t/demo/orders→payment_status = PAID - Mailpit http://localhost:8025 → email S-034 si el checkout incluyó email
- Tenant admin
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.
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.
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
STRIPE_SECRET_KEYendevops/docker/.env.docker+ stack confoodhub-stripe-cli(make local-status→ webhook secret synced).- Price IDs en env (seed los copia a
plans.stripe_price_id_monthly):
| Variable | Plan | Monto seed |
|---|---|---|
STRIPE_PRICE_LITE_MONTHLY | LITE | $49/mo |
STRIPE_PRICE_PLUS_MONTHLY | PLUS | $99/mo |
STRIPE_PRICE_PREMIUM_MONTHLY | PREMIUM | $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
- Customer Portal activo en Stripe Dashboard (test mode) — Settings → Billing → Customer portal.
Smoke manual
- Login
[email protected]/Demo1234!→/t/demo/billing - Subscribe en un plan (ej. Lite) → Stripe Checkout → tarjeta test
4242 4242 4242 4242 - Retorno a
/t/demo/billing?session=complete→ plan Lite + CTA Manage billing - Manage billing → Stripe Customer Portal (tarjeta, facturas, cancelar)
- Verificar webhook:
make local-logs→checkout.session.completedycustomer.subscription.*con HTTP 200 - 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
- Stack levantado:
make local-up(API + web + DB seed). - Para smoke pre-checkout (sin Stripe): basta con el stack.
- Para flujo completo con pago:
- Connect Active en
/t/demo/settings/payments(DOC-014 §5.5) STRIPE_SECRET_KEYen.env.docker+ stack Docker (foodhub-stripe-cliauto)E2E_STRIPE_CHECKOUT=1
- Connect Active en
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
| Variable | Default | Descripción |
|---|---|---|
E2E_BASE_URL | http://localhost:3001 | URL base web |
E2E_SKIP_WEB_SERVER | unset | 1 = no levantar pnpm dev (usar Docker) |
E2E_PUBLIC_LOCATION_SLUG | bbq-smoke-pit | Location demo |
E2E_DEMO_PRODUCT_NAME | Chef Special | Producto a añadir |
E2E_STRIPE_CHECKOUT | unset | 1 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):
- Service containers Postgres + Redis (sin Docker dev stack — arranque más rápido en CI)
prisma migrate deploy+db seeden el runner- API + Web nativos en background (
devops/scripts/start-native-e2e-stack.sh) - 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):
| Recurso | URL |
|---|---|
| Swagger UI | http://localhost:3000/api/docs |
| OpenAPI JSON (Postman) | http://localhost:3000/api/docs-json |
| OpenAPI YAML | http://localhost:3000/api/docs-yaml |
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íntoma | Acción |
|---|---|
Missing .env.docker | cp devops/docker/.env.docker.example devops/docker/.env.docker |
| Puerto 5432 ocupado | Cambiar POSTGRES_PORT en .env.docker o parar otro Postgres |
Cambiaste package.json / deps | pnpm install en host; en Docker el entrypoint reinstala al arrancar si hace falta |
| Cambiaste Dockerfile o compose | make local-up-build |
API no responde tras local-up | make local-logs; esperar arranque Nest/Next; docker ps |
| Tras pull: rutas 404 / UI o chrome desactualizado | make local-ui-reset (cache .next stale en volúmenes Docker o host) |
Cannot find module en API Docker | make local-reset-deps && make local-up-d-build (volumen node_modules desactualizado) |
pnpm install falla | node -v >= 20; borrar node_modules y reinstalar |
| Lint falla | pnpm lint en el package afectado |
| HMR lento en Docker (Mac) | Usar Modo B (infra Docker + pnpm dev) |
Checkout 422 STRIPE_CONNECT_NOT_READY | Tenant sin Connect active — completar onboarding S-032 (/t/demo/settings/payments) |
tax-preview 422 STRIPE_TAX_CALCULATION_FAILED | Stripe Tax no activo en cuenta Connect — DOC-014 §3.4 (head office + registration) |
| Orden queda UNPAID tras pagar | make local-status → Stripe CLI synced; STRIPE_SECRET_KEY en .env.docker; logs: make local-logs |
stripe listen manual / host | Preferir 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.
| Comando | Resultado |
|---|---|
make metronic-reference | Copia paquete completo Metronic a docs/ux/references/external/metronic-v9.5.0/ (gitignored, ~1 GB) |
make doctor | Confirma perfil full + manifest |
METRONIC_REFERENCE_PROFILE=admin make metronic-reference | Subset 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-upomake 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
- [ADR-010](../adr/ADR-010: Docker, Docker Compose y Entorno Local Estandarizado.md) — Docker local
- ADR-043 — Turnstile
- ADR-041 — Makefile, ESLint
- devops/docker/README.md — detalle Compose
- README-METODOLOGIA-SESION.md — sesiones IA
- AGENTS.md — instrucciones agentes
- Metronic reference — admin shell local (gitignored)
- Hostinger inventory — VPS KVM, Postgres en Docker, secretos
hostinger/ - Hostinger VPS deploy — bootstrap Ubuntu / DNS / SMTP