---
url: /reference/CURRENCY_AND_LANGUAGE.md
---
# Ninaku Core: multimoneda y multiidioma

> Documento migrado desde `franmc01/ninaku-core-api`.

Estado: revisión del SQL actual y contrato propuesto. Tener columnas de moneda y tablas de traducción no acredita todavía un flujo completo. No se implementó un backend en esta entrega.

## Qué está modelado hoy

| Necesidad | Evidencia en el baseline | Qué significa |
|---|---|---|
| Catálogo de monedas | `reference.currencies`, `reference.assert_currency_granularity` en `0010` | Código, unidad menor y validación de importes representables |
| Valores predeterminados | `organization.organizations.default_currency/default_locale` en `0020` | Preferencias iniciales del negocio; no sustituyen la moneda de un documento |
| Precios y ventas | `pricing.price_books.currency_code` en `0090`; `sales.orders.currency_code` en `0110` | Lista y orden tienen moneda explícita; ventas valida coincidencia con precios utilizados |
| Cobro en otra moneda | `payments.payment_intents/payment_transactions` en `0130` | Moneda e importe pagados, moneda e importe aplicados a la cuenta, tasa y fuente congeladas |
| Tipos de cambio | `treasury.fx_rate_sources/fx_rates/fx_exchanges` en `0250` | Fuentes por organización, par, tasa, vigencia y conversión registrada |
| Moneda funcional | `accounting.ledgers.functional_currency_code` y `journal_lines` en `0290` | Moneda del ledger, importes de transacción y funcionales, snapshot de tasa/fuente |
| Catálogo de idiomas/locales | `reference.locales` en `0010` | Identificador de idioma/región referenciado por otros módulos |
| Contenido traducible | Tablas `catalog.*_translations`, `pricing.discount_translations`, `commerce.storefront_translations` | Traducciones de contenido de negocio con pertenencia al tenant |
| Preferencia del cliente | `crm.customer_profiles.preferred_locale_code` en `0200` | Preferencia de comunicación del cliente; verificar integración del caso de uso |
| Documentos | Versiones de plantilla e instancias con `locale_code` en `0310` | El idioma forma parte de la identidad de renderizado |

Los seeds actuales incluyen **USD, COP, CLP y BHD**, y los locales **es y en**. No incluyen un catálogo mundial ni todas las variantes regionales. Los códigos regionales como `es-EC` requieren referencias aprobadas y un mecanismo de resolución; no basta con aceptar una cadena arbitraria desde el navegador.

## Separaciones que conservaría y por qué

| Decisión | Razón |
|---|---|
| Moneda de venta, moneda de pago y moneda funcional independientes | Un cliente puede pagar en otra moneda sin cambiar el precio histórico de la venta o el libro contable |
| Moneda de reporte como conversión explícita de lectura | Cambiar un dashboard a USD no debe reescribir ventas en COP ni sumarlas directamente |
| Listas de precio por moneda, con política de conversión opcional | Convertir el precio del día no siempre coincide con la estrategia comercial del negocio |
| Idioma de interfaz separado del contenido y del documento | Un cajero puede usar inglés, vender un producto con nombre en español y emitir el comprobante en el idioma configurado |
| Locale, moneda y zona horaria separados | Elegir español no define país, moneda ni fecha de cierre de caja |
| Política de fallback explícita | Una traducción ausente no debe producir una pantalla rota ni seleccionar un idioma al azar |

## Contrato propuesto para moneda

Los importes viajan como decimal exacto y código de moneda. No deducir la moneda del símbolo `$`, del idioma ni del país del usuario. Los cálculos intermedios pueden tener más precisión que el importe liquidable. La unidad menor de la moneda y el incremento de redondeo en efectivo son conceptos distintos; una política de efectivo se define por operación/configuración y no se inventa a partir de `minor_unit`.

Ejemplo comercial **hipotético, no una cotización**: orden por `40000.00 COP`; cliente entrega `10.00 USD`; la política aprobada utiliza `4000 COP por USD`. Guardar los dos importes, monedas, dirección de conversión, tasa aplicada, fuente/versionado, vigencia usada y regla de redondeo. Que la tasa cambie mañana no debe cambiar este cobro.

Para devoluciones, cobros parciales, comisiones y diferencias, registrar hechos y asignaciones explícitos. Definir si se devuelve en la moneda original del pago y cómo se trata la diferencia; no aplicar automáticamente la tasa del momento. Los reportes conservan importe original y exponen moneda/tasa/fecha de conversión. La definición contable y fiscal por jurisdicción requiere validación específica antes de producción.

En offline, la operación debe portar una política/tasa autorizada y versionada con su vigencia; si no puede aplicarse, se limita la operación o queda pendiente de conciliación. Nunca recalcular silenciosamente un cobro ya efectuado con la tasa de reconexión. Los valores predeterminados del negocio solo sirven para operaciones nuevas.

## Hallazgos que necesitan refinamiento

