Skip to main content

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.

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.

Sales (Ventas)

Venta model

FieldTypeDescription
idIntAuto-incremented primary key
vendedorIdString?FK to the Usuario who registered the sale
clienteIdInt?Optional FK to a Cliente record
totalFloatSum of all line subtotals
notasString?Optional free-text notes
canceladaEnDateTime?Set when the sale is cancelled
canceladaPorIdString?FK to the user who cancelled it
motivoCancelacionString?Reason supplied at cancellation
creadoEnDateTimeUTC timestamp of the sale
empresaIdStringTenant key

How a sale decrements stock

POST /api/ventas calls VentasService.registrarVenta, which opens a single Prisma transaction that:
  1. Acquires a pessimistic SELECT … FOR UPDATE lock on all affected product rows (sorted by ID to avoid deadlocks).
  2. Fetches current quantities and active quote reservations for those products.
  3. Validates that cantidad ≤ (stock_físico − stock_reservado) for every line item.
  4. Creates the Venta record and its ItemVenta children.
  5. Creates one Movimiento of type salida per product, linked via ventaId.
  6. Decrements Producto.cantidad for each product.
If stock is insufficient for any item the entire transaction is rolled back and a 400 error is returned.

ItemVenta structure

Each line item in a sale is stored as an ItemVenta:
FieldTypeDescription
idIntAuto-incremented primary key
ventaIdIntFK to the parent Venta
productoIdIntFK to the sold product
cantidadIntUnits sold
precioUnitarioFloatUnit price captured at sale time
subtotalFloatcantidad × precioUnitario
precioUnitario is captured at the moment of sale from Producto.precio. Future price changes do not affect historical sale records.

Sale cancellation

POST /api/ventas/[id]/cancelar
Content-Type: application/json
{
  "motivo": "Cliente solicitó devolución"
}
Cancellation sets 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.
Only users with the Admin or Super Admin role can cancel sales. Additionally, cancellation is only permitted on the same calendar day the sale was created — attempting to cancel an older sale returns a 400 error.

Client association

Both Venta 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

FieldTypeDescription
idIntAuto-incremented primary key
vendedorIdString?FK to the creating Usuario
clienteIdInt?Optional FK to a Cliente
totalFloatSum of all line subtotals
notasString?Optional notes
estadoEstadoCotizacionPENDIENTE, CONVERTIDA, or CANCELADA
validaHastaDateTimeExpiry date of the quote
convertidaEnDateTime?Set when the quote is converted to a sale
ventaIdInt?FK to the resulting Venta after conversion
canceladaEnDateTime?Set on cancellation
canceladaPorIdString?FK to the cancelling user
motivoCancelacionString?Reason for cancellation
creadoEnDateTimeUTC creation timestamp
empresaIdStringTenant 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

PENDIENTE ──────────────────► CONVERTIDA
    │         POST /api/cotizaciones/[id]/convertir

    └──────────────────────► CANCELADA
              POST /api/cotizaciones/[id]/cancelar
Non-admin users can only see and operate on their own quotes. Admin and Super Admin roles can see all company quotes.

Stock reservation

A quote in PENDIENTE 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

The validaHasta 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

FieldTypeDescription
idIntAuto-incremented primary key
cotizacionIdIntFK to the parent Cotizacion
productoIdIntFK to the quoted product
cantidadIntUnits quoted
precioUnitarioFloatUnit price at quote creation time
subtotalFloatcantidad × precioUnitario

Code examples

Create a sale

POST /api/ventas
Content-Type: application/json
{
  "items": [
    { "productoId": 42, "cantidad": 3 },
    { "productoId": 17, "cantidad": 1 }
  ],
  "notas": "Cliente mostrador — pago en efectivo"
}

Create a quote

POST /api/cotizaciones
Content-Type: application/json
{
  "items": [
    { "productoId": 42, "cantidad": 10 },
    { "productoId": 17, "cantidad": 5 }
  ],
  "cliente": "Distribuidora Pérez",
  "notas": "Pendiente aprobación de gerencia",
  "diasValidez": 14
}
The response is the created Cotizacion object including nested items with product names and codes.

Build docs developers (and LLMs) love