31 jul 2025·8 min de lectura

Modelo de datos de precios multimoneda para impuestos y facturas

Aprende un modelo de datos multimoneda que gestiona tipos de cambio, redondeo, impuestos y la presentación localizada de facturas sin sorpresas.

Modelo de datos de precios multimoneda para impuestos y facturas

Qué suele fallar con las facturas multimoneda

Las facturas multimoneda fallan de formas aburridas y caras. Los números se ven bien en la interfaz, luego alguien exporta un PDF, contabilidad lo importa y los totales ya no coinciden con las líneas.

La causa raíz es simple: las matemáticas del dinero no son solo multiplicar por un tipo de cambio. Los impuestos, el redondeo y el momento exacto en que capturas la tasa afectan el resultado. Si tu modelo de precios no hace explícitas esas decisiones, diferentes partes del sistema "amablemente" recalcularán y producirán respuestas diferentes.

Deben concordar tres vistas, aunque muestren distintas monedas:

  • Vista del cliente: precios claros en la moneda del cliente, con totales que sumen.
  • Vista de contabilidad: importes base consistentes para reportes y conciliaciones.
  • Vista de auditoría: un rastro que muestre qué tasa y reglas de redondeo produjeron la factura.

Los desajustes suelen venir de decisiones pequeñas tomadas en distintos sitios. Un equipo redondea cada línea; otro redondea solo el total. Una página usa el tipo actual; otra usa el tipo de la fecha de factura. Algunos impuestos se aplican antes de descuentos, otros después. Algunos impuestos están incluidos en el precio; otros se añaden encima.

Un ejemplo concreto: vendes un artículo a 19.99 EUR, facturas en GBP y reportas en USD. Si conviertes por línea y redondeas a 2 decimales, puedes obtener un total de impuestos distinto al de sumar primero y convertir una sola vez. Ambos enfoques pueden ser razonables, pero solo uno puede ser tu regla.

El objetivo es cálculos predecibles y valores almacenados claros. Cada factura debe responder, sin adivinar: qué importes se ingresaron, en qué moneda se ingresaron, qué tasa se usó (y cuándo), qué se redondeó (y cómo) y qué reglas fiscales se aplicaron. Esa claridad mantiene los totales estables en UI, PDFs, exportaciones y auditorías.

Términos clave que hay que acordar antes de diseñar el esquema

Antes de dibujar tablas, aseguraos de que todos usan las mismas palabras. La mayoría de bugs multimoneda no son técnicos, son bugs de "nos referíamos a cosas distintas". Un esquema limpio empieza con definiciones que producto, finanzas e ingeniería acepten.

Términos de moneda que afectan a la base de datos

Para cada flujo de dinero, acordad tres monedas:

  • Transactional currency: la moneda que el cliente ve y acepta (lista de precios, carrito, visualización de la factura).
  • Settlement currency: la moneda en la que realmente te pagan (la que liquida el proveedor de pagos o el banco).
  • Reporting currency: la moneda usada en dashboards y resúmenes contables.

También definid las unidades menores. USD tiene 2 (céntimos), JPY 0, KWD 3. Esto importa porque almacenar "12.34" como número flotante deriva, mientras que almacenar un entero en unidades menores (como 1234 centavos) permanece exacto y hace el redondeo predecible.

Términos fiscales que cambian totales

Los impuestos necesitan el mismo nivel de acuerdo. Decidid si los precios son tax-inclusive (el precio mostrado ya incluye el impuesto) o tax-exclusive (el impuesto se añade encima). También elegid si el impuesto se calcula por línea (y luego se suma) o por factura (sumas primero y luego aplicas impuesto). Estas elecciones afectan el redondeo y pueden cambiar el importe final pagadero por unos pocos centavos.

Finalmente, decidid qué debe almacenarse frente a lo que puede derivarse:

  • Almacena lo que es legal y financieramente importante: precios acordados, tasas de impuesto aplicadas, totales finales redondeados y la moneda usada.
  • Deriva lo que puedas recomputar con seguridad: cadenas formateadas, conversiones solo para presentación y la mayoría de matemáticas intermedias.

