# Aclaración: gastos administrativos, interés e IVA en ventas a cuotas

Notas de referencia sobre cómo funciona en el mercado paraguayo el desglose de una cuota
a crédito (interés, gastos administrativos, seguro), y qué de eso ya está implementado en
el motor de cuotas (`planes_cuotas` / sistema francés) vs. qué queda pendiente de decidir.

Contexto: esto surgió al implementar el sistema francés en `planes_cuotas` (ver
`docs/plan-motor-precios-rentabilidad.md`) y en Solicitud de Crédito. El usuario preguntó
por los desgloses que suele ver en compras a cuotas (interés, gastos administrativos, etc.)
para entender si hay que modelarlos.

## 1. IVA sobre el interés — pendiente de decisión con el contador

**El interés de financiación SÍ paga IVA en Paraguay**, a la tasa reducida del 5%, desde
que la Ley 5061/2013 eliminó la exoneración que tenían los intereses. Esto aplica no solo
a bancos/financieras sino también a comercios que venden a crédito con recargo propio.

**Estado actual del sistema**: el motor de cuotas (`planes_cuotas`, sistema francés/
compuesto/simple) calcula el interés y lo suma al monto de la cuota, pero **no toca el
cálculo de IVA de la factura** — el IVA de la factura sigue calculándose solo sobre el
precio de los productos, como si el interés no existiera fiscalmente. El interés queda
"por fuera", como un mayor monto a cobrar en las cuotas (`factura_cuotas`), sin pasar por
`factura_subtotales`/liquidación de IVA.

**Por qué no se tocó**: discriminar el interés como concepto propio y gravarlo con IVA
significa cambiar cómo se arma la factura en sí (nuevo ítem/concepto, o ajuste de la base
imponible), no solo el monto de la cuota. Es una decisión fiscal, no solo técnica — hay que
definir con el contador de la empresa si conviene facturar el interés como parte del precio
del producto (más simple, pero sube el precio de venta declarado) o como un concepto
financiero separado con su propio tratamiento de IVA.

**Acción sugerida cuando se retome**: definir con el contador cuál de los dos esquemas
usar, y recién ahí tocar `facturas.service.ts` (cálculo de subtotales/IVA) para reflejarlo.

## 2. Gastos administrativos — no implementado, arquitectura ya lo soporta

En créditos de entidades reguladas (bancos, financieras, cooperativas bajo BCP) es común
un cargo adicional al interés:

- **Gasto de apertura/otorgamiento**: % único sobre el capital, cobrado una sola vez al
  desembolsar (ej. Caja Bancaria cobra 1% del capital otorgado).
- Para créditos que superan 5 salarios mínimos, los gastos administrativos están topeados
  por regulación al **15% del monto desembolsado** — pero ese tope aplica a entidades
  financieras reguladas por BCP, no a un comercio minorista financiando su propia venta.

**Estado actual**: `planes_cuotas` no tiene ningún campo para esto.

**Si se quiere agregar más adelante**: lo más simple es un campo opcional en el plan
(ej. `gastos_administrativos_pct` o `gastos_administrativos_fijo`), sumado una sola vez al
monto financiado antes de calcular la tabla de amortización (afecta el capital inicial, no
cada cuota). Cambio chico, no requiere rediseño.

## 3. Seguro de vida del saldo deudor — no aplica a este negocio, no implementar

Común en créditos bancarios (cubre el saldo pendiente si el titular fallece). Prácticamente
inexistente en comercios que financian sus propias ventas de forma informal (no son
aseguradoras ni suelen contratar pólizas colectivas para esto). No corresponde modelarlo
salvo que la empresa efectivamente contrate un seguro de este tipo para sus créditos.

## 4. CFT (Costo Financiero Total) — concepto de referencia, no una feature a construir

Es el indicador que usan bancos/financieras para comparar ofertas de crédito: combina
interés + gastos administrativos + seguro, todo expresado como una tasa anual única. Es
la manera "justa" de comparar dos créditos con estructuras de costos distintas. No hace
falta implementarlo como feature — es más un concepto a tener en mente si el negocio algún
día ofrece varios planes con distinta combinación de interés/gastos y hay que compararlos
de forma homogénea.

