# Precios

> Lo que paga un cliente por un SKU en una cantidad, y exactamente qué conjunto de reglas tiene ese precio.

TradeCatalog calcula cada precio de cliente en un solo lugar, de la misma forma para la aplicación, el portal comercial, MCP y la API. Nunca calcula precios usted mismo.

Los productos configurados, tarifas medidas, umbrales de cantidad, extras netos y redondeo explícito también utilizan este servicio de precios. Cotice una línea completa con `POST /products/{id}/quote`; un precio faltante puede significar `configurationRequired`, mientras que una especificación no cotizada válida es Precio bajo solicitud. Utilice el total de línea devuelto. Vea [configuración del catálogo de comercio](/docs/guides/trade-catalogues) para ver la precedencia, tratamiento neto y publicación.

## ¿Cómo se calculan los precios

La regla más específica gana, y **los ajustes nunca se acumulan**:

1. **Precio acordado.** Si el cliente tiene un precio (fijo) acordado para el producto, ese es el precio.
2. De lo contrario, la **precio de lista con un porcentaje**, el primero que se aplique. Un porcentaje es un descuento (deducido) o un recargo (añadido):
   1. el porcentaje del cliente para ese **producto**
   2. el porcentaje del cliente para la **categoría** del producto (gana el más cercano)
   3. el porcentaje del cliente para la **marca** del producto
   4. el porcentaje del cliente en la **lista completa**
3. De lo contrario, el **precio de lista**.

Un descuento de categoría del 10% y un recargo de lista completa del 5% dan un 10% de descuento, no ambos. La unidad nunca es inferior a cero, y se redondea hacia arriba a una unidad menor entera. El total de la línea es `unit × qty`.

Los precios de lista y los precios acordados están versionados por fecha. La API siempre proporciona el precio válido **ahora**. Los precios futuros de un cambio programado se aplican desde su fecha.

## Listas de precios y acceso al catálogo

Standard es el catálogo principal del proveedor. Otras listas de precios pueden contener productos con precios y productos que esperan una cotización. Una falta de precio en una lista asignada no se retrotrae a Standard.

En el espacio de trabajo, abra **Listas de precios** y elija una lista con nombre. **Los clientes asignados pueden ver** tiene dos opciones:

- **Todos los productos del catálogo permitidos** es el predeterminado. La lista establece los precios; los compradores asignados también pueden navegar por otros productos permitidos bajo solicitud de precio.
- **Solo los productos de esta lista** limita el catálogo, búsquedas, enlaces de productos y descargas, así como los nuevos pedidos de cada comprador asignado a los productos de esa lista. Todavía aplican la visibilidad de otros productos y las restricciones de listas aprobadas. Los clientes en otras listas mantienen su acceso existente.

Cada lista muestra su cantidad de clientes, productos con precio, productos sin precio y total de productos. Los totales incluyen productos inactivos, por lo que pueden superar lo que un comprador actualmente ve. Un precio cero se cuenta como un producto con precio. Un precio futuro se cuenta como un producto sin precio hasta su fecha de inicio.

Para agregar productos, selecciónelos en **Productos**, elija **Agregar a lista de precios**, luego copie los precios Estándar con un porcentaje opcional o seleccione **Agregar sin precios**. Los miembros existentes mantienen sus precios. Los productos sin precio Estándar se agregan como Precio bajo solicitud. En la página de una lista con nombre, guardar un SKU con un precio en blanco lo agrega sin precio o borra su precio actual. **Eliminar** lo quita de esa lista, manteniendo el producto principal y los pedidos anteriores. Resuelva primero cualquier cambio pendiente de precio para ese producto.

Estos controles requieren la versión Pro y derechos de edición globales de precios. Las listas existentes mantienen su acceso al catálogo hasta que un proveedor lo cambie. Las importaciones de API en una lista nombrada incluyen filas ordinarias de productos con ningún precio como miembros sin precio; una celda vacía en una importación solo de precios deja el acceso sin cambios.

## El objeto de precio

Cada producto tiene `price` con esta forma. `GET /prices` devuelve los mismos campos en el nivel superior de `data`.

```json
{
  "unit": 1573,
  "line": 15730,
  "list": 1850,
  "discountBp": 1500,
  "adjustment": "discount",
  "source": "brand",
  "label": "15% off your list",
  "remark": null
}
```

| Campo        | Tipo                  | Descripción                                                                                                                                         |
| ------------ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `unit`       | entero o nulo         | Precio por unidad, en unidades menores. `null` si el producto no tiene precio.                                                                      |
| `line`       | entero o nulo         | `unit × qty`.                                                                                                                                       |
| `list`       | entero o nulo         | El precio de lista ahora.                                                                                                                           |
| `discountBp` | entero                | El ajuste aplicado, en puntos básicos. `0` para precios acordados y de lista.                                                                       |
| `adjustment` | `discount` o `markup` | En qué dirección `discountBp` mueve el precio de lista. `discount` cuando no hay porcentaje.                                                        |
| `source`     | cadena                | Qué conjunto de reglas estableció el precio: véase a continuación.                                                                                  |
| `label`      | cadena                | Texto que ven los compradores junto al precio.                                                                                                      |
| `remark`     | cadena o nulo         | La nota del precio acordado cuando al cliente se le permite verla. De lo contrario `null`. La nota interna exclusiva del personal nunca se incluye. |

