Ventas y productos en venta
Estos recursos cubren el módulo de Ventas: productos como tarjetas de memoria, baterías o cinta americana que una empresa de alquiler vende con control de stock, junto a sus alquileres.
RequiplyError con status403 y reasonfeature_disabled (selling_disabled con una clave publicable).| Método | Clave secreta | Clave publicable |
|---|---|---|
saleItems.list(), saleItems.get() | Sí | Solo los productos activos, mientras la organización vende a los clientes |
saleLines en pricing.quote() y bookings.create() | Sí | Sí, dentro del stock disponible, mientras la organización vende a los clientes |
sales.list(), sales.get(), sales.create() | Sí | No |
Los productos en venta se mantienen aparte de los productos de alquiler: inventory.list(), la disponibilidad y categories.list() nunca los devuelven.
Con una clave publicable
Una tienda online puede mostrar productos en venta y dejar que los clientes los añadan a una solicitud de reserva, como hace el portal de clientes en su paso Items to buy (artículos para comprar). Una clave publicable solo llega a ellos mientras la organización vende a los clientes, para lo que hacen falta las tres cosas siguientes:
- la función de ventas en el plan Advanced, como se indica arriba;
- Selling items (venta de artículos) activado en la configuración del portal de clientes;
- al menos un producto en venta que no esté archivado.
Si no, la llamada lanza un 403 con reason selling_disabled, así que una tienda online puede ocultar su sección de productos.
Con una clave publicable:
saleItems.list()solo devuelve los productos activos, diga lo que digafilter.barcodeypurchaseCostson siemprenull.saleItems.get()responde404para un producto archivado, ysoldUnits30d,revenue30dysoldUnitsTotalson0.saleLinesno puede pedir más destockOnHand. Una línea que supera el stock lanza un409conreasonout_of_stock;data.saleItemIdindica el producto ydata.availableQuantity, cuántos quedan (0cuando está agotado).
try {
await requiply.pricing.quote({ startDate, endDate, items, saleLines });
} catch (error) {
if (error instanceof RequiplyError && error.reason === 'out_of_stock') {
showNotice(`Only ${error.data?.availableQuantity} left`);
} else if (error instanceof RequiplyError && error.reason === 'selling_disabled') {
hideGoodsSection();
} else {
throw error;
}
}
saleItems.list()
saleItems.list(
params?: SaleItemsListParams,
options?: RequiplyRequestOptions,
): Promise<SaleItemsListResponse>
Parámetros opcionales: search (nombre, SKU o código de barras), category (ID de categoría), filter (active por defecto, archived o all; las claves publicables siempre reciben active), limit (de 1 a 100, por defecto 25) y cursor.
const { items, nextCursor } = await requiply.saleItems.list({ search: 'battery' });
for (const item of items) {
console.log(item.name, item.salePrice, item.currency, item.stockOnHand);
}
| Campo | Qué contiene |
|---|---|
id, name, sku, barcode | El producto. sku y barcode pueden ser null; barcode siempre es null con una clave publicable. |
categoryId, categoryName, imageUrl | Pueden ser null. |
salePrice, currency | Precio fijo por unidad en la moneda de la organización. |
stockOnHand | Las unidades en el almacén. Puede ser negativo cuando las ventas han superado el stock registrado. |
purchaseCost | El coste neto por unidad, o null salvo que la clave pueda gestionar el stock. Siempre null con una clave publicable. |
isArchived | Los productos archivados no se pueden vender. |
saleItems.get()
saleItems.get(
saleItemId: string,
options?: RequiplyRequestOptions,
): Promise<SaleItemResponse>
Devuelve saleItem con los campos de arriba más soldUnits30d, revenue30d y soldUnitsTotal.
const { saleItem } = await requiply.saleItems.get(itemId);
console.log(saleItem.soldUnits30d, saleItem.revenue30d);
sales.list()
sales.list(
params?: SalesListParams,
options?: RequiplyRequestOptions,
): Promise<SalesListResponse>
Lista las ventas, de la más reciente a la más antigua, tanto las independientes como las vendidas con reservas. Filtros opcionales:
| Parámetro | Efecto |
|---|---|
kind | standalone o booking. |
status | completed o cancelled. |
unpaid | true para las ventas independientes que todavía tienen importe pendiente. |
clientId | Las ventas de un cliente. |
soldFrom, soldTo | Fechas y horas ISO 8601. |
search | Número de venta, nombre, email o teléfono del cliente, nombre o SKU del producto. |
limit, cursor | El tamaño de página (de 1 a 100, por defecto 25) y el nextCursor de la página anterior. |
const { sales, currency } = await requiply.sales.list({ kind: 'standalone', unpaid: true });
Cada venta tiene id, saleOrgId (el número de venta que ve el personal en la app), status, soldAt, total, returnedTotal, net (el total menos las devoluciones), paidAmount, balance, paymentMethod, paidAt, note, el cliente, la reserva a la que pertenece (reservationId, reservationOrgId), itemCount e itemsLabel. balance es lo que todavía debe una venta independiente; siempre es 0 en una venta de una reserva, que se paga a través de la reserva.
sales.get()
sales.get(
id: string | number,
params?: { idKind?: 'internal' | 'org' },
options?: RequiplyRequestOptions,
): Promise<SaleResponse>
id es el UUID de la venta, o el número de venta de la app con idKind: 'org'.
const { sale } = await requiply.sales.get(12, { idKind: 'org' });
La venta también incluye lines (producto, cantidad, returnedQuantity, unitPrice, lineTotal), returns y payments. En una venta de una reserva, payments son los de la reserva, porque la reserva cobra el alquiler y los productos en un único pago.
sales.create()
sales.create(
input: SaleCreateInput,
options?: RequiplyRequestOptions,
): Promise<SaleCreateResponse>
Registra una venta independiente, como Nueva venta en la app.
const { sale, stockWarnings, payment } = await requiply.sales.create({
lines: [{ saleItemId: itemId, quantity: 2 }],
client: { name: 'Alex', email: 'customer@example.com' },
paymentMethod: 'monobank',
note: 'Counter sale',
});
if (payment?.paymentUrl) console.log('Pay here:', payment.paymentUrl);
| Campo | Reglas |
|---|---|
lines | Obligatorio. De 1 a 100 productos, cada uno una sola vez, con una cantidad entera mayor que cero. Cada línea se vende al salePrice del producto. |
clientId o client | Opcional. client se busca por email y, después, por teléfono, y se crea cuando no coincide ninguno. |
paymentMethod | payOnPickup (por defecto), o monobank, liqpay, wayforpay o stripe con una integración activa. liqpay necesita un cliente con email. |
soldAt | ISO 8601. Por defecto, el momento actual. |
note | Hasta 2000 caracteres. |
El stock baja al momento. Una venta puede dejar el stock por debajo de cero: se registra igualmente, y stockWarnings lista cada producto afectado con su stockOnHand y el stock after (después de) la venta. payment es el cobro pendiente con el que empieza la venta, con un paymentUrl para un método online.
Una venta rechazada lanza un RequiplyError con un reason como sale_item_archived, payment_method_unavailable o client_email_required. Consulta Gestión de errores para ver la lista completa.
Productos en una reserva
pricing.quote() y bookings.create() aceptan saleLines para vender productos junto con un alquiler:
const quote = await requiply.pricing.quote({
startDate: '2026-07-01T10:00:00',
endDate: '2026-07-03T18:00:00',
items: [{ groupId: cameraKitId, quantity: 1 }],
saleLines: [{ saleItemId: memoryCardId, quantity: 2 }],
});
console.log(quote.saleSubtotal, quote.lineSaleItems[0]?.stockAfter);
- Una clave secreta puede vender dejando el stock en negativo, igual que puede hacerlo el personal en la app. Una clave publicable no: consulta Con una clave publicable.
- Los productos se venden a su precio de catálogo y nunca se les aplica descuento ni ajuste. Su total es
saleSubtotal, ya incluido entotal. lineSaleItemsmuestra cada producto constockOnHandystockAfter, para que puedas avisar antes de que el stock quede en negativo.- En
bookings.create(), el stock baja al crear la reserva y se recupera si la reserva se cancela. El total de los productos se suma altotalCostde la reserva y a su pago. bookings.get()devuelve los productos comosaleItemsy su venta comosaleId.
Para cambiar los productos de una reserva existente, usa Vender artículos en una reserva en la app.