TutorialesHojas de cálculo

Precios del oro en tiempo real en Google Sheets

Consulta precios actuales e históricos del oro en Google Sheets sin guardar una clave de API, compara una cotización de referencia y actualiza cada 4 horas.

8 sept 2026 · 9 min de lectura

=GOLDPRICE() devuelve en cualquier celda el precio actual XAU/USD por onza troy de goldprice.dev tras añadir Apps Script. Los precios gratuitos llevan 15 minutos de retraso. IMPORTDATA no lee este endpoint JSON: necesitas una función personalizada. GOLDHISTORY() devuelve cierres diarios de 30 días; GOLDPRICEDELTA(), diferencias de precio y porcentaje con signo frente a una referencia normalizada. No se guarda ninguna clave. Ejecuta installGoldRefresh() una vez para vincular la celda de actualización y crear un activador cada cuatro horas que respete la cuota.

Empieza sin guardar una clave de API en la hoja

El endpoint de precio al contado XAU y el historial diario de 30 días funcionan sin identificarte. Este tutorial no guarda credenciales en Apps Script. El acceso anónimo usa los límites de Free: 10 solicitudes por minuto, 100 por hora y 5.000 al mes por IP. Actualizar cada cuatro horas cabe en esa cuota.

Google puede compartir la IP de salida entre usuarios de Sheets, así que la capacidad varía. Para una cuota por cuenta, usa un proxy en tu servidor y guarda allí la clave. No pegues tokens bearer en scripts que otros editores puedan ver. Una clave gratuita asigna las mismas 5.000 llamadas mensuales a tu cuenta.

Obtén tu propia cuota de solicitudes

Los pasos siguientes funcionan sin clave. Una clave de API gratuita te da tu propia cuota. Para acceder a más historial y límites mayores, necesitas un plan de pago.

Añade las tres funciones de precios del oro a Apps Script

Abre tu hoja y selecciona Extensiones → Apps Script. Sustituye function myFunction() {} por el código siguiente. Incluye GOLDPRICE(metal, currency, refreshToken) para precios actuales, GOLDHISTORY(fromDate, toDate, refreshToken) para cierres diarios y GOLDPRICEDELTA(referencePrice, metal, currency, refreshToken) para diferencias de precio y porcentaje con signo. Usa CacheService para solicitudes idénticas, omite la cabecera Authorization y devuelve errores como texto sin lanzar excepciones.

Para reunir cantidades, costes de compra y valoraciones orientativas, usa la plantilla de seguimiento de una cartera de oro.