Campos principales de dinero: qué almacenar y cómo

Empieza decidiendo qué números son hechos que almacenáis y cuáles son resultados que podéis recomputar. Mezclar ambos es la forma en que las facturas terminan mostrando un total en pantalla y otro diferente en las exportaciones.

Almacena dinero como enteros en unidades menores (como centavos) y siempre almacena el código de moneda junto a él. Un importe sin su moneda es dato incompleto. Los enteros también evitan pequeños errores de punto flotante que aparecen cuando sumas muchas líneas.

Un patrón práctico es mantener tanto las entradas crudas como los resultados calculados. Las entradas explican lo que el usuario ingresó. Los resultados explican lo que facturaste. Cuando alguien disputa una factura meses después, necesitas ambos.

Para las líneas de factura, un conjunto limpio y durable de campos se ve así:

  • unit_price_minor + unit_currency
  • quantity (y uom si hace falta)
  • line_subtotal_minor (antes de impuesto/descuento)
  • line_discount_minor
  • line_tax_minor (o desglosado por tipo de impuesto)
  • line_total_minor (importe final de la línea)

El redondeo no es solo un detalle de UI. Persiste el método y la precisión de redondeo usados para los cálculos, especialmente si soportas monedas con distintas unidades menores (JPY vs USD) o reglas de redondeo en efectivo. Un pequeño registro de "contexto de cálculo" puede capturar calc_precision, rounding_mode y si el redondeo ocurre por línea o solo sobre el total de la factura.

Manten el formateo de presentación separado de los valores almacenados. Los valores almacenados deben ser números y códigos; el formateo (símbolos de moneda, separadores, formato numérico localizado) pertenece a la capa de presentación. Por ejemplo, almacena 12345 + EUR, y deja que la UI decida si mostrar "€123.45" o "123,45 €".

Tipos de cambio: tablas, marcas temporales y rastro de auditoría

Trata los tipos de cambio como datos dependientes del tiempo con una fuente clara. "La tasa de hoy" no es algo que puedas recomputar de forma segura más adelante.

Una tabla de tipos de cambio práctica suele incluir:

  • base_currency (de la que se convierte, por ejemplo USD)
  • quote_currency (a la que se convierte, por ejemplo EUR)
  • rate (quote por 1 base, almacenado como decimal de alta precisión)
  • effective_at (timestamp para el que la tasa es válida)
  • source (proveedor) y source_ref (su ID o un hash del payload)

Esa información de fuente importa en auditorías. Si un cliente disputa un importe, puedes señalar exactamente de dónde vino el número.

Luego, elegid una regla para qué tasa usa una factura y mantenedla. Opciones comunes son la tasa en el momento del pedido, del envío o de la factura. La mejor elección depende del negocio. Lo importante es la consistencia y la documentación.

Sea cual sea la regla, almacenad la tasa exacta usada en la factura (y a menudo en cada línea de factura). No dependáis de buscarla de nuevo más tarde. Añadid campos como fx_rate, fx_rate_effective_at y fx_rate_source para que la factura pueda reproducirse exactamente.

Para tasas faltantes (fines de semana, festivos, caídas del proveedor), haced explícito el comportamiento de respaldo. Enfoques típicos: usar la tasa previa más reciente, bloquear la facturación hasta disponer de una tasa, o permitir una tasa manual con una bandera de aprobación.

Ejemplo: un pedido se realiza el sábado, se envía el lunes y se factura el lunes. Si vuestra regla es "momento de la factura" pero el proveedor no publica tasas de fin de semana, podríais usar la última tasa del viernes y registrar effective_at = Viernes 23:59, junto con un source_ref para trazabilidad.

Conversión de moneda y reglas de redondeo que deben mantenerse consistentes

Evita rehacer trabajo tras cambios
Genera código backend y de aplicación cuando cambien los requisitos, sin reescrituras desordenadas.
Comenzar

