Saltar al contenido principal

Gestión Unificada de Decimales y Redondeos

Este documento describe la arquitectura y las reglas de negocio de la API de BaseGes ERP para la gestión unificada de decimales y redondeos en precios, importes, importes intermedios, porcentajes y cantidades.

Principio de Unificación

Todos los motores de cálculo (ERP.DocumentEngine), tarifación (ERP.Catalog.Prices), presupuestos (ERP.Budgets), ventas (ERP.SalesDocuments) y facturación consumen el servicio centralizado IDecimalPrecisionService.


1. Fuentes de Configuración en BaseGes ERP​

La precisión decimal no está fijada de manera rígida en el código, sino que se obtiene dinámicamente de las tablas maestras de configuración del ERP:


### Detalle de Campos y Reglas

| Dimensión | Origen en BaseGes | Campo DB | Descripción | Valor por Defecto |
| :--- | :--- | :--- | :--- | :--- |
| **Precios** | Divisas (`mscr_009`) | `TDIVISAS5` | Decimales para precios unitarios de venta y catálogo. | `4` |
| **Importes** | Divisas (`mscr_009`) | `TDIVISAS4` | Decimales para importes brutos, netos, cuotas de IVA/RE y totales. | `2` |
| **Importes Intermedios**| Divisas (`mscr_009`) | `TDIVISAS14`| Subtotales e importes antes de recargos/descuentos. | `2` |
| **Cantidades** | Datos de Empresa (`mscr_055`)| `TDATOSDECONTROL179` | Decimales para unidades de artículos y partidas. | `3` (si es $\le 0$ o nulo) |
| **Porcentajes** | Estándar ERP | — | Descuentos de línea, cabecera y tipos de impuestos. | `2` |

> **Nota sobre Moneda:** Al operar exclusivamente en moneda Euros, el sistema consulta siempre el registro con código `'EUR'` (`TDIVISAS1 = 'EUR'`). Si no se encontrara el registro, se aplican automáticamente los valores por defecto indicados en la tabla superior.

---

## 2. Comportamiento en la API: Commands vs. Cálculos

Es importante entender la separación de responsabilidades entre la recepción de datos y el motor de cálculo:

1. **Recepción en los Commands (Entrada de datos):**
- Los endpoints de la API (`CreateBudgetCommand`, `UpdateBudgetCommand`, `CreateSalesDocumentCommand`, etc.) aceptan tipos `decimal` estándar.
- La API **no trunca ni rechaza** payloads que incluyan decimales adicionales en cantidades o precios (por ejemplo, si un sistema externo envía `quantity: 1.2505` o `price: 12.333333`).

2. **Ejecución en los Cálculos (Motor Financiero):**
- Durante la generación de líneas y recálculo del documento, `DocumentLineCalculationService` y `DocumentTotalsCalculationService` aplican los redondeos oficiales según la moneda y empresa:
- `RoundAmount(...)`: Aplica `MidpointRounding.AwayFromZero` con los decimales de `AmountDecimals`.
- `RoundPrice(...)`: Aplica `MidpointRounding.AwayFromZero` con los decimales de `PriceDecimals`.
- `RoundIntermediateAmount(...)`: Aplica `MidpointRounding.AwayFromZero` con los decimales de `IntermediateAmountDecimals`.
- `RoundPercentage(...)`: Aplica `MidpointRounding.AwayFromZero` con 2 decimales.
- `RoundQuantity(...)`: Aplica `MidpointRounding.AwayFromZero` con los decimales de `QuantityDecimals`.

---

## 3. Algoritmo de Redondeo: `AwayFromZero`

BaseGes ERP utiliza el método comercial estándar **Redondeo hacia el valor más alejado del cero** (`MidpointRounding.AwayFromZero`, también conocido como redondeo comercial simétrico):

* `2.555` con 2 decimales $\rightarrow$ `2.56`
* `-2.555` con 2 decimales $\rightarrow$ `-2.56`
* `1.234` con 2 decimales $\rightarrow$ `1.23`

Esto previene discrepancias con los informes oficiales de facturación, contabilidad e impuestos generados en las aplicaciones de escritorio de BaseGes.

---

## 4. Ejemplo Práctico: Entrada JSON vs. Respuesta Calculada

Si un cliente envía una petición a la API con un precio o cantidad con mayor número de decimales:

```json title="Payload enviado por el cliente (Request)"
{
"code": "ART-001",
"quantity": 1.2555,
"price": 10.333333,
"discount": 5.0
}
```

El motor de cálculo resolverá las líneas y los totales aplicando los redondeos oficiales de la empresa y la moneda (`EUR`):

```json title="Cálculos resultantes en la API (Response)"
{
"quantity": 1.2555,
"unitPrice": 10.3333,
"grossAmount": 12.97,
"discountPercent": 5.0,
"discountAmount": 0.65,
"netAmount": 12.32
}
```

* El importe bruto (`1.2555 * 10.333333 = 12.9734...`) se redondea a los 2 decimales oficiales de importe: `12.97`.
* El descuento (`12.97 * 5% = 0.6485`) se redondea a: `0.65`.
* La base neta resultante queda en `12.32`.


---

## 5. Tipado en la API: Uso de `decimal` frente a `double`

A nivel de arquitectura de software en toda la solución (.NET):

* **Precisión Financiera (`decimal`):** Todos los DTOs de entrada y salida (precios, importes, costes, márgenes, descuentos, porcentajes y existencias/cantidades), así como las entidades de dominio y los contratos de servicios, utilizan el tipo `decimal` de 128 bits en lugar de `double` (coma flotante IEEE 754). Esto elimina cualquier residuo de precisión binaria (como `0.1 + 0.2 != 0.3`) y asegura total coherencia en cálculos fiscales y sumatorios.
* **Compatibilidad con Base de Datos Legacy (SQL Server):** Aunque las tablas históricas de BaseGes almacenan internamente muchos de estos valores en columnas `float` (equivalente a `double`), Entity Framework Core realiza una conversión transparente y bidireccional (`.HasConversion<double>()`), permitiendo a la API operar siempre con tipos `decimal` exactos sin requerir modificaciones en el esquema de base de datos del ERP.