Code.gs
// google-sheets: Apps Script Code.gsconst SPOT_CACHE_TTL = 55; // Cache successful spot responses for 55 seconds.const BARS_CACHE_TTL = 300; // Cache successful daily bars for 5 minutes.const REFRESH_RANGE_NAME = 'GOLDPRICE_REFRESH_TOKEN';const SPREADSHEET_ID_KEY = 'GOLDPRICE_SPREADSHEET_ID';const INSTALLER_EMAIL_KEY = 'GOLDPRICE_INSTALLER_EMAIL';const LAST_REFRESH_AT_KEY = 'GOLDPRICE_LAST_REFRESH_AT';const MIN_REFRESH_INTERVAL_MS = 4 * 60 * 60 * 1000;/** * Returns the current spot price for a metal/currency pair. * @customfunction */function GOLDPRICE(metal, currency, refreshToken) {  const symbol = metal + '-' + currency + '-SPOT';  const cache = CacheService.getScriptCache();  const cacheKey = 'spot_' + symbol;  const cached = cache.get(cacheKey);  if (cached) return cached;  const url = `https://api.goldprice.dev/v1/spot/${symbol}`;  const resp = UrlFetchApp.fetch(url, {    muteHttpExceptions: true,  });  const code = resp.getResponseCode();  if (code === 401) return '#ACCESS_ERROR: use a server-side proxy';  if (code === 403) return '#PLAN_GATED: symbol not on your tier';  if (code === 429) return '#RATE_LIMIT: wait a minute and retry';  if (code !== 200) return '#ERROR_' + code;  const data = JSON.parse(resp.getContentText());  if (!data || !data.price) return '#NO_DATA';  // Price stays a string, same as the API wire format: Sheets displays and  // sorts it fine. Wrap in VALUE() if you need it in arithmetic.  cache.put(cacheKey, data.price, SPOT_CACHE_TTL);  return data.price;}/** * Compares live spot with a reference cell in the same currency and unit. * Spills [signed price delta, signed percentage delta] into two cells. * @customfunction */function GOLDPRICEDELTA(referencePrice, metal, currency, refreshToken) {  const reference = Number(referencePrice);  if (!Number.isFinite(reference) || reference <= 0) {    return [['#BAD_REFERENCE: enter a positive price', '']];  }  const liveResult = GOLDPRICE(metal, currency, refreshToken);  const live = Number(liveResult);  if (!Number.isFinite(live)) return [[liveResult, '']];  const signedDelta = live - reference;  return [[Number(signedDelta.toFixed(2)), signedDelta / reference]];}/** * Returns chart-ready daily closes for a date range. * @customfunction */function GOLDHISTORY(fromDate, toDate, refreshToken) {  const now = new Date();  const rollingFrom = new Date(now.getTime() - 29 * 24 * 60 * 60 * 1000);  const from = toIsoDate_(    fromDate === '' || fromDate == null ? rollingFrom : fromDate  );  const to = toIsoDate_(toDate === '' || toDate == null ? now : toDate);  if (!from || !to) return [['#BAD_DATE: put real dates in B2/B3']];  const cache = CacheService.getScriptCache();  const cacheKey = 'bars_' + from + '_' + to;  const cached = cache.get(cacheKey);  if (cached) return JSON.parse(cached);  const url = 'https://api.goldprice.dev/v1/bars?symbol=XAU-USD-SPOT&interval=1d'    + '&from=' + from + '&to=' + to;  const resp = UrlFetchApp.fetch(url, {    muteHttpExceptions: true,  });  const code = resp.getResponseCode();  if (code === 401) return [['#ACCESS_ERROR: use a server-side proxy']];  if (code === 403) return [['#PLAN_GATED: range not on your tier']];  if (code === 429) return [['#RATE_LIMIT: wait a minute and retry']];  if (code !== 200) return [['#ERROR_' + code]];  const data = JSON.parse(resp.getContentText());  const bars = Array.isArray(data.bars) ? data.bars : [];  if (bars.length === 0) return [['#NO_DATA: empty range']];  // /v1/bars returns bars newest-first; reverse to chronological order so  // a line chart built from this range reads left-to-right in time.  const rows = bars.slice().reverse().map(function (bar) {    return [bar.bar_start.slice(0, 10), Number(bar.close)];  });  cache.put(cacheKey, JSON.stringify(rows), BARS_CACHE_TTL);  return rows;}function installGoldRefresh() {  const lock = LockService.getScriptLock();  lock.waitLock(30000);  try {    const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();    if (!spreadsheet) throw new Error('Open the bound Google Sheet and run again');    const installerEmail = Session.getEffectiveUser().getEmail();    if (!installerEmail) throw new Error('Run the installer from your Google account');    const properties = PropertiesService.getScriptProperties();    const existingInstaller = properties.getProperty(INSTALLER_EMAIL_KEY);    if (existingInstaller && existingInstaller !== installerEmail) {      throw new Error('Gold refresh is already owned by another editor');    }    const refreshRange = spreadsheet.getActiveSheet().getRange('B1');    spreadsheet.setNamedRange(REFRESH_RANGE_NAME, refreshRange);    // getProjectTriggers() only returns this user's triggers. The shared    // installer-email claim above prevents a second editor from creating a    // second schedule, while this replacement keeps owner reruns idempotent.    ScriptApp.getProjectTriggers()      .filter(function (trigger) {        return trigger.getHandlerFunction() === 'refreshGoldData';      })      .forEach(function (trigger) {        ScriptApp.deleteTrigger(trigger);      });    ScriptApp.newTrigger('refreshGoldData')      .timeBased()      .everyHours(4)      .create();    properties.setProperties({      [SPREADSHEET_ID_KEY]: spreadsheet.getId(),      [INSTALLER_EMAIL_KEY]: installerEmail,    });  } finally {    lock.releaseLock();  }  refreshGoldData();}function uninstallGoldRefresh() {  const lock = LockService.getScriptLock();  lock.waitLock(30000);  try {    const properties = PropertiesService.getScriptProperties();    const installerEmail = Session.getEffectiveUser().getEmail();    const existingInstaller = properties.getProperty(INSTALLER_EMAIL_KEY);    if (existingInstaller && existingInstaller !== installerEmail) {      throw new Error('Only the trigger owner can uninstall gold refresh');    }    ScriptApp.getProjectTriggers()      .filter(function (trigger) {        return trigger.getHandlerFunction() === 'refreshGoldData';      })      .forEach(function (trigger) {        ScriptApp.deleteTrigger(trigger);      });    properties.deleteProperty(SPREADSHEET_ID_KEY);    properties.deleteProperty(INSTALLER_EMAIL_KEY);    properties.deleteProperty(LAST_REFRESH_AT_KEY);  } finally {    lock.releaseLock();  }}function refreshGoldData(event) {  const lock = LockService.getScriptLock();  lock.waitLock(30000);  try {    // Clock triggers pass an event object, but they cannot supply the metal,    // currency, date-range, or reference arguments required by sheet formulas.    // This wrapper only changes the named token cell those formulas reference.    const properties = PropertiesService.getScriptProperties();    const spreadsheetId = properties.getProperty(SPREADSHEET_ID_KEY);    if (!spreadsheetId) throw new Error('Run installGoldRefresh first');    // Script properties and the script lock are shared across editors. This    // gate coalesces legacy triggers owned by different users into one refresh    // per four hours even though Apps Script cannot enumerate their triggers.    const now = Date.now();    const lastRefreshAt = Number(properties.getProperty(LAST_REFRESH_AT_KEY));    if (lastRefreshAt && now - lastRefreshAt < MIN_REFRESH_INTERVAL_MS) return;    const spreadsheet = SpreadsheetApp.openById(spreadsheetId);    const refreshRange = spreadsheet.getRangeByName(REFRESH_RANGE_NAME);    if (!refreshRange) throw new Error('Run installGoldRefresh to restore B1');    refreshRange.setValue(new Date(now).toISOString());    properties.setProperty(LAST_REFRESH_AT_KEY, String(now));  } finally {    lock.releaseLock();  }}function toIsoDate_(value) {  const d = value instanceof Date ? value : new Date(value);  if (isNaN(d.getTime())) return null;  return d.toISOString().slice(0, 10);}

