# Changelog

Cambios de la API pública de Posdata, versión por versión.

Aquí anunciamos cada cambio de la API y de los webhooks. Los cambios que rompan la compatibilidad (quitar o renombrar un campo, un código de error, un permiso o un tipo de evento) no ocurren sin una nueva versión y un aviso previo. Agregar campos, endpoints, filtros o tipos de evento **no** se considera un cambio que rompe: tu integración debe tolerar campos nuevos.

## v1.0.0 — actualización — 2026-10-20 (compras a proveedores)

Las compras a proveedores. La API no tiene endpoints de proveedores ni de compras: el negocio las registra desde la app y las ves como movimientos de stock. Detalle en [Cómo funciona el stock](https://developers.posdata.so/stock/).

- **`origin_type: "purchase"`** en el movimiento de stock (`GET /inventory/movements` y el webhook `inventory.movement.created`): la entrada de una compra recibida. Sube el costo promedio con el costo de la compra (precio menos descuento, más flete y el impuesto que el negocio no descuenta).
- **`origin_type: "purchase_return"`**: una salida de mercancía devuelta al proveedor. Sale al costo promedio.
- **`origin_type: "reversal"`** también cubre ahora la **anulación de una compra** (una salida que deshace la entrada), además de la reposición de una venta anulada (una entrada).
- Son valores nuevos de un campo que ya existe: si tu integración ramifica por `origin_type`, trátalos como casos más. No cambia ningún campo ni endpoint.

## v1.0.0 — actualización — 2026-10-20 (traslados entre bodegas)

Los traslados entre bodegas con mercancía en tránsito. La API no tiene endpoints de traslados: los ves en los movimientos, el kardex y el valor del inventario. Detalle en [Cómo funciona el stock](https://developers.posdata.so/stock/).

- **`origin_type: "transfer"`** en el movimiento de stock (`GET /inventory/movements` y el webhook `inventory.movement.created`): una salida en la bodega de origen al despachar y una entrada en la de destino al recibir (o en la de origen, si se devuelve). Un traslado no cambia el costo promedio. Es un valor nuevo de un campo que ya existe: si tu integración ramifica por `origin_type`, trátalo como un caso más.
- **`transfer` en las filas del kardex** (campo nuevo, `null` si la fila no es de un traslado): `{ id, number, kind, counterpart }`, con el número del traslado y la bodega del otro extremo.
- **`in_transit_value` ahora es real** en `GET /inventory/valuation` (antes siempre `0`): el valor de la mercancía despachada y aún no recibida, aparte de `total_value` (no está en ningún nivel). Con `warehouse_id` cuenta lo que viene hacia esa bodega.
- **`in_transit`** en `GET /inventory/valuation` (campo nuevo): el detalle por línea. Una línea sin costo conocido viene con `unit_cost` y `value` en `null` y se cuenta en `missing_cost_lines`; nunca se valora en `0`.
- Todos los cambios son aditivos: tu integración debe tolerar campos y valores nuevos.

## v1.0.0 — actualización — 2026-10-20

Costos, kardex y valor del inventario. Todo el detalle en [Kardex y costo promedio](https://developers.posdata.so/stock/#kardex-y-costo-promedio) y [Valor del inventario](https://developers.posdata.so/stock/#valor-del-inventario).

- **`unit_cost` en `POST /inventory/movements`**: una entrada (`type: "in"`) puede declarar lo que costó cada unidad base. Alimenta el costo promedio ponderado móvil de la cuenta (uno por producto). Es opcional y debe ser mayor que `0`: si no lo conoces, no lo envíes. Con `type: "out"` responde `400 VALIDATION_ERROR`; una salida sale siempre al costo promedio.
- **`GET /inventory/kardex`** (permiso `inventory:read`): el libro de un producto, con cada movimiento, conteo y ajuste de costo en orden y el saldo corrido. Filtros `warehouse_id`, `variant_id`, `from`, `to`, `type` y `order`. Pagina con `page` y `page_size` (máximo 200), no con cursor. Con la cuenta en `legacy` responde `409 STOCK_MODE_LEGACY`.
- **`GET /inventory/valuation`** (permiso `inventory:costs`): el valor del inventario hoy o a una fecha de corte (`at`), por bodega. Los niveles negativos y los productos sin costo no suman: vienen aparte en `negatives` y `missing_cost`, nunca valorados en `0`. Incluye `computed_at`.
- **Nuevo permiso `inventory:costs`**: ver costos y valores del inventario. Se pide aparte de `inventory:read`: con solo `inventory:read` el kardex trae cantidades y saldos y todo costo y valor viene en `null` (`costs_hidden: true`). Las llaves existentes no lo tienen; créalo en una llave nueva ([cuenta.posdata.so](https://cuenta.posdata.so) → Desarrolladores) solo si lo necesitas.
- **`reason_code` en el movimiento de stock** (campo nuevo, `null` si no aplica): el motivo catalogado de un ajuste hecho en la app (`shrinkage`, `damage`, `expired`, `theft`, `internal_use`, `supplier_return`, `found`, `correction`, `other` o `transfer_shortage`). También llega en `data` del webhook `inventory.movement.created`.
- Todos los cambios son aditivos: tu integración debe tolerar campos nuevos en el movimiento y en el webhook.

## v1.0.0 — actualización — 2026-10-14

Packs y combos como producto.

- **`kind: "kit"` en los productos**: un pack o combo agrupa otros productos bajo un precio propio (un six-pack, un kit escolar, un combo de restaurante) y, al venderse, descuenta el stock de cada producto que contiene. El campo `kind` del producto ahora puede ser `simple`, `composite` o `kit`. Es un cambio aditivo en la respuesta: si tu integración ramifica por `kind`, trata el valor nuevo como un caso más, no como un error.
- **`stock` es `null` en un pack**, igual que en un producto que se arma con receta: el stock lo tienen los productos que contiene.
- **Mover o contar el stock de un pack** (`POST /inventory/movements` y `POST /inventory/counts`) responde `422 COMPOSITE_STOCK_UNSUPPORTED`, como con las recetas: mueve o cuenta el stock de los productos que contiene.

## v1.0.0 — actualización — 2026-09-29

El stock oficial por bodega. Todo el detalle en [Cómo funciona el stock](https://developers.posdata.so/stock/).

- **`stock` publicado** en las cuentas que ya llevan el stock en la nube (todas sus cajas principales con la versión de Posdata que lo soporta; el paso es automático): el `stock` de un producto es la suma de sus niveles y cada variante trae su propio `stock` (campo nuevo). Mientras la cuenta no haya pasado, `stock` sigue siendo `null`. Ahora es un `number` (antes `integer`): puede tener decimales en productos por peso y ser negativo.
- **`GET /inventory/levels`**: el stock por (bodega, producto, variante), con `available` (el stock menos el margen de la API) y el último conteo. Filtros `product_id`, `variant_id` y `warehouse_id`.
- **`POST /inventory/counts`**: registra un conteo físico en el instante en que se hizo (`counted_at`); las ventas posteriores se suman encima. `counted_quantity` es entero, salvo en productos que se venden por peso. Permiso `inventory:write` e `Idempotency-Key`.
- **`POST /inventory/movements`**: con el stock en la nube, una salida `out` es atómica y nunca compromete las últimas unidades que el negocio reserva para sus cajas (el margen de la API, 2 por defecto, se ajusta en cuenta.posdata.so → Inventario → Configuración). Si no alcanza, responde el nuevo código `409 INSUFFICIENT_STOCK` con `available` y no registra nada; antes, la API no verificaba existencias. Ahora acepta variantes (`variant_id`) y `quantity` con decimales en productos que se venden por peso.
- **Nuevo código `STOCK_MODE_LEGACY` (409)**: lo responden los niveles, los conteos y los movimientos de variantes mientras la cuenta no lleve el stock en la nube.
- **`VARIANT_STOCK_UNSUPPORTED` queda obsoleto y ya no se emite**: los movimientos de variantes se aceptan cuando la cuenta lleva el stock en la nube; antes de eso responden `409 STOCK_MODE_LEGACY`. El código se conserva en el catálogo hasta una nueva versión de la API. Si tu integración ramificaba por él, trata `STOCK_MODE_LEGACY` de la misma forma.
- **Permisos**: `inventory:read` ahora incluye el stock por bodega e `inventory:write`, los conteos.
- **Webhooks nuevos**: `inventory.count.created` (un conteo físico, desde una caja o por la API) e `inventory.stock.negative` (un nivel contado quedó negativo porque una caja vendió sin internet lo que ya no había).

## v1.0.0 — actualización — 2026-09-28

- **Editar productos y variantes**: `PATCH /products/{id}` (nombre, descripciones, SKU, categoría, precios, clase fiscal, estado) y `PATCH /products/{id}/variants/{variantId}` (etiqueta, SKU, precios, estado). El cambio llega a las cajas **sin stock**: cada caja conserva el suyo. Solo funciona cuando todos los dispositivos del negocio tienen la versión de Posdata que lo soporta; si no, responde el nuevo código `409 DEVICES_OUTDATED` con la lista de dispositivos y no modifica nada. El permiso `products:write` ahora incluye estas ediciones, y emiten `product.updated`. Detalle en [Productos: se crean y se editan](https://developers.posdata.so/sincronizacion/#productos-se-crean-y-se-editan).
- **`occurred_at` en las ventas**: cuándo se registró la venta en la caja (en ventas sincronizadas antes del 28-sep-2026, igual a la hora de llegada). Nuevos filtros `occurred_since` y `occurred_until` en `GET /sales`, y el reporte `GET /reports/daily-sales` ahora agrupa por `occurred_at`. `created_at` sigue siendo la hora de llegada a la nube. Mira [Las ventas traen dos fechas](https://developers.posdata.so/sincronizacion/#las-ventas-traen-dos-fechas).
- **Búsqueda literal**: en `q`, los caracteres `%` y `_` se buscan tal cual, no como comodines.
- **`stock` en productos**: el campo entró al contrato, todavía en `null`. Se publicó con la actualización del 2026-09-29 (arriba).

## v1.0.0 — 2026-09-28

Primera versión pública.

- **API REST** en `https://api.posdata.so/public/v1` (pruebas: `https://apidev.posdata.so/public/v1`), incluida en el plan Pro.
- **Autenticación** con API keys de cuenta (`pdk_live_`/`pdk_test_`) y 12 permisos de mínimo privilegio.
- **Lectura** de cuenta y frescura de datos (`/me`), productos y variantes, categorías, clientes, bodegas, movimientos de inventario, ventas, documentos electrónicos, cotizaciones, turnos de caja, ventas por día y registro de eventos.
- **Escritura**: crear y editar categorías y clientes, crear productos sin variantes (con stock inicial opcional) y registrar movimientos de inventario de productos sin variantes. Todo `POST` exige `Idempotency-Key`. La edición de productos y variantes llegó con la actualización del mismo día (arriba).
- **Webhooks** firmados con HMAC-SHA256 (`api_version` `2026-09-28`) para 12 tipos de evento, con reintentos durante unas 28 horas y reconciliación con `GET /events`.
- **Límites**: 300 peticiones por minuto por key, 600 por cuenta y 60 escrituras por minuto por key.

Limitaciones conocidas de esta versión: [Lo que la API no hace en la versión 1](https://developers.posdata.so/sincronizacion/#lo-que-la-api-no-hace-en-la-versi%C3%B3n-1).

---

Fuente: https://developers.posdata.so/changelog/ · Índice para agentes: https://developers.posdata.so/llms.txt
