Kantuta POS Backend is built with NestJS 11 following a domain-driven modular architecture. Each business domain — inventory, sales, cash registers, agent transactions, and more — lives in its own self-contained NestJS module with its own controllers, services, DTOs, and TypeORM entities. Modules communicate through dependency injection rather than direct coupling, making the codebase straightforward to extend and test in isolation. The rootDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/Eleazarguitar18/kantuta_pos_back/llms.txt
Use this file to discover all available pages before exploring further.
AppModule wires every domain module together along with shared infrastructure concerns like the database connection, Redis cache, JWT, and global request pipeline.
Module Map
The table below lists every module registered inAppModule and the domain it owns.
AppModule
Root orchestrator. Registers
ConfigModule (global env), TypeOrmModule (PostgreSQL via DatabaseConfig), CacheModule (Redis via RedisConfig), and the global JwtModule. Imports every domain module listed below.AuthModule
Owns authentication and access control. Provides
AuthGuard (JWT verification), RolesGuard (role-based authorization), and the AuthController. Entities: auth.entity, role.entity.PersonaModule
Manages natural-person profiles — names, surnames, date of birth, and gender. Each
Persona record is linked one-to-one with a Usuario account and stores identity information separately from credentials.UsuarioModule
Manages system user accounts (operators and administrators). Handles user registration, credential storage, and the association between a
Usuario and its Persona profile and Role.InventarioModule
Composed of two sub-modules: CategoriasModule and ProductosModule. Manages the product catalog, barcode lookup, pricing, stock levels, and stock alerts.
VentasModule
Handles point-of-sale transactions. Controller and service manage sale creation, void/edit flows, and stock reduction. Entities:
venta, detalle-venta.CajasModule
Manages physical cash drawers and shift sessions. Entities:
caja, sesion-caja, movimiento-caja. Handles open/close sessions, cash-in/cash-out movements, and end-of-shift reconciliation.AgentesModule
Records external financial agent transactions (Tigo Money, QR transfers, bank deposits/withdrawals). Associates each transaction to an active
SesionCaja.ComprasModule
Manages supplier purchases and stock replenishment. Entities:
compra, detalle-compra. Optionally debits the active cash session when pagado_con_caja is true.RecargasModule
Handles mobile top-ups. Controller and service manage recharge operations. Entities:
inyeccion-operadora, operadora-saldo, recarga-cliente.MailModule
Sends transactional emails using Nodemailer with Handlebars templates. Used by AuthModule for password-reset codes and other system notifications.
WhatsappModule
Integrates a Baileys WebSocket gateway for WhatsApp messaging. Enables sending receipts and notifications directly to customers.
ReportesModule
Provides dashboard aggregate queries and printable reports — daily sales summaries, cash-register reconciliation totals, and low-stock alerts.
RedisManagerModule
Wraps the shared
CacheModule (Redis) and exposes a typed RedisManagerService for storing and retrieving key-value data such as password-reset codes and session tokens.GatewayModule
Exposes a Socket.IO WebSocket gateway so connected frontends receive real-time push events (new sales, stock changes, session state updates) without polling the REST API.
GeminiModule
Provides a low-level integration with the Google Gemini API. Exposes a configured Gemini client that other modules (such as
AiAssistantModule) use to send prompts and receive generated responses.AiAssistantModule
Builds the AI-powered POS assistant on top of
GeminiModule. Handles natural-language queries against inventory and sales data, generates summaries, and provides operator guidance through the REST API.Request Lifecycle
Every HTTP request passes through the following pipeline before reaching business logic:Incoming HTTP Request
The NestJS HTTP adapter (Express) receives the request. Morgan logs the method, path, and status code in
dev format.AuthGuard — JWT Verification
The globally registered
AuthGuard checks whether the route is decorated with @Public(). If public, the request passes immediately. Otherwise, the guard extracts the Bearer token from the Authorization header and calls jwtService.verifyAsync() using the JWT_SECRET from environment. The decoded payload (including roleId and roleName) is attached to request.user. A missing or invalid token throws UnauthorizedException.RolesGuard — Role Verification
If the controller or handler is decorated with
@Roles(), the RolesGuard reads the required roles from metadata and compares them against request.user.roleName / request.user.roleId. Administrators (roleId 1) always pass. Operators (roleId 2) pass only if 'Operador' is in the required roles list.ValidationPipe — DTO Transformation & Whitelisting
The global
ValidationPipe transforms the raw JSON body into the typed DTO class, strips any undeclared fields (whitelist: true), and throws a 400 Bad Request if unknown properties are present (forbidNonWhitelisted: true). Type coercion is applied automatically (transform: true).Controller → Service → TypeORM → PostgreSQL
The route handler delegates to the domain service. The service interacts with the PostgreSQL database through TypeORM repositories. Queries may read from or write to Redis via
RedisManagerService for caching.ClassSerializerInterceptor — Response Serialization
The global
ClassSerializerInterceptor applies class-transformer rules on the response object. Properties decorated with @Exclude() on entity classes (such as password) are stripped before the JSON is sent to the client.AllExceptionsFilter — Unified Error Responses
If any layer throws an exception, the global
AllExceptionsFilter catches it and returns a consistent JSON error envelope (see Error Format below).The Redis cache layer sits alongside the Service layer — it is not a mandatory middleware but an opt-in call within individual services to cache expensive reads (product lists, report aggregates) and store short-lived tokens (password-reset codes with 15-minute TTL).
Global Pipes
TheValidationPipe is registered globally in main.ts with these options:
| Option | Effect |
|---|---|
whitelist: true | Silently removes extra fields from the request body |
forbidNonWhitelisted: true | Returns a 400 error listing unexpected fields |
transform: true | Converts primitive types and instantiates DTO classes automatically |
Global Filters
AllExceptionsFilter is the single catch-all filter registered globally. It handles three exception categories:
| Exception Type | HTTP Status | Behaviour |
|---|---|---|
HttpException | As defined by exception | Extracts message and error from the response object |
QueryFailedError (TypeORM) | 409 Conflict | Returns 'Error en consulta de Base de Datos' with the DB detail |
Generic Error | 500 Internal Server Error | Uses error.name as message, error.message as detail |
The
stack field is only included when NODE_ENV is not production.Global Interceptors
ClassSerializerInterceptor is registered globally and works with class-transformer decorators placed on entity classes:
@Exclude() — most notably password on the Usuario entity — is automatically removed from every HTTP response without any manual mapping.
BaseEntityAudit
Most entities in the system inherit from the abstractBaseEntityAudit class defined in src/common/entities/base-entity.audit.ts:
| Field | Type | Purpose |
|---|---|---|
estado | boolean | Soft-delete flag. true = active, false = deleted |
id_user_create | number | ID of the user who created the record (audit trail) |
id_user_update | number | ID of the last user who modified the record |
created_at | timestamp | Auto-set on insert by TypeORM @CreateDateColumn |
updated_at | timestamp | Auto-updated on every save by TypeORM @UpdateDateColumn |
Soft Delete Pattern
Kantuta POS does not physicallyDELETE rows from the database for most domain entities. Instead, a delete operation sets estado = false on the target record.
estado = true so deleted records are invisible to the API consumers while remaining available for historical reports and audit queries.