> ## Documentation Index
> Fetch the complete documentation index at: https://docs.filexpress.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Precios y promociones

> Listas de precios por sucursal, sincronización de precios y cálculo de promociones sobre un carrito

| Permiso | Endpoints |
| - | - |
| `pricing` | `GET /pricing/price-lists`, `GET /pricing/price-lists/{id}/prices`, `GET /pricing/promotions`, `POST /pricing/promotions/evaluate` |
| `pricing.write` | `PUT /pricing/price-lists/{id}/prices` |

<Note>
  Son permisos **explícitos**: deben marcarse uno por uno en **Integraciones externas**; las integraciones existentes no los reciben automáticamente.
</Note>

Pensado para tiendas en línea y otros canales de venta: consulta las listas de precios de una sucursal, sincroniza precios en lote y calcula las promociones de un carrito con **el mismo motor que el POS** de FileXpress, para que el precio final coincida en todos los canales.

## Listas de precios

```http theme={null}
GET /pricing/price-lists?branch_id={branch_id}
```

| Parámetro | Tipo | Requerido | Descripción |
| - | - | - | - |
| `branch_id` | string | Sí | Sucursal. |

```bash theme={null}
curl "https://api.filexpress.app/api/external/pricing/price-lists?branch_id=9d1c5a3e-..." \
  -H "X-API-Key: fx_xxxxxxxx"
```

```json theme={null}
{
  "status": true,
  "data": [
    {
      "id": "7b21...",
      "branch_id": "9d1c5a3e-...",
      "name": "Precio tienda en línea",
      "description": "Precios para el canal web",
      "is_default": false,
      "products_count": 342
    }
  ]
}
```

Primero la lista predeterminada (`is_default: true`) y luego el resto por nombre. **Errores**: `404` `Branch not found` (sin `branch_id` o de otro negocio).

## Precios de una lista

```http theme={null}
GET /pricing/price-lists/{id}/prices?search={texto}&per_page=100
```

| Parámetro | Tipo | Requerido | Descripción |
| - | - | - | - |
| `search` | string | No | Busca en nombre o SKU del producto (máx. 100). |
| `per_page` | integer | No | 1 a 500. Por defecto 100. |
| `page` | integer | No | Página. |

```json theme={null}
{
  "status": true,
  "data": {
    "price_list": { "id": "7b21...", "name": "Precio tienda en línea", "is_default": false },
    "data": [
      {
        "product_id": "8f3a...",
        "sku": "CAF-250",
        "name": "Café molido 250 g",
        "price": 4.25,
        "base_price": 3.7611,
        "updated_at": "2026-10-01T09:15:00-06:00"
      }
    ],
    "pagination": { "current_page": 1, "last_page": 4, "per_page": 100, "total": 342 }
  }
}
```

| Campo | Descripción |
| - | - |
| `price` | Precio del producto en esta lista. |
| `base_price` | Precio sin impuestos registrado en el producto. |

Ordenado por nombre de producto. **Errores**: `404` `Price list not found` (inexistente o de otro negocio); `422` validación.

## Actualizar precios

Crea o actualiza en lote los precios de una lista (sincronización desde la tienda). Requiere `pricing.write`.

```http theme={null}
PUT /pricing/price-lists/{id}/prices
```

| Campo | Tipo | Requerido | Descripción |
| - | - | - | - |
| `prices` | array | Sí | 1 a 1000 elementos. |
| `prices[].product_id` | string | Sí | Producto de la **misma sucursal** que la lista. |
| `prices[].price` | number | Sí | Precio (mayor o igual a 0). |

```bash theme={null}
curl -X PUT https://api.filexpress.app/api/external/pricing/price-lists/7b21.../prices \
  -H "X-API-Key: fx_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"prices":[{"product_id":"8f3a...","price":4.25},{"product_id":"8f3b...","price":12.00}]}'
```

```json theme={null}
{
  "status": true,
  "data": {
    "updated": 1,
    "rejected": ["8f3b..."]
  }
}
```

**Reglas**

* Si el producto ya tiene precio en la lista se actualiza; si no, se crea.
* Los productos que no pertenecen a la sucursal de la lista (o no existen) no se aplican y se devuelven en `rejected`; el resto sí se guarda.
* Si un `product_id` se repite en el arreglo, se toma el último.

## Promociones

```http theme={null}
GET /pricing/promotions?branch_id={branch_id}&active_only=true
```

| Parámetro | Tipo | Requerido | Descripción |
| - | - | - | - |
| `branch_id` | string | Sí | Sucursal. |
| `active_only` | boolean | No | Por defecto `true`: solo las activas y vigentes hoy. Con `false` devuelve todas las de la sucursal. |

