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 maintains a complete, immutable ledger of every stock change through the Movimiento model. Whether stock increases because a purchase order arrived, decreases because a sale was processed, or is adjusted manually by a warehouse operator, a movement record is always created. This gives you a traceable history of every unit that has entered or left your inventory.

Movimiento model

Each movement record captures:
FieldTypeDescription
idIntAuto-incremented primary key
tipoentrada | salidaDirection of the stock change
cantidadIntNumber of units moved (always a positive integer)
productoIdIntFK to the affected Producto
ventaIdInt?FK to the originating Venta, if applicable
ordenCompraIdInt?FK to the originating OrdenCompra, if applicable
notasString?Optional free-text note describing the reason
empresaIdStringTenant isolation key
creadoEnDateTimeUTC timestamp of the movement
ventaId and ordenCompraId are mutually exclusive in practice: a movement is either linked to a sale, to a purchase order receipt, or to neither (manual adjustment).

Automatic movement creation

The system creates movements automatically in two situations:
1

Sale confirmed

When POST /api/ventas is called, VentasService.registrarVenta runs inside a database transaction. For every product in the sale it creates a Movimiento of type salida linked to the new Venta via ventaId, and decrements Producto.cantidad by the sold quantity. The movement note is set to "Venta #<id>".
2

Purchase order received

When POST /api/ordenes-compra/[id]/recibir is called, OrdenesCompraService.recibir runs a transaction that iterates over every OrdenCompraItem. For each item it creates a Movimiento of type entrada linked to the order via ordenCompraId, and increments Producto.cantidad. The note is set to "Orden de compra #<id>".
Both flows use pessimistic row-level locking (SELECT … FOR UPDATE) on the affected Producto rows to prevent race conditions under concurrent requests.

Manual movements

Operators with the REGISTRAR_MOVIMIENTOS permission (or REALIZAR_VENTAS for exits) can record ad-hoc adjustments via POST /api/movimientos:
POST /api/movimientos
Content-Type: application/json
{
  "productoId": 42,
  "tipo": "entrada",
  "cantidad": 50,
  "notas": "Ajuste por inventario físico — conteo anual"
}
The service validates that:
  • tipo is exactly "entrada" or "salida".
  • cantidad is a positive integer (fractional values are rejected).
  • For exits, the available stock after subtracting active quote reservations is sufficient.
A successful response returns the created Movimiento including the updated Producto snapshot.

Filtering and pagination

GET /api/movimientos supports cursor-based pagination:
Query parameterDescription
cursorID of the last record from the previous page
limitePage size (default 50, maximum 100)
The response shape is:
{
  "items": [ /* array of Movimiento objects with nested producto */ ],
  "nextCursor": 198,
  "total": 50
}
Pass nextCursor as cursor in the next request to load the following page. When nextCursor is null there are no more records.

Bulk delete

To delete multiple movement records at once:
POST /api/movimientos/bulk-delete
Content-Type: application/json
{
  "ids": [101, 102, 103]
}
Deleting a movement does not reverse the associated stock change. Use bulk delete only to remove erroneous or duplicate records after manually correcting the product quantities.

Relationship to analytics

Every Movimiento is included in the analytics calculations performed by src/lib/analisis.ts:
  • obtenerResumenMovimientos — aggregates daily entry and exit counts over the last 30 days for the GraficoMovimientos chart.
  • obtenerAltaRotacion — sums exit movements per product to identify the top-10 highest-rotation items.
  • obtenerStockPorAgotarse — computes the average daily consumption from exit movements to project when each product will run out.
See Analytics for the full breakdown of the dashboard.

Code example — create a manual entry

const response = await fetch('/api/movimientos', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    productoId: 42,
    tipo: 'entrada',
    cantidad: 100,
    notas: 'Reposición de emergencia — proveedor express',
  }),
})

const movimiento = await response.json()
// movimiento.id, movimiento.tipo, movimiento.cantidad, movimiento.producto …

Build docs developers (and LLMs) love