Inventory System provides two complementary flows for moving product out of stock: direct sales that immediately decrement inventory, and quotations that softly reserve stock while a customer decides. Both flows are multi-tenant, permission-gated, and protected by database-level pessimistic locking to prevent overselling under concurrent load.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/juadariasmar/inventory_project/llms.txt
Use this file to discover all available pages before exploring further.
Sales (Ventas)
Venta model
| Field | Type | Description |
|---|---|---|
id | Int | Auto-incremented primary key |
vendedorId | String? | FK to the Usuario who registered the sale |
clienteId | Int? | Optional FK to a Cliente record |
total | Float | Sum of all line subtotals |
notas | String? | Optional free-text notes |
canceladaEn | DateTime? | Set when the sale is cancelled |
canceladaPorId | String? | FK to the user who cancelled it |
motivoCancelacion | String? | Reason supplied at cancellation |
creadoEn | DateTime | UTC timestamp of the sale |
empresaId | String | Tenant key |
How a sale decrements stock
POST /api/ventas calls VentasService.registrarVenta, which opens a single Prisma transaction that:
- Acquires a pessimistic
SELECT … FOR UPDATElock on all affected product rows (sorted by ID to avoid deadlocks). - Fetches current quantities and active quote reservations for those products.
- Validates that
cantidad ≤ (stock_físico − stock_reservado)for every line item. - Creates the
Ventarecord and itsItemVentachildren. - Creates one
Movimientoof typesalidaper product, linked viaventaId. - Decrements
Producto.cantidadfor each product.
400 error is returned.
ItemVenta structure
Each line item in a sale is stored as anItemVenta:
| Field | Type | Description |
|---|---|---|
id | Int | Auto-incremented primary key |
ventaId | Int | FK to the parent Venta |
productoId | Int | FK to the sold product |
cantidad | Int | Units sold |
precioUnitario | Float | Unit price captured at sale time |
subtotal | Float | cantidad × precioUnitario |
precioUnitario is captured at the moment of sale from Producto.precio. Future price changes do not affect historical sale records.Sale cancellation
canceladaEn to the current timestamp, records canceladaPorId, and stores the motivoCancelacion. The system also creates a compensating Movimiento of type entrada per sold item to return the units to stock, ensuring the cancellation is fully reflected in inventory and analytics.
Client association
BothVenta and Cotizacion support an optional clienteId. If you pass a cliente name string to the quote creation endpoint and no matching Cliente record exists for that name in the company, the service automatically creates one.
Quick-sell terminal
The/venta-rapida page provides a point-of-sale terminal optimized for fast throughput. It includes a floating cart component where operators scan or search products, adjust quantities, and confirm the sale in a single action. The cart consolidates duplicate product entries automatically before calling POST /api/ventas.
Quotes (Cotizaciones)
Cotizacion model
| Field | Type | Description |
|---|---|---|
id | Int | Auto-incremented primary key |
vendedorId | String? | FK to the creating Usuario |
clienteId | Int? | Optional FK to a Cliente |
total | Float | Sum of all line subtotals |
notas | String? | Optional notes |
estado | EstadoCotizacion | PENDIENTE, CONVERTIDA, or CANCELADA |
validaHasta | DateTime | Expiry date of the quote |
convertidaEn | DateTime? | Set when the quote is converted to a sale |
ventaId | Int? | FK to the resulting Venta after conversion |
canceladaEn | DateTime? | Set on cancellation |
canceladaPorId | String? | FK to the cancelling user |
motivoCancelacion | String? | Reason for cancellation |
creadoEn | DateTime | UTC creation timestamp |
empresaId | String | Tenant key |
EstadoCotizacion enum
PENDIENTE
Active quote. The reserved quantities are counted against available stock for all subsequent sales and new quotes.
CONVERTIDA
The quote was converted to a
Venta. Stock is now physically decremented. ventaId points to the resulting sale.CANCELADA
Cancelled manually. The reservation is released and the quantities become available again.
Quote lifecycle
Stock reservation
A quote inPENDIENTE state with validaHasta > now() creates an implicit reservation. No physical stock is decremented, but the reserved quantity is subtracted from available stock in all subsequent validation checks (both in VentasService and CotizacionesService). The reservation is computed at query time by summing ItemCotizacion.cantidad for all pending, non-expired quotes.
Quote validity
ThevalidaHasta field is set at creation time. You may supply a custom diasValidez integer (1–365); the default is 7 days. Once validaHasta passes, the quote’s reservation is no longer counted and the stock is implicitly released — the record itself remains in PENDIENTE state until explicitly converted or cancelled.
The
COTIZACION_POR_VENCER notification fires automatically when a quote’s validaHasta is within 24 hours, prompting the sales team to follow up.ItemCotizacion structure
| Field | Type | Description |
|---|---|---|
id | Int | Auto-incremented primary key |
cotizacionId | Int | FK to the parent Cotizacion |
productoId | Int | FK to the quoted product |
cantidad | Int | Units quoted |
precioUnitario | Float | Unit price at quote creation time |
subtotal | Float | cantidad × precioUnitario |
Code examples
Create a sale
Create a quote
Cotizacion object including nested items with product names and codes.