```json theme={null}
{
  "status": true,
  "data": [
    {
      "id": "5c90...",
      "name": "15% en café",
      "description": null,
      "discount_type": "percentage",
      "discount_value": 15.0,
      "combo_price": null,
      "apply_on": "unit_price",
      "min_quantity": 2,
      "start_date": "2026-10-01",
      "end_date": "2026-10-31",
      "is_active": true,
      "priority": 10,
      "conditions": [
        { "type": "category", "id": "c12e..." }
      ]
    }
  ]
}
```

| Campo | Descripción |
| - | - |
| `discount_type` | `percentage` (porcentaje), `fixed` (monto fijo), `override_price` (precio especial: `discount_value` es el nuevo precio) o `combo`. |
| `apply_on` | `unit_price`: descuento por unidad del ítem. `subtotal`: descuento sobre el subtotal del ítem, que se devuelve como descuento de orden. |
| `combo_price` | Precio del combo (solo `combo`). |
| `min_quantity` | Cantidad mínima del ítem para que aplique. |
| `conditions[].type` | `product`, `category` o `brand`. Sin condiciones, la promoción aplica a todos los productos. |

Ordenadas de mayor a menor `priority`. **Errores**: `404` `Branch not found`.

## Calcular promociones de un carrito

```http theme={null}
POST /pricing/promotions/evaluate
```

| Campo | Tipo | Requerido | Descripción |
| - | - | - | - |
| `branch_id` | string | Sí | Sucursal cuyas promociones se evalúan. |
| `items` | array | Sí | 1 a 500 líneas. |
| `items[].product_id` | string | Sí | Producto. |
| `items[].quantity` | number | Sí | Cantidad (mayor o igual a 0). |
| `items[].unit_price` | number | Sí | Precio unitario usado en el carrito. |

```bash theme={null}
curl -X POST https://api.filexpress.app/api/external/pricing/promotions/evaluate \
  -H "X-API-Key: fx_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"branch_id":"9d1c5a3e-...","items":[{"product_id":"8f3a...","quantity":2,"unit_price":4.25},{"product_id":"8f3c...","quantity":1,"unit_price":6.00}]}'
```

```json theme={null}
{
  "status": true,
  "data": {
    "items": [
      {
        "product_id": "8f3a...",
        "quantity": 2,
        "unit_price": 4.25,
        "promo_discount": 0.6375,
        "promo_id": "5c90...",
        "promo_name": "15% en café",
        "promo_type": "percentage"
      },
      {
        "product_id": "8f3c...",
        "quantity": 1,
        "unit_price": 6.0,
        "promo_discount": 0,
        "promo_id": null,
        "promo_name": null,
        "promo_type": null
      }
    ],
    "order_discounts": [
      { "promo_id": "5d02...", "promo_name": "Combo desayuno", "promo_type": "combo", "discount": 1.25 }
    ],
    "totals": {
      "subtotal": 14.5,
      "item_discounts": 1.28,
      "order_discounts": 1.25,
      "total": 11.98
    }
  }
}
```

| Campo | Descripción |
| - | - |
| `items[].promo_discount` | Descuento **por unidad** de la promoción aplicada al ítem (promociones con `apply_on: unit_price`). |
| `order_discounts[]` | Descuentos a nivel de orden: promociones con `apply_on: subtotal` y combos. |
| `totals.item_discounts` | Suma de `promo_discount × quantity`. |
| `totals.total` | `subtotal − item_discounts − order_discounts` (nunca menor que 0). |

**Reglas**

* Solo se evalúan promociones activas cuya fecha de inicio y fin incluyen el día de hoy.
* **Por ítem gana una sola promoción**: la de mayor prioridad que aplique al producto (por producto, categoría o marca) y cumpla `min_quantity`.
* `percentage` descuenta un porcentaje; `fixed`, un monto fijo (sin superar la base); `override_price`, la diferencia hasta el precio especial.
* Un **combo** aplica cuando están en el carrito todos sus productos: descuenta la suma de sus precios unitarios menos `combo_price`.
* Si la sucursal no tiene promociones vigentes, los ítems se devuelven sin campos de promoción y `order_discounts` vacío.
* El cálculo no guarda nada: úsalo para mostrar el precio final y luego envía los descuentos al [crear la venta](/docs/api/facturacion/ventas).

## Errores

| Código | Cuándo |
| - | - |
| `401` | API key ausente o inválida. |
| `403` | La integración no tiene el permiso `pricing` (o `pricing.write` para actualizar precios). |
| `404` | `Branch not found` / `Price list not found` (inexistente o de otro negocio). |
| `422` | Validación (`Validation errors` con `errors`). |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.