Configura las celdas de precio, historial y referencia

Pon un valor de actualización en B1 y, opcionalmente, una referencia en USD por onza troy en C1. Deja B2 y B3 vacías para consultar los últimos 30 días naturales o introduce fechas fijas. GOLDPRICEDELTA() devuelve diferencias de precio actual menos referencia y porcentajes con signo. Ambas cotizaciones deben compartir moneda y unidad.

GOOGLEFINANCE puede llevar hasta 20 minutos de retraso, no admite todos los futuros ni permite consultar históricos desde Apps Script. Normaliza tu referencia a USD por onza troy en C1; goldprice.dev aporta precios actuales e históricos. No uses NOW() ni TODAY() como argumentos: GOLDHISTORY() calcula las fechas cuando B2/B3 están vacías.

Fórmulas de Sheets · conexión B1/B2/B3/C1
// B1: token de actualización que escribe refreshGoldData()// B2/B3: deja en blanco para los últimos 30 días naturales o introduce fechas fijas// C1: cotización de referencia opcional normalizada a USD por onza troy// Precio spot actual en USD: se recalcula cuando cambia B1=GOLDPRICE("XAU", "USD", $B$1)// Spot en vivo menos C1: devuelve [diferencia en USD con signo, diferencia porcentual con signo]// Aplica el formato Porcentaje a la segunda celda del resultado.=GOLDPRICEDELTA($C$1, "XAU", "USD", $B$1)// Precio spot actual en EUR: ambos usan la misma celda del token=GOLDPRICE("XAU", "EUR", $B$1)// 30 días de cierres diarios para gráficos (fecha, cierre)=GOLDHISTORY($B$2, $B$3, $B$1)

