Estructura del proyecto
Layout del monorepo, capas DDD y dónde vive cada cosa.
Nivel superior
apps/
client/ Next.js — landing, dashboard, auth, /docs
server/ Express — API, auth, billing, jobs
packages/
contracts/ Schemas Zod compartidos + tipos inferidos
shared/ Catálogo de permissions, Result, errors, branded types
tsconfig/ tsconfig bases
eslint-config/ flat configs que aplican el layering DDD
tests/ Playwright e2eEl repo es un workspace de Bun. Todo import cross-package interno pasa por @app/contracts, @app/shared, etc. — nunca relativo entre apps.
apps/server
src/
index.ts Entry point. otel-init DEBE ser el primer import.
otel-init.ts Arranca OTel antes que cualquier otra cosa.
bootstrap/
env.ts Schema Zod para env. Fuente de verdad.
container.ts Registry de DI con Awilix.
app.ts Construye la app de Express y monta todos los routers bajo /api.
worker.ts Bootstrap del worker BullMQ.
infrastructure/
db/ Cliente Prisma + helpers.
http/ Helper api-route, error handler, rate-limit, registry de OpenAPI, requirePermission.
events/ Bus de eventos en memoria.
jobs/ Factories de queue + worker BullMQ, Bull Board.
cache/ Adapter de Redis (no-op cuando REDIS_URL no está).
logger/ Setup de pino.
observability/ Sentry, exporters de OTel, helper captureError.
i18n/ Runtime de traducción server-side.
modules/<context>/ Bounded contexts (ver abajo).
shared/
aggregate-root.ts Base AggregateRoot + tipo DomainEvent.
usecase.ts Contrato UseCase<Input, Output>.
value-object.ts Base de value object.Capas DDD dentro de un módulo
Todo módulo bajo apps/server/src/modules/<context>/ sigue el mismo layout:
domain/ Entidades, value objects, repository interfaces.
Sin I/O. Sin imports de framework. Sin Prisma. Sin Express.
application/ Use cases, ports (output adapters), DTOs.
infrastructure/ Repos de Prisma, adapters de terceros, mappers.
interfaces/http/ Controladores Express, registración de rutas, schemas Zod.La regla de dependencias es sólo hacia adentro: interfaces/ depende de application/, que depende de domain/. Infrastructure implementa los ports declarados en application/ (o las repository interfaces declaradas en domain/).
ESLint lo aplica. domain/ no puede importar express, @prisma/client, ni nada desde application/ / infrastructure/ / interfaces/. Probá — el import es flagged antes del commit.
Los bounded contexts incluidos hoy: iam, tenancy, billing, notifications, storage, audit, feature-flags. Ver Bounded contexts.
apps/client
app/
page.tsx, es/, pt/ Landing pages (i18n).
_landing/ Componentes de landing + diccionario.
(auth)/ Sign-in, sign-up, forgot-password.
(dashboard)/ Surface autenticada de la app.
docs/[[...slug]]/ Fumadocs.
api/search/ Endpoint de búsqueda Orama.
globals.css Tokens (OKLCH) + base + utilities.
layout.tsx Carga Geist Sans + Geist Mono una sola vez.
components/
brand/ Primitives del design system UseDeploy. Leer /docs/es/frontend.
ui/ Primitives derivados de shadcn.
content/docs/ El MDX que estás leyendo.
lib/
api/ Cliente openapi-fetch + tipos generados.
validations/ Refinements client-only construidos sobre @app/contracts.
source.ts Content source de Fumadocs.El client habla con el server vía openapi-fetch y los paths tipados en lib/api/openapi-types.ts. Nunca redefinas un campo conocido del server en un schema Zod del client — extendé el contract, no lo bifurques.
packages/contracts
Schemas Zod + tipos inferidos que cruzan el límite client/server. Forms y mutations importan desde acá:
src/
auth.ts Login, register, password reset.
user.ts Perfil de user, update, list.
common.ts Primitives compartidos (paginación, IDs).
index.ts Barrel.Si un endpoint del server cambia su body o response, el contract cambia acá, el OpenAPI se regenera (bun run generate:api), y el client compile-checkea la nueva forma.
packages/shared
src/
permissions/ Catálogo resource:action + helper hasPermission.
result/ Result<T, E> + constructores ok/err.
errors/ DomainError, UnauthorizedError, ForbiddenError, etc.
types/ Tipos branded para IDs.
index.ts Barrel.Importado como @app/shared desde ambas apps.