Cómo obtener tipos de cambio en tiempo real en Google Sheets (GOOGLEFINANCE, Apps Script y una API de divisas)
Obtener tipos de cambio en tiempo real en Google Sheets parece un problema resuelto. Escribes =GOOGLEFINANCE("CURRENCY:USDEUR"), aparece un número y sigues con lo tuyo. Eso funciona perfectamente para el presupuesto de un viaje personal. Deja de funcionar en el momento en que la hoja de cálculo alimenta una factura, una nómina, un informe de ingresos o cualquier cosa sobre la que un compañero vaya a tomar decisiones: por una restricción de licencia, una peculiaridad documentada con las fechas y una limitación de Apps Script que casi ningún tutorial menciona.
Esta guía cubre los tres métodos prácticos —la función integrada GOOGLEFINANCE, una función personalizada de Apps Script respaldada por una API de divisas y una tabla de tipos programada que se actualiza mediante un activador temporal— con las cifras reales de las cuotas, la redacción exacta de las advertencias de la propia Google y código ejecutable. Toda afirmación sobre el comportamiento de Google que aparece más abajo está citada de la documentación oficial de Google, y toda afirmación que no está documentada se señala como tal.
Las tres formas de traer tipos de cambio a Google Sheets
GOOGLEFINANCE | Función personalizada de Apps Script | Tabla de tipos programada | |
|---|---|---|---|
| Tiempo de configuración | Segundos | ~10 minutos | ~20 minutos |
| ¿Requiere clave de API? | No | Sí | Sí |
| Actualización | Automática, al recalcular | Al recalcular | Con un activador que tú controlas |
| Tipos históricos | Sí (con matices) | Depende de tu plan | Sí |
| Funciona en Apps Script / API de Sheets | Históricos: no | Sí | Sí |
| Uso comercial / profesional | Restringido por los términos de Google | Regido por los términos de tu proveedor de API | Regido por los términos de tu proveedor de API |
| Ideal para | Consultas rápidas, hojas personales | Columnas de conversión puntuales | Cualquier cosa de la que dependa otra persona |
Método 1: GOOGLEFINANCE, y lo que Google dice exactamente sobre él
La fórmula básica de divisas
El patrón que enseñan todos los tutoriales es un ticker CURRENCY: formado por dos códigos de divisa ISO 4217 concatenados:
=GOOGLEFINANCE("CURRENCY:USDEUR")Para convertir un importe de la celda A2:
=A2 * GOOGLEFINANCE("CURRENCY:USDEUR")Para construir el par a partir de dos celdas que contienen códigos de divisa:
=GOOGLEFINANCE("CURRENCY:" & B2 & C2)Una advertencia honesta que ningún artículo de la competencia hace: la forma de ticker CURRENCY:XXXYYY no aparece en ninguna parte de la documentación oficial de GOOGLEFINANCE. La firma documentada es GOOGLEFINANCE(ticker, [attribute], [start_date], [end_date|num_days], [interval]), y el único contenido sobre divisas de la página de ayuda es un atributo "currency" (la moneda en la que cotiza un valor) y un ejemplo de gráfico. La sintaxis de pares CURRENCY: funciona y lleva años funcionando, pero es un comportamiento establecido por la comunidad, no un contrato documentado, lo que significa que Google puede cambiarlo sin previo aviso de obsolescencia. Google tampoco publica ninguna lista de pares de divisas admitidos, así que cualquier artículo que te diga que admite "unas 50 divisas" está adivinando.
Tipos históricos: la regla de atributos que todo el mundo se salta
Pasa una fecha y obtendrás datos históricos:
=GOOGLEFINANCE("CURRENCY:USDEUR", "close", DATE(2026,1,15))=GOOGLEFINANCE("CURRENCY:USDEUR", "close", DATE(2026,1,1), DATE(2026,1,31), "DAILY")Fíjate en el atributo. Google lo documenta con claridad: "Si se especifica algún parámetro de fecha, la solicitud se considera histórica y solo se permiten los atributos históricos". Los atributos históricos son open, close, high, low, volume y all, y "price" no está entre ellos. "price" está documentado únicamente como atributo en tiempo real ("Cotización de precio en tiempo real, con un retraso de hasta 20 minutos").
Ve ahora mismo a mirar los tutoriales de divisas para Google Sheets mejor posicionados: la mayoría te ofrecen GOOGLEFINANCE("CURRENCY:USDEUR", "price", DATE(...)). Esa combinación contradice la propia documentación de Google. Usa "close".
Dos comportamientos documentados más que conviene interiorizar:
- "Si se especifica
start_datepero noend_date|num_days, solo se devuelven los datos de ese único día". - "Los resultados en tiempo real se devuelven como un valor dentro de una sola celda. Los datos históricos, incluso los de un solo día, se devuelven como una matriz expandida con encabezados de columna". Por eso una fórmula histórica que parece correcta derrama dos columnas y una fila de encabezado en tu pulcro diseño, y por eso normalmente necesitas envolverla en
INDEX(...,2,2).
El desplazamiento del mediodía UTC
Este corrompe informes emparejados por fecha de forma silenciosa. Textualmente, según Google:
"Google trata las fechas que se pasan a GOOGLEFINANCE como las 12:00 del mediodía en horario UTC. Los mercados que cierran antes de esa hora pueden aparecer desplazados un día".
Si estás conciliando un libro mayor en el que cada línea debe usar el tipo correspondiente a esa fecha de asiento, un desplazamiento de un día es una ruptura real de la conciliación, no un problema de redondeo. Si eso describe tu hoja, lee nuestra guía sobre tipos de cambio y declaración fiscal antes de fiarte de una matriz derramada de GOOGLEFINANCE como registro de auditoría.
El retraso es de 3 minutos, no de 20
Casi todos los artículos sobre este tema te dicen que los tipos de cambio de GOOGLEFINANCE tienen "un retraso de hasta 20 minutos". Ese número de 20 minutos procede del descargo de responsabilidad genérico sobre cotizaciones de valores del atributo "price". La propia página de descargo de responsabilidad de Google Finance publica una tabla de retrasos por tipo de activo, y la fila de Divisas —global, suministrada por Morningstar— indica 3 minutos. Las criptomonedas también tienen 3 minutos.
Así que los datos son más frescos de lo que cree internet. Lo que Google no publica es con qué frecuencia Sheets recalcula la fórmula, que es justo el dato que de verdad te importa: un tipo de hace 3 minutos alojado en una celda que no se recalcula desde el martes sigue siendo un tipo del martes. Quien cite un intervalo de recálculo concreto está citando algo que Google nunca ha documentado.
Como comparación, Finexly actualiza los tipos cada minuto y los devuelve bajo demanda, así que la frescura depende de cuándo llamas tú, no de cuándo decide recalcular la hoja de cálculo. Si quieres entender qué significa "en tiempo real" entre proveedores en general, consulta de dónde obtienen sus datos las API de tipos de cambio.
La restricción de uso que nadie cita
Este es el párrafo más importante de este artículo, y no aparece en ninguno de los tutoriales mejor posicionados. De la página de ayuda de GOOGLEFINANCE de Google, textualmente:
"Restricciones de uso: Los datos no están destinados al uso profesional del sector financiero ni al uso por parte de otros profesionales de empresas no financieras (incluidas entidades gubernamentales). El uso profesional puede estar sujeto a tarifas de licencia adicionales de un proveedor de datos externo".
Y del descargo de responsabilidad de Google Finance:
"Aceptas no copiar, modificar, reformatear, descargar, almacenar, reproducir, reprocesar, transmitir ni redistribuir ningún dato o información que se encuentre aquí, ni utilizar dichos datos o información en una empresa comercial sin obtener consentimiento previo por escrito".
"Google no puede garantizar la exactitud de los tipos de cambio mostrados. Deberías confirmar los tipos vigentes antes de realizar cualquier transacción que pueda verse afectada por variaciones en los tipos de cambio".
Contrasta eso con lo que hace realmente tu hoja. Fijar el precio de una factura a un cliente, convertir facturas de proveedores para el equipo contable o exportar una cifra de ingresos convertida a un informe que envías a un cliente no encaja de forma evidente en "fines informativos". Una API de tipos de cambio con licencia elimina la duda por completo y eso —no la precisión en bruto— es el verdadero motivo por el que los equipos financieros abandonan GOOGLEFINANCE.
Y el bloqueo insalvable
"Los datos históricos no se pueden descargar ni consultar a través de la API de Sheets ni de Apps Script. Si lo intentas, verás un error #N/A en lugar de los valores en las celdas correspondientes de tu hoja de cálculo".
En cuanto quieras automatizar algo —una exportación nocturna, una extracción con la API de Sheets, un script que capture instantáneas de tipos—, los datos históricos de GOOGLEFINANCE quedan descartados por diseño. Ese es el muro que empuja a la gente hacia el método 2.
Método 2: una función personalizada de Apps Script respaldada por una API de divisas
Una función personalizada es una función de JavaScript en Apps Script que invocas desde una celda como cualquier función integrada. Google confirma explícitamente que esto funciona con peticiones web: las funciones personalizadas "solo pueden llamar a servicios que no tengan acceso a datos personales" y URL Fetch está en la lista de servicios permitidos.
Paso 1: consigue una clave de API y guárdala como es debido
Consigue una clave gratuita en el panel de Finexly: 1.000 solicitudes al mes, sin tarjeta de crédito. Y luego, esto importa: no la escribas directamente en el código. Dos de los tutoriales de divisas para Google Sheets más enlazados pegan la clave de API tal cual en el script, en un archivo que viaja con la hoja de cálculo a cualquiera con quien la compartas.
En el editor de Apps Script (Extensiones → Apps Script), ejecuta esto una sola vez desde el editor para guardar la clave en las propiedades del script:
function storeApiKey() {
PropertiesService.getScriptProperties()
.setProperty('FINEXLY_API_KEY', 'YOUR_API_KEY');
}Elimina el literal del archivo después. Ten en cuenta los límites documentados: el valor de una propiedad está limitado a 9 KB, y un almacén de propiedades a 500 KB en total; de sobra para una clave, pero no es un sitio para cachear datos.
Paso 2: una función personalizada para un solo par
/**
* Returns the live exchange rate for a currency pair.
*
* @param {string} from Base currency code, e.g. "USD".
* @param {string} to Quote currency code, e.g. "EUR".
* @return The exchange rate.
* @customfunction
*/
function FX_RATE(from, to) {
if (!from || !to) throw new Error('Both currency codes are required.');
var pair = String(from).toUpperCase() + '_' + String(to).toUpperCase();
var cache = CacheService.getScriptCache();
var hit = cache.get(pair);
if (hit !== null) return Number(hit);
var key = PropertiesService.getScriptProperties().getProperty('FINEXLY_API_KEY');
var res = UrlFetchApp.fetch(
'https://api.finexly.com/v1/rate?from=' + encodeURIComponent(from) +
'&to=' + encodeURIComponent(to),
{
headers: { Authorization: 'Bearer ' + key },
muteHttpExceptions: true
}
);
var code = res.getResponseCode();
var body = JSON.parse(res.getContentText());
if (code !== 200) {
throw new Error(body.error ? body.error.code + ': ' + body.error.message : 'HTTP ' + code);
}
cache.put(pair, String(body.rate), 300); // 5 minutes
return body.rate;
}Úsala en una celda:
=FX_RATE("USD","EUR")
=A2 * FX_RATE($B$1, $C$1)El endpoint /v1/rate devuelve {"pair": "USD_EUR", "rate": 0.9215}. La referencia completa de parámetros está en la documentación de la API de Finexly.
Paso 3: procesa el rango por lotes, la parte que todos los competidores pasan por alto
Aquí está el modo de fallo. Google lo documenta directamente:
"Cada vez que se usa una función personalizada en una hoja de cálculo, Sheets realiza una llamada independiente al servidor de Apps Script. Si tu hoja de cálculo contiene decenas (¡o cientos, o miles!) de llamadas a funciones personalizadas, este proceso puede ser lento".
Arrastra FX_RATE 400 filas hacia abajo y habrás hecho 400 idas y vueltas. Con un límite de 30 segundos por ejecución en las funciones personalizadas, te toparás con #ERROR! y la nota Exceeded maximum execution time (line 0). mucho antes de agotar tu cuota de API.
La solución que recomienda Google es el procesamiento por lotes con matrices: acepta un rango, devuelve una matriz. El endpoint /v1/convert de Finexly admite pares separados por comas, así que toda la columna se convierte en una sola petición HTTP:
/**
* Converts a column of amounts from one currency to another in a single API call.
*
* @param {A2:A400} amounts Range of amounts.
* @param {string} from Base currency code.
* @param {string} to Quote currency code.
* @return {Array} Converted amounts.
* @customfunction
*/
function FX_CONVERT_RANGE(amounts, from, to) {
var rate = FX_RATE(from, to); // one fetch, then cached
var rows = Array.isArray(amounts) ? amounts : [[amounts]];
return rows.map(function (row) {
return row.map(function (v) {
return (v === '' || v === null) ? '' : Number(v) * rate;
});
});
}=FX_CONVERT_RANGE(A2:A400, "USD", "EUR")Una fórmula, una llamada de red, 399 idas y vueltas menos. Si necesitas varios pares a la vez, llama a /v1/convert?q=USD_EUR,USD_GBP,USD_JPY y lee body["USD_EUR"].rate.
Lo que Google dice realmente sobre la caché en las funciones personalizadas
Arriba se usa CacheService, pero seamos precisos sobre el motivo. La guía de funciones personalizadas de Google califica el servicio Cache como "Funciona, pero no resulta especialmente útil en las funciones personalizadas". La ventaja del cacheo solo es real entre ejecuciones distintas —recálculos repetidos del mismo par—, no dentro de una única matriz derramada. El procesamiento por lotes con matrices es la optimización documentada; el cacheo es un complemento útil.
Los límites documentados de la caché: claves de hasta 250 caracteres, valores de hasta 100 KB, un tope de 1.000 elementos y una caducidad de entre 1 segundo y 21.600 segundos (6 horas), con 600 segundos por defecto. No hay ninguna cuota diaria publicada para las llamadas a la caché.
Otra trampa documentada más, porque el apaño circula ampliamente: añadir NOW() como argumento para forzar una actualización rompe la función. Google: "Los argumentos de las funciones personalizadas deben ser deterministas… Si una función personalizada intenta devolver un valor basado en una de estas funciones integradas volátiles, mostrará Loading... indefinidamente".
Método 3: una tabla de tipos programada (el patrón de producción)
Las funciones personalizadas se recalculan cuando a Sheets le apetece, que es exactamente la propiedad equivocada para una hoja que lee otra persona. El patrón robusto consiste en dejar de hacer peticiones desde las celdas por completo: escribe los tipos en una tabla siguiendo una programación y luego consúltalos con fórmulas normales.
El script de actualización
var PAIRS = ['USD_EUR', 'USD_GBP', 'USD_JPY', 'USD_CAD', 'USD_AUD', 'USD_CHF'];
function refreshRates() {
var key = PropertiesService.getScriptProperties().getProperty('FINEXLY_API_KEY');
var res = UrlFetchApp.fetch(
'https://api.finexly.com/v1/convert?q=' + PAIRS.join(','),
{ headers: { Authorization: 'Bearer ' + key }, muteHttpExceptions: true }
);
if (res.getResponseCode() !== 200) {
console.error('Finexly refresh failed: ' + res.getContentText());
return; // keep yesterday's rates rather than blanking the sheet
}
var data = JSON.parse(res.getContentText());
var stamp = new Date();
var rows = PAIRS.map(function (p) {
return [p, p.split('_')[0], p.split('_')[1], data[p].rate, stamp];
});
var sheet = SpreadsheetApp.getActive().getSheetByName('Rates') ||
SpreadsheetApp.getActive().insertSheet('Rates');
sheet.clear();
sheet.getRange(1, 1, 1, 5)
.setValues([['Pair', 'From', 'To', 'Rate', 'Updated (UTC)']])
.setFontWeight('bold');
sheet.getRange(2, 1, rows.length, 5).setValues(rows);
}Dos detalles que diferencian esto de los scripts que encontrarás en otros sitios. Primero, una sola llamada a setValues(), no una por celda: las escrituras de rango por lotes son muchísimo más rápidas. Segundo, una petición fallida sale de la función en lugar de vaciar la tabla, de modo que un tropiezo del proveedor deja tipos antiguos pero etiquetados como tales, en vez de una hoja llena de blancos que convierte en silencio todos los totales posteriores en cero.
Crear el activador
function installTrigger() {
ScriptApp.getProjectTriggers().forEach(function (t) {
if (t.getHandlerFunction() === 'refreshRates') ScriptApp.deleteTrigger(t);
});
ScriptApp.newTrigger('refreshRates').timeBased().everyHours(1).create();
}Google documenta que los activadores temporales se ejecutan "con una frecuencia de hasta cada minuto o tan espaciada como una vez al mes", y que la hora de disparo se difumina deliberadamente: "si creas un activador recurrente a las 9:00, Apps Script elige una hora entre las 9:00 y las 10:00". Si usas everyMinutes(n), n debe ser 1, 5, 10, 15 o 30; no se acepta ningún otro valor.
Leer los tipos
=XLOOKUP("USD_EUR", Rates!A:A, Rates!D:D)
=A2 * XLOOKUP($B$1 & "_" & $C$1, Rates!A:A, Rates!D:D)Ahora todas las conversiones del libro se resuelven al instante desde celdas locales, usan un mismo tipo coherente y llevan una marca de tiempo visible. Ese último punto es el que preguntan los auditores.
Cuotas y límites con los que realmente te vas a topar
Estas son las cifras publicadas por Google para Apps Script; conviene conocerlas antes de diseñar en torno a ellas:
| Límite | Cuenta de consumidor (gmail.com) | Cuenta de Google Workspace |
|---|---|---|
| Llamadas de URL Fetch | 20.000 / día | 100.000 / día |
| Tiempo total de ejecución de activadores | 90 min / día | 6 h / día |
| Lecturas/escrituras de propiedades | 50.000 / día | 500.000 / día |
| Tiempo de ejecución de funciones personalizadas | 30 s / ejecución | 30 s / ejecución |
| Tiempo de ejecución del script | 6 min / ejecución | 6 min / ejecución |
| Activadores por usuario y script | 20 | 20 |
| Longitud de URL en URL Fetch | 2 KB / llamada | 2 KB / llamada |
?q=: bastante más de cien, pero no ilimitados.Del lado de la API, el plan gratuito de Finexly permite 1.000 solicitudes al mes a 10 solicitudes por minuto. Un activador horario consume alrededor de 730 solicitudes al mes, dentro del nivel gratuito y con margen de sobra. Añade un puñado de llamadas puntuales a funciones personalizadas y puede que te interese el plan Starter; los niveles actuales están en la página de precios. Ten en cuenta que los tipos históricos requieren un plan de pago, así que si necesitas tipos con fecha retroactiva y presupuesto cero, GOOGLEFINANCE sigue siendo la respuesta honesta para uso personal y no profesional.
Todas las respuestas incluyen las cabeceras X-RateLimit-Limit, X-RateLimit-Used y X-RateLimit-Units, así que puedes registrar el consumo desde res.getAllHeaders() y ver venir un límite.
Errores habituales y cómo solucionarlos
| Lo que ves | Causa | Solución |
|---|---|---|
#N/A de GOOGLEFINANCE | Par no admitido o mal escrito, o datos históricos solicitados a través de Apps Script / la API de Sheets | Comprueba ambos códigos ISO; para automatizar, usa una API en su lugar |
#ERROR! con "Exceeded maximum execution time (line 0)." | Demasiadas llamadas individuales a funciones personalizadas | Cambia al patrón por lotes FX_CONVERT_RANGE |
Loading... para siempre | NOW(), RAND() u otra función volátil pasada como argumento de una función personalizada | Quítala: Google documenta que no está admitida |
401 UNAUTHORIZED / invalid token | Clave de API ausente o incorrecta | Confirma que la propiedad del script está definida y que la cabecera dice Bearer <key> |
429 RATE_LIMIT_EXCEEDED | Se ha alcanzado el tope por minuto o mensual | Alarga el TTL de la caché, agrupa más pares por llamada o amplía el plan |
You do not have permission to call X service. | Una función personalizada llamó a un servicio que requiere autorización | Traslada esa lógica a una función ejecutada por un activador |
| La fórmula histórica derrama columnas de más | Comportamiento documentado: los resultados históricos se devuelven como matriz con encabezados | Envuélvela en INDEX(..., 2, 2) |
¿Qué método deberías usar?
- Hoja personal, consulta rápida, sin dinero en juego:
GOOGLEFINANCE. Es gratis, instantáneo y la restricción de licencia no te afecta. - Una columna de conversión en una hoja de cálculo de trabajo: la función personalizada de Apps Script con procesamiento por lotes. Fuente de datos predecible, sin ambigüedad de licencia, una llamada por rango.
- Cualquier cosa que lea un compañero, un cliente o un auditor: la tabla de tipos programada. Un tipo por ciclo de actualización, una marca de tiempo visible y ninguna dependencia de cuándo decida recalcular Sheets.
¿Trabajas en Excel en su lugar? Los métodos equivalentes —Power Query, WEBSERVICE y el tipo de datos Monedas— se cubren en cómo obtener tipos de cambio en tiempo real en Excel. Si todavía estás evaluando proveedores, nuestra comparativa de API de divisas expone las diferencias, y puedes contrastar cualquier tipo con el conversor de divisas de Finexly.
Preguntas frecuentes
¿Cómo obtengo tipos de cambio en tiempo real en Google Sheets gratis?
=GOOGLEFINANCE("CURRENCY:USDEUR") no cuesta nada y no requiere configuración. Ten presente la restricción de uso que declara Google —los datos "no están destinados al uso profesional del sector financiero ni al uso por parte de otros profesionales de empresas no financieras"— y que los valores históricos no se pueden leer mediante Apps Script ni la API de Sheets. Como alternativa con licencia y coste cero, un nivel gratuito de API de 1.000 solicitudes al mes cubre holgadamente una actualización horaria (unas 730 llamadas).
¿Con qué frecuencia actualiza GOOGLEFINANCE los tipos de cambio?
La tabla del descargo de responsabilidad de Google Finance indica un retraso de 3 minutos para los datos de divisas, suministrados por Morningstar; no los 20 minutos que suelen repetirse en internet, que son la cifra genérica de cotización de valores asociada al atributo "price". Por otro lado, Google nunca ha publicado con qué frecuencia recalcula Sheets la fórmula, así que la antigüedad del número de tu celda no es algo en lo que puedas confiar.
¿Puedo usar GOOGLEFINANCE dentro de Apps Script?
Para datos históricos, no. Google afirma: "Los datos históricos no se pueden descargar ni consultar a través de la API de Sheets ni de Apps Script. Si lo intentas, verás un error #N/A". Tampoco existe ningún método GOOGLEFINANCE en ningún servicio de Apps Script: un script solo puede leer un valor que una celda ya haya calculado. Si necesitas los tipos en código, llama a una API de divisas con UrlFetchApp.
¿Por qué mi fórmula de divisas en Google Sheets va lenta o muestra #ERROR!?
Cada llamada a una función personalizada es una ida y vuelta independiente al servidor de Apps Script, y cada ejecución está limitada a 30 segundos. Cientos de llamadas individuales acabarán en tiempo de espera agotado. Acepta un rango y devuelve una matriz para que una sola llamada cubra toda la columna, cachea el tipo y, para cualquier cosa programada, mueve la petición a un activador temporal en lugar de a una celda.
¿Puedo obtener tipos de cambio históricos en Google Sheets?
Sí, de dos formas. GOOGLEFINANCE("CURRENCY:USDEUR", "close", DATE(2026,1,1), DATE(2026,1,31), "DAILY") devuelve una serie diaria: usa "close", ya que "price" no es un atributo histórico válido, y recuerda que Google trata las fechas como mediodía UTC, lo que puede desplazar un valor un día. O bien extrae tipos históricos de una API con un plan de pago y escríbelos en una hoja mediante un activador, que es la única vía que sobrevive a la automatización.
¿Listo para poner tipos de cambio con licencia y actualizados al minuto en tu hoja de cálculo? Consigue tu clave gratuita de la API de Finexly: sin tarjeta de crédito. Empieza con 1.000 solicitudes al mes en más de 170 divisas y amplía cuando tus hojas se te queden pequeñas.
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 →