Los problemas de redondeo rara vez parecen bugs obvios. Aparecen como brechas de 1 céntimo entre el total de la factura y la suma de las líneas, o pequeñas diferencias de impuesto entre lo que muestras y lo que tu proveedor de pagos espera. Los buenos modelos convierten el redondeo en una regla que puedes explicar, no en una sorpresa que parchees después.

Decide exactamente dónde ocurre el redondeo

Elige los puntos donde permites redondeo y mantén el resto a mayor precisión. Puntos comunes de redondeo incluyen:

  • Extensión de la línea (cantidad x precio unitario, después de descuentos)
  • Cada importe de impuesto (por línea o por factura, según la jurisdicción)
  • El total final de la factura

Si no defines esos puntos, distintas partes del sistema redondearán cuando les convenga y los totales se desviarán.

Usa un único modo de redondeo, con excepciones claras para reglas fiscales

Elige un modo de redondeo (half-up o "bankers"/round-to-even) y aplícalo consistentemente. Half-up es más fácil de explicar a clientes. Bankers puede reducir sesgos en grandes volúmenes. Cualquiera puede funcionar, pero tu API, UI, exportaciones e informes contables deben usar el mismo modo.

Mantén mayor precisión durante la conversión y los pasos intermedios (por ejemplo, almacena tasas FX con muchas decimales) y redondea solo en los puntos que hayas elegido.

Los descuentos también necesitan una regla única: aplicar descuentos antes de impuestos (común para cupones) o después de impuestos (a veces requerido para tarifas específicas). Documentadlo y codificadlo una sola vez.

Algunas jurisdicciones requieren impuestos redondeados por línea, por impuesto o sobre el total de la factura. En lugar de endurecer casos puntuales por todo el código, almacenad una configuración de "política de redondeo" (por país/estado/régimen fiscal) y hace que los cálculos sigan esa política.

Una comprobación simple: si reconstruyes la misma factura mañana con las mismas tasas y política almacenadas, deberías obtener exactamente las mismas centésimas cada vez.

Campos fiscales: patrones para IVA, sales tax y múltiples impuestos

Congela correctamente los tipos de cambio
Almacena marcas temporales y fuentes de tipo de cambio, y reutilízalas en toda tu app.
Comenzar

Los impuestos se complican rápido porque dependen de dónde está el comprador, qué vendes y si los precios se muestran netos o brutos. Un modelo limpio mantiene los impuestos explícitos, no implícitos.

Haz que la base imponible sea inequívoca. Almacena si el precio que gravaste es neto (impuesto añadido encima) o bruto (impuesto incluido). Luego almacena tanto la tasa aplicada como el importe de impuesto calculado como instantánea, para que cambios posteriores en las reglas no reescriban la historia.

En cada línea de factura, un conjunto mínimo que siga claro años después:

  • tax_basis (NET o GROSS)
  • tax_rate (decimal, por ejemplo 0.20)
  • taxable_amount_minor (la base que realmente gravaste)
  • tax_amount_minor
  • tax_method (PER_LINE o ON_SUBTOTAL)

Si puede aplicarse más de un impuesto (por ejemplo IVA más un recargo municipal), añade una tabla de desglose como InvoiceLineTax con una fila por impuesto aplicado. Cada fila debe incluir tax_code, tax_rate, taxable_amount_minor, tax_amount_minor, moneda e indicios de jurisdicción usados en el cálculo (país, región y código postal cuando sea relevante).

Guarda una instantánea de los detalles de la regla aplicada en la factura o en la línea, como rule_version o un blob JSON de entradas decisorias (estado fiscal del cliente, inversión del sujeto pasivo, exenciones). Si las reglas de IVA cambian el año siguiente, las facturas antiguas deben seguir coincidiendo con lo que realmente cobraste.

Ejemplo: una suscripción SaaS vendida a un cliente en Alemania puede aplicar 19% de IVA sobre un precio NETO por línea, más un 1% local. Almacena los totales de línea tal como se facturaron y guarda una fila de desglose por cada impuesto para visualización y auditoría.

