Índice
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.
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.
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.
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.
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.
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.
| Componente | Tipo | Descripción |
|---|---|---|
| NETO_IVA_21 | Monto | Neto gravado a tasa 21% |
| IVA_21 | Porcentaje | IVA 21% sobre el neto |
| IMPUESTOINTERNO_IVA_21 | Monto fijo | Impuesto interno (monto por unidad) |
| PERCEPCION_IIBB | Porcentaje variable | Varía según tipodecliente del cliente |
| PERCEPCION_COMIND | Porcentaje variable | Varí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.
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.
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.
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.
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.
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.
Semántica de referencias
origenidreferencia eliddel registro maestro que origina el movimiento:items[].idpara VENTA_ITEM,promociones[].idpara PROMOCION,pagos[].idpara PAGO.movimientoidreferencia eliddel 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.
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.
movimientoid poblado).movimientoid = null, con origenid del mismo pago que generó el excedente.movimientoid = null, con origenid del registro de vuelto en pagos[] (un pago diferente, con monto negativo en el maestro).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.
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.
articulos[].promociones[] y distribuir en movimientos[].datosreferenciales.nucleoimpositivo.pagos[] y aplicar promociones por medio de pago.movimientos[].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).
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.
Ledger completo — tabla de referencia
| id | concepto | origenid | movimientoid | NETO_IVA_21 | IVA_21 | II_IVA_21 | total mov |
|---|---|---|---|---|---|---|---|
| 1 | VENTA_ITEM | 1 | null | 1000.00 | 210.00 | 100.00 | +1310.00 |
| 2 | VENTA_ITEM | 1 | null | 1000.00 | 210.00 | 100.00 | +1310.00 |
| 3 | VENTA_ITEM | 2 | null | 1000.00 | 210.00 | 100.00 | +1310.00 |
| 4 | PROMOCION | 1 | 1 | −500.00 | −105.00 | −50.00 | −655.00 |
| 5 | PROMOCION | 1 | 2 | −500.00 | −105.00 | −50.00 | −655.00 |
| 6 | PAGO | 1 | 1 | −500.00 | −105.00 | −50.00 | −655.00 |
| 7 | PAGO | 1 | 2 | −500.00 | −105.00 | −50.00 | −655.00 |
| 8 | PAGO | 1 | 3 | −1000.00 | −210.00 | −100.00 | −1310.00 |
| 9 | PAGO excedente | 1 | null | −290.08 | −60.92 | −29.00 | −380.00 |
| 10 | PAGO vuelto | 2 | null | 290.08 | 60.92 | 29.00 | +380.00 |
| SUMA | 0.00 | 0.00 | 0.00 | 0.00 ✓ | |||
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
Convenciones de naming
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
ArrayListsalvo razón fuerte en contrario. - Enums Java para catálogos del dominio.
- Sin streams complejos si no aportan claridad real.
Enums del dominio
Estructura de paquetes
com.tipre.ruleengine.model — POJOs del dominiocom.tipre.ruleengine.model.enums — enums del dominiocom.tipre.ruleengine.model.catalog — catálogos y configuracióncom.tipre.ruleengine.logic — reglas de cálculo y distribucióncom.tipre.ruleengine.validation — validación y reconciliacióncom.tipre.ruleengine.example — ejemplos y tests del ticket de referenciaCada 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.