Appearance
Retenciones de Ganancias con Acumulados del Período
Concepto
Las retenciones de ganancias deben considerar los acumulados del período para determinar:
- Si se alcanzó el mínimo no imponible
- Sobre qué monto se debe retener (excedente)
⚠️ IMPORTANTE: Este documento describe el cálculo general. El método específico depende de la inscripción del proveedor:
- Inscriptos (
insgana='S'): Pueden usar escalas progresivas (ver RG_5423_Codigo_119_Calculo_Retenciones.md) - No inscriptos (
insgana='N'): SIEMPRE usan porcentaje fijo de 28%, nunca escalas
Reglas de Negocio
RN-007: Validación contra Acumulado Total
La validación del mínimo no imponible NO se hace sobre el neto del comprobante actual, sino sobre el total acumulado en el período:
total_acumulado = acumulado_anterior + neto_comprobante_actualSi total_acumulado < mínimo_no_imponible: NO se aplica retención
Si total_acumulado >= mínimo_no_imponible: Se aplica retención sobre el excedente
RN-008: Retención sobre el Excedente
La retención SIEMPRE se aplica sobre el excedente del mínimo, NO sobre el neto completo:
excedente = total_acumulado - mínimo_no_imponible
monto_imponible = min(excedente, monto_aplicado_en_este_pago)
retención = monto_imponible * porcentajeEjemplos
Ejemplo 1: Sin Acumulado Anterior
Contexto:
- Acumulado anterior: $0
- Mínimo no imponible: $1,200
- Neto del comprobante: $300
- Monto del pago: $300
Cálculo:
total_acumulado = 0 + 300 = 300
¿300 >= 1200? NOResultado: ❌ No se retiene (no alcanza el mínimo)
Ejemplo 2: Con Acumulado que Supera el Mínimo
Contexto:
- Acumulado anterior: $1,000
- Mínimo no imponible: $1,200
- Neto del comprobante: $300
- Monto del pago: $300
- Porcentaje: 10%
Cálculo:
total_acumulado = 1000 + 300 = 1300
¿1300 >= 1200? SÍ
excedente = 1300 - 1200 = 100
monto_imponible = min(100, 300) = 100
retención = 100 * 10% = $10Resultado: ✅ Se retiene $10 (sobre los $100 de excedente, NO sobre los $300 de neto)
Ejemplo 3: Acumulado que Ya Superó el Mínimo
Contexto:
- Acumulado anterior: $1,500
- Mínimo no imponible: $1,200
- Neto del comprobante: $500
- Monto del pago: $500
- Porcentaje: 10%
Cálculo:
total_acumulado = 1500 + 500 = 2000
¿2000 >= 1200? SÍ
excedente = 2000 - 1200 = 800
monto_imponible = min(800, 500) = 500
retención = 500 * 10% = $50Resultado: ✅ Se retiene $50 (como el acumulado ya superó el mínimo, se retiene sobre todo el neto actual)
Ejemplo 4: Pago Parcial del Comprobante
Contexto:
- Acumulado anterior: $1,000
- Mínimo no imponible: $1,200
- Neto del comprobante: $600
- Monto del pago: $300 (pago parcial)
- Porcentaje: 10%
Cálculo:
total_acumulado = 1000 + 600 = 1600
¿1600 >= 1200? SÍ
excedente = 1600 - 1200 = 400
monto_imponible = min(400, 300) = 300
retención = 300 * 10% = $30Resultado: ✅ Se retiene $30 (el excedente es 400, pero solo se pagan 300 en este momento)
Diferencias entre Inscriptos y No Inscriptos
Proveedores INSCRIPTOS (insgana='S')
Pueden usar escalas progresivas según RG AFIP 5423/2023:
Si tiene escalas configuradas:
→ Buscar escala donde: desde <= total_acumulado <= hasta
→ Aplicar: retención = fijo + (porcentaje × (acumulado - excedente))
Si NO tiene escalas:
→ Aplicar porcentaje simple sobre excedenteEjemplo con escalas RG 5423:
Tabla de escalas (Código 119):
| Más de $ | A $ | Monto Fijo $ | % | S/exc. de $ |
|---|---|---|---|---|
| 426,000 | 568,000 | 56,090 | 23% | 426,000 |
| 568,000 | 852,000 | 88,750 | 27% | 568,000 |
Acumulado anterior: $700,000
Neto comprobante: $100,000
Total acumulado: $800,000
1. Identificar escala: $800,000 está entre $568,000 y $852,000
2. Obtener datos del tramo:
- Monto fijo: $88,750 (retención acumulada hasta $568,000)
- Porcentaje: 27%
- S/exc. de $: $568,000
3. Calcular excedente del tramo:
Excedente = $800,000 - $568,000 = $232,000
4. Calcular retención:
Retención = $88,750 + ($232,000 × 27%)
= $88,750 + $62,640
= $151,390
✅ El monto fijo $88,750 NO se recalcula, es la suma de retenciones
de todos los tramos anteriores (desde $0 hasta $568,000)Proveedores NO INSCRIPTOS (insgana='N')
⚠️ SIEMPRE usan porcentaje fijo de 28%, sin considerar escalas:
Si total_acumulado >= mínimo:
excedente = total_acumulado - mínimo
retención = excedente × 28%
Las escalas se IGNORAN aunque estén configuradasEjemplo NO inscripto:
Acumulado anterior: $1,500,000
Monto mínimo: $450,000
Neto comprobante: $300,000
Total acumulado: $1,800,000
Excedente = $1,800,000 - $450,000 = $1,350,000
Retención = $1,350,000 × 28% = $378,000
⚠️ No se usan escalas, solo porcentaje fijoComparación:
| Aspecto | Inscripto | No Inscripto |
|---|---|---|
| Método de cálculo | Escalas progresivas o % simple | Siempre % fijo (28%) |
| Usa escalas | Sí, si están configuradas | NO, nunca |
| Porcentaje | Variable según escala | Fijo: 28% |
| Complejidad | Mayor (rangos, fijos, %) | Menor (solo excedente × 28%) |
Impacto en el Sistema
Base de Datos
Tabla: acuganancias
sql
CREATE TABLE acuganancias (
id SERIAL PRIMARY KEY,
codpro INT NOT NULL, -- Código del proveedor
mes INT NOT NULL, -- Mes (1-12)
ano INT NOT NULL, -- Año (ej: 2024)
codgan INT NOT NULL, -- Código del concepto de ganancia
monacu DECIMAL(15,2) NOT NULL, -- Monto acumulado (base para validar mínimo)
monret DECIMAL(15,2), -- Monto retenido acumulado
UNIQUE(codpro, mes, ano, codgan)
);Flujo de Actualización
Cuando se registra una orden de pago con retenciones:
- Consultar acumulados del período actual
- Calcular retenciones considerando acumulados
- Actualizar acumulados sumando:
monacu += neto_del_comprobantemonret += retención_calculada
Servicios Involucrados
- AcumuladoGananciaService: Gestiona los acumulados del período
- RetencionGananciaPagoService: Calcula retenciones considerando acumulados
- CalculadorRetenciones (Domain): Lógica pura de cálculo
Código de Ejemplo
php
// En el servicio
$acumulados = $this->acumuladoGananciaService->getByProveedor(
$idProveedor,
$mesActual,
$anoActual
);
// En el calculador de dominio
$acumuladoAnterior = $this->acumuladosPorConcepto[$idConcepto] ?? 0.0;
$totalAcumulado = $acumuladoAnterior + $concepto->neto;
if ($totalAcumulado < $configuracion->monto) {
// No retener: no alcanza el mínimo
}
$excedente = $totalAcumulado - $configuracion->monto;
$montoImponible = min($excedente, $montoAplicado);
$retencion = $montoImponible * ($porcentaje / 100);Casos Edge
Caso 1: Sin Acumulado Registrado
Si no existe registro de acumulado para el concepto → acumulado = 0
Caso 2: Primer Comprobante del Período
Si es el primer comprobante → acumulado = 0 → validar neto_actual >= mínimo
Caso 3: Cambio de Período
Al cambiar de mes/año, los acumulados se reinician automáticamente (nueva consulta retorna vacío)
Caso 4: Múltiples Conceptos
Cada concepto tiene su propio acumulado independiente
Testing
Ver: Tests/Unit/CtaCte/RetencionGananciaPagoServiceTest.php
Tests específicos para acumulados:
testConAcumuladoBajoMinimo- No retiene si acumulado + neto < mínimotestConAcumuladoQueSupera- Retiene sobre excedentetestConAcumuladoYaSuperado- Retiene sobre neto completo