Cómo diseñar las tablas paso a paso

Se trata menos de matemáticas ingeniosas y más de congelar los hechos adecuados en el momento adecuado. El objetivo es que una factura pueda reabrirse meses después y seguir mostrando los mismos números.

Empieza decidiendo dónde vive la verdad para los precios de producto. Muchos equipos mantienen un precio en moneda base por producto y opcionalmente añaden sobrescrituras por mercado (por ejemplo, filas de precio separadas para USD y EUR). Sea lo que sea, házlo explícito en el esquema para no mezclar "precio de catálogo" con "precio convertido".

Una secuencia simple que mantiene las tablas entendibles:

  • Productos y precios: product_id, price_amount_minor, price_currency, effective_from (si los precios cambian con el tiempo).
  • Cabeceras de pedido y factura: document_currency, customer_locale, billing_country y timestamps (issued_at, tax_point_at).
  • Líneas: unit_price_amount_minor, quantity, discount_amount_minor, tax_amount_minor, line_total_amount_minor, y la moneda para cada campo monetario almacenado.
  • Instantánea de tipo de cambio: la tasa exacta usada (rate_value, rate_provider, rate_timestamp) referenciada desde el pedido o la factura.
  • Registros de desglose fiscal: una fila por impuesto (tax_type, rate_percent, taxable_base_minor, tax_amount_minor) más un flag de calculation_method.

No confiéis en recalcular más tarde. Cuando creas una factura, copia los precios unitarios finales, descuentos y totales a las líneas de factura, aunque vinieran de un pedido.

Para trazabilidad, añade un calculation_version (o calc_hash) en la factura y una pequeña tabla calculation_log que registre quién disparó una recalculación y por qué (por ejemplo, "tasa actualizada antes de emitir").

Visualización localizada de la factura sin romper los números

Añade una pantalla de auditoría
Construye una vista de auditoría que explique totales usando importes y políticas almacenadas.
Probar AppMaster

La localización debería cambiar cómo se ve una factura, no lo que significa. Haced todos los cálculos usando valores numéricos almacenados (unidades menores o decimales de precisión fija), luego aplicad el formateo de locale al final.

Guardad las opciones de presentación de la factura en la propia factura, no solo en el perfil del cliente. Los clientes cambian de país, contactos de facturación y preferencias con el tiempo. Una factura es una instantánea legal. Almacenad cosas como invoice_language, invoice_locale y flags de formateo (por ejemplo, si mostrar ceros finales) con el documento para que una reimpresión en seis meses coincida con el original.

Los símbolos de moneda son una preocupación de presentación. Algunas locales ponen el símbolo antes de la cantidad, otras después. Algunas requieren un espacio, otras no. Manejad la posición del símbolo, el espaciado, separadores decimales y agrupación de miles en el renderizado, según la locale de la factura y la moneda. No incorporéis símbolos en los campos numéricos almacenados y no parseéis cadenas formateadas de vuelta a números.

Si necesitáis reportes en una segunda moneda (a menudo una moneda doméstica como USD o EUR), mostradlo explícitamente como total secundario, no como reemplazo. La moneda del documento sigue siendo la fuente legal de la verdad.

Una configuración práctica para la salida de facturas:

  • Mostrad líneas y totales en la moneda del documento, usando el formateo de la invoice-locale.
  • Opcionalmente mostrad un total secundario de reporte etiquetado con la fuente y timestamp de la tasa.
  • Mostrad el desglose de impuestos como líneas separadas (base imponible, cada impuesto, impuesto total), no como una única cantidad mezclada.
  • Renderizad PDFs y emails desde los mismos totales almacenados para que los números no puedan desviarse.

Ejemplo: un cliente francés factura en CHF. La locale de la factura usa coma decimal y coloca la moneda después de la cantidad, pero los cálculos siguen usando importes CHF almacenados y totales de impuestos guardados. La salida formateada cambia; los números no.

Errores comunes y trampas a evitar

