Recetas
Integraciones completas con la API de Turbodato. Productos en rojo a Google Sheets, sincronizar sólo si hay datos nuevos, alertas de PVP y carga a un BI.
Cinco integraciones completas, con los detalles que se descubren tarde. Todas leen la key
de la variable de entorno TURBODATO_API_KEY y asumen un solo tablero; si tenés más, sumá
clientId a cada URL.
Los productos en rojo, todos los días en una planilla
Una hoja de Google Sheets que cada mañana se llena con los productos donde sos el más caro, con el precio de cada competidor. Se hace con Apps Script, sin servidor propio.
Guardá la key en el script
En la planilla, Extensiones → Apps Script. En Configuración del proyecto → Propiedades del
script, agregá TURBODATO_API_KEY con tu key. Así no queda escrita en el código.
Pegá la función
function productosEnRojo() {
const key = PropertiesService.getScriptProperties().getProperty("TURBODATO_API_KEY");
const url =
"https://api.turbodato.com/v1/export?format=json&semaphore=red" +
"&dataColumns=sku,marca,producto&priceColumns=precioFinal";
const res = UrlFetchApp.fetch(url, {
headers: { "X-API-Key": key },
muteHttpExceptions: true,
});
if (res.getResponseCode() !== 200) {
throw new Error(`Turbodato respondió ${res.getResponseCode()}: ${res.getContentText()}`);
}
const filas = JSON.parse(res.getContentText());
const hoja = SpreadsheetApp.getActive().getSheetByName("En rojo");
hoja.clear();
if (filas.length === 0) {
hoja.getRange(1, 1).setValue("Hoy no hay productos en rojo.");
return;
}
const columnas = Object.keys(filas[0]);
const valores = filas.map((fila) => columnas.map((c) => fila[c] ?? ""));
hoja.getRange(1, 1, 1, columnas.length).setValues([columnas]).setFontWeight("bold");
hoja.getRange(2, 1, valores.length, columnas.length).setValues(valores);
}Programalo
En Apps Script, Activadores → Agregar activador: función productosEnRojo, basado en tiempo,
diario. Elegí un horario en que el relevamiento del día ya haya terminado; si no sabés cuál es,
preguntale a tu contacto de Turbodato. Creá antes una hoja llamada "En rojo".
Cada fila es un producto, con una columna Sitio - Precio Final por sitio, el tuyo incluido. "Rojo" es que tu
precio es el más alto de la fila ese día y al menos un competidor está más barato. Si querés
también los amarillos, hacé una segunda llamada con semaphore=yellow.
El horario importa. Si el activador corre antes de que termine el relevamiento del día, vas a
traer el de ayer. Para confirmar, mirá lastMatchDate en /status.
Sincronizar cuando hay datos nuevos
El patrón para un proceso que corre seguido y no quiere bajar el catálogo entero cada vez: preguntar primero si algo cambió.
import json
import os
import pathlib
import requests
BASE = "https://api.turbodato.com/v1"
HEADERS = {"X-API-Key": os.environ["TURBODATO_API_KEY"]}
HUELLA = pathlib.Path(".turbodato_huella.json")
def get(path, **params):
res = requests.get(f"{BASE}{path}", params=params, headers=HEADERS, timeout=120)
res.raise_for_status()
return res.json()
# 1. La huella: tres números, barata de pedir.
huella = get("/status")
if HUELLA.exists() and json.loads(HUELLA.read_text()) == huella:
print("Sin novedades")
raise SystemExit(0)
# 2. Hay novedad: recorrer el catálogo en páginas de 200, sin las cuentas caras.
productos, page = [], 1
while True:
body = get("/data", page=page, limit=200, stats="false")
productos += body["anchors"]
if page >= body["pagination"]["totalPages"]:
break
page += 1
print(f"{len(productos)} productos del {body['referenceDate']}")
# ...guardalos donde los uses...
# 3. Recién al final, guardar la huella: si algo falló, la próxima corrida reintenta.
HUELLA.write_text(json.dumps(huella))Sondear más de una vez cada cinco minutos no trae nada nuevo: /status se recalcula a ese ritmo.
Igual bajá todo una vez por día
La huella no ve un precio que cambia sin mover el último día, la cantidad de ofertas ni la de productos. Una recarga completa diaria cubre ese hueco.
Quién está debajo de tu PVP hoy
Un solo request contesta si hay algo que reclamar y a quién, de la infracción más grave a la menos grave.
import os
import requests
res = requests.get(
"https://api.turbodato.com/v1/price-control",
params={"limit": 1000},
headers={"X-API-Key": os.environ["TURBODATO_API_KEY"]},
timeout=120,
)
res.raise_for_status()
r = res.json()
# Sólo las que rompen el PVP cargado, no las que se midieron contra tu propio precio.
contra_pvp = [b for b in r["breaches"] if b["referenceSource"] == "sheet"]
print(f"{r['date']}: {len(contra_pvp)} ofertas debajo del PVP")
for b in contra_pvp:
seller = b["seller"] or "sin vendedor"
pesos = f"$ {b['price']:,.0f}".replace(",", ".")
print(f" {b['deltaPercent']:>7.2f}% {b['site']} · {seller} · {b['product']} a {pesos}")2026-10-16: 3 ofertas debajo del PVP
-12.50% Mercado Libre · Electro del Sur · Smart TV Samsung 55" Crystal UHD 4K DU7000 a $ 787.499
-5.56% Mercado Libre · Hogar Total · Smart TV Samsung 55" Crystal UHD 4K DU7000 a $ 849.999
-1.10% Frávega · Frávega · Lavarropas Drean Next 11.14 Eco 11 kg a $ 1.087.899El filtro por referenceSource == "sheet" evita la confusión más común: un producto sin PVP
cargado se mide contra tu propio precio del día, y una oferta más barata que la tuya no es un
reclamo a nadie por el precio de la marca.
Se activa por tablero
/price-control necesita Control de PVP activo en el tablero; si no, responde 403. Pedile a tu
contacto de Turbodato que lo active.
Dónde estás más caro que el más barato
El semáforo dice quién está en rojo. Esta receta dice por cuánto: para cada producto, tu precio contra la oferta más barata de la competencia, ordenado de la brecha más grande a la más chica.
import os
import requests
BASE = "https://api.turbodato.com/v1"
HEADERS = {"X-API-Key": os.environ["TURBODATO_API_KEY"]}
# True si tu tablero compara por pack: ahí el semáforo hace la cuenta por unidad.
POR_PACK = False
def unitario(m):
if POR_PACK and m["quantity"]:
return m["realPrice"] / m["quantity"]
return m["realPrice"]
filas, page = [], 1
while True:
res = requests.get(f"{BASE}/data", headers=HEADERS, timeout=120,
params={"semaphore": "red", "limit": 200, "page": page, "stats": "false"})
res.raise_for_status()
body = res.json()
mio = body["mainSiteId"]
for p in body["anchors"]:
propias = [m for m in p["matches"] if m["siteId"] == mio and m["realPrice"]]
ajenas = [m for m in p["matches"] if m["siteId"] != mio and m["realPrice"]]
if not propias or not ajenas:
continue
yo = min(unitario(m) for m in propias)
rival = min(ajenas, key=unitario)
brecha = (yo - unitario(rival)) / unitario(rival) * 100
filas.append((brecha, p["name"], yo, unitario(rival), rival["siteName"], rival["sellerName"]))
if page >= body["pagination"]["totalPages"]:
break
page += 1
for brecha, nombre, yo, otro, sitio, seller in sorted(filas, reverse=True)[:20]:
print(f"+{brecha:5.1f}% {nombre}: vos {yo:,.0f}, {sitio} / {seller} {otro:,.0f}")Pedir sólo semaphore=red ya deja afuera los productos donde sos el más barato. Si querés ver
también los amarillos (hay competidores más baratos y más caros que vos a la vez), sacá ese
filtro: la cuenta de la brecha es la misma.
Carga diaria a un BI
Un archivo por día, con las columnas que tu modelo necesita y nada más.
#!/usr/bin/env bash
set -euo pipefail
BASE="https://api.turbodato.com/v1"
AUTH=(-H "X-API-Key: $TURBODATO_API_KEY")
# Confirmar que el relevamiento de hoy ya cerró antes de exportar.
hoy=$(date +%F)
ultimo=$(curl -sf "${AUTH[@]}" "$BASE/status" | jq -r .lastMatchDate)
if [ "$ultimo" != "$hoy" ]; then
echo "Todavía no hay datos de $hoy (último: $ultimo)" >&2
exit 1
fi
curl -sf "${AUTH[@]}" \
"$BASE/export?format=csv&date=$hoy\
&dataColumns=ean,sku,marca,producto,categoria\
&priceColumns=precioFinal,precioLista,seller,url" \
> "turbodato_$hoy.csv"Cada celda de precio es la oferta más barata de ese sitio ese día. Si necesitás todas las
ofertas, una fila por seller, armalas desde /data.
/export es la llamada más pesada de la API: programala una vez por día y no la pongas en un
loop. Si el script falla porque el día todavía no cerró, que tu programador de tareas lo
reintente en una hora.