Documentación técnica del modelo de dominio
Modelo POS
Ticket / Ledger
Diseño del agregado raíz, reglas de itemización,
ledger distribuido y reconciliación algebraica
Versión
202604070108
Fuente
core.md
Implementación
Java 8
JSON de referencia
jsonmodel_optionB_corregido
Contenidos

Índice

1Objetivo y principios del modelo
2Agregado raíz — estructura del Ticket
3Objetos principales del dominio
4Granos del modelo y regla de itemización
5Reglas del ledger y convenciones de signos
6Regla de excedente y vuelto
7Orden de cálculo
8Reconciliación — ejemplo de referencia completo
9Catálogos del sistema
10Convenciones Java 8
Sección 1

Objetivo y principios del modelo

Este modelo representa una operación de venta POS de forma completa, trazable y reconciliable. Su propósito es servir como base para el cálculo del ticket, la aplicación de promociones, el cómputo de impuestos, el registro de pagos, el ledger distribuido, las validaciones de consistencia y la generación de comprobantes fiscales.

Principios fundamentales

  • Detallado — cada operación queda registrada al nivel de granularidad mínima necesaria.
  • Trazable — todo monto puede explicarse a partir de su origen en el ledger.
  • Reconciliable — la suma algebraica de todos los movimientos debe ser exactamente 0 al cierre.
  • Auditable — toda transformación relevante puede rastrearse en los registros maestros y distribuidos.
  • Extensible — el modelo admite nuevos tipos de impuestos, medios de pago y reglas promocionales sin romper la estructura base.
  • No se deben introducir reglas no confirmadas. Ante una duda de modelado, se prefiere la solución más conservadora y más explicable.
Principio de diseño central

El cálculo debe ser explícito, el resultado trazable, y el sistema capaz de reconciliar y auditar lo ocurrido a través de los movimientos del ledger. Ningún monto relevante puede aparecer sin origen explicable.

Sección 2

Agregado raíz — estructura del Ticket

El agregado raíz es Ticket. Representa el estado completo de una operación POS y es la unidad principal para cálculo, validación, testing, auditoría y reconciliación. Todo lo que ocurre en una venta vive dentro del Ticket.

Fig. 1 Estructura del Ticket — agregado raíz con sus 7 componentes
Ticket agregado raíz — estado completo de la operación POS datosreferenciales nroTicket · total · saldo · vuelto cliente cuit · tipodecliente · impuestos articulos[] deduplicación de producto id incremental items[] captura comercial original promociones[] registros maestros aplicados pagos[] registros maestros de pago movimientos[] ledger distribuido — core de la explicación económica del ticket VENTA_ITEM · PROMOCION · PAGO id · concepto · origenid · movimientoid · nucleoimpositivo ∑ nucleoimpositivo de todos los movimientos = 0 al cierre articuloid
Los registros maestros (items, promociones, pagos) son complementados pero no reemplazados por movimientos[]. La flecha punteada verde muestra la referencia de deduplicación: items[] apunta a articulos[] mediante articuloid, evitando duplicar los datos del producto.

Separación maestro / distribuido

Una regla central del diseño es mantener la diferencia entre registros maestros y registros distribuidos. Los registros maestros (pagos[], promociones[]) contienen un registro por operación. Los registros distribuidos (movimientos[]) contienen el detalle expandido de cada impacto económico.

articulos[]Artículos reutilizables dentro del ticket. Evita duplicar datos de producto cuando varios items refieren al mismo artículo.
items[]Captura comercial original. Uno por ingreso operativo, conserva las unidades tal como fueron ingresadas.
promociones[]Registros maestros de promociones aplicadas. No distribuidos — su detalle vive en movimientos[].
pagos[]Registros maestros de pago. Un registro por ingreso de pago o vuelto.
movimientos[]Ledger distribuido. Uno por unidad de item, por unidad promovida, por unidad pagada.
Sección 3

Objetos principales del dominio

