Usar obligatoriamente:
pnpm-workspace.yaml.Usar obligatoriamente:
Usar estas versiones o rangos específicos:
Usar:
pg / node-postgreshttps://orm.drizzle.team/docs/get-started/postgresql-new
Instalación esperada con pnpm:
pnpm add drizzle-orm pg
pnpm add -D drizzle-kit @types/pg
company_idCrear una arquitectura tipo monorepo:
labxus-platform/
├── apps/
│ ├── web/ # Frontend Next.js v16.2.x
│ └── api/ # Backend NestJS v11.1.19
│
├── packages/
│ ├── shared/ # Zod schemas, types, roles, permisos
│ ├── ui/ # Opcional: componentes compartidos
│ └── config/ # Configuración compartida
│
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
├── README.md
└── .gitignore
Usar preferiblemente:
Crear un package.json raíz parecido a este:
{
"name": "labxus-platform",
"version": "0.1.0",
"private": true,
"packageManager": "pnpm@latest",
"scripts": {
"dev": "turbo dev",
"build": "turbo build",
"lint": "turbo lint",
"typecheck": "turbo typecheck",
"format": "prettier --write .",
"db:generate": "pnpm --filter @labxus/api db:generate",
"db:migrate": "pnpm --filter @labxus/api db:migrate",
"db:studio": "pnpm --filter @labxus/api db:studio",
"db:seed": "pnpm --filter @labxus/api db:seed"
},
"devDependencies": {
"turbo": "latest",
"typescript": "latest",
"prettier": "latest"
}
}
Crear:
packages:
- "apps/*"
- "packages/*"
La plataforma será SaaS multitenant.
Reglas obligatorias:
SUPER_ADMIN.
company_id,
excepto el SUPER_ADMIN, que puede tener
company_id nulo.
SUPER_ADMIN de Labxus puede ver y administrar todas las
empresas.
COMPANY_ADMIN solo puede administrar usuarios, roles y
permisos de su propia empresa.
company_id enviado
desde el frontend para usuarios normales.
company_id debe obtenerse desde el token del usuario
autenticado.
Crear los siguientes módulos:
auth
companies
users
roles
permissions
database
common
Estructura esperada en backend:
apps/api/src/
├── app.module.ts
├── main.ts
├── config/
│ ├── env.schema.ts
│ └── config.module.ts
│
├── database/
│ ├── db.ts
│ ├── database.module.ts
│ ├── drizzle.config.ts
│ ├── schema/
│ │ ├── companies.schema.ts
│ │ ├── users.schema.ts
│ │ ├── roles.schema.ts
│ │ ├── permissions.schema.ts
│ │ ├── role-permissions.schema.ts
│ │ ├── refresh-tokens.schema.ts
│ │ ├── password-reset-tokens.schema.ts
│ │ ├── modules.schema.ts
│ │ ├── company-modules.schema.ts
│ │ └── audit-logs.schema.ts
│ ├── migrations/
│ └── seed.ts
│
├── common/
│ ├── decorators/
│ │ ├── current-user.decorator.ts
│ │ ├── require-roles.decorator.ts
│ │ └── require-permissions.decorator.ts
│ │
│ ├── guards/
│ │ ├── jwt-auth.guard.ts
│ │ ├── roles.guard.ts
│ │ ├── permissions.guard.ts
│ │ └── tenant.guard.ts
│ │
│ ├── filters/
│ ├── interceptors/
│ ├── pipes/
│ ├── utils/
│ └── types/
│
├── auth/
│ ├── auth.module.ts
│ ├── auth.controller.ts
│ ├── auth.service.ts
│ ├── auth.repository.ts
│ ├── strategies/
│ └── dto/
│
├── companies/
│ ├── companies.module.ts
│ ├── companies.controller.ts
│ ├── companies.service.ts
│ ├── companies.repository.ts
│ └── dto/
│
├── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ ├── users.repository.ts
│ └── dto/
│
├── roles/
│ ├── roles.module.ts
│ ├── roles.controller.ts
│ ├── roles.service.ts
│ ├── roles.repository.ts
│ └── dto/
│
└── permissions/
├── permissions.module.ts
├── permissions.controller.ts
├── permissions.service.ts
├── permissions.repository.ts
└── dto/
Crear un package compartido:
packages/shared/
├── src/
│ ├── schemas/
│ │ ├── auth.schema.ts
│ │ ├── company.schema.ts
│ │ ├── user.schema.ts
│ │ ├── role.schema.ts
│ │ ├── permission.schema.ts
│ │ └── common.schema.ts
│ │
│ ├── types/
│ │ ├── auth.types.ts
│ │ ├── company.types.ts
│ │ ├── user.types.ts
│ │ ├── role.types.ts
│ │ ├── permission.types.ts
│ │ └── common.types.ts
│ │
│ ├── constants/
│ │ ├── roles.ts
│ │ ├── permissions.ts
│ │ ├── modules.ts
│ │ └── statuses.ts
│ │
│ └── index.ts
│
├── package.json
└── tsconfig.json
Este package debe usarse tanto en:
Reglas:
z.infer.Crear schemas para:
loginSchema
forgotPasswordSchema
resetPasswordSchema
refreshTokenSchema
createCompanySchema
updateCompanySchema
companyStatusSchema
createUserSchema
updateUserSchema
userStatusSchema
changePasswordSchema
createRoleSchema
updateRoleSchema
assignPermissionsToRoleSchema
permissionSchema
permissionCodeSchema
La contraseña debe cumplir:
Crear constantes compartidas para roles:
SUPER_ADMIN
COMPANY_ADMIN
MANAGER
EMPLOYEE
READ_ONLY
Reglas:
SUPER_ADMIN pertenece a Labxus.SUPER_ADMIN puede crear empresas.SUPER_ADMIN puede ver todas las empresas.SUPER_ADMIN puede administrar módulos globales.SUPER_ADMIN puede crear usuarios administradores de
empresa.
COMPANY_ADMIN administra solo su empresa.COMPANY_ADMIN no puede crear otros
SUPER_ADMIN.
COMPANY_ADMIN no puede modificar empresas globalmente.
MANAGER puede tener permisos administrativos limitados.
EMPLOYEE puede operar módulos permitidos.READ_ONLY solo puede consultar información.Crear constantes compartidas para permisos:
companies.create
companies.read
companies.update
companies.delete
users.create
users.read
users.update
users.delete
roles.create
roles.read
roles.update
roles.delete
permissions.read
permissions.assign
dashboard.read
settings.read
settings.update
Preparar también permisos futuros para módulos:
inventory.create
inventory.read
inventory.update
inventory.delete
warehouses.create
warehouses.read
warehouses.update
warehouses.delete
orders.create
orders.read
orders.update
orders.delete
customers.create
customers.read
customers.update
customers.delete
invoicing.create
invoicing.read
invoicing.update
invoicing.delete
employees.create
employees.read
employees.update
employees.delete
payroll.create
payroll.read
payroll.update
payroll.delete
Crear schemas de Drizzle para las siguientes tablas:
companies
users
roles
permissions
role_permissions
refresh_tokens
password_reset_tokens
audit_logs
modules
company_modules
Usar Drizzle ORM con PostgreSQL y pg.
Instalación esperada en apps/api:
pnpm add drizzle-orm pg
pnpm add -D drizzle-kit @types/pg
Ejemplo esperado de conexión:
import "dotenv/config";
import { drizzle } from "drizzle-orm/node-postgres";
export const db = drizzle(process.env.DATABASE_URL!);
Ejemplo esperado de drizzle.config.ts:
import "dotenv/config";
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./src/database/schema/index.ts",
out: "./src/database/migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
Scripts esperados en apps/api/package.json:
{
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio",
"db:seed": "tsx src/database/seed.ts"
}
}
Campos:
id uuid primary key
name varchar not null
legal_name varchar
nit varchar unique
email varchar
phone varchar
address text
status varchar not null default 'active'
created_at timestamp
updated_at timestamp
Estados:
active
inactive
suspended
Reglas:
SUPER_ADMIN puede crear empresas.SUPER_ADMIN puede listar todas las empresas.COMPANY_ADMIN puede ver información básica de su propia
empresa.
Campos:
id uuid primary key
company_id uuid nullable references companies(id)
name varchar not null
email varchar not null unique
password_hash text not null
status varchar not null default 'active'
is_super_admin boolean default false
created_at timestamp
updated_at timestamp
Estados:
active
inactive
pending
blocked
Reglas:
SUPER_ADMIN puede ver usuarios de todas las empresas.
COMPANY_ADMIN solo puede ver usuarios de su empresa.COMPANY_ADMIN solo puede crear usuarios dentro de su
empresa.
company_id debe salir
del token.
company_id enviado desde frontend.SUPER_ADMIN puede asignar manualmente
company_id.
password_hash.Campos:
id uuid primary key
company_id uuid nullable references companies(id)
name varchar not null
code varchar not null
description text
is_system_role boolean default false
created_at timestamp
updated_at timestamp
Reglas:
company_id nulo pueden ser roles globales del
sistema.
company_id pertenecen a una empresa.SUPER_ADMIN puede crear roles globales.COMPANY_ADMIN puede crear roles solo para su empresa.
is_system_role = true.
Campos:
id uuid primary key
name varchar not null
code varchar not null unique
description text
module varchar not null
created_at timestamp
updated_at timestamp
Reglas:
SUPER_ADMIN puede crear, editar o eliminar permisos.
Campos:
id uuid primary key
role_id uuid references roles(id)
permission_id uuid references permissions(id)
created_at timestamp
Reglas:
SUPER_ADMIN puede asignar permisos a cualquier rol.COMPANY_ADMIN puede asignar permisos solo a roles de su
empresa.
COMPANY_ADMIN no puede asignar permisos reservados de
SUPER_ADMIN.
Campos:
id uuid primary key
user_id uuid references users(id)
token_hash text not null
expires_at timestamp not null
revoked_at timestamp nullable
created_at timestamp
Reglas:
Campos:
id uuid primary key
user_id uuid references users(id)
token_hash text not null
expires_at timestamp not null
used_at timestamp nullable
created_at timestamp
Reglas:
Campos:
id uuid primary key
company_id uuid nullable references companies(id)
user_id uuid nullable references users(id)
action varchar not null
entity varchar not null
entity_id uuid nullable
metadata jsonb
ip_address varchar
user_agent text
created_at timestamp
Reglas:
Campos:
id uuid primary key
name varchar not null
code varchar not null unique
description text
is_active boolean default true
created_at timestamp
updated_at timestamp
Ejemplos:
inventory
orders
invoicing
employees
payroll
reports
Reglas:
SUPER_ADMIN puede administrar módulos.Campos:
id uuid primary key
company_id uuid references companies(id)
module_id uuid references modules(id)
is_active boolean default true
created_at timestamp
updated_at timestamp
Reglas:
SUPER_ADMIN puede activar o desactivar módulos para
empresas.
COMPANY_ADMIN solo puede leer módulos activos de su
empresa.
COMPANY_ADMIN no puede activar módulos manualmente si eso
depende comercialmente de Labxus.
Implementar Row Level Security, RLS, en PostgreSQL para
reforzar el aislamiento multitenant por company_id.
company_id.
SUPER_ADMIN puede consultar y administrar datos de todas
las empresas.
company_id en
una consulta, la base de datos debe impedir fugas de información entre
empresas.
company_id.SELECT, INSERT,
UPDATE y DELETE.
company_id usado por RLS debe salir del contexto de la
sesión de base de datos.
company_id del usuario
autenticado antes de ejecutar consultas.
SUPER_ADMIN.
company_id.
SUPER_ADMIN puede acceder a todos los registros.COMPANY_ADMIN y usuarios normales solo pueden acceder a
registros de su propia empresa.
permissions o
modules, pueden tener reglas especiales.
companies debe tener reglas especiales.company_id desde el backend.
PostgreSQL debe aplicar RLS obligatoriamente en las tablas multitenant.
Implementar variables personalizadas de PostgreSQL:
app.current_company_id
app.current_user_id
app.is_super_admin
Ejemplo:
SELECT set_config('app.current_company_id', 'uuid-de-la-empresa', true);
SELECT set_config('app.current_user_id', 'uuid-del-usuario', true);
SELECT set_config('app.is_super_admin', 'false', true);
Para SUPER_ADMIN:
SELECT set_config('app.current_company_id', '', true);
SELECT set_config('app.current_user_id', 'uuid-del-super-admin', true);
SELECT set_config('app.is_super_admin', 'true', true);
Crear un helper, servicio o middleware para establecer el contexto RLS por request.
Ejemplo conceptual:
await db.execute(sql`
SELECT set_config('app.current_company_id', ${currentUser.companyId ?? ""}, true)
`);
await db.execute(sql`
SELECT set_config('app.current_user_id', ${currentUser.id}, true)
`);
await db.execute(sql`
SELECT set_config('app.is_super_admin', ${currentUser.isSuperAdmin ? "true" : "false"}, true)
`);
Reglas:
Ejemplo para products:
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
ALTER TABLE products FORCE ROW LEVEL SECURITY;
CREATE POLICY products_select_policy
ON products
FOR SELECT
USING (
current_setting('app.is_super_admin', true) = 'true'
OR company_id::text = current_setting('app.current_company_id', true)
);
CREATE POLICY products_insert_policy
ON products
FOR INSERT
WITH CHECK (
current_setting('app.is_super_admin', true) = 'true'
OR company_id::text = current_setting('app.current_company_id', true)
);
CREATE POLICY products_update_policy
ON products
FOR UPDATE
USING (
current_setting('app.is_super_admin', true) = 'true'
OR company_id::text = current_setting('app.current_company_id', true)
)
WITH CHECK (
current_setting('app.is_super_admin', true) = 'true'
OR company_id::text = current_setting('app.current_company_id', true)
);
CREATE POLICY products_delete_policy
ON products
FOR DELETE
USING (
current_setting('app.is_super_admin', true) = 'true'
OR company_id::text = current_setting('app.current_company_id', true)
);
Aplicar RLS a tablas como:
companies
users
roles
role_permissions
company_modules
audit_logs
Y futuras tablas de negocio:
customers
products
warehouses
product_stocks
orders
order_items
employees
payroll
invoices
Reglas:
SUPER_ADMIN puede ver todas las empresas.SUPER_ADMIN puede crear, editar y eliminar empresas.COMPANY_ADMIN solo puede ver su propia empresa.Reglas:
SUPER_ADMIN puede ver todos los usuarios.COMPANY_ADMIN puede ver usuarios de su empresa.COMPANY_ADMIN puede crear usuarios solo dentro de su
empresa.
SUPER_ADMIN desde una empresa.Reglas:
SUPER_ADMIN puede ver roles globales y roles de todas las
empresas.
COMPANY_ADMIN puede ver roles globales permitidos y roles
de su empresa.
COMPANY_ADMIN solo puede crear roles para su empresa.
Reglas:
SUPER_ADMIN puede crear, editar o eliminar permisos.
Reglas:
SUPER_ADMIN puede crear, editar o eliminar módulos.
Reglas:
SUPER_ADMIN puede administrar todos.COMPANY_ADMIN puede leer solo módulos activos de su
empresa.
COMPANY_ADMIN no puede activar o desactivar módulos.Reglas:
Reglas:
Crear migraciones de Drizzle o SQL para:
ALTER TABLE ... ENABLE ROW LEVEL SECURITY
ALTER TABLE ... FORCE ROW LEVEL SECURITY
CREATE POLICY ...
DROP POLICY ...
Las migraciones deben incluir:
Crear pruebas o ejemplos para verificar:
SUPER_ADMIN sí puede leer todas las empresas.app.current_company_id, las queries
multitenant deben fallar o no retornar datos.
app.is_super_admin es false, no debe poder ver datos
globales restringidos.
Crear estas rutas backend:
POST /auth/login
POST /auth/logout
POST /auth/refresh
POST /auth/forgot-password
POST /auth/reset-password
GET /auth/me
Reglas:
password_hash.El access token debe incluir:
user_id
company_id
role
permissions
is_super_admin
Reglas:
Reglas:
Reglas:
Crear flujo:
Usuario solicita recuperación
↓
Escribe email
↓
Si existe, crear token seguro
↓
Guardar hash del token
↓
Enviar email con enlace
↓
Usuario entra a /reset-password?token=...
↓
Escribe nueva contraseña
↓
Backend valida token
↓
Backend cambia contraseña
↓
Backend marca token como usado
↓
Backend revoca refresh tokens anteriores
Reglas:
forgot-password.Mensaje recomendado:
Si el correo existe en nuestra plataforma, enviaremos instrucciones para recuperar la contraseña.
Crear rutas:
POST /companies
GET /companies
GET /companies/:id
PATCH /companies/:id
DELETE /companies/:id
Reglas:
SUPER_ADMIN puede crear empresas.SUPER_ADMIN puede listar todas las empresas.SUPER_ADMIN puede editar o eliminar empresas.COMPANY_ADMIN puede leer únicamente su propia empresa.
COMPANY_ADMIN.active, salvo que se
indique otra cosa.
Crear rutas:
POST /users
GET /users
GET /users/:id
PATCH /users/:id
DELETE /users/:id
Reglas:
SUPER_ADMIN puede ver usuarios de todas las empresas.
COMPANY_ADMIN solo puede ver usuarios de su propia empresa.
COMPANY_ADMIN puede crear usuarios solo para su propia
empresa.
users.create.
company_id sale del token.
company_id enviado desde frontend.SUPER_ADMIN puede asignar manualmente
company_id.
COMPANY_ADMIN cree
SUPER_ADMIN.
Crear rutas:
POST /roles
GET /roles
GET /roles/:id
PATCH /roles/:id
DELETE /roles/:id
Reglas:
SUPER_ADMIN puede crear roles globales.COMPANY_ADMIN puede crear roles solo para su empresa.
Crear rutas:
GET /permissions
POST /roles/:id/permissions
DELETE /roles/:id/permissions/:permissionId
Reglas:
SUPER_ADMIN puede crear permisos si se implementa
creación.
COMPANY_ADMIN puede asignar permisos permitidos a roles de
su empresa.
SUPER_ADMIN.
Crear:
@CurrentUser()
@RequireRoles()
@RequirePermissions()
JwtAuthGuard
RolesGuard
PermissionsGuard
TenantGuard
Debe permitir obtener:
id
companyId
email
role
permissions
isSuperAdmin
Debe:
Debe:
SUPER_ADMIN cuando aplique.
Debe:
Debe:
company_id.SUPER_ADMIN operar globalmente.Implementar:
password_hash.COMPANY_ADMIN cree
SUPER_ADMIN.
Crear archivo .env.example para backend:
NODE_ENV=development
PORT=4000
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/labxus
JWT_ACCESS_SECRET=change-me-access-secret
JWT_REFRESH_SECRET=change-me-refresh-secret
JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d
PASSWORD_RESET_EXPIRES_IN=30m
FRONTEND_URL=http://localhost:3000
SMTP_HOST=
SMTP_PORT=
SMTP_USER=
SMTP_PASS=
SMTP_FROM=
BCRYPT_SALT_ROUNDS=10
Crear archivo .env.example para frontend:
NEXT_PUBLIC_API_URL=http://localhost:4000
Crear estas rutas públicas:
/login
/forgot-password
/reset-password
Crear estas rutas privadas:
/dashboard
/dashboard/companies
/dashboard/companies/new
/dashboard/users
/dashboard/users/new
/dashboard/roles
/dashboard/roles/new
/dashboard/settings
Reglas frontend:
/login.
SUPER_ADMIN ve empresas.COMPANY_ADMIN no ve el módulo global de empresas.COMPANY_ADMIN ve usuarios y roles solo de su empresa.
Crear:
LoginForm
ForgotPasswordForm
ResetPasswordForm
CreateCompanyForm
CreateUserForm
CreateRoleForm
Sidebar
DashboardLayout
ProtectedRoute
PermissionGate
UserMenu
DataTable
EmptyState
LoadingState
AccessDenied
ThemeToggle
Crear componente para mostrar contenido según permisos.
Ejemplo esperado:
<PermissionGate permission="users.create">
<Button>Crear usuario</Button>
</PermissionGate>
Reglas:
El menú debe renderizarse según permisos.
Ejemplo:
Dashboard
Empresas
Usuarios
Roles
Configuración
Reglas:
SUPER_ADMIN ve Empresas.COMPANY_ADMIN no ve Empresas globales.users.read no ven Usuarios.roles.read no ven Roles.Configurar Tailwind CSS v4.1 y CSS variables con HSL.
Usar marca visual de Labxus:
Ejemplo base:
:root {
--background: 240 20% 99%;
--foreground: 240 10% 8%;
--card: 0 0% 100%;
--card-foreground: 240 10% 8%;
--primary: 258 90% 58%;
--primary-foreground: 0 0% 100%;
--secondary: 221 83% 53%;
--secondary-foreground: 0 0% 100%;
--muted: 240 10% 96%;
--muted-foreground: 240 5% 45%;
--border: 240 8% 90%;
--input: 240 8% 90%;
--ring: 258 90% 58%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--radius: 0.75rem;
}
.dark {
--background: 240 10% 4%;
--foreground: 0 0% 98%;
--card: 240 10% 7%;
--card-foreground: 0 0% 98%;
--primary: 258 90% 65%;
--primary-foreground: 0 0% 100%;
--secondary: 221 83% 60%;
--secondary-foreground: 0 0% 100%;
--muted: 240 8% 14%;
--muted-foreground: 240 5% 65%;
--border: 240 8% 18%;
--input: 240 8% 18%;
--ring: 258 90% 65%;
--destructive: 0 72% 51%;
--destructive-foreground: 0 0% 100%;
}
POST /auth/login
POST /auth/logout
POST /auth/refresh
POST /auth/forgot-password
POST /auth/reset-password
GET /auth/me
POST /companies
GET /companies
GET /companies/:id
PATCH /companies/:id
DELETE /companies/:id
POST /users
GET /users
GET /users/:id
PATCH /users/:id
DELETE /users/:id
POST /roles
GET /roles
GET /roles/:id
PATCH /roles/:id
DELETE /roles/:id
GET /permissions
POST /roles/:id/permissions
DELETE /roles/:id/permissions/:permissionId
Crear seed inicial para:
Debe crear un usuario super administrador.
Ejemplo:
name: David Labxus Admin
email: admin@labxus.com
password: cambiar123*
is_super_admin: true
company_id: null
status: active
La contraseña debe hashearse con bcrypt.
Crear:
SUPER_ADMIN
COMPANY_ADMIN
MANAGER
EMPLOYEE
READ_ONLY
Crear todos los permisos definidos anteriormente.
Crear:
inventory
orders
invoicing
employees
payroll
reports
Crear pruebas o ejemplos para validar:
SUPER_ADMIN crea empresa.COMPANY_ADMIN no puede crear empresa.COMPANY_ADMIN crea usuario en su empresa.COMPANY_ADMIN no puede crear usuario en otra empresa.
COMPANY_ADMIN no puede crear SUPER_ADMIN.
COMPANY_ADMIN crea rol en su empresa.COMPANY_ADMIN no modifica rol de otra empresa.SUPER_ADMIN sí puede ver todo.Entregar documentación con:
.env.Usar comandos con pnpm.
Ejemplos:
pnpm install
pnpm dev
pnpm build
pnpm lint
pnpm typecheck
pnpm --filter @labxus/api db:generate
pnpm --filter @labxus/api db:migrate
pnpm --filter @labxus/api db:seed
pnpm --filter @labxus/api start:dev
pnpm --filter @labxus/web dev
No generes todo de golpe de forma desordenada.
Primero entrega el diseño técnico completo:
Después empieza la implementación por fases.
Crear estructura base del monorepo:
apps/web
apps/api
packages/shared
Configurar:
Crear base de datos:
SUPER_ADMINCrear autenticación:
Crear recuperación de contraseña:
Crear empresas:
SUPER_ADMINCrear usuarios:
Crear roles y permisos:
Implementar RLS:
Crear frontend:
Testing y documentación:
Quiero que entregues:
No usar:
Sí usar:
No basta con ocultar botones en el frontend.
La seguridad debe existir en 3 niveles:
1. Frontend:
Ocultar opciones según permisos.
2. Backend:
Proteger rutas con guards de auth, roles, permisos y tenant.
3. Base de datos:
Proteger filas con PostgreSQL Row Level Security.
El sistema debe estar preparado para crecer después con módulos como:
inventario
órdenes
facturación electrónica
empleados
nómina
reportes
clientes
bodegas
productos
kardex