Arquitectura y contratos de error
Comprende las capas obligatorias de Sushi SaaS, el flujo de datos del navegador, la autorización y el sistema de errores traducidos sin filtraciones.
Sincronizado con el commit
7d452a6del starter.
Una sola dirección en el servidor
src/app/** rutas y páginas: entrada y salida HTTP
↓
src/services/** reglas de negocio, coordinación e invariantes
↓
src/models/** persistencia tipada; única capa que llama a db()
↓
src/db/** esquema, migraciones y conexiónLas rutas autentican, validan y traducen HTTP. Toda escritura pasa por un servicio. Los modelos son dueños de consultas, filtros de tenant y transacciones. tests/unit/architecture.test.ts rechaza importaciones que rompen estas fronteras.
No crees src/features/: cada dominio se reparte horizontalmente entre modelos, servicios, configuración y componentes.
Flujo de datos del navegador
Server Component → servicio directo
Client Component → src/api/** → cliente API común → /api/**Los Server Components no llaman a la API propia. Los Client Components no usan fetch directamente; usan un módulo de dominio en src/api/ que procesa el sobre y los errores de forma uniforme.
Dos preguntas de autorización
El rol de organización y el plan de suscripción son independientes:
const ctx = await getOrgContext(request);
if (!ctx || !can(ctx, "file:delete", file)) return respForbidden();
await requireEntitlement(ctx.orgUuid, "storage.upload");can() responde si el rol permite la acción. Los entitlements responden si el plan efectivo de la organización incluye la capacidad o el límite.
Contrato de errores sin filtraciones
El servidor lanza AppError con un código estable del catálogo. Cada ruta termina sus excepciones en respError, que registra el detalle interno y devuelve texto seguro traducido.
La interfaz decide por error_code y resuelve el texto con resolveErrorMessage o resolveAuthError; nunca muestra error.message. Las traducciones viven en src/lib/errors/i18n/locales/ y cada código debe existir en los cinco idiomas.
Añadir un dominio con seguridad
- Define constantes en
src/config/. - Añade CRUD tipado en
src/models/. - Coloca invariantes, autorización, idempotencia y efectos en
src/services/. - Mantén las rutas finas y usa el catálogo de errores.
- Añade pruebas del nivel adecuado.
- Ejecuta
pnpm lint,pnpm test:runypnpm build.
Los contratos completos siguen versionados en docs/errors.md, docs/frontend.md y AGENTS.md del starter.