# Clientes

> Listar clientes comerciales, leer los descuentos, precios acordados y compradores de un cliente, y crear o actualizar clientes.

Un cliente es una cuenta comercial: una empresa a la que vende, con un código de cuenta, un descuento de cuenta opcional y las personas (compradores) que pueden iniciar sesión para realizar pedidos por su cuenta.

## El objeto cliente

```json
{
  "id": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
  "accountCode": "OAK01",
  "name": "Oakfield Joinery",
  "legalName": "Oakfield Joinery Ltd",
  "email": "accounts@oakfieldjoinery.co.uk",
  "discountBp": 500,
  "active": true,
  "newsOptOut": false,
  "catalogueScope": "all",
  "createdAt": "2026-07-02T10:12:44.000Z"
}
```

| Campo            | Tipo          | Descripción                                                                                                                                                                  |
| ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`             | string        | Identificador del cliente.                                                                                                                                                   |
| `accountCode`    | string        | Su código de cuenta. Único en el espacio de trabajo, ignorando mayúsculas. Puede cambiarse en la aplicación, por lo que conserve el `id` para cualquier cosa de largo plazo. |
| `name`           | string        | Nombre comercial.                                                                                                                                                            |
| `legalName`      | string o nulo | Nombre registrado, cuando es diferente.                                                                                                                                      |
| `email`          | string o nulo | Correo electrónico principal, en minúsculas.                                                                                                                                 |
| `discountBp`     | entero o nulo | El porcentaje propio del cliente sobre el listado completo, en puntos básicos. `null` para ninguno.                                                                          |
| `adjustment`     | string        | `discount` (eliminado de la lista) o `markup` (añadido). Los clientes existentes son `discount`.                                                                             |
| `active`         | booleano      | Los clientes inactivos no pueden iniciar sesión ni realizar pedidos.                                                                                                         |
| `newsOptOut`     | booleano      | El cliente desactivó las ofertas y correos electrónicos de noticias. Los correos electrónicos de cambios de precio aún se envían.                                            |
| `catalogueScope` | string        | `all` (ve todo el catálogo) o `approved` (ve y ordena solo su lista aprobada). Se establece en la página del cliente; de solo lectura en la API.                             |
| `createdAt`      | string        | Cuando se agregó al cliente.                                                                                                                                                 |

En cualquier lugar donde la API tome un cliente, puede usar el `id` o el `accountCode`.

## Listar clientes

**GET** `/api/v1/customers`

Todos los clientes, ordenados por nombre.

| Nombre   | Tipo   | Requerido | Descripción                             |
| -------- | ------ | --------- | --------------------------------------- |
| `limit`  | entero | no        | 1 a 100. Valor predeterminado 100.      |
| `offset` | entero | no        | Filas a omitir. Valor predeterminado 0. |

```bash
curl "https://tradecatalog.app/api/v1/customers?limit=100" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

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

```json
{
  "data": [
    {
      "id": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
      "accountCode": "OAK01",
      "name": "Oakfield Joinery",
      "legalName": "Oakfield Joinery Ltd",
      "email": "accounts@oakfieldjoinery.co.uk",
      "discountBp": 500,
      "active": true,
      "newsOptOut": false,
      "catalogueScope": "all",
      "createdAt": "2026-07-02T10:12:44.000Z"
    }
  ],
  "nextOffset": null
}
```

## Obtener un cliente

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

El cliente más todo lo que afecta a sus precios, y quién puede iniciar sesión por ellos.

| Nombre | En   | Tipo   | Requerido | Descripción                                   |
| ------ | ---- | ------ | --------- | --------------------------------------------- |
| `id`   | ruta | string | sí        | Identificador del cliente o código de cuenta. |

