# Crear (/docs/developers/api-reference/job-positions/create)

## Descripcion general [#descripcion-general]

Este endpoint permite crear uno o varios puestos dentro de tu organización. Acepta tanto **un objeto** como un **arreglo de hasta 100 objetos**, y la respuesta refleja la forma del cuerpo enviado. El único campo obligatorio por fila es `name`; el resto son opcionales. Una solicitud exitosa devuelve `201` con los puestos creados, incluyendo los campos generados por el servidor (`id`, `job_position_number`, `created_at`, `updated_at`). A diferencia de los departamentos, los puestos no tienen jerarquía: no existen `parent_id` ni `depth`, es una creación plana.

En modo lote, si **cualquier** fila falla la validación, **ningún** puesto se crea — la respuesta lista los problemas por fila para corregirlos en una sola pasada.

Las bandas salariales (`salary_band_min`, `salary_band_max`) se aceptan como números JSON en el cuerpo, pero se serializan como **strings decimales** en la respuesta (por ejemplo `"15000.00"`) para preservar la precisión.

## Endpoint [#endpoint]

`POST https://app.integranom.com/api/v1/job-positions`

## Solicitud unica — minima [#solicitud-unica--minima]

***

`name` es el único campo requerido. `is_active` toma el valor `true` por defecto y `sort_order` toma `0`. Los demás campos quedan en `null`.

```json
{ "name": "Vendedor" }
```

**cURL**

```bash
curl -X POST "https://app.integranom.com/api/v1/job-positions" \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Vendedor" }'
```

**Respuesta**

```json
{
"id": "ck...",
"job_position_number": 5,
"name": "Vendedor",
"code": null,
"description": null,
"salary_band_min": null,
"salary_band_max": null,
"is_active": true,
"sort_order": 0,
"created_at": "2026-05-24T10:00:00.000Z",
"updated_at": "2026-05-24T10:00:00.000Z"
}
```

## Solicitud unica — todos los campos [#solicitud-unica--todos-los-campos]

***

El siguiente ejemplo utiliza todos los campos aceptados. Si envías ambas bandas salariales, debe cumplirse que `salary_band_min ≤ salary_band_max` (si no, la respuesta es `400` con el campo `salary_band_max`).

Observa cómo las bandas se devuelven como strings decimales (`"15000.00"`) aunque se envíen como números (`15000`).

```json
{
  "name": "Gerente de Ventas",
  "code": "GER-VEN",
  "description": "Gerencia comercial",
  "salary_band_min": 15000,
  "salary_band_max": 35000,
  "is_active": true,
  "sort_order": 0
}
```

**cURL**

```bash
curl -X POST "https://app.integranom.com/api/v1/job-positions" \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
  "name": "Gerente de Ventas",
  "code": "GER-VEN",
  "description": "Gerencia comercial",
  "salary_band_min": 15000,
  "salary_band_max": 35000,
  "is_active": true,
  "sort_order": 0
}'
```

**Respuesta**

```json
{
"id": "ck...",
"job_position_number": 5,
"name": "Gerente de Ventas",
"code": "GER-VEN",
"description": "Gerencia comercial",
"salary_band_min": "15000.00",
"salary_band_max": "35000.00",
"is_active": true,
"sort_order": 0,
"created_at": "2026-05-24T10:00:00.000Z",
"updated_at": "2026-05-24T10:00:00.000Z"
}
```

## Solicitud por lote (1–100 puestos) [#solicitud-por-lote-1100-puestos]

***

Envía un **arreglo** de hasta 100 puestos en el mismo endpoint. Si **cualquier** fila falla la validación, **ningún** puesto se crea y la respuesta lista los problemas por fila.

```json
[
  { "name": "Vendedor", "code": "VEN" },
  { "name": "Cajero", "code": "CAJ" },
  { "name": "Almacenista", "code": "ALM", "salary_band_min": 8000, "salary_band_max": 12000 }
]
```

**cURL**

```bash
curl -X POST "https://app.integranom.com/api/v1/job-positions" \
-H "Authorization: Bearer TU_API_KEY" \
-H "Content-Type: application/json" \
-d '[
  { "name": "Vendedor", "code": "VEN" },
  { "name": "Cajero", "code": "CAJ" },
  { "name": "Almacenista", "code": "ALM", "salary_band_min": 8000, "salary_band_max": 12000 }
]'
```

**Respuesta**

```json
{
"summary": { "total": 3, "created": 3 },
"created": [
  {
    "id": "ck...",
    "job_position_number": 6,
    "name": "Vendedor",
    "code": "VEN",
    "description": null,
    "salary_band_min": null,
    "salary_band_max": null,
    "is_active": true,
    "sort_order": 0,
    "created_at": "2026-05-24T10:00:00.000Z",
    "updated_at": "2026-05-24T10:00:00.000Z"
  },
  {
    "id": "ck...",
    "job_position_number": 7,
    "name": "Cajero",
    "code": "CAJ",
    "description": null,
    "salary_band_min": null,
    "salary_band_max": null,
    "is_active": true,
    "sort_order": 0,
    "created_at": "2026-05-24T10:00:00.000Z",
    "updated_at": "2026-05-24T10:00:00.000Z"
  },
  {
    "id": "ck...",
    "job_position_number": 8,
    "name": "Almacenista",
    "code": "ALM",
    "description": null,
    "salary_band_min": "8000.00",
    "salary_band_max": "12000.00",
    "is_active": true,
    "sort_order": 0,
    "created_at": "2026-05-24T10:00:00.000Z",
    "updated_at": "2026-05-24T10:00:00.000Z"
  }
]
}
```

