Fotos por LUGAR en el panel — Brave Image Search

Fecha: 2026-09-19 · Proyecto: ~/nef/estado_turismo Script: spab-retrieval/scripts/retrieval-imagenes-lugares.mjs Datos: spab-retrieval/data/base/imagenes-lugares.json (clave = nombre normalizado)

Por qué Brave y no Google/SerpAPI

  • La Places API (New) también da places.photos, pero es SKU de pago por llamada y requiere proxy para no exponer la llave.
  • SerpAPI habría que contratarlo y las imágenes son de Google (licencia difusa).
  • Brave Image Search ya tiene llave (BRAVE_SEARCH_API_KEY), devuelve thumbnail.src proxeado (embebible), properties.url, page_url y source (dominio) → atribución verificable. Free tier ~1 req/s, 2000/mes.

Pipeline

  1. Objetivos = todos los lugares de lugares.csv + los 35 nombres curados de Ruta Maya (extraídos de tren-maya-api.js), deduplicados por nombre normalizado (minúsculas, sin acentos, espacios colapsados).
  2. Una consulta Brave por lugar: "<nombre> <destino>" (o Quintana Roo, México para los curados), count=6, safesearch=strict.
  3. Se prefieren dominios de licencia libre/oficial (Wikimedia, Unsplash, Pexels, gob.mx, inah.gob.mx, UNESCO).
  4. Se guardan hasta 3 por lugar: {thumb, url, page, source, titulo}.
  5. Pausa de ~1.1 s entre llamadas + backoff ante 429.

Consumo

  • 825 lugares, 2475 imágenes. Se corren por muestra para no quemar cuota: node scripts/retrieval-imagenes-lugares.mjs 20.
  • El script hace merge con el JSON existente, así se puede avanzar por lotes.

Integración en el front

  • Rutas generadas (Pacífico, Colonial, Sur, Sophia): generar-rutas.mjs inyecta fotos en detail() y thumb en index(), leyendo el JSON por nombre normalizado.
  • Ruta Maya: tren-maya-api.js carga imagenes-lugares.json, adjunta las fotos en detail() y el thumb en index(); si no hay, cae al tinte de categoría (placeholder).
  • El panel (places-panel.js) pinta photos: [{url, caption, tint}] en una rejilla de 3 con el dominio como caption.

Dónde se muestran las imágenes (tres superficies)

  1. Hero del detalle (.pp-hero): #fillT2 toma la primera foto con url y la pone de fondo; clase has-photo + velo inferior para que la etiqueta de categoría se lea. Sin foto → tinte.
  2. Rejilla de fotos (.pp-photos): hasta 3 fotos del lugar, caption = dominio.
  3. Miniatura de la lista (.pp-swatch): #buildList pinta p.thumb (de index()); clase has-photo. Sin thumb → tinte.

Contrato de datos: PlaceIndex.thumb?: string (primera foto) y PlaceDetail.photos: {url, caption, tint}[].

Fixes de paso en el generador (afectaban a las 4 rutas)

  • cards() no devolvía tint/vol/summary → c.tint[0] tronaba.
  • index() devolvía coords:{lat,lng} en vez de lng/lat planos → Invalid LngLat object: (NaN, NaN).
  • detail() no devolvía desc/hours/hist/tags (forma que espera el panel).

Caveat de licencia

Brave devuelve imágenes de terceros (blogs, OTAs, wikis). No todas son de licencia libre: por eso cada foto muestra su dominio de origen y se prefieren dominios libres/oficiales. Si se requiere 100% libre, usar Commons (menor cobertura para comercios) — ver 2026-09-19-imagenes-libres-commons.md.