# Pedidos

> Crear órdenes de clientes, registrar la ejecución y correcciones, y agregar seguimiento de entrega opcional.

Los compradores colocan pedidos en su portal de comercio. Los proveedores manejan la facturación. Pueden ofrecer opcionalmente [pagos de clientes alojados por Stripe](/docs/guides/customer-payments) después de confirmar el total final. Las solicitudes de pedido y el crédito de comercio permanecen disponibles. Use la API para extraer pedidos en su ERP o sistema contable y mantener actualizado su estado.

## Estados de los pedidos

| Estado         | Significado                                |
| -------------- | ------------------------------------------ |
| `new`          | Colocado                                   |
| `pending`      | En espera                                  |
| `acknowledged` | Visto y en proceso de selección            |
| `processed`    | Listo para envío                           |
| `dispatched`   | Enviado por el proveedor                   |
| `completed`    | Proveedor marcado como completo            |
| `cancelled`    | Cancelado con un motivo visible al cliente |

El personal puede omitir etapas intermedias de selección. Moverse hacia atrás o reabrir un pedido completado o cancelado requiere un `correctionReason` interno. El historial anterior permanece. Las correcciones nunca alteran el estado de pago. Lea `revision` y envíe `expectedRevision` con las actualizaciones para rechazar ediciones obsoletas.

## Crear un pedido para un cliente

**POST** `/api/v1/orders/preview`

Requiere `orders.create` y acceso al cliente. Envíe `customerAccountId` y `lines` que contengan `productId` y un entero `qty` (de 1 a 1.000.000). Elija cada producto ordinario una vez, como máximo 100 líneas. Una línea puede incluir un `configuration` opcional que contenga las claves de medición y elección devueltas; las diferentes configuraciones de un mismo producto permanecen separadas. El `lineKey` opcional otorga a cada línea de vista previa una identidad de llamada estable. Un `additionalDiscountBp` adicional requiere permiso de precios y `discountReason`. Las órdenes configuradas incluyen un `configuration` inmutable con una instantánea de entradas, especificación, versión, redondeo y cargos. La vista previa devuelve `total`, `fingerprint`, precios y moneda del cliente actual. Las cuentas pausadas, productos no disponibles y cantidades inferiores al mínimo son rechazadas. Un catálogo solo de precios sigue aceptando un pedido ingresado aquí; los compradores no pueden enviar uno ellos mismos mientras esté desactivado.

**POST** `/api/v1/orders`

Envíe el mismo cliente y líneas, `expectedTotal` de la vista previa, `expectedBasket` establecido en su huella dactilar, y un UUID nuevo `requestKey`. Se admiten `poNumber` opcional (60 caracteres) y `notes` visible para el cliente (2.000 caracteres). Reutilice exactamente la misma solicitud y clave después de una respuesta incierta: un reintentó exitoso devuelve el pedido original en lugar de crear otro. Los precios modificados requieren otra revisión. El resultado es `placed` con `orderId` y `reference`, o `prices_changed`, `empty` o `unavailable`.

El personal actúa bajo su propia identidad. La cesta del cliente no se ve afectada. Los contactos por correo electrónico del cliente pueden recibir una copia; esto no carga el pago. El Representante de Ventas tiene acceso de creación por defecto; las actualizaciones de procesamiento y entrega requieren el permiso separado `orders.process`.

## Entrega y seguimiento

**PATCH** `/api/v1/orders/{id}/delivery`

Requiere `orders.process`. Envíe `deliveryMethod`: `unspecified`, `courier`, `own_delivery` o `collection`. Se admiten `courierName` (100 caracteres), `trackingReference` (120 caracteres) y `trackingUrl` (1.000 caracteres) visibles para el cliente. Los enlaces de seguimiento deben usar HTTPS sin credenciales de inicio de sesión incrustadas. Los detalles opcionales omitidos los borran. Envíe `expectedRevision` del pedido actual. Hay un registro de entrega por pedido; las integraciones de transportistas y envíos divididos no están incluidas. Los detalles del transportista en blanco nunca implican entrega propia.