1. **Redondeo FX en Tesorería: corregido en revisión 3.** El siguiente párrafo describe el defecto del baseline anterior; la implementación actual y las pruebas están en [REFINEMENT\_03.md](../history/REFINEMENT_03.md). `treasury_fx_exchanges_rate_math_chk` exige `abs(destination_amount - source_amount * applied_rate) <= 0.000001`. Para un destino de dos decimales, `1 × 1.2345` redondeado a `1.23` deja diferencia `0.0045` y no cumple ese CHECK. Esta comprobación puede rechazar una conversión liquidable bajo esa política. Es un hallazgo aritmético del constraint, no una prueba completa de intercambio. Antes de cambiarlo, definir precisión, modo de redondeo, residuo y tratamiento de comisiones; probar el flujo con sus movimientos reales. No resolverlo aumentando una tolerancia arbitraria.
2. **Tasa elegida y tasa aplicada: reforzado en revisión 3.** El siguiente análisis describe la comprobación anterior; ahora se valida organización, par, dirección, valor, vigencia y snapshot de fuente. `treasury.validate_fx_exchange` comprueba que la referencia cubra el par, incluso invertido; eso por sí solo no prueba vigencia, origen aprobado o correspondencia con `applied_rate`. Definir cómo autorizar y auditar una cotización negociada o una inversión de tasa.
3. **Contabilidad.** La validación inspeccionada exige tasa 1 e importes iguales para la misma moneda, y fuente para moneda extranjera. Falta acreditar con escenarios la conversión, el balanceo y diferencias de redondeo de principio a fin. No atribuir garantías de moneda funcional solo a la existencia de columnas.
4. **Cobertura real.** Probar pagos cruzados, devolución parcial, reintento idempotente y conciliación antes de presentar multimoneda como funcionalidad lista. La instalación limpia no demuestra esas operaciones.

La revisión 3 implementa los cambios acotados en Reference/Treasury, con fixtures y pruebas de concurrencia. Accounting, Payments y políticas de operación offline requieren el siguiente bloque de validación; un resultado verde de FX no certifica esos módulos.

## Contrato propuesto para idioma

* **Interfaz:** textos versionados en recursos de localización del frontend/backend. Botones y códigos de error no necesitan una tabla SQL genérica por cada texto.
* **Contenido del negocio:** conservar las tablas de traducción del dominio. Un producto mantiene un ID/SKU estable; cambia su nombre mostrado, no su identidad.
* **Resolución:** selección explícita permitida → preferencia del usuario/contexto → negociación de `Accept-Language` contra locales soportados → valor predeterminado del negocio. La preferencia del cliente aplica al contenido/comunicación dirigida a ese cliente, no a la sesión del cajero.
* **Fallback de contenido:** locale resuelto → idioma base soportado → locale predeterminado del contenido/negocio → valor canónico. Acordar fallback por campo si la traducción es parcial, porque `name` y `description` pueden faltar por separado. No crear una fila traducida falsa cuando se usó fallback.
* **Respuesta de pantalla:** devolver contenido ya resuelto, locale efectivo y fallback cuando importe. Incluir contexto de localización en una de las dos llamadas iniciales; no una consulta adicional por cada etiqueta o producto.
* **Caché:** separar por tenant, permisos relevantes y locale resuelto; incluir `Vary: Accept-Language` cuando esa cabecera determine la representación. Cambiar de idioma invalida la representación localizada, no la operación de negocio ni su clave idempotente.
* **Documentos:** conservar locale y versión de plantilla/contenido necesarios para reproducir el emitido. Cambiar luego la traducción del catálogo no debe alterar el significado histórico del comprobante.
* **Offline:** descargar solo los recursos/locales necesarios con versiones; conservar idioma del documento y snapshots requeridos por el comando. La interfaz puede cambiar de idioma sin duplicar la venta pendiente.

No se encontró un backend que implemente esta resolución: aún no existe en el repositorio. No confundir el catálogo `reference.locales` con una implementación completa de internacionalización.

## Validación siguiente

| Caso | Resultado esperado |
|---|---|
| Monedas de 0, 2 y 3 decimales | Importes liquidables aceptados; fracciones fuera de granularidad rechazadas |
| Conversión con redondeo | Monto destino representable y residuo explícito según la política |
| Misma moneda | Tasa 1, importes coherentes; no conversión inventada |
| Pago/devolución en otra moneda | Snapshots estables y asignaciones coherentes tras retry |
| Dos monedas en reporte | Totales separados o convertidos con criterio visible; nunca suma directa |
| Offline y tasa caducada | Decisión explícita y evidencia preservada, sin reinterpretación silenciosa |
| Locale sin traducción o traducción parcial | Fallback determinista por campo y contenido del tenant correcto |
| Español/inglés en una misma venta | Mismo ID, importe, estado y efectos; cambia presentación |
| Cambiar idioma con caché activa | No reutiliza contenido de otro locale ni otro tenant |
| Reimprimir documento histórico | Respeta idioma y versión emitidos, sin recalcular desde datos actuales |

La revisión 3 incorpora pruebas para granularidad, locales, conversiones de Tesorería y competencia de escrituras. El flujo completo de cobro/devolución, reportes, resolución de idioma y offline sigue pendiente. Consultar el alcance exacto en [REFINEMENT\_03.md](../history/REFINEMENT_03.md).
