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.