Skip to content

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

NecesidadEvidencia en el baselineQué significa
Catálogo de monedasreference.currencies, reference.assert_currency_granularity en 0010Código, unidad menor y validación de importes representables
Valores predeterminadosorganization.organizations.default_currency/default_locale en 0020Preferencias iniciales del negocio; no sustituyen la moneda de un documento
Precios y ventaspricing.price_books.currency_code en 0090; sales.orders.currency_code en 0110Lista y orden tienen moneda explícita; ventas valida coincidencia con precios utilizados
Cobro en otra monedapayments.payment_intents/payment_transactions en 0130Moneda e importe pagados, moneda e importe aplicados a la cuenta, tasa y fuente congeladas
Tipos de cambiotreasury.fx_rate_sources/fx_rates/fx_exchanges en 0250Fuentes por organización, par, tasa, vigencia y conversión registrada
Moneda funcionalaccounting.ledgers.functional_currency_code y journal_lines en 0290Moneda del ledger, importes de transacción y funcionales, snapshot de tasa/fuente
Catálogo de idiomas/localesreference.locales en 0010Identificador de idioma/región referenciado por otros módulos
Contenido traducibleTablas catalog.*_translations, pricing.discount_translations, commerce.storefront_translationsTraducciones de contenido de negocio con pertenencia al tenant
Preferencia del clientecrm.customer_profiles.preferred_locale_code en 0200Preferencia de comunicación del cliente; verificar integración del caso de uso
DocumentosVersiones de plantilla e instancias con locale_code en 0310El 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ónRazón
Moneda de venta, moneda de pago y moneda funcional independientesUn 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 lecturaCambiar un dashboard a USD no debe reescribir ventas en COP ni sumarlas directamente
Listas de precio por moneda, con política de conversión opcionalConvertir el precio del día no siempre coincide con la estrategia comercial del negocio
Idioma de interfaz separado del contenido y del documentoUn 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 separadosElegir español no define país, moneda ni fecha de cierre de caja
Política de fallback explícitaUna 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. 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

CasoResultado esperado
Monedas de 0, 2 y 3 decimalesImportes liquidables aceptados; fracciones fuera de granularidad rechazadas
Conversión con redondeoMonto destino representable y residuo explícito según la política
Misma monedaTasa 1, importes coherentes; no conversión inventada
Pago/devolución en otra monedaSnapshots estables y asignaciones coherentes tras retry
Dos monedas en reporteTotales separados o convertidos con criterio visible; nunca suma directa
Offline y tasa caducadaDecisión explícita y evidencia preservada, sin reinterpretación silenciosa
Locale sin traducción o traducción parcialFallback determinista por campo y contenido del tenant correcto
Español/inglés en una misma ventaMismo ID, importe, estado y efectos; cambia presentación
Cambiar idioma con caché activaNo reutiliza contenido de otro locale ni otro tenant
Reimprimir documento históricoRespeta 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.

Application Foundation in progress. Tracked in issue #13.