## Listar pedidos

**GET** `/api/v1/orders`

Pedidos más recientes primero. La lista cubre los **200 pedidos más recientes** que coincidan con sus filtros. Para capturar nuevos pedidos, consulte con `status=new`.

| Nombre     | Tipo    | Requerido | Descripción                             |
| ---------- | ------- | --------- | --------------------------------------- |
| `status`   | string  | no        | Uno de los estados anteriores.          |
| `customer` | string  | no        | Id del cliente o código de cuenta.      |
| `limit`    | integer | no        | 1 a 100. Valor predeterminado 100.      |
| `offset`   | integer | no        | Filas a omitir. Valor predeterminado 0. |

```bash
curl "https://tradecatalog.app/api/v1/orders?status=new" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const res = await fetch('https://tradecatalog.app/api/v1/orders?status=new', {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: orders } = await res.json()
```

```json
{
  "data": [
    {
      "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
      "reference": "SO-000142",
      "status": "new",
      "total": 31460,
      "currency": "GBP",
      "poNumber": "PO-88231",
      "createdAt": "2026-09-27T07:48:12.000Z",
      "customer": "Oakfield Joinery"
    }
  ],
  "nextOffset": null
}
```

| Campo       | Tipo          | Descripción                                                                              |
| ----------- | ------------- | ---------------------------------------------------------------------------------------- |
| `id`        | string        | Id de la orden.                                                                          |
| `reference` | string        | Número de orden que ven los compradores, como `SO-000142`.                               |
| `status`    | string        | Véase [estados](#order-statuses).                                                        |
| `total`     | integer       | Total de la orden según el precio (véase `taxLabel`), en unidades menores de `currency`. |
| `currency`  | string        | Fecha fija en que se colocó la orden.                                                    |
| `poNumber`  | string o null | El número de orden de compra del comprador.                                              |
| `createdAt` | string        | Cuándo se colocó.                                                                        |
| `customer`  | string        | Nombre del cliente. Obtenga el código de cuenta de `GET /orders/{id}`.                   |

## Obtener una orden

**GET** `/api/v1/orders/{id}`

La orden con sus líneas y historial de estados.

| Nombre | En   | Tipo   | Requerido | Descripción     |
| ------ | ---- | ------ | --------- | --------------- |
| `id`   | path | string | sí        | Id de la orden. |

```bash
curl https://tradecatalog.app/api/v1/orders/5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

```js
const res = await fetch(`https://tradecatalog.app/api/v1/orders/${orderId}`, {
  headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
})
const { data: order } = await res.json()
```

```json
{
  "data": {
    "id": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
    "customerAccountId": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
    "placedBy": "Fq3L0cZ7tN1pW9aYx2Rk",
    "reference": "SO-000142",
    "poNumber": "PO-88231",
    "notes": "Deliver to the Hebden Road site, gate 2.",
    "status": "new",
    "cancelReason": null,
    "currency": "GBP",
    "total": 31460,
    "createdAt": "2026-09-27T07:48:12.000Z",
    "updatedAt": "2026-09-27T07:48:12.000Z",
    "customer": { "name": "Oakfield Joinery", "accountCode": "OAK01" },
    "lines": [
      {
        "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
        "orderId": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
        "productId": "3f8a2c1e-9b4d-4e6f-a7c8-1d2e3f4a5b6c",
        "sku": "HX-8510",
        "name": "Hex bolt M10 x 50 zinc",
        "unitLabel": "Box of 100",
        "qty": 20,
        "unitPrice": 1573,
        "listPrice": 1850,
        "priceLabel": "Brand discount −15%",
        "lineTotal": 31460
      }
    ],
    "events": [
      {
        "status": "new",
        "note": null,
        "createdAt": "2026-09-27T07:48:12.000Z",
        "actor": "Sam Patel"
      }
    ]
  }
}
```

| Campo                                      | Descripción                                                                                                                                                                     |
| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customerAccountId`                        | El id del cliente.                                                                                                                                                              |
| `placedBy`                                 | Id de la persona que la ingresó. `source` distingue entre órdenes de comprador y de personal.                                                                                   |
| `notes`                                    | Las notas de entrega o de orden del comprador.                                                                                                                                  |
| `cancelReason`                             | Por qué fue cancelada, si lo fue.                                                                                                                                               |
| `lines`                                    | Lo que se ordenó a qué precio. Una instantánea: los cambios posteriores en los precios nunca la alteran. `productId` es `null` si el producto desde entonces ha sido eliminado. |
| `lines[].priceLabel`                       | La regla que estableció el precio, como en [Precios](/docs/api/prices#the-price-object).                                                                                        |
| `lines[].priceLabel` es `Price on request` | El producto no tiene precio: `unitPrice` y `lineTotal` son `0` y la línea no está en `total`. El proveedor confirma su precio.                                                  |
| `events`                                   | Historial de estados, el más antiguo primero. `actor` es el nombre de la persona, o `null`.                                                                                     |

## Actualizar o corregir una orden

**PATCH** `/api/v1/orders/{id}`

Cambia el estado de una orden. Devuelve la orden actualizada en la misma estructura que `GET /orders/{id}`.

- Los movimientos normales de envío siguen las etapas de cumplimiento anteriores. Otros movimientos requieren un motivo de corrección privada; un estado actual repetido no hace ningún cambio.
- Cancelar requiere un `note`, que se muestra al comprador como razón.
- Los cambios de estado pueden enviar correos electrónicos a los contactos del cliente, incluida la nota pública. Un correo electrónico generado no demuestra la entrega.
- El cambio se registra en el historial de la orden contra la persona que hizo la clave.

| Nombre   | En   | Tipo   | Requerido | Descripción                                     |
| -------- | ---- | ------ | --------- | ----------------------------------------------- |
| `id`     | path | string | sí        | Id de la orden.                                 |
| `status` | body | string | sí        | Nuevo estado.                                   |
| `note`   | body | string | no        | Máximo 1.000 caracteres. Requerido al cancelar. |

```bash
curl -X PATCH https://tradecatalog.app/api/v1/orders/5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "dispatched", "note": "DPD tracking 1554 2210 0931" }'
```

```js
const res = await fetch(`https://tradecatalog.app/api/v1/orders/${orderId}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ status: 'acknowledged' }),
})
const { data: order } = await res.json()
```

| Estado | Código               | Cuando                                                                                                                               |
| ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| 400    | `invalid_request`    | El movimiento no está permitido, por ejemplo, "Una orden que se ha recibido no puede marcarse como completada", o cancelar sin nota. |
| 403    | `insufficient_scope` | La clave es de solo lectura.                                                                                                         |
| 404    | `not_found`          | No existe esa orden en este espacio de trabajo.                                                                                      |

Volver a intentar un `PATCH` que ya tuvo éxito devuelve `400 invalid_request`, porque la orden ha avanzado. Compruebe el estado de la orden antes de tratarlo como un error.

## No disponible

La API no puede colocar órdenes para un comprador, ni cambiar las líneas, cantidades o precios de una orden. Los compradores colocan órdenes en su portal comercial; los cambios en una orden se acuerdan con el cliente y se facturan en su propio sistema.

Las listas de órdenes también incluyen `paymentStatus`: null para términos de cuenta normales, o el estado de pago opcional de Stripe. El estado de cumplimiento es separado y nunca demuestra el pago. La página de detalles de la orden es el lugar para revisar el total final confirmado y aprobar el pago. La API no puede crear ni aprobar un pago.

`internalNote` (2.000 caracteres) y `correctionReason` (1.000 caracteres) son campos de historial solo para personal. `note` permanece público, incluyendo notas antiguas. Envíe `expectedRevision` para rechazar una edición concurrente con una solicitud inválida. Las lecturas de la API de personal incluyen historial privado; las lecturas de herramientas de comprador y portal excluyen identidades de personal y campos privados. Cancelar aún requiere una razón pública y la retirada o el reembolso completo de un pago pendiente.
