PROYECTO PERSONAL·EN DESARROLLO ACTIVO·2026

Noctis Commerce

Un SaaS multi-tenant que estoy construyendo para gestión comercial de PYMEs — punto de venta, facturación electrónica SRI e inventario, diseñado para que una sola base de código pueda servir a muchos negocios aislados.

ROL
Proyecto en solitario — arquitectura y build
STACK
NestJS · Next.js 14 · PostgreSQL · Nx
REPOSITORIO
Privado — arquitectura mostrada aquí

01 — CONTEXTO Y PRODUCTO

Una plataforma para ventas, facturación e inventario.

Noctis es un proyecto personal — un SaaS multi-tenant de gestión comercial para PYMEs que reúne punto de venta, facturación electrónica SRI e inventario en una sola plataforma, con cada negocio aislado en la base de datos. Está en desarrollo activo, no es un producto lanzado.

◇ PENDIENTEUsuario objetivo y el problema que Noctis aborda — lo aportas tú.
PARA
◇ PENDIENTE

FACTURACIÓN SRI
Alcance: En curso

MODELO
SaaS multi-tenant
POS · tablet1280 × 800 · arrastra imagen
Punto de venta — cobro en mostrador
Facturaciónarrastra imagen
Factura electrónica SRI
Inventarioarrastra imagen
Inventario — stock en tiempo real

02 — ARQUITECTURA

Clean Architecture en un monorepo Nx

Cuatro apps desplegables y doce librerías, repartidas en cuatro bounded contexts — identity, auth, commerce y hr. Las dependencias apuntan hacia adentro: el dominio no sabe nada del framework.

apps/ · 4 desplegables + 3 e2e
apicommercebackofficeweb+3 e2e
libs/ · 12 librerías · 4 bounded contexts
identity
Domain · users, tenants
Application · permissions, org
Infrastructure · Prisma, RLS
auth
Domain · sessions, policy
Application · JWT, argon2id
Infrastructure · guards
commerce
Domain · products, sales
Application · POS, SRI, stock
Infrastructure · repos, outbox
hr
Domain · employees
Application · labor params
Infrastructure · repos
PostgreSQLRow-Level Security · deny-by-default

03 — AISLAMIENTO DE TENANTS

Aislamiento en la base de datos, no en la aplicación.

Cada request fija su tenant por transacción; la RLS de PostgreSQL filtra cada fila antes de salir de la base. Deny-by-default significa que un tenant sin fijar devuelve nada — no los datos de otro.

request
JWT → RequestContext
set_config
app.current_tenant · por tx
tenant_isolation
deny-by-default
-- bind the tenant per transaction (tx-local, parameter-bound)
SELECT set_config('app.current_tenant', $tenant, true);

ALTER TABLE commerce.products ENABLE ROW LEVEL SECURITY;
ALTER TABLE commerce.products FORCE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON commerce.products
  USING (public.is_platform_admin()
     OR tenant_id = public.current_tenant_id());
-- unset GUC → current_tenant_id() IS NULL → deny by default

04 — EVIDENCIA

Los números, con lo que significan.

1,600+
Tests automatizados
En 259 archivos spec — dominio, REST y e2e; corren en CI en cada commit.
54
Migraciones versionadas
El esquema evoluciona solo hacia adelante; cada cambio es reproducible.
19
Decision records
Decisiones no obvias escritas con sus trade-offs.
19
Controladores REST
La superficie HTTP, dividida por bounded context.
7
Roles RBAC
El acceso es por rol, aplicado del gateway a la fila.
Estado actual de un proyecto en desarrollo activo — una foto del código, no un resultado final.

05 — DECISION RECORD

ADR-002
Aislamiento de tenants — Row-Level Security + RequestContext
Aceptado · enmendado 2026-06-10
DECISIÓN

Adoptar Row-Level Security de PostgreSQL en modo force + deny-by-default. Cada request fija app.current_tenant por transacción vía set_config; la policy de cada tabla operacional filtra con is_platform_admin() OR tenant_id = current_tenant_id().

CONTEXTO

Cada fila operacional lleva un tenant_id. Filtrar solo por convención estaba a un WHERE olvidado — o a una query raw / psql — de una fuga entre tenants, el riesgo de mayor severidad en un SaaS de base compartida.

CONSECUENCIA

El aislamiento se sostiene en la base aunque el código de aplicación olvide su filtro; un tenant sin fijar devuelve cero filas, no los datos de otro. Costo: un GUC por transacción y migraciones conscientes de RLS, compatible con el transaction pooling de RDS Proxy — aceptado.