La forma más rápida de romper facturas multimoneda es tratar el dinero como un número normal. Los tipos de punto flotante para precios, impuestos y totales crean pequeños errores que luego aparecen como "desfase de $0.01". Almacena importes como enteros en unidades menores (centavos) o usa un tipo decimal fijo con escala clara, y úsalo consistentemente.

Otra trampa clásica es cambiar la historia por accidente. Si recomputas una factura antigua con el tipo de cambio de hoy o con reglas fiscales actualizadas, ya no tienes el documento que el cliente vio y pagó. Las facturas deben ser inmutables: una vez emitidas, almacena la tasa de cambio exacta, reglas de redondeo y método fiscal usados, y no recalcules los totales almacenados.

Mezclar monedas dentro de una sola línea también es un bug silencioso de esquema. Si el precio unitario está en EUR, el descuento en USD y el impuesto en GBP, no puedes explicar las cuentas después. Elige una moneda de documento para visualización y liquidación, y una moneda base para reporting interno (si hace falta). Cada importe almacenado debe tener una moneda explícita.

Los errores de redondeo suelen venir de redondear demasiado a menudo. Si redondeas en precio unitario, luego en total de línea, luego impuesto por línea y después subtotal, los totales pueden dejar de coincidir con la suma de las líneas.

Trampas comunes a vigilar:

  • Usar floats para dinero o tasas de cambio sin precisión fija
  • Volver a ejecutar conversiones para facturas antiguas en vez de usar tasas almacenadas
  • Permitir que una línea contenga importes en varias monedas
  • Redondear en muchos pasos en vez de en puntos claramente definidos
  • No almacenar la marca de tiempo de la tasa, modo de redondeo y método fiscal por documento

Ejemplo: creas una factura en CAD, conviertes un servicio cotizado en EUR y luego actualizas tu tabla de tasas. Si almacenaste solo el importe en EUR y lo conviertes al mostrarse, el total CAD cambiará la semana siguiente. Almacena el importe EUR, la tasa FX aplicada (y su hora) y los importes CAD finales usados en la factura.

Lista de comprobación rápida antes del lanzamiento

Elige tu vía de despliegue
Despliega en la nube o exporta el código fuente cuando necesites control total.
Probar AppMaster

Antes de declarar las facturas multimoneda "listas", haced una última pasada centrada en la consistencia. La mayoría de errores no son complejos. Vienen de desajustes entre lo que almacenas, lo que muestras y lo que sumas.

Usa esto como puerta de lanzamiento:

  • Cada factura tiene exactamente una document_currency en la cabecera, y cada total almacenado en la factura está en esa moneda.
  • Cada valor monetario que almacenas es un entero en unidades menores, incluyendo totales de línea, impuestos, descuentos y envío.
  • La factura almacena la tasa de cambio exacta usada (como decimal preciso), además del timestamp y la fuente de la tasa.
  • Las reglas de redondeo están documentadas e implementadas en un lugar compartido.
  • Si puede aplicarse más de un impuesto, almacenas un desglose por línea (y opcionalmente por jurisdicción), no solo un total fiscal en la cabecera.

Tras validar el esquema, verificad las matemáticas como lo haría un auditor. Los totales de factura deben igualar la suma de totales de línea almacenados y los importes fiscales almacenados. No recomputes totales desde valores mostrados o cadenas formateadas.

Una prueba práctica: elige una factura con al menos tres líneas, aplica un descuento e incluye dos impuestos en una línea. Luego imprímela en otra locale (separadores y símbolo de moneda diferentes) y confirma que los números almacenados no cambian.

Escenario de ejemplo: un pedido, tres monedas e impuestos

Mantén los impuestos explícitos
Crea registros de líneas y desgloses de impuestos que sigan siendo legibles meses después.
Probar AppMaster

Un cliente en EE. UU. recibe la factura en USD, tu proveedor europeo te cobra en EUR y tu equipo financiero reporta en GBP. Aquí es donde un modelo se mantiene tranquilo o se convierte en un montón de desfases de 1 céntimo.