Artículo

Representa el elemento vendible reutilizable por referencia. Contiene identificadores comerciales (EAN, PLU), descripción, si es pesable, precio de lista, atributos comerciales y su nucleoimpositivo base por unidad.

Regla fiscal

El campo preciolista debe coincidir con la suma de los monto del nucleoimpositivo del artículo. Ejemplo: NETO_IVA_21 (1000) + IVA_21 (210) + IMPUESTOINTERNO_IVA_21 (100) = 1310.

NucleoImpositivo

Es la estructura central de importes e impuestos. Se expresa como una colección de componentes, cada uno con un identificador de impuesto y su monto calculado. No asumir que todo impuesto es porcentual: el modelo admite impuestos de monto fijo, percepciones variables por cliente, y recargos.

ComponenteTipoDescripción
NETO_IVA_21MontoNeto gravado a tasa 21%
IVA_21PorcentajeIVA 21% sobre el neto
IMPUESTOINTERNO_IVA_21Monto fijoImpuesto interno (monto por unidad)
PERCEPCION_IIBBPorcentaje variableVaría según tipodecliente del cliente
PERCEPCION_COMINDPorcentaje variableVaría según tipodecliente del cliente

Pago

Registro maestro de pago dentro del ticket. Forma conservadora del modelo: id, mediodepagoid, descripcion, monto. Un pago negativo es válido por diseño — representa un egreso (vuelto) entregado al cliente mediante un medio de pago habilitado.

Regla de diseño — no sobrecargar Pago

No agregar campos como montoingresado, montoaplicado o vuelto en Pago salvo necesidad futura explícita. El detalle de distribución vive en movimientos[]. La versión base usa solo monto.

Promocion (aplicada)

Distinguir dos niveles: la definición vive en listapromociones (catálogo configurable con beneficio, método, decisión y lista de elementos). El resultado aplicado vive en promociones[] del ticket, con los campos: id, promocionid, descripcion, tipoPromo, monto.

Movimiento

Línea del ledger distribuido. Contiene: id incremental, concepto (VENTA_ITEM, PROMOCION, PAGO), origenid que referencia el registro maestro origen, movimientoid que referencia el movimiento VENTA_ITEM sobre el que aplica, y nucleoimpositivo con el importe impactado desagregado por componente fiscal.

Sección 4

Granos del modelo y regla de itemización

El modelo trabaja con más de un grano funcional. Esta separación es obligatoria y no debe colapsarse.

Array
Grano
Propósito
items[]
Operativo
Una entrada por ingreso del operador. Conserva las unidades tal como se registraron.
movimientos[]
Unitario
Una entrada por unidad vendida, promovida o pagada. Es el grano mínimo del ledger.
articulos[]
Referencia
Un registro por EAN único en el ticket. Evita duplicación de datos de producto.

Regla de itemización

Si un item tiene unidades = N (enteras), se generan N movimientos VENTA_ITEM en el ledger, cada uno representando una unidad con su propio nucleoimpositivo. Esta expansión es lo que permite distribuir promociones y pagos con precisión sobre unidades individuales.

Distinción fundamental

items[] = captura comercial (cómo ingresó el operador). movimientos[] = impacto económico distribuido (cómo se explica el ledger). Esta distinción no debe perderse.

Cantidades decimales (artículos pesables)

El modelo permite unidades decimales porque existen artículos pesables (campo pesable: true en el artículo). El criterio de distribución para unidades no enteras debe resolverse por lógica de cálculo manteniendo siempre trazabilidad, explicabilidad y reconciliación. Mientras no exista una regla más específica, la itemización se entiende como expansión al menor nivel explicable que requiera el dominio.

