Todas las guías sobre conversión de divisas en Power BI caen en uno de dos bandos. El bando DAX te enseña una medida elegante y da por hecho que ya existe una tabla ExchangeRate en tu modelo. El bando de las API te enseña un fragmento de Power Query que funciona de maravilla en Power BI Desktop y que falla en cuanto publicas, porque la actualización programada lo rechaza.
Esta guía cubre las dos mitades, y en orden: cómo traer tipos de cambio en vivo a Power BI desde una API REST de forma que sobreviva a la publicación en el servicio Power BI, cómo modelar esas tasas para que tus cifras sean defendibles, y cómo convertir importes en el momento de la importación o en el de la consulta, según lo que tu informe realmente necesite.
Todos los ejemplos de Power Query y DAX que verás abajo se escribieron contra las formas de respuesta documentadas de la API de Finexly.
Primero decide qué problema de conversión tienes
"Conversión de divisas en Power BI" son tres problemas de ingeniería distintos con el mismo nombre, y elegir el equivocado es el error más caro de todo este artículo.
- Muchas divisas de origen, una divisa de reporte. Tu tabla de ventas tiene filas en EUR, GBP y JPY y el director financiero quiere una sola cifra en USD. Convierte en la importación. La tasa es una propiedad de la transacción, no del informe.
- Una divisa de origen, muchas divisas de reporte. Todo se guarda en USD y el usuario elige la divisa de visualización en una segmentación. Convierte en la consulta, con DAX. Precalcular todas las divisas no es práctico.
- Muchas divisas de origen, muchas de reporte. Normaliza a una única divisa pivote en la importación y luego aplica el caso 2 encima. No intentes resolverlo en un solo paso.
La regla general que repiten los modeladores con experiencia, y que conviene repetir otra vez: aplica la conversión lo antes que puedas permitirte. Cada conversión que empujas al momento de la consulta te cuesta en cada objeto visual, en cada cambio de filtro y en cada clic de segmentación.
Traer tasas en vivo a Power Query de la forma correcta
Empieza por la tabla de tasas. La llamada más eficiente es una consulta multipar, para obtener todas las divisas que te interesan en una sola petición en lugar de una petición por divisa.
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://api.finexly.com/v1/convert?q=USD_EUR,USD_GBP,USD_JPY"{
"USD_EUR": { "rate": 0.9215 },
"USD_GBP": { "rate": 0.7892 }
}Este es el equivalente como consulta M de Power Query. Crea una consulta en blanco (Inicio → Nuevo origen → Consulta en blanco → Editor avanzado) y pega:
let
ApiKey = "YOUR_API_KEY",
Pairs = "USD_EUR,USD_GBP,USD_JPY,USD_CAD,USD_AUD,USD_CHF",
Source = Json.Document(
Web.Contents(
"https://api.finexly.com",
[
RelativePath = "v1/convert",
Query = [ q = Pairs ],
Headers = [ #"Authorization" = "Bearer " & ApiKey ]
]
)
),
ToTable = Record.ToTable(Source),
Expanded = Table.ExpandRecordColumn(ToTable, "Value", {"rate"}, {"Rate"}),
Split = Table.SplitColumn(
Expanded, "Name",
Splitter.SplitTextByDelimiter("_", QuoteStyle.None),
{"BaseCurrency", "Currency"}
),
Typed = Table.TransformColumnTypes(
Split,
{{"BaseCurrency", type text}, {"Currency", type text}, {"Rate", type number}}
),
Stamped = Table.AddColumn(Typed, "RetrievedAt", each DateTimeZone.UtcNow(), type datetimezone)
in
StampedLlámala FxRates. Obtienes una tabla de cuatro columnas — BaseCurrency, Currency, Rate, RetrievedAt — lista para usar en relaciones y medidas.
El error de Web.Contents que rompe la actualización programada
Fíjate en lo que la consulta anterior no hace: nunca concatena los parámetros dentro de la cadena de la URL. Esta es la razón más común de que un informe de conversión de divisas funcione en el escritorio y muera en la nube.
Si en su lugar escribes esto:
// Do NOT do this
Source = Json.Document(
Web.Contents("https://api.finexly.com/v1/convert?q=" & Pairs)
)…Power BI Desktop lo actualizará sin quejarse, y el servicio Power BI lo rechazará con "Este conjunto de datos incluye un origen de datos dinámico. La actualización no es compatible." El servicio necesita resolver una URL base estática en tiempo de análisis para poder asociarle credenciales. Pasar las partes variables por RelativePath y Query le da exactamente eso: la base sigue siendo https://api.finexly.com y todo lo dinámico vive en las opciones.
Lo mismo vale para cualquier consulta que construya una URL a partir de un parámetro, una fecha o un valor de otra tabla. Si te llevas una sola cosa de este artículo, que sea RelativePath.
Cómo manejar la clave de API sin escribirla a fuego
ApiKey = "YOUR_API_KEY" en línea está bien para una prueba de cinco minutos y mal para cualquier cosa compartida. El código M de un modelo semántico es visible para cualquiera con permiso de compilación sobre él.
Dos opciones que funcionan:
- Un parámetro de Power Query (Inicio → Administrar parámetros), referenciado como
ApiKey = KeyParam. Sigue almacenándose con el modelo, pero está centralizado, es fácil de rotar y puedes sobrescribirlo por entorno con canalizaciones de implementación. - El tipo de credencial Web API. En el servicio Power BI, ve a Configuración del modelo semántico → Credenciales del origen de datos → Editar credenciales y elige Web API, indicando ahí la clave. El servicio inyecta entonces la cabecera
Authorizationpor su cuenta y puedes eliminar por completo la opciónHeadersde tu M. Así el secreto queda fuera de la definición del modelo.
Elijas lo que elijas, usa una clave limitada a reporting y rótala cuando alguien deje el equipo. Si la clave acaba en la cadena de consulta de la URL en lugar de en una cabecera, puede aparecer en los registros de acceso de proxies y servidores: la documentación de Finexly lo advierte, y aplica a cualquier proveedor.
Construye una dimensión de divisa, no solo una lista de tasas
Una tabla de tasas por sí sola te da números. No te da etiquetas correctas, ni decimales correctos, ni una segmentación que ordene con sentido. Trae la lista de divisas como su propia dimensión:
let
ApiKey = "YOUR_API_KEY",
Source = Json.Document(
Web.Contents(
"https://api.finexly.com",
[
RelativePath = "v1/currencies",
Headers = [ #"Authorization" = "Bearer " & ApiKey ]
]
)
),
ToTable = Table.FromList(Source, Splitter.SplitByNothing(), {"Currency"}),
Typed = Table.TransformColumnTypes(ToTable, {{"Currency", type text}})
in
TypedDespués añade las dos columnas que Power BI no puede deducir por ti:
MinorUnits— el número de decimales que la divisa usa de verdad. El JPY tiene 0, el KWD tiene 3, la mayoría tiene 2. Formatear un total en yenes con dos decimales es un error de corrección visible en un informe financiero, y redondear en el paso equivocado lo agrava. La guía de redondeo de divisas explica dónde se cuela el error.FormatString— por ejemplo"\€#,0.00","\¥#,0". La necesitarás más adelante para las cadenas de formato dinámicas.
Ambas siguen el estándar ISO 4217, no algo que Power BI conozca de forma nativa; la referencia ISO 4217 tiene la tabla completa. Marca esta tabla como dimensión, relaciónala con FxRates[Currency] de uno a varios y úsala a ella —no la tabla de tasas— como origen de tu segmentación.
Vía A: convertir en la importación (rápido, aburrido, correcto)
Para el escenario 1 —muchas divisas dentro, una divisa fuera— haz el trabajo en Power Query y deja que el modelo guarde un único número limpio.
- Carga tu consulta de transacciones en Power Query.
- Inicio → Combinar consultas, uniendo
Transactions[Currency]conFxRates[Currency](externa izquierda). - Expande la columna combinada y conserva
Rate. - Agregar columna → Columna personalizada:
= if [Currency] = "USD" then [Amount]
else if [Rate] = null then null
else [Amount] / [Rate]Fíjate en la rama null. Una unión externa izquierda contra una tabla de tasas a la que le falta una divisa produce null, y null en la aritmética de Power Query produce null en silencio en lugar de un error: eso se convierte en un blanco en tu objeto visual y en un total discretamente demasiado bajo. Haz el hueco explícito para poder filtrarlo y verlo.
Fíjate también en la división. USD_EUR = 0.9215 significa que un USD compra 0,9215 EUR, así que convertir un importe en EUR a USD divide. Convertir un importe en USD a EUR multiplica. Invertir esto es el segundo error más común en informes multidivisa y, con tasas cercanas a 1,0, es casi invisible: un error del 3 % en una cifra EUR/USD parece una diferencia de redondeo hasta que alguien la concilia.
Vía B: convertir en la consulta con DAX
Para el escenario 2 —una divisa almacenada y una divisa de visualización elegida por el usuario— la conversión tiene que ocurrir en una medida.
La versión ingenua hace un LOOKUPVALUE por fila y es lenta en cuanto pasas de unos cientos de miles de filas. Agrega primero, convierte una vez:
Sales (Reporting Currency) =
VAR SelectedCurrency = SELECTEDVALUE ( Currency[Currency], "USD" )
VAR Rate =
CALCULATE (
SELECTEDVALUE ( FxRates[Rate] ),
FxRates[Currency] = SelectedCurrency
)
VAR Result =
IF (
SelectedCurrency = "USD",
[Sales Amount],
[Sales Amount] * Rate
)
RETURN
IF ( ISBLANK ( Rate ) && SelectedCurrency <> "USD", BLANK (), Result )Dos detalles que importan más de lo que parece:
SELECTEDVALUEcon valor por defecto. Sin el respaldo"USD", la medida devuelve blanco siempre que no haya divisa seleccionada, que es justo el estado en el que se carga tu informe.- La protección explícita de blanco. Si una divisa no tiene tasa, devuelve blanco a propósito en lugar de dejar que
[Sales Amount] * BLANK()devuelva cero. Un cero en una tarjeta de ingresos es una mentira; un blanco es un hueco visible.
Cuando la tasa varía en el tiempo
La medida anterior usa una única tasa actual para todo el conjunto de datos. Eso es correcto para "cuánto valdrían hoy los ingresos del año pasado" y equivocado para casi todo lo demás. Si tu tabla de tasas tiene una fila por divisa y día, agrupa por fecha antes de convertir:
Sales (Historical Rates) =
SUMX (
VALUES ( 'Date'[Date] ),
VAR DayRate =
CALCULATE (
SELECTEDVALUE ( FxRates[Rate] ),
FxRates[Currency] = SELECTEDVALUE ( Currency[Currency], "USD" )
)
RETURN
[Sales Amount] * DayRate
)Iterar sobre VALUES('Date'[Date]) en lugar de sobre la tabla de hechos mantiene pequeño el iterador: días, no transacciones.
Cadenas de formato dinámicas
Un número convertido con un prefijo $ fijo es peor que no tener símbolo. En Power BI, pon el Formato de la medida en Dinámico y proporciona una expresión:
SELECTEDVALUE ( Currency[FormatString], "#,0.00" )Ahora la tarjeta que muestra ¥ muestra ¥, con cero decimales y sin una segunda medida. Esto antes exigía grupos de cálculo en Analysis Services; las cadenas de formato dinámicas para medidas lo trajeron a Power BI propiamente dicho.
¿Qué tasa deberías estar usando en realidad?
Esta es la pregunta que separa un panel de un informe que finanzas firmará, y ninguna API puede responderla por ti.
- Tasa spot de la fecha de la transacción — para registrar transacciones individuales. Máxima fidelidad, tabla de tasas más grande.
- Media mensual o del periodo — el estándar para partidas de la cuenta de resultados tanto bajo la NIC 21 como bajo ASC 830. Suaviza la volatilidad intramensual y es lo que usan la mayoría de las consolidaciones.
- Tasa de cierre del periodo — para partidas del balance: caja, cuentas por cobrar, cuentas por pagar.
- Tasa presupuestaria o de plan — una tasa fija mantenida todo el año para que el análisis de desviaciones aísle el rendimiento operativo del movimiento cambiario.
Un modelo serio suele necesitar dos o tres de estas a la vez, como columnas separadas de la misma tabla de tasas (SpotRate, AverageRate, ClosingRate) y no como tablas distintas. Si tu informe alimenta algo que acaba en una declaración, la guía de tipos de cambio y declaración fiscal explica qué fuente y qué marca de tiempo debes poder defender, y la guía de tipos de cambio históricos cubre cómo obtener tasas con fecha en lugar de tasas en vivo.
El hueco del fin de semana que rompe los totales en silencio
Los mercados de divisas cierran. Una tabla de tasas diaria construida desde una API en vivo no tiene sábado, ni domingo, ni 25 de diciembre. Une una transacción fechada un sábado con esa tabla y obtienes null, y null se convierte en blanco, y el blanco se convierte en un total demasiado bajo justo por las ventas del fin de semana.
Arréglalo en la tabla de tasas, no en la medida. Genera una lista de fechas completa y rellena hacia abajo:
let
Dates = List.Dates(#date(2026,1,1), Duration.Days(Date.From(DateTime.LocalNow()) - #date(2026,1,1)) + 1, #duration(1,0,0,0)),
DateTable = Table.FromList(Dates, Splitter.SplitByNothing(), {"Date"}),
Typed = Table.TransformColumnTypes(DateTable, {{"Date", type date}}),
Joined = Table.NestedJoin(Typed, {"Date"}, RateHistory, {"Date"}, "r", JoinKind.LeftOuter),
Expanded = Table.ExpandTableColumn(Joined, "r", {"Currency", "Rate"}),
Filled = Table.FillDown(Expanded, {"Currency", "Rate"})
in
FilledTable.FillDown arrastra la tasa del viernes por todo el fin de semana, que es el tratamiento convencional y, lo que importa, un tratamiento declarado en lugar de accidental. Ordena por divisa y fecha antes de rellenar, o arrastrarás la tasa de la divisa equivocada por el hueco.
Si tu plan no incluye endpoints históricos, puedes construir el histórico hacia delante: añade las tasas de hoy a una tabla almacenada en cada actualización —un flujo de datos o una tabla de Fabric Lakehouse funcionan bien— y en un trimestre tendrás una serie temporal real. No es retroactivo, pero cuesta una llamada a la API al día.
Programación de actualizaciones y la aritmética de la cuota
Power BI Pro permite 8 actualizaciones programadas al día por modelo semántico; las capacidades Premium y Fabric permiten 48. Ese es el número que tu cuota de API tiene que cubrir, y la aritmética es más amable de lo que la gente supone.
La tabla de tasas anterior son dos llamadas por actualización: una a /v1/currencies y otra a /v1/convert. Así que:
| Cadencia de actualización | Actualizaciones/mes | Llamadas API/mes | Plan Finexly |
|---|---|---|---|
| 8/día (máximo Pro) | ~240 | ~480 | Gratis (1.000/mes) |
| 48/día (Fabric, cada 30 min) | ~1.440 | ~2.880 | Starter |
| 48/día + histórico horario en flujo de datos | ~2.160 | ~4.320 | Growth |
El techo de 10 por minuto es el que hay que vigilar. Si construyes una consulta que llama a /v1/rate una vez por divisa dentro de un Table.AddColumn, veinte divisas significan veinte llamadas en un par de segundos y una ráfaga de respuestas 429 a mitad de la actualización. Para eso existe precisamente la llamada multipar /v1/convert. Agrupa y cachea: la guía de caché y manejo de errores cubre patrones de reintento con retroceso que aplican igual a una actualización programada.
Puertas de enlace, Excel y Fabric
Unas cuantas notas de entorno que te ahorran una tarde cada una:
- No hace falta puerta de enlace. Una API REST en la nube no es un origen local, así que no necesitas una puerta de enlace de datos local para esto. Si la actualización falla y alguien sugiere instalar una, casi siempre es el error de origen de datos dinámico disfrazado.
- Excel usa el mismo motor. Power Query en Excel acepta exactamente el M de arriba. Si tu audiencia vive en libros de cálculo y no en paneles, la guía de tipos de cambio en vivo en Excel cubre
WEBSERVICE,LAMBDAy la matriz de versiones; también hay un equivalente para Google Sheets. - Dataflow Gen2 de Fabric es mejor sitio para la tabla de tasas en cuanto más de un informe la necesita. Aterriza las tasas una vez, deja que todos los modelos semánticos lean la misma tabla y tu uso de API deja de escalar con el número de informes.
- Compara con una cifra conocida antes de publicar. Saca un par del conversor de divisas y compáralo con lo que muestra tu modelo para el mismo par en el mismo instante. Si no coinciden, tienes un problema de dirección o de redondeo, y quieres encontrarlo ahora y no en una reunión de consejo.
Preguntas frecuentes
¿Puede Power BI convertir divisas sin una API? Puede, si tú aportas las tasas: una tabla mantenida a mano, una exportación del sistema financiero o una vista de base de datos. Power BI no tiene una fuente de tasas incorporada. La API importa cuando necesitas tasas que se actualicen sin que alguien se acuerde de actualizarlas.
¿Por qué mi informe de conversión se actualiza en Power BI Desktop pero falla en el servicio?
Casi siempre por el error de origen de datos dinámico. Tu llamada a Web.Contents construye la URL concatenando cadenas. Mueve las partes variables a las opciones RelativePath y Query para que la URL base sea estática, vuelve a publicar y reintroduce las credenciales.
¿Conviene convertir divisas en Power Query o en DAX? En Power Query cuando el informe tiene una sola divisa de reporte: es más rápido y más simple. En DAX cuando el usuario elige la divisa en tiempo de ejecución. Si necesitas ambas, normaliza a una divisa pivote en Power Query y monta la medida DAX encima.
¿Cuántas peticiones a la API consume una actualización de Power BI?
Dos por actualización si agrupas todos los pares en una única llamada a /v1/convert. Con el máximo de 8 actualizaciones diarias de Power BI Pro son unas 480 peticiones al mes, dentro de un plan gratuito. Solo se encarece si llamas a la API una vez por divisa o una vez por fila.
¿Cómo gestiono fines de semana y festivos en una tabla de tasas diaria? Genera una tabla de fechas continua, une las tasas por la izquierda, ordena por divisa y fecha y rellena hacia abajo. La tasa del viernes se arrastra por el fin de semana. Lo importante es que el tratamiento sea deliberado y esté documentado, no que las filas desaparezcan en silencio.
¿Qué tipo de cambio debo usar para reporting financiero? Tasas medias del periodo para partidas de la cuenta de resultados y tasas de cierre para partidas del balance, tanto bajo la NIC 21 como bajo ASC 830. Guárdalas como columnas separadas de una misma tabla de tasas para que un informe pueda alternar entre ellas sin cambiar el modelo.
¿Listo para poner tasas en vivo detrás de tus paneles? Consigue tu clave gratuita de la API de Finexly — sin tarjeta de crédito. Empieza con 1.000 peticiones al mes, suficientes para actualizar un área de trabajo Power BI Pro a su ritmo máximo, y sube de plan cuando necesites datos históricos o más frecuencia. Si todavía estás comparando proveedores, la página de comparación los pone lado a lado.
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →