Modelo navegable del POS con una lectura más institucional, visual y compartible
Esta segunda versión transforma la especificación del dominio en una pieza de documentación orientada a alinear negocio, arquitectura e implementación. Explica cómo se estructura el ticket, cómo se distribuyen promociones y pagos en el ledger, y cómo se preserva la trazabilidad completa del cálculo sin mezclar responsabilidades.
Qué resuelve este modelo
El modelo describe una operación POS completa de forma explicable. La venta no termina en el detalle comercial del ticket: también debe poder explicarse cómo impactó cada promoción, cómo se distribuyó cada pago, qué saldo quedó, si hubo excedente, en qué medio se entregó el vuelto y por qué el resultado final concilia exactamente.
Explicación del resultado
Permite responder con precisión cuánto valía el ticket, qué promociones modificaron los importes, qué medios de pago participaron y cuál fue el total efectivamente cancelado.
Separación de responsabilidades
Separa catálogo, captura operativa, registros maestros y ledger distribuido. Esa separación evita sobrecargar estructuras y simplifica evolución futura.
Trazabilidad fuerte
Cada transformación económica vive en movimientos[], de modo que el cierre del ticket y la explicación fiscal no dependan de inferencias posteriores.
items[] conserva cómo se ingresó la venta; movimientos[] conserva cómo impactó económicamente. Esa diferencia es la base de toda la claridad del modelo.Arquitectura del Ticket en una sola vista
items[], promociones[] ni pagos[], sino en el ledger distribuido de movimientos[].Tres decisiones de diseño que ordenan todo el sistema
- Catálogo vs. registro:
articulos[]evita duplicación;items[]conserva el ingreso operativo. - Maestro vs. distribuido:
promociones[]ypagos[]son maestros; su efecto económico se distribuye enmovimientos[]. - Reconciliación fuerte: el ticket cerrado debe poder validarse algebraicamente sin reglas implícitas.
Flujo operativo end-to-end
La secuencia de cálculo es tan importante como las estructuras. El modelo define un orden estable para evitar dobles interpretaciones del precio vigente, de las promociones acumulables y del saldo aplicable a pagos posteriores.
Ingreso comercial
Se identifica cliente, se deduplican artículos y se registran items como captura original de la venta.
Promociones ITEM
Se computan promociones por método y prioridad, recalculando la base vigente del ítem según corresponda.
Núcleo impositivo
Con el precio resultante se calcula el núcleo impositivo del ticket y su total operativo antes de pagos.
Promociones de pago
Se consultan o aplican beneficios/recargos por medio de pago, según el modo del motor.
Ledger y cierre
Se distribuyen pagos, excedentes y vueltos. Luego se valida que el ledger cierre exactamente en cero.
model.md, usadas como base para esta guía navegable.Ledger, signos y reconciliación
Convención de signos
| Concepto | Signo esperado | Lectura de negocio |
|---|---|---|
VENTA_ITEM | Positivo | Valor comercial que el ticket genera. |
PROMOCION descuento | Negativo | Reduce el valor a cobrar. |
PROMOCION recargo | Positivo | Incrementa el valor a cobrar. |
PAGO aplicado | Negativo | Cancela parte del valor del ticket. |
PAGO excedente | Negativo | Refleja un ingreso mayor al saldo requerido. |
PAGO vuelto | Positivo | Devuelve valor al cliente por el medio definido. |
La regla de oro del cierre
El ticket no se considera consistente si el ledger final no concilia. La condición fuerte del modelo es:
Esto obliga a que ventas, promociones, pagos, excedentes y vueltos queden plenamente explicados en la misma estructura distribuida.
Cómo se organiza la lógica promocional
Las promociones no se modelan como un bloque único. El diseño adopta tres capas para mantener trazabilidad y permitir que la promoción sea, al mismo tiempo, una regla configurable, un registro aplicado y un efecto económico distribuido.
listapromociones
Define reglas de negocio configurables: método, beneficio, condiciones, vigencia, sucursales, medios de pago y listas de ítems.
promociones[]
Registra qué promoción concreta quedó aplicada en un ticket, cuál fue su monto total y qué elementos alcanzó.
movimientos[]
Distribuye el efecto económico final sobre movimientos de venta específicos. Es la capa fiscalmente explicable.
Modos operativos
- ITEM: promociones sobre ítems, antes del pago.
- PAGO (consulta): informa beneficios/recargos posibles para un medio de pago sin modificar el ticket.
- PAGO (aplicar): registra el pago y aplica la promoción correspondiente sobre el saldo neto.
- POSTPAGO: distribuye un ajuste informado por un sistema externo luego de autorizar el pago.
Orden de métodos en ITEM
MAYORISTAVENTAXBULTOGRUPOMAYORISTACOMBOCANTIDAD
La prioridad importa: el ítem pasa por estos métodos con su último precio vigente, y algunas promociones excluyen evaluación en métodos posteriores.
COMBO y CANTIDAD primero se computan las promociones acumulativas; las no acumulativas solo se evalúan si ninguna acumulativa se cumplió para ese ítem.POSTPAGO explicado para arquitectura y operación
Qué problema resuelve
Después de autorizar un pago en un sistema externo, puede aparecer un descuento o un recargo adicional. Ese ajuste no viene de listapromociones, pero debe quedar trazado en el ticket y distribuido proporcionalmente sobre los ítems alcanzados.
Cómo queda registrado
- Se crea una promoción sintética en
promociones[]. promocionid = 0reservado para promociones de sistema.pagoorigenidvincula el ajuste al pago que lo disparó.- El impacto económico vive en
movimientos[]comoPROMOCION.
Artefactos JSON incluidos en el modelo
Además del documento maestro, el ZIP aporta ejemplos estructurales concretos para validar naming, jerarquía y objetos del dominio. Esto ayuda a alinear la documentación con el modelo serializable real.
| Archivo | Tipo | Punto de entrada / estructura |
|---|---|---|
articulo.json | Ejemplo estructural | articulo |
articulos.json | Ejemplo estructural | articulos |
cliente.json | Ejemplo estructural | cliente |
datosreferenciales.json | Ejemplo estructural | datosreferenciales |
impuestos.json | Ejemplo estructural | impuestos |
item.json | Ejemplo estructural | item |
listapromociones.json | Ejemplo estructural | listapromociones |
metodocomputo.json | Ejemplo estructural | metodocomputo |
movimientos.json | Ejemplo estructural | movimientos |
nucleoimpositivo.json | Ejemplo estructural | nucleoimpositivo |
pagos.json | Ejemplo estructural | pagos |
promociones.json | Ejemplo estructural | promociones |
promociones_postpago_ejemplo.json | Ejemplo estructural | promociones |
promocionestado.json | Ejemplo estructural | promocionestado |
promocionlistanumber.json | Ejemplo estructural | promocionlistanumber |
promocionlistatype.json | Ejemplo estructural | promocionlistatype |
promociontipoelemento.json | Ejemplo estructural | promociontipoelemento |
ticket.json | Ejemplo estructural | ticket |
tickettipo.json | Ejemplo estructural | tickettipo |
tipobeneficio.json | Ejemplo estructural | tipobeneficio |
tipocomprobante.json | Ejemplo estructural | tipocomprobante |
tipoconcepto.json | Ejemplo estructural | tipoconcepto |
tipodecliente.json | Ejemplo estructural | tipodecliente |
tipodeimpuesto.json | Ejemplo estructural | tipodeimpuesto |
tiposdepago.json | Ejemplo estructural | tiposdepago |
Ejemplos estructurales para lectura rápida
ticket.json
{
"ticket": {
"datosreferenciales": {
"nroTicket": 12213,
"comercio": "SuperX",
"nroSucursal": 1,
"nroPv": 54,
"fechaHora": "2026-03-11 10:16:00",
"tickettipo": {
"id": "VENTA"
},
"tipocomprobante": {
"id": "A"
},
"total": 2620.0,
"saldo": 0.0,
"vuelto": 380.0,
"nucleoimpositivo": [
{
"impuesto": {
"id": "NETO_IVA_21"
},
"monto": 2000.0
},
{
"impuesto": {
"id": "IVA_21"
},
"monto": 420.0
},
{
"impuesto": {
"id": "IMPUESTOINTERNO_IVA_21"
},
"monto": 200.0
}
]
},
"cliente": {
"id": 1,
"nombre": "GOMEZ",
"cuit": "30112233451",
"tipodecliente": {
"id": 3,
"descripcion": "RESPONSABLE_INSCRIPTO",
"tipocomprobante": {
"id": "A"
}
},
"convenio": {
"id": 1,
"nombre": "CONVENIO EMPLEADOS"
},
"impuestos": [
{
"id": "PERCEPCION_IIBB",
"porcentaje": 1.0,
"netominimo": 0.0
},
{
"id": "PERCEPCION_COMIND",
"porcentaje": 1.0,
"netominimo": 0.0
},
{
"id": "PERCEPCION_IVA_21",
"porcentaje": 1.0,
"netominimo": 0.0
},
{
"id": "PERCEPCION_IVA_10_5",
"porcentaje": 0.5,
"netominimo": 0.0
}
]
},
"articulos": [
{
"id": 1,
"articulo": {
"ean": "7791234567890",
"plu": "112233",
"descripcion": "ARROZ",
"pesable": false,
"preciolista": 1310.0,
"rubro": "ALIMENTOS",
"depto": "ALMACEN",
"marca": "GALLO",
"codigoclasificacion": 102234,
"proveedor": "LEVER",
"esarticuloconoferta": false,
"nucleoimpositivo": [
{
"impuesto": {
"id": "NETO_IVA_21"
},
"monto": 1000.0
},
{
"
...listapromociones.json
{
"listapromociones": [
{
"id": 1,
"descripcion": "PROMO_2X1_ARROZ",
"metodocomputo": {
"id": "CANTIDAD"
},
"tipobeneficio": {
"id": "PORCENTAJE_DESCUENTO"
},
"valorbeneficio": 50.0,
"codigodescarga": null,
"conveniosclientes": [],
"condiciones": {
"acumulativa": false,
"maximacantidadpromosxticket": 1,
"montominimo": 0.0,
"excluyearticulosoferta": false,
"usapreciolistaarticulo": true
},
"vigencia": {
"fechadesde": "20260301",
"fechahasta": "20260331",
"diassemana": [
"MIERCOLES"
],
"horadesde": "10",
"horahasta": "11"
},
"sucursales": [],
"mediosdepago": [],
"listasitems": [
{
"listaindex": 1,
"tipodeLista": {
"id": "INCLUSION"
},
"nrolista": {
"id": "LISTA1"
},
"tipoelemento": {
"id": "EAN"
},
"cantidad": 2.0,
"monto": null,
"valores": [
"7791234567890"
]
}
]
}
]
}movimientos.json
{
"movimientos": [
{
"id": 1,
"concepto": "VENTA_ITEM",
"origenid": 1,
"movimientoid": null,
"nucleoimpositivo": [
{
"impuesto": {
"id": "NETO_IVA_21"
},
"monto": 1000.0
},
{
"impuesto": {
"id": "IVA_21"
},
"monto": 210.0
},
{
"impuesto": {
"id": "IMPUESTOINTERNO_IVA_21"
},
"monto": 100.0
}
]
},
{
"id": 2,
"concepto": "VENTA_ITEM",
"origenid": 1,
"movimientoid": null,
"nucleoimpositivo": [
{
"impuesto": {
"id": "NETO_IVA_21"
},
"monto": 1000.0
},
{
"impuesto": {
"id": "IVA_21"
},
"monto": 210.0
},
{
"impuesto": {
"id": "IMPUESTOINTERNO_IVA_21"
},
"monto": 100.0
}
]
},
{
"id": 3,
"concepto": "VENTA_ITEM",
"origenid": 2,
"movimientoid": null,
"nucleoimpositivo": [
{
"impuesto": {
"id": "NETO_IVA_21"
},
"monto": 1000.0
},
{
"impuesto": {
"id": "IVA_21"
},
"monto": 210.0
},
{
"impuesto": {
"id": "IMPUESTOINTERNO_IVA_21"
},
"monto": 100.0
}
]
},
{
"id": 4,
"concepto": "PROMOCION",
"origenid": 1,
"movimientoid": 1,
"nucleoimpositivo": [
{
"impuesto": {
"id": "NETO_IVA_21"
},
"monto": -500.0
},
{
"impuesto": {
"id": "IVA_21"
},
"monto": -105.0
},
{
"impuesto": {
"id": "IMPUESTOINTERNO_IVA_21"
},
"monto": -50.0
}
]
},
{
"id": 5,
"concepto": "PROMOCION",
"origenid": 1,
"movimientoid": 2,
"nucleoimpositivo": [
{
"impuesto": {
"id": "NETO_IVA_21"
...promociones_postpago_ejemplo.json
{
"promociones": [
{
"id": 2,
"promocionid": 0,
"pagoorigenid": 1,
"descripcion": "PROMO POSTPAGO CHEQUE 0 CUOTAS",
"tipoPromo": "PAGO",
"promocionestado": {
"id": "APLICADA"
},
"monto": -20.0,
"elementos": [
{
"movimientoid": 1,
"articuloid": 1,
"unidadesimpactadas": 1.0,
"monto": -10.0
},
{
"movimientoid": 2,
"articuloid": 1,
"unidadesimpactadas": 1.0,
"monto": -10.0
}
]
}
]
}tiposdepago.json
{
"tiposdepago": [
{
"id": 1,
"idmdep": 1,
"ididmdep": 10,
"cuota": 0,
"descripcion": "EFECTIVO",
"davuelto": true,
"vueltomediodepago": null
},
{
"id": 2,
"idmdep": 2,
"ididmdep": 20,
"cuota": 0,
"descripcion": "CHEQUE",
"davuelto": false,
"vueltomediodepago": 1
},
{
"id": 3,
"idmdep": 3,
"ididmdep": 20,
"cuota": 3,
"descripcion": "VISA 3 CUOTAS",
"davuelto": false,
"vueltomediodepago": 1
}
]
}Lectura recomendada para arquitectura y desarrollo
Qué mantener estable
No volver a mezclar items[] con ledger, no reintroducir conceptos obsoletos y mantener la prioridad documental: primero model.md, luego JSON.
Qué es clave en Java 21
Usar tipado fuerte, enums para catálogos cerrados, records para value objects simples y clases explícitas donde la mutabilidad del agregado lo necesite.
Qué revisar en cambios futuros
Impacto en cálculo, impacto en JSON, impacto en reconciliación y efecto sobre ejemplos de ticket antes de modificar el núcleo del modelo.