Fig. 2 Ledger de movimientos — detalle de los 10 registros del ejemplo de referencia
items[] item 1 · unidades=2 item 2 · unidades=1 articuloid=1 (ARROZ $1310) promociones[] id=1 · PROMO_2X1_ARROZ tipoPromo: ITEM monto: −1310.00 pagos[] id=1 · CHEQUE +3000.00 id=2 · EFECTIVO −380.00 vuelto: monto negativo movimientos[] mov1 · VENTA_ITEM orig=1 · mid=null · +1310 mov2 · VENTA_ITEM orig=1 · mid=null · +1310 mov3 · VENTA_ITEM orig=2 · mid=null · +1310 item1→2 movs item2→1 mov mov4 · PROMOCION orig=1 · mid=1 · −655 mov5 · PROMOCION orig=1 · mid=2 · −655 mov6 · PAGO orig=1 · mid=1 · −655 mov7 · PAGO orig=1 · mid=2 · −655 mov8 · PAGO orig=1 · mid=3 · −1310 mov9 excedente orig=1 · −380 mov10 vuelto orig=2 · +380 −380 + 380 = 0 — — — movimientoid: referencia al VENTA_ITEM sobre el que aplica el PAGO o PROMOCION
Los registros maestros (arriba) originan los movimientos distribuidos (abajo). Las flechas sólidas muestran la creación de movimientos desde los maestros. Las flechas punteadas muestran la referencia movimientoid: cada PAGO y PROMOCION apunta al VENTA_ITEM específico que afecta.
Sección 5

Reglas del ledger y convenciones de signos

Tipos de movimiento

El catálogo tipoconcepto define tres valores. Cada movimiento en el ledger pertenece a uno de ellos y sigue reglas estrictas de signo y referencia.

Concepto
Signo NI
Regla de movimientoid
VENTA_ITEM
Positivo
Siempre null. Representa el ingreso de valor al ticket por venta.
PROMOCION
Negativo
Apunta al VENTA_ITEM sobre el que se aplica el descuento.
PAGO (normal)
Negativo
Apunta al VENTA_ITEM que cancela.
PAGO (excedente)
Negativo
null. Porción del medio de pago que excede el neto del ticket.
PAGO (vuelto)
Positivo
null. Valor entregado al cliente. Contrapartida del excedente.

Semántica de referencias

  • origenid referencia el id del registro maestro que origina el movimiento: items[].id para VENTA_ITEM, promociones[].id para PROMOCION, pagos[].id para PAGO.
  • movimientoid referencia el id del movimiento VENTA_ITEM sobre el que aplica. Es null para VENTA_ITEM, para el excedente y para el vuelto.

Núcleo por movimiento

Cada movimiento tiene su propio nucleoimpositivo con el detalle completo de componentes fiscales. No debe reemplazarse por un simple monto agregado si eso implica perder trazabilidad fiscal. El nucleoimpositivo de cada VENTA_ITEM debe ser idéntico al nucleoimpositivo del artículo por unidad.

Sección 6

Regla de excedente y vuelto

Cuando un medio de pago ingresa un monto mayor al necesario para cancelar el ticket, se producen dos movimientos complementarios en el ledger.

La parte del pago que cubre el saldo real del ticket se distribuye en movimientos PAGO negativos contra movimientos VENTA_ITEM individuales (con movimientoid poblado).
El excedente — la porción del medio de pago que sobra — se registra como un movimiento PAGO negativo con movimientoid = null, con origenid del mismo pago que generó el excedente.
El vuelto entregado al cliente se registra como un movimiento PAGO positivo con movimientoid = null, con origenid del registro de vuelto en pagos[] (un pago diferente, con monto negativo en el maestro).
El excedente (negativo) y el vuelto (positivo) tienen el mismo valor absoluto y se anulan entre sí en el ledger, preservando la condición de cierre.
Por qué el vuelto es positivo en el ledger

El ledger mide el saldo pendiente del ticket. El CHEQUE entra como negativo (reduce saldo). El excedente reduce el saldo más allá de lo necesario, dejándolo en −380. El vuelto sube el saldo de vuelta a 0: es una corrección positiva, no un pago que cancela deuda. La clave es que el excedente y el vuelto tienen origenid distintos: refieren a medios de pago diferentes.