## Recomendación general

Por ahora, con solo interés (sin gastos administrativos ni seguro) es razonable — es lo
que hace la mayoría de comercios que arman sus propias cuotas. Las dos extensiones que
tienen sentido a futuro, en orden de prioridad si algún día se necesitan:

1. **IVA sobre interés** — requiere definición fiscal con el contador antes de tocar código
   (cambia el cálculo de la factura, no solo el de la cuota).
2. **Gastos administrativos como % único al capital** — cambio chico en `planes_cuotas`,
   sin impacto en facturación, se puede agregar cuando haga falta.

## Referencias

- [Intereses de préstamos estarán gravados por el IVA — DNIT](https://www.dnit.gov.py/documents/20123/263766/Intereses+de+pr%C3%A9stamos+estar%C3%A1n+gravados+por+el+IVA.pdf/876d9e24-cb53-7719-6557-376ce2b7946d)
- [Los gastos ocultos pueden costar hasta 15% del crédito — Última Hora](https://www.ultimahora.com/los-gastos-ocultos-pueden-costar-15-del-credito-n566970)
- [CFT: qué es el Costo Financiero Total y cómo calcularlo](https://www.naranjax.com/blog/que-es-costo-financiero-total)

---

# Aclaración: motor de reglas de precio — dónde vive cada precio

Guía de referencia sobre el flujo real de `productos.precio` vs. `lista_precios_productos`
vs. `reglas_precio`, para que una futura sesión de IA no asuma que "Recalcular" escribe el
precio de venta del producto. Surgió porque el usuario vio que, tras crear una regla y
recalcular, el campo "Precio" del producto seguía en `0` y preguntó si eso era correcto.

## El flujo real

1. **`productos.precio`** (campo "Precio" del formulario de edición, tabla `productos`) es
   un precio base **manual**, de referencia/fallback. **Las reglas de precio nunca lo
   tocan.** Que quede en `0` después de recalcular es el comportamiento esperado, no un bug.

2. **`reglas_precio`** define tramos de costo (`costo_desde` inclusive, `costo_hasta`
   **exclusivo**) → método de cálculo (`margen_sobre_costo` / `margen_sobre_precio` /
   `precio_fijo`) → una **lista de precios destino** (`lista_precios_id`). Una regla no
   calcula nada por sí sola: necesita que alguien dispare el recálculo.

3. **"Recalcular"** ejecuta `PreciosEngineService.recalcularLista()`
   (`src/reglas-precio/precios-engine.service.ts:370`). Por cada producto con costo
   cargado:
   - Busca la regla activa de esa lista cuyo rango de costo cubre `producto.precio_costo`
     (`calcularPrecio()`, línea 211-273; el filtro de rango está en línea 245-246).
   - Aplica la fórmula (`aplicarRegla()`, línea 275-308) y el redondeo de la regla.
   - Escribe el resultado en **`lista_precios_productos`** (`precio_base`,
     `origen_precio='regla'`, `regla_precio_id`) — nunca en `productos.precio`.
   - Si `preservarOverride=true` (default) y el producto ya tenía un precio puesto a mano
     en esa lista (`origen_precio='manual'`), lo saltea (no lo pisa).

4. Lo que factura el vendedor sale de la **lista de precios asignada a la venta/cliente**
   (`lista_precios_productos.precio_base` de esa lista), no de `productos.precio`.
   `productos.precio` es solo un valor de referencia interno que casi nunca se usa en la
   práctica si la empresa trabaja con listas + reglas.

## Ejemplo real verificado (empresa Comercial Kety)

Producto "ASPIRADORA GAMMA 25L", `precio_costo = 500.000`. Regla "Costo medio (500k–3M)
+25%" aplicó (no "Costo bajo (0–500k) +40%", porque `costo_hasta` es exclusivo y
500.000 no es `< 500.000`, cae en el tramo siguiente):

500.000 × 1,25 = 625.000 → ya es múltiplo de 1.000, sin ajuste de redondeo.

Resultado: `lista_precios_productos.precio_base = 625.000` para la lista "Contado (demo
motor de precios)", `productos.precio` sigue en `0`. Ambos datos son correctos y
consistentes con el diseño.

## Punto suelto detectado de paso (no se tocó)

`generarGrillaCuotas()` (mismo archivo, línea 469-521) — la grilla que llena
`producto_precio_cuota` para el dropdown "18 x Gs. X" del ecommerce — usa un cálculo de
interés simple y plano propio (línea 492-494), **no** reutiliza
`calcularMontoCuotaConInteres`/`generarTablaAmortizacion` de
`src/common/utils/calculo-interes.util.ts` (el motor correcto de `planes_cuotas`
corregido en la sesión de "reglas-precios"). Si un plan está configurado como francés/
compuesto, esta grilla igual le aplica interés simple — no refleja la fórmula real del
plan. Documentado como Fase 1 en el propio código; unificar si se activa cuotas
automáticas para ecommerce.

---

# Aclaración: cómo se resuelve el precio final (lista general vs. lista del cliente)

Dudas recurrentes: "¿todos los clientes deberían tener una lista de precios asignada?" y
"si el cliente tiene otra lista, ¿trae el precio de esa lista y no el de la regla/general?".

## Orden de resolución

`ListaPreciosService.obtenerPrecioProducto()` (y su variante batch
`obtenerPreciosProductosBatch()`) en `src/lista-precios/lista-precios.service.ts:15-105`
arma un set de listas candidatas y las prueba **en este orden**:

1. **Lista asignada directamente al cliente** (`clientes.lista_precios_id`, el campo
   "Lista de Precios" del formulario de cliente) — línea 29-37.
2. **Listas asignadas por `lista_precios_asignaciones`** (tipo `cliente`/`zona`/`canal`,
   con vigencia por fecha) — línea 39-75. Mismo nivel de prioridad que el punto 1: ambas
   se juntan en el mismo `Set` y se ordenan por `lista_precios.prioridad` ascendente
   (línea 91-96, comentario explícito: *"Listas asignadas al cliente van primero (tienen
   prioridad sobre las generales)"*).
3. **Listas generales** (`tipo_aplicacion='general'`, activas y vigentes) — línea 98-102,
   se usan como fallback ordenadas por `prioridad` ascendente (P1 antes que P5).
4. **`productos.precio`** (campo manual, casi siempre `0`) — si ninguna lista de las
   anteriores tiene el producto ni aplica descuento/recargo general (línea 108-138).

El loop (línea 113-131) recorre `listas = [...listasCliente, ...listasGenerales]` en ese
orden y se queda con la **primera** que tenga precio específico del producto (o que
aplique descuento/recargo general); no seguimos buscando en las siguientes.

## Conclusión práctica

- Si un cliente **no tiene** `lista_precios_id` ni asignaciones propias, recibe
  automáticamente el precio de la lista general activa de mayor prioridad (ej. la que
  las reglas de precio recalculan, ver sección anterior). No hace falta configurar nada
  por cliente.
- Si un cliente **sí tiene** una lista propia asignada (directa o por
  `lista_precios_asignaciones`), esa lista gana siempre sobre cualquier lista general,
  aunque el producto también exista en la lista general con otro precio.
- **No es necesario ni recomendable** asignar la lista general a cada cliente uno por
  uno — para eso existe `tipo_aplicacion='general'`. Asignar una lista específica solo
  tiene sentido para la excepción (mayorista, convenio especial, cliente VIP, etc.), no
  para el caso por defecto.

Verificado en código junto con el usuario (empresa Comercial Kety) el 2026-08-26, a raíz
de una duda sobre el cliente PASCUAL FRANCO NOGUERA y el producto ASPIRADORA GAMMA 25L.

---

# Aclaración: tope de descuento (Fase 3, Módulo C) — diseño acordado, aún no implementado

Diseño consensuado con el usuario para la Fase 3 del `plan-motor-precios-rentabilidad.md`
(Módulo C — descuentos con tope y autorización). Documentado acá para que una futura sesión
retome exactamente esto y no re-derive el diseño desde cero.

## Problema de partida

El descuento en POS hoy es libre (0–100% por ítem, y un descuento global de factura), sin
ningún tope ni validación — ni en el frontend más allá del 100%, ni en el backend al crear la
factura. Se quiso definir un tope automático ligado al margen real del producto, no un
número arbitrario configurado a mano por política.

## Cómo se calcula el tope (sin tabla nueva)

```
piso = lista_precios_productos.precio_minimo si está seteado y > 0
       si no, productos.precio_costo

tope_pct = max(0, (1 − piso / precio_venta) × 100)
```

Importante: **no alcanza con tomar el `valor` % de la `regla_precio` aplicada tal cual**. Se
verificó con el caso real de Kety (costo 500.000, regla "Costo medio +25%", precio venta
625.000): un descuento del 25% literal sobre 625.000 da 468.750, **por debajo del costo**. El
% exacto de equilibrio con este producto es **20%** (`1 − 500.000/625.000`). La fórmula
`1 − costo/precio_venta` da el punto de equilibrio exacto sin importar el método de la regla
(`margen_sobre_costo`, `margen_sobre_precio`, etc.) — solo necesita costo y precio de venta
actuales, no la fórmula de la regla que los generó.

**Campo de respaldo manual** (para cuando no hay costo ni precio_minimo cargados, o el
negocio no usa el motor de reglas): nuevo campo opcional `lista_precios_productos.
descuento_maximo_pct`. Si tampoco está cargado, tope = 0% (cualquier descuento pide
autorización). Se edita con el mismo permiso que ya existe para editar precios en
`PreciosListasSection.jsx` — no se creó submódulo ni permiso nuevo para esto.

**Se descartó** una tabla `politicas_descuento` con tope configurable por perfil/rol: el tope
por producto (fórmula de arriba) es único para todos los perfiles; cualquier exceso, sin
importar quién lo pida, requiere autorización de supervisor. Simplifica bastante vs. el diseño
inicial del plan (que preveía tope por producto/categoría/rol vía tabla nueva).

## Descuento global de factura

No tiene fórmula propia: se prorratea entre los ítems del carrito según su peso en el
subtotal, se suma al descuento propio de cada ítem, y esa suma se valida contra el
`tope_pct` de cada producto — mismo camino de validación que el descuento por ítem, sin
lógica duplicada.

## Autorización: dos caminos, mismo mecanismo de fondo

Cuando un descuento supera el tope, ya existe toda la infraestructura de autorización — solo
hay que conectarla (nunca se usó para `tipo='descuento'` aunque el enum y las UI ya lo
contemplan):

1. **PIN presencial** — `ModalPinSupervisor` (`src/components/organismos/POSDesign/
   CajaDesign/ModalPinSupervisor.jsx`) ya tiene `"descuento"` en `OPERACION_LABELS`. Llama a
   `validarPinSupervisor` (`users.service.ts:629`), que verifica PIN + privilegio
   `POS_SUPERVISOR` (superadmin bypasea) y registra en `autorizaciones_caja` con
   `estado='aprobada'` directamente. Síncrono, requiere al supervisor físicamente ahí.
2. **Remoto vía websocket** — `PanelSupervisor.jsx` ya escucha `autorizacion:solicitud`/
   `aprobada`/`rechazada` y ya tiene `"descuento": "Descuento"` en su `TIPO_LABELS`, listo para
   mostrar cualquier pendiente con `tipo='descuento'` sin cambios. Falta solo el lado del
   cajero: un botón "Pedir autorización remota" que llame a `solicitarAutorizacion` (`api/
   pos-security.service.js`, ya existe) y un listener de socket nuevo en el POS que escuche la
   resolución de esa solicitud puntual para desbloquear la venta. Así el supervisor no necesita
   estar en el salón de ventas.

## Backend: la validación tiene que repetirse al crear la factura

El tope no puede ser solo un candado de UI (se salta con devtools). Al crear la factura,
recalcular el mismo `tope_pct` por línea y exigir que exista una `autorizaciones_caja` con
`estado='aprobada'`, `tipo='descuento'`, del mismo usuario/contexto y reciente, si el
descuento de esa línea lo supera. Sin esto, el control es cosmético.

## Precio mínimo

`precio_minimo` puede pisarse con autorización de supervisor igual que cualquier otro exceso
de tope (no es un piso absoluto e inquebrantable) — mismo mecanismo de arriba, no hay caso
especial.

Diseñado junto con el usuario el 2026-08-26. Pendiente de implementar.
