Skip to main content

Documentation 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.

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 root 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 in AppModule 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:
1

Incoming HTTP Request

The NestJS HTTP adapter (Express) receives the request. Morgan logs the method, path, and status code in dev format.
2

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.
3

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.
4

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).
5

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.
6

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.
7

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

The ValidationPipe is registered globally in main.ts with these options:
app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,            // Strip properties not declared in the DTO
    forbidNonWhitelisted: true, // Throw 400 if unknown properties are present
    transform: true,            // Auto-coerce types (e.g. string "1" → number 1)
  }),
);
OptionEffect
whitelist: trueSilently removes extra fields from the request body
forbidNonWhitelisted: trueReturns a 400 error listing unexpected fields
transform: trueConverts primitive types and instantiates DTO classes automatically

Global Filters

AllExceptionsFilter is the single catch-all filter registered globally. It handles three exception categories:
Exception TypeHTTP StatusBehaviour
HttpExceptionAs defined by exceptionExtracts message and error from the response object
QueryFailedError (TypeORM)409 ConflictReturns 'Error en consulta de Base de Datos' with the DB detail
Generic Error500 Internal Server ErrorUses error.name as message, error.message as detail
Error response envelope:
{
  "statusCode": 400,
  "timestamp": "2025-06-01T14:32:00.000Z",
  "path": "/inventario/producto",
  "message": ["nombre should not be empty"],
  "error_detail": "Bad Request",
  "stack": "..." 
}
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:
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
Any entity property decorated with @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 abstract BaseEntityAudit class defined in src/common/entities/base-entity.audit.ts:
export abstract class BaseEntityAudit {
  @Column({ default: true })
  estado: boolean;

  @Column({ nullable: true })
  id_user_create: number;

  @Column({ nullable: true })
  id_user_update?: number;

  @CreateDateColumn({ type: 'timestamp' })
  created_at: Date;

  @UpdateDateColumn({ type: 'timestamp' })
  updated_at: Date;
}
FieldTypePurpose
estadobooleanSoft-delete flag. true = active, false = deleted
id_user_createnumberID of the user who created the record (audit trail)
id_user_updatenumberID of the last user who modified the record
created_attimestampAuto-set on insert by TypeORM @CreateDateColumn
updated_attimestampAuto-updated on every save by TypeORM @UpdateDateColumn

Soft Delete Pattern

Kantuta POS does not physically DELETE rows from the database for most domain entities. Instead, a delete operation sets estado = false on the target record.
// Example: soft-deleting a product
await this.productoRepository.update(id, {
  estado: false,
  id_user_update: userId,
});
All list queries filter by estado = true so deleted records are invisible to the API consumers while remaining available for historical reports and audit queries.
When building frontend delete confirmations, communicate to users that records are deactivated rather than permanently removed. This preserves referential integrity in sale history and purchase records.

Build docs developers (and LLMs) love