```bash
curl https://tradecatalog.app/api/v1/customers/OAK01 \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

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

```json
{
  "data": {
    "id": "7d2e4f0a-1b3c-4d5e-8f90-a1b2c3d4e5f6",
    "accountCode": "OAK01",
    "name": "Oakfield Joinery",
    "legalName": "Oakfield Joinery Ltd",
    "email": "accounts@oakfieldjoinery.co.uk",
    "discountBp": 500,
    "priceListId": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "priceList": "Standard",
    "active": true,
    "newsOptOut": false,
    "createdAt": "2026-07-02T10:12:44.000Z",
    "discountRules": [
      {
        "id": "d0e1f2a3-4b5c-4d6e-9f7a-8b9c0d1e2f3a",
        "scope": "product",
        "discountBp": 2000,
        "productId": "5b6c7d8e-9f0a-4b1c-8d2e-3f4a5b6c7d8e",
        "sku": "HX-8510",
        "target": "Hex bolt M10 x 50 zinc",
        "list": 2280,
        "unit": 1824
      },
      {
        "id": "e1f2a3b4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
        "scope": "brand",
        "discountBp": 1500,
        "productId": null,
        "sku": null,
        "target": "Northgate",
        "list": null,
        "unit": null
      },
      {
        "id": "f2a3b4c5-6d7e-4f80-9a1b-2c3d4e5f6a7b",
        "scope": "category",
        "discountBp": 1250,
        "productId": null,
        "sku": null,
        "target": "Hinges",
        "list": null,
        "unit": null
      }
    ],
    "agreedPrices": [
      {
        "id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
        "productId": "6c7d8e9f-0a1b-4c2d-9e3f-4a5b6c7d8e9f",
        "price": 1450,
        "note": "Contract price to March 2027",
        "noteVisible": true,
        "internalNote": "Agreed with their buyer",
        "sku": "HX-8512",
        "name": "Hex bolt M12 x 50 zinc",
        "list": 1980,
        "unit": 1450
      }
    ],
    "buyers": [{ "name": "Sam Patel", "email": "sam@oakfieldjoinery.co.uk" }],
    "pendingInvitations": [{ "email": "orders@oakfieldjoinery.co.uk" }]
  },
  "currency": "GBP"
}
```

| Campo                | Descripción                                                                                                                                                                                                                                                              |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `priceList`          | Nombre de la lista de precios de la que compra este cliente. `priceListId` es el id de esa lista. Un producto sin precio en la lista tiene precio bajo petición.                                                                                                         |
| `discountRules`      | Porcentajes de producto, categoría y marca. `scope` es `product`, `category` o `brand`; `adjustment` es `discount` o `markup`; `target` es su nombre. Las reglas de producto también proporcionan `sku`, `list` y `unit` (lo que pagan).                                 |
| `agreedPrices`       | Precios acordados (fijos) válidos ahora, por SKU, en unidades menores, con el precio `list` actual y el precio `unit` que pagan. `note` es el comentario; `noteVisible` indica si ese cliente lo ve. `internalNote` es solo para personal y nunca se muestra al cliente. |
| `buyers`             | Personas que pueden iniciar sesión y realizar pedidos por este cliente.                                                                                                                                                                                                  |
| `pendingInvitations` | Invitaciones enviadas pero no aceptadas, y no caducadas.                                                                                                                                                                                                                 |

Vea [cómo se calculan los precios](/docs/api/prices#how-prices-are-worked-out) para ver cómo se combinan.

## Crear o actualizar clientes

**PUT** `/api/v1/customers`

Envíe hasta **100 clientes** por llamada. Cada fila se compara con un cliente existente de la siguiente manera:

1. Si se establece `accountCode`, por código de cuenta. Sin coincidencia significa un nuevo cliente con ese código.
2. De lo contrario, si se establece `email`, por correo electrónico. Sin coincidencia significa un nuevo cliente.
3. De lo contrario por nombre, ignorando mayúsculas.

Los nuevos clientes sin código de cuenta reciben uno generado a partir de su nombre.

### Cuerpo

| Campo                     | Tipo                  | Requerido | Descripción                                                                                                                                                                                                                  |
| ------------------------- | --------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `customers`               | matriz                | sí        | 1 a 100 clientes. Los códigos de cuenta deben ser únicos dentro de la llamada.                                                                                                                                               |
| `customers[].accountCode` | cadena o nulo         | sí        | Máximo 50 caracteres. `null` para coincidir por correo electrónico o nombre.                                                                                                                                                 |
| `customers[].name`        | cadena                | sí        | Nombre comercial, máximo 160 caracteres.                                                                                                                                                                                     |
| `customers[].email`       | cadena o nulo         | sí        | Correo electrónico principal de contacto. `null` lo borra.                                                                                                                                                                   |
| `customers[].discountBp`  | entero o nulo         | sí        | Porcentaje de lista completa, 0 a 10.000, para un descuento o recargo. `null` lo borra.                                                                                                                                      |
| `customers[].adjustment`  | `discount` o `markup` | no        | La forma en que `discountBp` mueve el precio. Omita para dejar la dirección actual. Los nuevos clientes tienen como predeterminado `discount`.                                                                               |
| `customers[].priceList`   | cadena o nulo         | no        | Nombre de una lista de precios existente. Omita para dejar la lista sin cambios. `null` coloca al cliente en Standard. Un nombre desconocido se rechaza.                                                                     |
| `customers[].region`      | cadena o nulo         | no        | Nombre de una región existente. Omita para dejar la región sin cambios. `null` la borra. Un nombre desconocido se rechaza.                                                                                                   |
| `customers[].rep`         | cadena o nulo         | no        | Correo electrónico o nombre de usuario de un miembro del personal asignado a este cliente. Omita para dejar al representante sin cambios. `null` lo borra. Un correo electrónico o nombre de usuario desconocido se rechaza. |

`accountCode`, `name`, `email` y `discountBp` son obligatorios y se escriben como se envían: `null` borra `email` y `discountBp`. De esta forma, un campo faltante en su código no puede dejar en silencio un porcentaje antiguo en su lugar. `adjustment` y `priceList` son opcionales: deje `adjustment` fuera para mantener un recargo o un descuento como está.

La hoja de cálculo del cliente tiene la misma opción. Junto con el descuento % puede tener una columna de ajuste (`discount` o `markup`), una columna de lista de precios y una columna de representante de ventas. La celda del representante es el correo electrónico de esa persona, o su nombre de usuario cuando se inicia sesión sin correo electrónico. Una celda en blanco deja ese campo como está. Un nombre de lista desconocido o un representante es un error en esa fila.

Actualizar un cliente no envía correos electrónicos ni invita a compradores. Invite a compradores desde la página del cliente en la aplicación. El nombre registrado, el estado activo, las reglas de descuento y los precios acordados también se cambian en la aplicación.

```bash
curl -X PUT https://tradecatalog.app/api/v1/customers \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customers": [
      {
        "accountCode": "OAK01",
        "name": "Oakfield Joinery",
        "email": "accounts@oakfieldjoinery.co.uk",
        "discountBp": 500
      },
      {
        "accountCode": "BRK07",
        "name": "Brookside Builders",
        "email": null,
        "discountBp": null
      }
    ]
  }'
```

```js
const res = await fetch('https://tradecatalog.app/api/v1/customers', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ customers }),
})
const { data } = await res.json()
console.log(`${data.created} new, ${data.updated} updated`)
```

```json
{
  "data": { "created": 1, "updated": 1 }
}
```

El cambio aparece en el registro de actividad como el nombre de la clave.