Crea un gráfico de líneas con el historial

Selecciona las filas que devuelve GOLDHISTORY(), abre Insertar → Gráfico y elige Gráfico de líneas. Las filas dependen del periodo solicitado y los datos diarios disponibles. La función ordena los registros de la API cronológicamente, así que el eje horizontal va del más antiguo al más reciente sin ordenar nada más.

Añade un activador cada 4 horas

Elige una cuenta de Google para gestionar la programación. Quienes siguieran el tutorial anterior deben borrar su activador refreshGoldData en Apps Script → Activadores. Ejecuta installGoldRefresh y autorízalo. Vincula B1 y crea un activador cada cuatro horas. Un bloqueo y una marca temporal compartidos limitan los activadores antiguos inaccesibles a una actualización cada cuatro horas.

Para cambiar responsable, ejecuta uninstallGoldRefresh y deja que el nuevo instale. Si el anterior no está disponible, borra las tres entradas GOLDPRICE_* en Configuración del proyecto → Propiedades del script. El control compartido evita duplicados. Cuatro fórmulas × seis actualizaciones: máximo 24 llamadas/día, unas 720/mes, dentro de Free: 100 solicitudes/hora y 5.000/mes por IP.

Resultado esperado

JSON
{  "symbol": "XAU",  "quote_currency": "USD",  "unit": "troy_ounce",  "contract_type": "spot",  "price": "4012.75",  "is_stale": false,  "computed_at": "2026-10-23T07:00:18Z"}

GOLDPRICE muestra el campo price anterior como texto, por ejemplo, "4012.75". GOLDPRICEDELTA distribuye las diferencias de precio y porcentaje decimal con signo en dos celdas; aplica formato de porcentaje a la segunda. GOLDHISTORY devuelve filas [date, close] disponibles para el periodo solicitado.

Errores comunes

CódigoQué vesSolución
401La celda muestra #ACCESS_ERROR: use a server-side proxyLa solicitud anónima no se ha aceptado. Revisa primero Apps Script → Ejecuciones. Si necesitas capacidad autenticada, envía las solicitudes mediante un proxy en tu servidor; no pegues una clave en el script vinculado.
403La celda muestra #PLAN_GATED: symbol not on your tierFree incluye precios al contado de oro (XAU), plata (XAG) y cobre (HG). Comprueba el símbolo; no se ofrecen platino ni paladio. Consulta /pricing.
429La celda muestra #RATE_LIMIT: wait a minute and retryLa IP compartida alcanzó un límite de Free: 10 solicitudes/minuto, 100/hora o 5.000/mes. La caché suele evitarlo; aumenta las constantes TTL, reduce las fórmulas distintas o envía solicitudes autenticadas mediante un proxy en tu servidor.
N/AGOLDHISTORY muestra #BAD_DATE: put real dates in B2/B3B2 o B3 contiene texto que Apps Script no reconoce como fecha, por ejemplo, una etiqueta. Introduce fechas válidas o referencia celdas con formato de fecha mediante Formato → Número → Fecha.
N/AGOLDPRICEDELTA muestra #BAD_REFERENCE: enter a positive priceC1 está vacía o contiene texto, cero o un valor negativo. Introduce un precio de referencia positivo con la misma moneda y unidad por onza troy que la fórmula del precio actual.
N/ALa celda muestra Loading… indefinidamenteApps Script necesita autorización para las solicitudes externas. Selecciona una función en la barra del editor y ejecútala manualmente para abrir la solicitud de autorización. Después, acepta.