Pedido: 3 unidades de un producto.

  • Precio al cliente: $19.99 por unidad (USD)
  • Descuento: 10% en la línea
  • Sales tax en EE. UU.: 8.25% (gravado después del descuento)
  • Coste del proveedor: EUR 12.40 por unidad (EUR)
  • Moneda de reporte: GBP

Recorrido: qué pasa y cuándo convertir

Elige un momento de conversión y mantenlo. En muchos sistemas de facturación, una opción segura es convertir en el momento de emisión de la factura y luego almacenar la tasa exacta usada.

Al crear la factura:

  1. Calcula el subtotal USD de la línea: 3 x 19.99 = 59.97 USD.
  2. Aplica el descuento: 59.97 x 10% = 5.997, redondeado a 6.00 USD.
  3. Neto de la línea: 59.97 - 6.00 = 53.97 USD.
  4. Impuesto: 53.97 x 8.25% = 4.452525, redondeado a 4.45 USD.
  5. Total: 53.97 + 4.45 = 58.42 USD.

El redondeo ocurre solo en puntos definidos (descuento, cada importe de impuesto, totales de línea). Almacena esos resultados redondeados y suma siempre los valores almacenados. Eso evita el clásico problema donde tu PDF muestra 58.42 pero una exportación recalcula 58.43.

Qué almacenar para poder reproducir la factura después

En la factura (y en las líneas), almacena el código de moneda (USD), importes en unidades menores (centavos), desglose fiscal por tipo de impuesto y los IDs de registro de tipos de cambio usados para convertir USD a GBP para reporting. Para el coste del proveedor, almacena el coste EUR y su propio registro de tasa si también conviertes costes a GBP.

El cliente ve una factura clara en USD (precios, descuento, impuesto, total). Finanzas exporta importes en USD más los equivalentes GBP congelados y las marcas temporales de tasas exactas, de modo que los números de fin de mes sigan coincidiendo aunque las tasas cambien mañana.

Siguientes pasos: implementar, probar y mantenerlo manejable

Escribid vuestro esquema mínimo como un contrato corto: qué importes se almacenan (original, convertido, impuesto), en qué moneda está cada importe, qué regla de redondeo aplica y qué timestamp bloquea una tasa para una factura. Mantenedlo aburrido y específico.

Antes de construir pantallas, construid pruebas. No probéis solo facturas normales. Añadid casos extremos que sean lo bastante pequeños para exponer ruido de redondeo y bastante grandes para exponer problemas de agregación.

Un conjunto de pruebas iniciales:

  • Precios unitarios minúsculos (como 0.01) multiplicados por grandes cantidades
  • Descuentos que generan decimales repetidos tras la conversión
  • Cambios de tipo de cambio entre la fecha del pedido y la fecha de la factura
  • Reglas fiscales mixtas (impuesto incluido vs impuesto excluido) en el mismo tipo de factura
  • Reembolsos y notas de crédito que deben coincidir con el redondeo original

Para acortar tickets de soporte, añade una vista de auditoría que explique cada número en una factura: importes almacenados, códigos de moneda, ID y timestamp de la tasa de cambio y el método de redondeo usado. Cuando alguien pregunte "¿por qué este total es diferente?", podrás responder desde los hechos almacenados.

Si estás construyendo una herramienta de facturación interna, una plataforma sin código como AppMaster (appmaster.io) puede ayudarte a mantener esto consistente poniendo el esquema en un lugar y la lógica de cálculo en un flujo reutilizable, de modo que las pantallas web y móviles no estén cada una haciendo su propia versión de las cuentas.

Finalmente, asignad responsabilidades. Decidid quién actualiza tipos de cambio, quién actualiza reglas fiscales y quién aprueba cambios que afectan facturas emitidas. La estabilidad es un proceso, no solo un esquema.

Fácil de empezar
Crea algo sorprendente

Experimente con AppMaster con plan gratuito.
Cuando esté listo, puede elegir la suscripción adecuada.

Empieza