Appearance
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
- 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_chkexigeabs(destination_amount - source_amount * applied_rate) <= 0.000001. Para un destino de dos decimales,1 × 1.2345redondeado a1.23deja diferencia0.0045y 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. - 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_exchangecomprueba que la referencia cubra el par, incluso invertido; eso por sí solo no prueba vigencia, origen aprobado o correspondencia conapplied_rate. Definir cómo autorizar y auditar una cotización negociada o una inversión de tasa. - 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.
- 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-Languagecontra 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
nameydescriptionpueden 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-Languagecuando 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.