## Reglas de los campos [#reglas-de-los-campos]

| Campo             | Tipo               | Requerido | Notas                                                                                                                          |
| ----------------- | ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `name`            | string             | Sí        | No vacío tras recortar espacios.                                                                                               |
| `code`            | string             | No        | ≤ 60 caracteres, se almacena en mayúsculas, único por organización.                                                            |
| `description`     | string             | No        | Texto libre.                                                                                                                   |
| `salary_band_min` | number ≥ 0 \| null | No        | Banda salarial inferior (MXN). Por defecto `null`. Se acepta como número en la entrada; se devuelve como string decimal.       |
| `salary_band_max` | number ≥ 0 \| null | No        | Banda salarial superior (MXN). Por defecto `null`. Si envías ambas bandas, debe cumplirse `salary_band_min ≤ salary_band_max`. |
| `is_active`       | boolean            | No        | Por defecto `true`.                                                                                                            |
| `sort_order`      | integer ≥ 0        | No        | Por defecto `0`.                                                                                                               |

El puesto no lleva clase de riesgo IMSS: si envías `risk_class`, con cualquier valor, la respuesta es `400` con ese campo. La clase de riesgo se captura en el [registro patronal](/docs/developers/api-reference/employer-registrations/create).

## Forma de las respuestas [#forma-de-las-respuestas]

### `201` — solicitud única [#201--solicitud-única]

Cuando el cuerpo es **un objeto**, la respuesta es el objeto del puesto creado.

```json
{
  "id": "ck...",
  "job_position_number": 5,
  "name": "Gerente de Ventas",
  "code": "GER-VEN",
  "description": "Gerencia comercial",
  "salary_band_min": "15000.00",
  "salary_band_max": "35000.00",
  "is_active": true,
  "sort_order": 0,
  "created_at": "2026-05-24T10:00:00.000Z",
  "updated_at": "2026-05-24T10:00:00.000Z"
}
```

### `201` — solicitud por lote [#201--solicitud-por-lote]

Cuando el cuerpo es **un arreglo**, la respuesta envuelve los resultados en `summary` + `created[]`.

```json
{
  "summary": { "total": 3, "created": 3 },
  "created": [
    { "id": "ck...", "job_position_number": 6, "name": "Vendedor", "...": "..." },
    { "id": "ck...", "job_position_number": 7, "name": "Cajero", "...": "..." },
    { "id": "ck...", "job_position_number": 8, "name": "Almacenista", "...": "..." }
  ]
}
```

## Errores [#errores]

### `400` — validación fallida (solicitud única) [#400--validación-fallida-solicitud-única]

Para fallas de validación de esquema (campo faltante, tipo incorrecto), `errors` es un objeto indexado por campo:

```json
{
  "message": "Validation failed.",
  "errors": { "name": ["Ingresa el nombre."] }
}
```

### `400` — validación fallida (solicitud por lote) [#400--validación-fallida-solicitud-por-lote]

Reporte por fila. **Ningún** puesto del lote fue creado.

```json
{
  "message": "Validation failed for 2 job position(s). No job positions were created.",
  "summary": { "total": 3, "with_errors": 2 },
  "errors": [
    {
      "index": 0,
      "job_position": { "name": "Vendedor", "code": "VEN" },
      "issues": [
        {
          "field": "code",
          "code": "DUPLICATE_CODE",
          "message": "A job position with code 'VEN' already exists in this organization."
        }
      ]
    },
    {
      "index": 2,
      "job_position": { "name": "Cobranza", "code": "VEN" },
      "issues": [
        {
          "field": "code",
          "code": "DUPLICATE_CODE_IN_BATCH",
          "message": "Code 'VEN' appears multiple times in this batch."
        }
      ]
    }
  ]
}
```

* `index` — posición 0-based de la fila que falló dentro del arreglo enviado.
* `job_position.name` / `job_position.code` — extraídos de tu entrada para que identifiques la fila sin contar.
* `issues[]` — todos los problemas de esa fila listados juntos para corregirlos antes de reintentar.

### `409` — condición de carrera [#409--condición-de-carrera]

Otra solicitud reclamó un `code` entre el momento en que validamos el tuyo y el momento en que escribimos. En modo lote, la transacción completa hace rollback — ningún puesto es creado. Reintenta la misma solicitud.

## Vocabulario de códigos de error [#vocabulario-de-códigos-de-error]

Estos códigos aparecen dentro de `issues[].code` (o como `code` en el cuerpo de error en single) y son estables — tu cliente puede ramificar lógica con base en ellos:

| Codigo                    | Significado                                                                                              |
| ------------------------- | -------------------------------------------------------------------------------------------------------- |
| `INVALID_INPUT`           | Falla genérica de validación de esquema (formato, longitud, tipo, orden de las bandas salariales, etc.). |
| `DUPLICATE_CODE`          | El `code` ya existe en otro puesto de la organización.                                                   |
| `DUPLICATE_CODE_IN_BATCH` | El mismo `code` aparece en más de una fila del lote.                                                     |
| `CONCURRENT_MODIFICATION` | Señal de reintento por condición de carrera (respuesta `409`).                                           |