Embeds y marca blanca
Cualquier periódico, ONG o micrositio gubernamental puede llevar una cifra real y citada de Futuros — el valor más un chip de fuente que enlaza en profundidad a la fuente primaria — con un único <iframe>. El contrato de honestidad viaja con el embed: un embed nunca muestra un número sin su procedencia.
Tres rutas de widget más un builder:
| Ruta | Renderiza |
|---|---|
/embed/stat | Una cifra citada — valor grande + unidad, chip de delta con signo, chip de fuente, wordmark |
/embed/chart | El mismo indicador como un gráfico de líneas pequeño (con el último punto etiquetado) |
/embed/board | Una cuadrícula responsiva de varios paneles stat/chart — un tablero completo en un solo iframe |
/incrustar | La UI del builder en ES — elige opciones, previsualiza en vivo, copia el snippet |
Todas las rutas de embed son enmarcables cross-origin: vercel.json fija Content-Security-Policy: frame-ancestors * en /embed/(.*) (el resto del sitio lleva X-Frame-Options: SAMEORIGIN). El chrome de la app (navegación, onboarding) se desactiva bajo /embed, así que el widget pinta únicamente su propio contenido.
/embed/stat y /embed/chart
Parámetros de query compartidos:
| Parámetro | Valores | Por defecto | Notas |
|---|---|---|---|
param | slug de pilar (salud, educacion, …) | salud | qué pilar |
iso | ISO3 (MEX, BRA, …) o LATAM | MEX | se pasa a mayúsculas automáticamente; LATAM renderiza el agregado regional ("América Latina y el Caribe") |
indicator | id de indicador | primer indicador de la celda | elige una métrica específica |
theme | light | dark | dark | |
lang | es | en | pt | es | textos trilingües completos + etiquetas/unidades de métrica localizadas |
accent | color hex #RGB–#RRGGBBAA | azul Oxford (#11457e claro / #6496d5 oscuro) | acento de marca blanca (debe cumplir /^#[0-9a-fA-F]{3,8}$/); sobrescribe tanto el acento como el color del delta positivo |
El valor, el delta (frente a la media de 3 años), la unidad y el chip de fuente salen del mismo payload estático de celda que usa la app; el chip de fuente enlaza a la fuente primaria del indicador y el wordmark enlaza de vuelta a la celda viva del atlas.
Cuatro mecánicas detrás de ese párrafo (todas en src/routes/_embed-shared.tsx):
- Compuerta solo-estático.
fetchParameter()carga la celda horneada/data/parameter-cache/<param>__<ISO>.jsony reportasource: "static" | "mock"másstatus: ready | not_generated | quarantined. Ante un fallo de caché, la app principal pinta un mock determinista para mantener vivo su layout; el embed trata todo lo que no seasource:"static"— o que seanot_generated— como sin datos, porque un valor mock dentro de un iframe de terceros sería un número fabricado vistiendo un chip de fuente real. - Orden de resolución del chip de fuente. Tres pasos, gana el primer acierto: campos inline en el indicador (
source,source_url,vintage_year) → la fila decitations[]del propio payload emparejada porcitation_id→ el registro de fuentes (inferSourceKey(citation_id)→ metadatos de la institución → plantilla de URL dedeepLinkFor()). El paso del registro es la misma maquinaria quesyntheticCitation(): el chip puede reconstruirse a partir del id solo, y únicamente un id que no mapea a ninguna institución conocida no produce nada. - Etiquetas. Los payloads horneados llevan solo etiquetas/unidades canónicas en ES;
?lang=en|ptlas superpone desde el registro horneado de métricas, cargado de forma asíncrona y cacheado a nivel de módulo. El texto en ES se pinta de inmediato y permanece si ese fetch nunca resuelve, y el embed respeta?lang=al pie de la letra — a diferencia de la app principal, nunca recae en la preferencia de idioma almacenada del visitante. - El color del delta es consciente de la dirección. El chip se colorea según el
good_directiondel indicador (up/down/neutral): un valor a la baja en una métrica donde bajar es bueno se renderiza como bueno;neutralse renderiza en gris.
/embed/chart exige además una series no vacía en el indicador — un valor citado sin historial renderiza el estado sin datos, nunca un gráfico vacío. El último punto se marca con un punto de referencia y se repite como valor titular.
Embed mínimo de stat:
<iframe
src="https://futuros.xyz/embed/stat?param=salud&iso=MEX&theme=light"
width="320" height="180" style="border:0" loading="lazy"
title="Futuros — Salud, México"></iframe>Embed de gráfico con marca blanca (acento personalizado, en inglés, indicador específico):
<iframe
src="https://futuros.xyz/embed/chart?param=educacion&iso=BRA&indicator=se_xpd_totl_gd_zs&theme=dark&lang=en&accent=%23004b87"
width="360" height="240" style="border:0" loading="lazy"
title="Futuros — Education spending, Brazil"></iframe>Nota el # codificado en URL (%23). Los tamaños sugeridos coinciden con el builder /incrustar: stat 320×180, chart 360×240.
/embed/board — tablero multipanel
Un solo iframe renderiza una cuadrícula tematizada y responsiva de paneles stat/chart. Un socio (BID / CAF / CELAC) inserta un único iframe y obtiene un tablero completo, con marca blanca y sin cambiar código.
Parámetros:
| Parámetro | Valores | Por defecto | Notas |
|---|---|---|---|
panels | lista separada por comas kind:param:iso[:indicator] | — | kind = stat | chart; hasta 12 paneles |
theme | light | dark | dark | tematiza el contenedor y cada panel |
accent | color hex | — | se propaga a cada panel |
brand | texto libre | Futuros | reemplaza el wordmark; con un brand personalizado la atribución del pie dice "powered by Futuros ↗", en caso contrario dice "futuros.xyz ↗" |
lang | es | en | pt | es | se reenvía a cada panel |
Cada especificación de panel es kind:param:iso[:indicator] — p. ej. stat:salud:MEX o chart:educacion:BRA:se_xpd_totl_gd_zs. El board monta cada panel como un iframe same-origin de /embed/stat o /embed/chart, reenviando theme, lang y accent.
El parseo se degrada por campo, nunca a nivel del board completo: un kind desconocido se convierte en stat, un param vacío en salud, un iso vacío en MEX (siempre en mayúsculas), y todo lo que pase de 12 paneles se trunca en silencio. Los paneles se renderizan como iframes de altura fija (stat 160 px, chart 220 px) en una cuadrícula auto-fill con columna mínima de 260 px, de modo que el board se reacomoda con el ancho de la página anfitriona.
Un tablero de país con marca blanca:
<iframe
src="https://futuros.xyz/embed/board?panels=stat:salud:MEX,chart:educacion:MEX,stat:seguridad:MEX,chart:trabajo-economia:MEX&theme=light&accent=%23004b87&brand=IDB&lang=es"
width="100%" height="640" style="border:0" loading="lazy"
title="Tablero Futuros — México"></iframe>Si panels viene vacío, el board muestra una pista (?panels=stat:salud:MEX,chart:educacion:BRA) en lugar de un marco en blanco.
/incrustar — el builder
https://futuros.xyz/incrustar es el builder sin código para los embeds de widget individual. Elige pilar, país, indicador, tipo (stat/chart) y tema; muestra una previsualización en vivo en iframe del valor citado real y te entrega un snippet de una línea listo para copiar y pegar. El desplegable de indicadores se puebla con los indicadores reales de la celda seleccionada, así que solo puedes incrustar una métrica que existe.
Tematización — semántica de sobrescritura de themeTokens
Cada widget inserta inline un conjunto completo de variables CSS en su propia raíz (themeTokens() en _embed-shared.tsx) — no hay clase de tema global, así que los estilos de la página anfitriona no pueden filtrarse hacia dentro y el widget no puede fugarse hacia fuera:
| Token | Rol | ¿?accent= lo sobrescribe? |
|---|---|---|
--e-bg / --e-panel / --e-line | Lienzo, tarjeta, borde | No |
--e-ink / --e-ink-2 / --e-ink-3 | Jerarquía de texto | No |
--e-accent | Wordmark, trazo de la línea del gráfico | Sí |
--e-good | Delta en dirección buena | Sí — el mismo hex que el acento |
--e-bad | Delta en dirección mala, salvedad de cuarentena | No — se mantiene rojo |
--e-cite | Chip de cita | No |
La sobrescritura es deliberadamente parcial: accent reemplaza exactamente --e-accent y --e-good, nunca --e-bad ni --e-cite. Un socio puede poner su marca al widget y a sus deltas positivos; el color de advertencia y el chip de cita no admiten marca — puedes poner marca blanca al número, no a la salvedad.
Garantías de honestidad
El contrato de procedencia tiene dientes dentro del propio widget:
- Sin números fabricados. Un embed se niega a renderizar payloads mock/de respaldo — si la fuente de datos de la celda no es el corpus estático, muestra "Datos no disponibles" en lugar de una cifra inventada.
- La cuarentena es visible. Una celda marcada por los validadores se renderiza con una salvedad explícita de "en revisión" en lugar de presentar en silencio un número puesto en duda.
- La cita viaja. El chip de fuente se reconstruye a partir del
citation_idsolo (ver Procedencia), así que el enlace profundo funciona aunque el embed nunca cargue el registro completo de citas.
Enmarcado y seguridad
- Las rutas de embed envían
Content-Security-Policy: frame-ancestors *— incrustables en cualquier página anfitriona. - Todas las demás rutas envían
X-Frame-Options: SAMEORIGIN— la app principal no es enmarcable fuera de su origen. - Los embeds son GETs puros de datos estáticos; no hay autenticación ni nada que enviar.
- Los widgets son autocontenidos: el tema se aplica mediante sobrescrituras inline de variables CSS en la raíz del embed (no una clase global), así que el mismo número se lee correctamente dentro de cualquier página anfitriona.
Las cifras detrás de cada embed son las mismas observaciones citadas que expone la API Pública v1 — el embed es solo una vista renderizada y enlazable de una celda.