Ejemplo numérico

Ticket ARROZ 3 unidades × $1310 = $3930. Promo 2×1: −$1310. Neto: $2620. Pago CHEQUE: $3000.

mov6,7,8 (PAGO)CHEQUE distribuido: −$655, −$655, −$1310. Cubre mov1, mov2, mov3. Total: −$2620.
mov9 (excedente)PAGO negativo, origenid=1 (CHEQUE), movimientoid=null. Monto: −$380.
pagos[id=2]EFECTIVO vuelto, monto −$380 en el registro maestro (egreso de caja).
mov10 (vuelto)PAGO positivo, origenid=2 (EFECTIVO), movimientoid=null. Monto: +$380.
Sección 7

Orden de cálculo

El orden de cálculo es obligatorio. No se deben evaluar pagos antes de aplicar promociones, ni distribuir el ledger antes de registrar los pagos maestros.

Identificar cliente — determina tipo de comprobante y percepciones aplicables.
Ingresar items — con deduplicación de artículos en articulos[].
Resolver referencias a artículos — verificar coherencia entre items y artículos.
Determinar bases iniciales — nucleoimpositivo por item, precio de lista.
Aplicar promociones de item o de venta — registrar en promociones[] y distribuir en movimientos[].
Recalcular bases afectadas por promociones.
Calcular impuestos y nucleoimpositivo del ticket — consolidar en datosreferenciales.nucleoimpositivo.
Determinar total del ticket.
Precálculo de promociones por medio de pago.
Registrar pagos maestros en pagos[] y aplicar promociones por medio de pago.
Calcular excedente y vuelto.
Distribuir pagos en ledger — generar movimientos PAGO en movimientos[].
Calcular saldo final.
Validar reconciliación — verificar que la suma del ledger es 0.
Restricción temporal clave

Las promociones que modifican el valor económico del ticket deben impactar antes del cierre final de pagos (pasos 5–6 antes del paso 10). Los pagos y beneficios asociados a medios de pago se evalúan sobre el saldo realmente resultante del ticket (post-promociones).

Sección 8

Reconciliación — ejemplo de referencia completo

El ticket de referencia usa 3 unidades de ARROZ a $1310 c/u, una promoción 2×1, pago con CHEQUE $3000 y vuelto en EFECTIVO $380. A continuación se presenta el ledger completo y la verificación de consistencia.

Fig. 3 Reconciliación algebraica del ledger — suma de todos los movimientos = 0
concepto movimientos subtotal acumulado VENTA_ITEM mov1 +1310 · mov2 +1310 · mov3 +1310 +3930.00 +3930.00 PROMOCION mov4 −655 · mov5 −655 −1310.00 +2620.00 PAGO CHEQUE mov6 −655 · mov7 −655 · mov8 −1310 −2620.00 0.00 PAGO excedente mov9 origenid=1 movimientoid=null −380.00 −380.00 PAGO vuelto mov10 origenid=2 movimientoid=null +380.00 0.00 SUMA TOTAL 0.00 ✓ ticket cerrado reconciliación maestro: total = 3×1310 − 1310 = 2620 pagado neto = 3000 − 380 = 2620 saldo = 0 vuelto = 380
La columna "acumulado" muestra el saldo corriente del ledger después de cada grupo de movimientos. El excedente lleva el acumulado a −380; el vuelto lo devuelve a 0. La condición de cierre (suma = 0) es la validación algebraica de consistencia del ticket.

Ledger completo — tabla de referencia

idconceptoorigenidmovimientoidNETO_IVA_21IVA_21II_IVA_21total mov
1VENTA_ITEM1null1000.00210.00100.00+1310.00
2VENTA_ITEM1null1000.00210.00100.00+1310.00
3VENTA_ITEM2null1000.00210.00100.00+1310.00
4PROMOCION11−500.00−105.00−50.00−655.00
5PROMOCION12−500.00−105.00−50.00−655.00
6PAGO11−500.00−105.00−50.00−655.00
7PAGO12−500.00−105.00−50.00−655.00
8PAGO13−1000.00−210.00−100.00−1310.00
9PAGO excedente1null−290.08−60.92−29.00−380.00
10PAGO vuelto2null290.0860.9229.00+380.00
SUMA0.000.000.000.00 ✓
Sección 9

Catálogos del sistema

El modelo distingue dos categorías: catálogos de configuración (maestros del sistema, no varían por ticket) y registros operativos del ticket (creados durante la operación).

Catálogos de configuración

tickettipoVENTA · NOTA_DE_CREDITO
tipocomprobanteA · B · C
tipodeclienteCONSUMIDOR_FINAL(1,B) · RESPONSABLE_INSCRIPTO(3,A) · EXENTO(4,B) · CF_FACTURA_B(5,B) · MONOTRIBUTO(6,A)
tipodeimpuesto0=PORCENTAJE · 1=MONTO (fijo)
tipoconceptoVENTA_ITEM · PROMOCION · PAGO
tiposdepagoid · descripcion · davuelto (bool) · cuota (int)
listapromocionesDefinición de reglas: beneficio (PORCENTAJE/MONTO/NUEVOPRECIOITEM) · método (CANTIDAD/COMBO) · decisión (NOACUMULATIVA/ACUMULATIVA) · lista de elementos por tipo (EAN/PLU/DEPTO/RUBRO/etc.)

Convenciones de naming

articuloidReferencia desde items[] hacia articulos[] del ticket.
mediodepagoidReferencia desde pagos[] hacia el catálogo tiposdepago.
promocionidReferencia desde promociones[] hacia el catálogo listapromociones.
origenidReferencia desde movimientos[] hacia el registro maestro origen.
movimientoidReferencia desde movimientos[] hacia el VENTA_ITEM afectado.
tipoPromoCampo en promociones[] (casing mixto por convención de los JSON del modelo).
Sección 10

Convenciones Java 8

Estilo de implementación

  • Java 8 clásico, sin Lombok, sin records, sin builders, sin frameworks innecesarios.
  • POJOs simples con campos privados, getters y setters clásicos, constructor por defecto.
  • Métodos explícitos con nombres descriptivos (addItem, findByOrigenid, calcularSaldo, etc.).
  • Colecciones como ArrayList salvo razón fuerte en contrario.
  • Enums Java para catálogos del dominio.
  • Sin streams complejos si no aportan claridad real.

Enums del dominio

TipoConceptoVENTA_ITEM, PROMOCION, PAGO
PromocionBeneficioPORCENTAJE, MONTO, NUEVOPRECIOITEM
PromocionMetodoCANTIDAD, COMBO
PromocionDecisionNOACUMULATIVA, ACUMULATIVA
PromocionListaTypeINCLUSION, EXCLUSION
PromocionTipoElementoEAN, PLU, DEPTO, RUBRO, CODIGOCLASIFICACION, PROVEEDOR, MARCA, MEDIODEPAGO, SUCURSAL, TICKET

Estructura de paquetes

modelcom.tipre.ruleengine.model — POJOs del dominio
enumscom.tipre.ruleengine.model.enums — enums del dominio
catalogcom.tipre.ruleengine.model.catalog — catálogos y configuración
logiccom.tipre.ruleengine.logic — reglas de cálculo y distribución
validationcom.tipre.ruleengine.validation — validación y reconciliación
examplecom.tipre.ruleengine.example — ejemplos y tests del ticket de referencia
Criterio de revisión permanente

Cada revisión del modelo debe validar: objetos bien separados sin responsabilidades mezcladas, orden de cálculo coherente, separación entre maestro y distribuido en el ledger, y que todo monto relevante puede explicarse y rastrearse hasta su origen.