Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Glemynart/SaaS/llms.txt

Use this file to discover all available pages before exploring further.

La Oficina Nítida is built multi-tenant from the ground up. Every organization that registers on the platform receives its own isolated data space, enforced at the database level through a tenantId column present on every table. There is no shared data between organizations, no cross-tenant queries, and no administrative backdoor that bypasses this boundary.

How Isolation Works

Tenant isolation operates at three layers simultaneously:
  1. Database layer — Every table (employees, contracts, branches, documents, invoices, and more) carries a tenantId UUID as its first indexed column. No query returns data without filtering on that column.
  2. JWT layer — When a user authenticates, the server embeds their tenantId directly into the signed access token payload. All subsequent requests carry this value automatically.
  3. Service layer — Every service method receives tenantId extracted from the verified JWT via the @CurrentUser() decorator. It is never read from the request body or query parameters.
// ✅ Correct — tenantId comes from the verified JWT payload
@Get()
findAll(@CurrentUser() user: CurrentUserPayload, @Query() query: SedeQueryDto) {
  return this.sedesService.findAll(user.tenantId, query);
}

// ❌ Never done — tenantId is never accepted from a client-supplied field
Every Prisma query in the platform follows the pattern where: { tenantId } as its primary filter, ensuring rows from other tenants are structurally unreachable.

Tenant Data Model

The Tenant model is the root entity of the entire system. All other records cascade from it.
FieldTypeDescription
idString (UUID)Primary key — used as tenantId throughout all tables
nombreStringDisplay name of the organization
nitString (unique)Colombian tax ID number, without the check digit
digitoVerifStringNIT check digit
razonSocialStringLegal business name
planPlan enumSubscription plan: STARTER, PROFESIONAL, or OPERADOR_EDUCATIVO
activoBooleanWhether the tenant can currently operate on the platform
trialEndsAtDateTime?Expiry of the trial period, if applicable
direccionString?Physical address
ciudadString?City
departamentoString?Department (Colombian state)
telefonoString?Contact phone number
emailString?Contact email address
logoUrlString?URL of the uploaded logo in Cloudflare R2
representanteLegalString?Legal representative’s full name
firmaRepresentanteUrlString?URL of the digitized signature in Cloudflare R2
municipioIdString?FK to the DANE municipality catalog
aportesExoneradosBooleanArt. 114-1 ET payroll exemption flag

Subscription Plans

The platform currently defines three plans via the Plan enum:
PlanIntended for
STARTEROrganizations getting started; default plan assigned at registration
PROFESIONALGrowing businesses needing more capacity
OPERADOR_EDUCATIVOColombian educational operators managing multiple campuses, administrative staff, and teachers
All three plans share the same data isolation guarantees. Feature gating per plan is enforced at the application level.

Tenant Registration

A new tenant is created through a single atomic call to POST /auth/register. The registration creates the Tenant record and the first User (always an ADMIN) inside a single PostgreSQL transaction. If either step fails, neither record is persisted.
curl -X POST https://api.example.com/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "nombreTenant": "Colegio Los Andes",
    "nit": "900123456",
    "digitoVerif": "7",
    "razonSocial": "Colegio Los Andes S.A.S.",
    "email": "admin@losandes.edu.co",
    "passwordPlain": "SecurePass123!",
    "nombre": "María",
    "apellido": "Gómez"
  }'
The NIT must be globally unique across the entire platform. Attempting to register a duplicate NIT returns 409 Conflict.

ActiveTenantGuard

Every protected route in the platform stacks ActiveTenantGuard immediately after JwtAuthGuard. Before a request reaches any controller method, the guard calls TenantsService.ensureActive(tenantId), which queries the database and throws BadRequestException if the tenant’s activo field is false. This means a deactivated organization loses access to all endpoints instantly — no individual user deactivation is necessary. The guard cannot be bypassed because it runs at the NestJS middleware level, before controller logic executes.
Request → JwtAuthGuard → ActiveTenantGuard → RolesGuard → Controller
Never pass tenantId in a request body, query parameter, or URL path segment. The platform derives it exclusively from the signed JWT access token. Any API call that accepts a client-supplied tenantId would be a critical security vulnerability — if you encounter such a field in an API response, treat it as read-only metadata, not an input accepted by the server.

Build docs developers (and LLMs) love