Más opciones

  • Crea una fórmula de alerta de precio: =IF(GOLDPRICE("XAU","USD",$B$1) > 4800, "ALERT", "OK")
  • Para consultar históricos diarios más antiguos, usa un proxy en tu servidor con clave: Basic incluye 1 año; Plus y Pro, todo el historial
  • Sigue la plata junto al oro con Free y usa la misma celda de actualización para ambos
  • Usa las filas de GOLDHISTORY para probar una estrategia con datos históricos: consulta cómo probar una estrategia de oro con datos OHLC
  • Referencia =GOLDPRICE(...) en un modelo de valoración o costes para actualizarlo con datos actuales

Preguntas

¿Con qué frecuencia se actualiza el precio?

El endpoint al contado se actualiza cada 60 segundos. Free y el acceso sin clave llevan 15 minutos de retraso; los planes de pago reciben precios en tiempo real. La caché de GOLDPRICE respeta ese ritmo: durante su TTL, devuelve el mismo valor sin consumir otra llamada.

¿Actualizar cada 4 horas cabe en el plan gratuito?

Sí. $B$1 actualiza cuatro fórmulas: dos GOLDPRICE, una GOLDPRICEDELTA y una GOLDHISTORY. Cada 4 horas son 6 actualizaciones × 4 llamadas = 24 llamadas/día, unas 720/mes. GOLDPRICEDELTA reutiliza la consulta USD en caché, reduciendo llamadas. Los límites anónimos son 10 solicitudes/minuto, 100/hora y 5.000/mes por IP.

¿Por qué no usar GOOGLEFINANCE para el precio del oro?

GOOGLEFINANCE no documenta una API al contado XAU/USD. Las cotizaciones pueden retrasarse hasta 20 minutos; no admite todos los futuros ni históricos desde Apps Script. Usa GOLDPRICE() para precios actuales, GOLDHISTORY() para históricos y GOLDPRICEDELTA() para comparar referencias normalizadas, sin asumir que ambos instrumentos equivalen.

¿Por qué el activador ejecuta refreshGoldData y no GOLDPRICE?

Apps Script pasa un objeto de evento de reloj al controlador, pero no los argumentos de metal, moneda, fechas o referencia. Por eso installGoldRefresh() crea el activador para refreshGoldData(event), que escribe una marca temporal en el rango B1 vinculado y recalcula las cuatro fórmulas.

¿Puedo compartir esta hoja con otros editores?

Sí. Este ejemplo usa acceso anónimo y no guarda ninguna clave de API. Los editores pueden revisar o copiar el script sin recibir credenciales. Si necesitas capacidad autenticada, guarda el token bearer en un proxy en tu servidor y conecta la hoja a ese servicio.

¿Puedo usarlo con fines comerciales?

Todos los planes permiten el uso comercial. Los planes de pago no exigen un enlace de atribución; Free requiere un enlace visible a goldprice.dev junto a los precios.

¿Qué metales se admiten?

Todos los planes, incluido Free, ofrecen precios al contado de oro (XAU), plata (XAG) y cobre (HG). Ningún plan ofrece platino ni paladio. Consulta los detalles en la referencia de la API.

¿El plan gratuito es realmente gratis?

Sí. El precio XAU y el historial diario de 30 días funcionan sin clave: 10 solicitudes/minuto, 100/hora y 5.000/mes por IP. Una clave gratuita ofrece la misma cuota mensual por cuenta, sin tarjeta ni caducidad, para integraciones en tu servidor.

¿Puede un asistente como Claude escribir esto por mí?

Sí. Conecta Claude, Cursor o ChatGPT al servidor MCP de goldprice.dev o su llms.txt y pide: Crea funciones personalizadas de Google Sheets con api.goldprice.dev y un instalador idempotente que vincule una celda de actualización con nombre y cree un activador cada cuatro horas. El esquema actualizado permite usar los campos y errores correctos desde el principio.

¿Apps Script funciona con Excel?

No. Excel utiliza Power Query M para esta conexión. Sigue el tutorial de Excel para configurar la consulta y actualizar los precios del oro en tu libro.

Sigue leyendo

Empieza con una clave API gratis

5.000 solicitudes al mes en el plan gratuito. Sin tarjeta de crédito.

Obtén una clave gratis