GOOGLEFINANCE es la opción más corta para una hoja: sirve para el tipo actual y para una serie diaria con fechas. Apps Script te da más control cuando la hoja necesita llamar a una API.
- Usa GOOGLEFINANCE para el tipo actual o histórico en una celda.
- Las series históricas de GOOGLEFINANCE no se pueden leer desde Apps Script.
- La función FXRATE de Apps Script es un respaldo sencillo para obtener JSON.
- No pegues una clave en un proyecto de Apps Script compartido.
- Estos tipos son indicativos; conserva la hora y el origen cuando importen.
Consigue una clave gratuita
Regístrate en exchangerate.dev/signup. Tras confirmar el correo, el panel muestra una clave que empieza por exr_live_. Cópiala: la vas a pegar en Apps Script en un momento. El plan gratuito son 5,000 llamadas al mes, sin tarjeta.
Abre Apps Script desde tu hoja
Abre cualquier hoja de Google. En el menú, haz clic en Extensiones → Apps Script. Se abre una pestaña nueva con una función function myFunction() {} por defecto. Borra su contenido: lo vas a sustituir en el siguiente paso.
Pega la función FXRATE
Sustituye el contenido del editor por esta función. Llama a exchangerate.dev, guarda cada par en caché diez minutos con CacheService y devuelve un marcador legible para los dos errores que puedes encontrarte (401 y 429).
Usa =FXRATE() en cualquier celda
De vuelta en la hoja, escribe =FXRATE("USD","EUR") en una celda vacía. Al cabo de un segundo la celda muestra el tipo actual. La misma función vale para cualquier par soportado: cambia los dos códigos de moneda:
Por dentro, cada llamada devuelve la base, el par por el que has filtrado y dos campos de frescura, source y market_session, para que sepas siempre cómo de actual es el número:
exchangerate.dev publica un servidor MCP y un llms.txt, así que Claude, Cursor o ChatGPT pueden leer el esquema en vivo y escribirte la función. Pídeselo así: «crea una función personalizada de Google Sheets llamada FXRATE que reciba una moneda de origen y una de destino y devuelva el tipo en vivo de exchangerate.dev, con una caché de diez minutos». Ver la guía de MCP.
Actualización y encaje en el plan gratuito
Apps Script no vuelve a ejecutar las funciones personalizadas por temporizador. Para actualizaciones periódicas, añade un activador por tiempo: en el panel izquierdo de Apps Script, haz clic en Activadores → Añadir activador, elige FXRATE, origen Según tiempo, Temporizador por minutos. La caché de diez minutos más una cuota generosa dejan tranquilo hasta a un libro muy usado:
- 5,000 llamadas al mes, 10 peticiones por minuto, sin tarjeta
CacheServicedevuelve el tipo cacheado durante 10 minutos, así que editar repetidamente no cuesta nada- Sube
CACHE_TTLsi una hoja con muchos pares llega a rozar el tope por minuto - Cubre
/v1/latest,/v1/convert,/v1/rangey/v1/{date}/{base}
| Código | Síntoma | Solución |
|---|---|---|
| 401 | La celda muestra #AUTH_ERROR | La clave falta o no es válida. Vuelve a copiarla desde tu panel a API_KEY, arriba del todo en Code.gs. |
| 429 | La celda muestra #RATE_LIMIT | Has pasado el tope por minuto. La caché suele evitarlo; sube CACHE_TTL, o cambia de plan en /pricing. |
| — | La celda muestra #NO_PAIR | La moneda de destino no venía en la respuesta. Comprueba que el código sea una moneda ISO soportada (por ejemplo JPY, no JYP). |
| — | La celda se queda en Loading… | Apps Script necesita autorización para hacer peticiones externas. Ejecuta la función una vez desde la barra del editor para que salga el aviso, y acéptalo. |
Estos tipos se publican para modelos, análisis y visualización. No son una cotización en firme: no los uses para liquidar una operación ni una transferencia. Cada respuesta lo indica en su campo notice.
Empieza con GOOGLEFINANCE
Para una hoja de cálculo, prueba primero =GOOGLEFINANCE("CURRENCY:USDEUR"). Para una serie diaria, pasa una fecha inicial, una fecha final y la frecuencia DAILY. Google puede retrasar las cotizaciones y no ofrece el histórico a través de Sheets API o Apps Script.
=GOOGLEFINANCE("CURRENCY:USDEUR")=GOOGLEFINANCE("CURRENCY:USDEUR", "price", DATE(2026, 1, 1), DATE(2026, 1, 31), "DAILY")// google-sheets: Apps Script Code.gsconst CACHE_TTL = 600; // Cache successful responses for 10 minutes./** * Returns an indicative exchange rate for one currency pair. * @param {string} from Three-letter base currency code. * @param {string} to Three-letter quote currency code. * @return {number|string} The rate or a readable error marker. * @customfunction */function FXRATE(from, to) { from = (from || 'USD').toString().trim().toUpperCase(); to = (to || 'EUR').toString().trim().toUpperCase(); if (!/^[A-Z]{3}$/.test(from) || !/^[A-Z]{3}$/.test(to)) return '#NO_PAIR'; const cacheKey = `fx_${from}_${to}`; const cache = CacheService.getScriptCache(); const cached = cache.get(cacheKey); if (cached) return Number(cached); const url = `https://api.exchangerate.dev/v1/latest/${from}?symbols=${to}`; let resp; try { resp = UrlFetchApp.fetch(url, { muteHttpExceptions: true }); } catch { return '#FETCH_ERROR'; } const code = resp.getResponseCode(); if (code === 401 || code === 403) return '#ACCESS_ERROR'; if (code === 429) return '#RATE_LIMIT'; if (code !== 200) return '#ERROR'; let data; try { data = JSON.parse(resp.getContentText()); } catch { return '#INVALID_RESPONSE'; } const rate = data && data.rates && data.rates[to]; if (rate == null) return '#NO_PAIR'; if (typeof rate !== 'number' || !Number.isFinite(rate) || rate <= 0) return '#INVALID_RESPONSE'; cache.put(cacheKey, String(rate), CACHE_TTL); return Number(rate); // coerce so cells sum and sort}// Euro per US dollar=FXRATE("USD", "EUR") // 0.9245// Japanese yen per US dollar=FXRATE("USD", "JPY") // 157.32// British pound per euro=FXRATE("EUR", "GBP") // 0.8531// Indonesian rupiah per US dollar=FXRATE("USD", "IDR") // 16285.0{ "result": "success", "base": "USD", "source": "live", "market_session": "open", "timestamp": "2026-06-29T09:14:02Z", "data_updated_at": "2026-06-29T09:14:00Z", "rates": { "JPY": 157.32 }, "sources": { "JPY": "live" }, "notice": "Indicative rates, not for settlement."}=FXRATE("USD", "EUR")