Saltar a contenido

Núcleo impositivo

El núcleo impositivo (NucleoImpositivoDto, en el kernel shared) es la pieza más transversal del sistema. Cada importe que viaja por el ticket —el precio de una línea, un descuento de promo, un crédito de envase, el total— no es un BigDecimal suelto: es un núcleo impositivo, es decir, un monto con su descomposición fiscal adentro. Entender esto es entender cómo el sistema mantiene el IVA y los impuestos cuadrados a lo largo de todo el cómputo.

Por qué un objeto y no un número

Si un descuento fuera solo "−\$100", al aplicarlo perderías sobre qué neto y qué IVA impactó. El núcleo impositivo lleva el monto y sus componentes (neto gravado, IVA, impuestos internos…), de modo que sumar, restar o prorratear importes preserva la composición fiscal. Esto es lo que permite emitir un comprobante con el IVA discriminado correcto al final.

Estructura

shared/NucleoImpositivoDto.java:

public class NucleoImpositivoDto {
    private BigDecimal monto;     // Total final (neto + impuestos)
    private BigDecimal neto;      // Base gravable
    private List<NucleoComponenteDto> componentes;  // Desglose por tipo

    public static final int SCALE = 8;          // precisión interna de cálculo
    public static final int DISPLAY_SCALE = 2;  // escala de moneda (mostrar)
}

Cada componente:

public static class NucleoComponenteDto {
    private NucleoComponenteTipo tipo;
    private BigDecimal base;      // importe base sobre el que se calcula
    private BigDecimal alicuota;  // % (ej. 21.00, 10.5)
    private BigDecimal monto;     // base × alícuota / 100
}

Tipos de componente

NucleoComponenteTipo modela el sistema impositivo argentino de retail:

Grupo Componentes Qué es
Neto (base imponible) NETO_IVA_21, NETO_IVA_10_5, NETO_IVAEXENTO La mercadería antes de impuestos, separada por la alícuota de IVA que le corresponde.
IVA IVA_21, IVA_10_5 El IVA calculado sobre cada neto.
Impuestos internos IMPINTERNO Impuesto interno (tabaco, bebidas, etc.), del campo fImpinterno del artículo.
Percepciones PERCEPCION_IIBB, PERCEPCION_COMIND, PERCEPCION_IVA_21, PERCEPCION_IVA_10_5 Percepciones impositivas (Ingresos Brutos, etc.).
Retenciones RETENCION_GANANCIAS, RETENCION_IVA Retenciones.

Qué está implementado y qué está modelado

Los componentes de neto, IVA e impuestos internos se computan y viajan en cada ticket. Los de percepción y retención existen en el enum (el modelo los contempla) pero, según el código actual, CaeService todavía no arma tributos de percepción en la solicitud a AFIP. Documentamos el modelo completo y marcamos lo que aún no se ejercita.

Aritmética que preserva la composición

NucleoImpositivoDto ofrece operaciones que operan sobre el monto y propagan a los componentes:

Operación Qué hace
add(otro) Suma montos y componentes, normaliza.
subtract(otro) Resta (ej. aplicar un descuento), normaliza.
multiply(factor) / divide(factor) Escala (ej. precio × cantidad).
recomponer(nuevoMonto) Recalcula los componentes para un monto nuevo, manteniendo las proporciones (ej. fijar un precio de promo y que el IVA se reparta solo).
normalized() Recompone monto/neto desde los componentes.
zero() / isZero() Cero impositivo.

recomponer(): el método clave de las promos

Cuando una promo fija un precio nuevo (un combo a \$1.000, un precio mayorista), no se puede simplemente cambiar el monto: hay que recalcular el neto y el IVA para que sigan cuadrando. recomponer(nuevoMonto) hace eso, prorrateando el nuevo monto entre los componentes según su peso original.

recomponer() y la base cero

Si el monto base es cero, recomponer() dividiría por cero (ArithmeticException). El fix TSPM-016 lo evita: un ítem con base cero no recibe descuento (su porción es cero). Si tocás aritmética de núcleo, respetá ese guard.

Normalización: el fix TSTK-025

Cuando un NucleoImpositivoDto se deserializa desde JSON, a veces monto/neto llegan en cero aunque los componentes tengan valores. normalized() recompone los totales desde los componentes para no perder precisión en las sumas/restas posteriores (fix TSTK-025). Por eso casi todas las operaciones llaman a normalized() al final.

Escala y redondeo

  • Cálculo interno: SCALE = 8 decimales — se calcula con holgura para no acumular error.
  • Moneda: DISPLAY_SCALE = 2, HALF_UP — se redondea al mostrar/persistir el importe final.
  • El módulo promos usa la misma escala (MONEY_SCALE + 8 para intermedios) justamente para ser consistente con el núcleo.

Regla práctica

Trabajá siempre con NucleoImpositivoDto, no con su monto pelado. Si extraés el BigDecimal y operás aparte, perdés la composición fiscal y vas a descuadrar el IVA del comprobante. La aritmética del dinero vive acá a propósito.

Dónde se usa

  • En cada línea del ticket (paso 3 del cómputo).
  • En cada beneficio de promoción (el descuento es un núcleo, no un número).
  • En el crédito de envases (envases y vales).
  • En el comprobante fiscal, donde el desglose por alícuota se vuelca al CAE/CAEA y al QR de AFIP (facturación fiscal).