| `source`   | Significado                     | Ejemplo `label`                |
| ---------- | ------------------------------- | ------------------------------ |
| `fixed`    | El precio acordado del cliente  | Precio acordado                |
| `product`  | Porcentaje en un producto       | 10% de descuento en su lista   |
| `category` | Porcentaje en una categoría     | 12.5% de descuento en su lista |
| `brand`    | Porcentaje en una marca         | 15% por encima de su lista     |
| `account`  | Porcentaje en toda la lista     | 10% por encima de su lista     |
| `list`     | Precio de lista, sin ajuste     | (vacío)                        |
| `unpriced` | El producto aún no tiene precio | (vacío)                        |

## Verificar el precio de un cliente

**GET** `/api/v1/prices`

Cuánto paga un cliente por un SKU en una cantidad, y por qué. Úselo para responder "¿Cuánto paga Oakfield por 10 cajas de HX-8510?".

| Nombre     | Tipo   | Requerido | Descripción                                |
| ---------- | ------ | --------- | ------------------------------------------ |
| `customer` | cadena | sí        | ID del cliente o código de cuenta.         |
| `sku`      | cadena | sí        | El SKU, ignorando mayúsculas y minúsculas. |
| `qty`      | entero | no        | 1 a 1.000.000. Valor predeterminado 1.     |

```bash
curl "https://tradecatalog.app/api/v1/prices?customer=OAK01&sku=HX-8510&qty=10" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const params = new URLSearchParams({
  customer: 'OAK01',
  sku: 'HX-8510',
  qty: '10',
})
const res = await fetch(`https://tradecatalog.app/api/v1/prices?${params}`, {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: price, currency } = await res.json()
```

```json
{
  "data": {
    "customer": {
      "id": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
      "accountCode": "OAK01",
      "name": "Oakfield Joinery"
    },
    "sku": "HX-8510",
    "name": "Hex bolt M10 x 50 zinc",
    "qty": 10,
    "unit": 1573,
    "line": 15730,
    "list": 1850,
    "discountBp": 1500,
    "source": "brand",
    "label": "15% off your list",
    "remark": null
  },
  "currency": "GBP"
}
```

| Estado | Código             | Cuando                                                |
| ------ | ------------------ | ----------------------------------------------------- |
| 400    | `validation_error` | `customer` o `sku` faltante, o `qty` fuera de rango.  |
| 404    | `not_found`        | No existe ese cliente, o no hay producto con ese SKU. |

Este punto final verifica solo el precio. No verifica si el cliente puede ver el producto. Para eso, use [`GET /products/{id}?customer=…`](/docs/api/products#get-one-product).

## Precios para muchos productos

Para precioar una página completa de productos para un cliente, use `GET /products?customer=OAK01`. El `price` de cada fila es el precio de ese cliente, y los productos exclusivos para otros clientes se omiten. Vea [Productos](/docs/api/products#list-or-search-products).

## Cambiar precios

- **Precios de lista** cambian mediante cambios de precios, por lo que los clientes reciben notificación. Envíe nuevos precios con [`PUT /products`](/docs/api/products#create-or-update-products); van a un borrador que publica en la aplicación.
- **Recargos, descuentos y precios acordados** se establecen por cliente en la aplicación. Léalos con [`GET /customers/{id}`](/docs/api/customers#get-one-customer).
- **Precios acordados** para muchos clientes se establecen con [`PUT /agreed-prices`](/docs/api/prices#set-agreed-prices).
- El **porcentaje de toda la lista** de un cliente se puede establecer con [`PUT /customers`](/docs/api/customers#create-or-update-customers) (`discountBp`, y opcional `adjustment`).

## Establecer precios acordados

**PUT** `/api/v1/agreed-prices`

Crear o reemplazar precios acordados para clientes y productos existentes. Envíe hasta 2.000 filas. Los precios son unidades menores enteras. Deje `commit` en falso para previsualizar: nada se guarda, y cada cuenta o SKU desconocida vuelve como un error de fila. Establezca `commit` en verdadero para guardar. Guardar cierra el precio acordado válido ahora e inserta el nuevo desde este momento, en lotes de 100. Solo para Pro.

| Nombre        | Tipo     | Requerido | Descripción                                                      |
| ------------- | -------- | --------- | ---------------------------------------------------------------- |
| `commit`      | booleano | no        | `true` guarda. Valor predeterminado `false` solo previsualiza.   |
| `prices`      | matriz   | sí        | 1 a 2.000 precios.                                               |
| `accountCode` | cadena   | sí        | Código de cuenta del cliente, ignorando mayúsculas y minúsculas. |
| `sku`         | cadena   | sí        | SKU del producto, ignorando mayúsculas y minúsculas.             |
| `price`       | entero   | sí        | Precio acordado en unidades menores.                             |
| `note`        | cadena   | no        | Máximo 200 caracteres, mostrado al personal.                     |

```bash
curl -X PUT https://tradecatalog.app/api/v1/agreed-prices \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"commit":false,"prices":[{"accountCode":"OAK01","sku":"HX-8510","price":1250,"note":"Contract"}]}'
```

| Estado | Código             | Cuando                                                               |
| ------ | ------------------ | -------------------------------------------------------------------- |
| 400    | `validation_error` | Falta un precio, es negativo o excede el límite.                     |
| 403    | `read_only`        | El espacio de trabajo no está en Pro, o la clave es de solo lectura. |
