Ir al contenido

Cómo se sincroniza con las cajas

Posdata funciona sin internet: cada caja (el computador, la tablet o el celular donde se vende) guarda sus datos localmente y los sube a la nube cuando tiene conexión. La API pública trabaja sobre esa nube. Entender esto te evita reportes equivocados.

Cuando lees ventas, turnos de caja o movimientos, ves lo que las cajas ya subieron. Si una caja lleva horas sin internet, sus ventas de esas horas todavía no están en la API; aparecerán cuando se reconecte.

Para saber qué tan al día están los datos, consulta GET /me → data_freshness:

{
"data_freshness": {
"last_device_sync_at": "2026-09-28T14:58:12.000Z",
"devices": [
{ "name": "Caja 01", "kind": "primary", "platform": "windows", "last_sync_at": "2026-09-28T14:58:12.000Z" },
{ "name": "Caja 02", "kind": "primary", "platform": "android", "last_sync_at": "2026-09-28T09:12:40.000Z" },
{ "name": "Tablet bodega", "kind": "primary", "platform": "android", "last_sync_at": null }
]
}
}
  • last_sync_at es el último cambio que esa caja subió de verdad a la nube, no la última vez que se conectó. Es null si la caja nunca ha subido nada.
  • last_device_sync_at es el más reciente de todos.

En el ejemplo, la Caja 02 no sube nada desde las 9:12 (UTC). Puede que no haya vendido, o puede que esté sin internet con ventas pendientes: la API no puede distinguirlo. Antes de cerrar un reporte del día, revisa que las cajas que vendieron hayan subido cambios después de su cierre.

Cada venta trae dos momentos distintos:

Campo Qué es
occurred_at Cuándo se registró la venta en la caja, con el reloj de esa caja. Es la fecha del negocio: úsala para reportes.
created_at Cuándo la venta llegó a la nube. Una caja que vendió sin internet sube sus ventas al reconectarse, y quedan con la hora de subida.

En una caja con internet las dos fechas casi coinciden. Si la caja vendió el sábado sin conexión y subió el lunes, occurred_at dice sábado y created_at dice lunes.

Qué usa cada cosa:

  • occurred_since y occurred_until de GET /sales filtran por occurred_at (la fecha del negocio).
  • created_since, created_until y updated_since filtran por la llegada a la nube (y la última modificación). Son los filtros para la sincronización incremental: una venta que llega tarde tiene un created_at reciente, así que nunca te la saltas. Si sincronizas por occurred_since, una venta del sábado que sube el lunes cae en un rango que quizás ya procesaste.
  • El reporte GET /reports/daily-sales suma las ventas completadas por día calendario de Bogotá (America/Bogota) según occurred_at, con un rango de hasta 92 días. La venta del sábado cuenta el sábado, pero solo aparece en el reporte cuando la caja la sube: un día puede cambiar después de cerrado.

Tus cambios llegan a las cajas por su sincronización normal

Sección titulada «Tus cambios llegan a las cajas por su sincronización normal»

Cuando creas algo por la API (un producto, una categoría, un cliente, un movimiento de inventario) o editas un producto, una variante, una categoría o un cliente, el cambio queda en la nube de inmediato y las cajas lo reciben por su sincronización normal:

  • Cajas en línea: en segundos. Posdata les avisa que hay cambios nuevos.
  • Cajas sin conexión: cuando se reconecten.

La API responde en cuanto el cambio está guardado en la nube; no espera a que las cajas lo reciban. Para las cajas, tu integración es un dispositivo más del negocio. En las ediciones gana el cambio más reciente: si alguien edita el mismo cliente desde una caja, se queda el que llegue último.

  • Crear: POST /products crea productos sin variantes, con initial_stock opcional.
  • Editar un producto: PATCH /products/{id} cambia el nombre, las descripciones, el SKU, la categoría, los precios (base_price, final_price, percent_discount), la clase fiscal y el estado (active, sellable).
  • Editar una variante: PATCH /products/{id}/variants/{variantId} cambia su etiqueta, SKU, precios y estado (active). Responde el producto completo, con todas sus variantes.

Una edición nunca toca el inventario: el cambio viaja a las cajas sin stock, y cada caja conserva el suyo. Por eso tampoco se pueden editar el stock, la unidad (base_unit), la venta por peso (sold_by_weight) ni el tipo de producto (kind): cambiarían el significado del stock que ya tiene cada caja. Para mover existencias usa movimientos.

Si cambias base_price sin enviar final_price, final_price toma el mismo valor.

Una caja con una versión vieja de Posdata interpretaría una edición sin stock como «stock en cero». Para que eso nunca pase, editar productos y variantes solo funciona cuando todos los dispositivos del negocio tienen la versión de Posdata que lo soporta. Si alguno no la tiene, la respuesta es 409 DEVICES_OUTDATED, no se modifica nada y el error trae la lista de dispositivos que faltan:

{
"error": {
"code": "DEVICES_OUTDATED",
"message": "No se modificó nada: estas cajas todavía no tienen la versión de Posdata que acepta ediciones de productos desde la API: Caja 02. Ábrelas con internet para que se actualicen solas (tarda un minuto en reportarse) o, si ya no las usas, revócalas en cuenta.posdata.so → Dispositivos. Crear productos y mover stock sí funciona mientras tanto.",
"devices": [
{
"name": "Caja 02",
"kind": "primary",
"platform": "android",
"app_version": "260901-a1b2c3d",
"last_seen_at": "2026-09-20T17:42:10.000Z"
}
],
"request_id": "req_…"
}
}

Cómo resolverlo:

  1. Abre esos dispositivos con internet. Posdata se actualiza solo y, un minuto después, el dispositivo reporta la versión nueva. Luego reintenta la edición.
  2. Si ya no usas alguno, revócalo en cuenta.posdata.so → Dispositivos. Un dispositivo olvidado en un cajón también cuenta.

last_seen_at es la última vez que ese dispositivo se reportó (null si nunca lo hizo) y te ayuda a reconocer los que ya no se usan. Mientras tanto, crear productos y mover stock sí funciona.

Los productos traen el campo stock, pero hoy siempre es null. Todavía no publicamos un número de existencias porque no podemos garantizar que coincida con el de las cajas: cada caja lleva su propio inventario y descuenta sus ventas aunque esté sin internet. Preferimos no darte un número que parece correcto y no lo es: una tienda en línea podría vender lo que ya no hay. Lo activaremos cuando podamos garantizarlo y lo anunciaremos en el changelog.

Mientras tanto:

  • No infieras el stock (ni de null, ni sumando lo que ves en la API como si fuera el total real).
  • Si tu sistema necesita una cifra, lleva tu propio conteo: parte de un inventario inicial que conozcas, suma y resta tus movimientos (POST /inventory/movements) y concilia con el historial (GET /inventory/movements), que incluye los movimientos de las ventas que las cajas ya subieron.
  • Las variantes nunca traen stock.

Para sumar o restar existencias usa POST /inventory/movements con type: "in" (entrada) o type: "out" (salida) y una cantidad entera mayor que cero en la unidad base del producto. Nunca se fija el stock a un valor absoluto.

  • Cada caja aplica el movimiento una sola vez, aunque haya vendido sin conexión mientras tanto: un movimiento es una diferencia (sumar 24, restar 3), no un valor que pisa al de la caja.
  • La bodega debe ser una que las cajas conocen. Si no envías warehouse_id, se usa la bodega predeterminada (o la única activa); si no hay una clara, la respuesta es 409 NO_WAREHOUSE.
  • La API no verifica existencias antes de una salida (no conoce el stock real de cada caja).
  • Los movimientos creados por la API aparecen en el historial con origin_type: "api" y, si no envías reason, con el motivo «Movimiento vía API».
  • Crear un producto con initial_stock registra además un movimiento de entrada.
  • Puedes leer los movimientos (GET /inventory/movements), incluidos los de las ventas que las cajas ya subieron. Son el historial de lo que pasó, no una cifra de existencias.

Hay dos casos que la versión 1 rechaza:

  • Productos con variantes (VARIANT_STOCK_UNSUPPORTED): el stock de cada variante viaja entre las cajas como un valor absoluto, no como movimientos. Si la API moviera ese stock mientras una caja vende la misma variante sin conexión, uno de los dos cambios podría perderse. Por ahora, ajústalo desde Posdata.
  • Productos que se arman con receta (COMPOSITE_STOCK_UNSUPPORTED): no tienen stock propio; lo tienen sus insumos. Mueve el stock de los insumos.
  • Dinero: pesos colombianos (COP) en enteros, sin decimales ni centavos. 25900 son $25.900.
  • Precios de productos: tal como los digita el negocio. Lo normal es que incluyan impuestos; si el negocio configuró precios antes de impuestos, vienen así.
  • Tarifas de impuesto: porcentaje entero. 19 es 19 %, nunca 0.19. Los impuestos por unidad (como el de bolsas) traen per_unit y rate: null.
  • Impuestos de una venta: cada línea trae taxes congelados al momento de vender, y la venta trae tax_breakdown agrupado por tributo y tarifa, con el código DIAN (01 IVA, 04 INC…). La propina (tip_amount) nunca es base gravable. Ingreso neto = total - tax - tip_amount.
  • Cantidades: pueden tener decimales en productos que se venden por peso; se expresan en la unidad base del producto.
  • Fechas: ISO 8601 en UTC.

Para que sepas con qué contar hoy:

  • No publica el stock: el campo stock de los productos es siempre null por ahora (explicado arriba). Sí mueve stock por movimientos.
  • No edita el stock, la unidad, la venta por peso ni el tipo de un producto, y solo edita productos y variantes cuando todos los dispositivos del negocio están actualizados (409 DEVICES_OUTDATED).
  • No mueve stock de productos con variantes ni de productos con receta.
  • No crea ventas, facturas ni pedidos externos, y no emite facturas electrónicas de ventas hechas fuera del POS.
  • No sube imágenes, no elimina registros y no crea variantes.
  • No publica el PDF ni el XML de los documentos electrónicos.
  • No ofrece OAuth para aplicaciones de terceros: cada negocio crea sus propias keys.
  • No restringe el acceso por dirección IP.

Si alguna de estas limitaciones te frena, cuéntanos tu caso en Soporte.