El pipeline de datos
Futuros no consulta APIs en vivo cuando alguien abre una página. Todo el corpus se ingiere, se hornea y se congela como JSON estático antes del despliegue; la SPA solo lee archivos. Eso hace la plataforma rápida, cacheable, offline-capaz — y, sobre todo, auditable: cada cifra que ves fue verificada en tiempo de build contra su registro de fuente. Este pipeline sirve directamente al Pilar 1 — Fideicomiso de Datos: un corpus con procedencia y licencia limpia es el único activo que un modelo regional no puede copiar.
Transportes meta: una API, muchas agencias
Dos conectores no traen una fuente sino decenas, y por eso rinden más por línea de código que cualquier adaptador individual:
- DBnomics (
makeDbnomicsAdapter) — 93 proveedores tras una sola API abierta; es el transporte de FMI WEO, UNCTAD y OMC. - World Bank Data360 (
scripts/sources/gap/data360.ts) — ~35 bases estadísticas del Banco y de agencias asociadas (UIT, WIPO, WEF, Yale) tras una API sin clave con código ISO3 en cada fila. El motor descarga primero el CSV masivo de cada base (data360files.worldbank.org, sin límite de tasa) y lo filtra en streaming; solo cae a la API paginada cuando el archivo supera el tope de tamaño. La API responde con HTTP 417 cuando limita el tráfico, así que el motor lleva su propio backoff de 3 a 60 segundos.
Dos disciplinas que estos transportes imponen y que valen para cualquier fuente nueva:
- Fijar los miembros de cada dimensión. Una misma serie puede traer el presupuesto aprobado y el ejecutado, en moneda local y como porcentaje del gasto, bajo un único código. Si no se fija la dimensión, la serie mezcla dos cosas distintas sin avisar. Los codelists se leen del endpoint
/portal/v1/dimensions, no se adivinan. - Cortar las series de flujo en el último año completo. Los agregados anuales de aprobaciones (Banco Mundial, GCF, CFI, MIGA) tendrían un año parcial al final, y la celda lo leería como un desplome. Se corta en el último año calendario cerrado.
El registro de fuentes
Cada fuente externa es un SourceRecord declarado en scripts/sources/registry/. Define de dónde sale el dato y bajo qué reglas puede publicarse:
AdapterEngine— el tipo de conector:series,bulk,sdmx,ckan,ocds,event,entity,microdata,providerometa. Cada API externa tiene un solo adaptador enadapters.ts(WHO GHO, UNESCO UIS, OpenAlex, Climate Watch, UN SDG, UNHCR, OWID, IMF DataMapper, SDMX/DBnomics genérico…). Las fuentes añadidas por olas posteriores viven enscripts/sources/gap/, un archivo por fuente, y entran aALL_ADAPTERScomo un solo...GAP_ADAPTERS: añadir una fuente es un archivo nuevo más una línea de import, nunca una edición dentro deladapters.tsde 4.400 líneas. La ola E lleva ese mismo principio un nivel más abajo: sus adaptadores viven enscripts/sources/gap/wave-e/<fuente>.tscon su propio índice, porque la ola D había concentrado sus 19 fuentes en un sologap/index.tsy eso recreaba, un nivel más adentro, el punto de colisión que este diseño existe para evitar — cada persona que añade una fuente quiere la última línea del mismo archivo. La ola F (granularidad plena,wave-f/+src/lib/gap-sources-f/) usa el mismo corte: un archivo por fuente para emaciación JME, matrícula terciaria UIS, nutrición DHS, espacio cívico CIVICUS, relleno Ember/IRENA, y sondas de hueco honesto (RICYT, ILIA/GIRAI/GDB, INE.Stat salud/innovación de Chile, ENDI Ecuador) que re-verifican y devuelven[]en vez de inventar un número.scripts/refresh-wave-f.tsrefresca solo las series que producen valores (mensual). Con el corte por archivo, dos personas que añaden dos fuentes tocan dos archivos distintos.scripts/sources/gap/wave-e/probe-file.tscompleta el patrón: ejecuta los adaptadores de un módulo sin escribir nada, de modo que una fuente se construye y se verifica entera antes de que su línea entre en ningún índice compartido.LicensePosture—kind/redistribute/aggregates_only: qué se puede republicar y qué solo se puede citar.- Verificación por sonda —
sources.tsesSOURCE_REGISTRY_V2, con endpoints probados;regulation-sources.tses el registro hermano para el rastreo de leyes de IA. Losnso/*.jsonson 27 listas de fuentes por oficina nacional de estadística. - Anatomía de verificación — cada registro declara además su
deep_link(la URL por-registro que se estampa encitations.json), su cadencia (cadence_days), un specverifyde re-verificación por muestreo (refetch-compare: volver a pedir una muestra y comparar contra lo guardado) y unexpected_volumedimensionado para un centinela de volumen de ±50%. Ojo: el centinela y el re-verificador son hoy contrato declarado del esquema — ningún ingestor los ejecuta todavía; el guardián que sí corre en cada refresco es el guardián de frescura.
El esquema completo de un SourceRecord (scripts/sources/registry/schema.ts) — cada campo con la regla que validateRegistry() le aplica:
| Campo | Tipo / valores | Regla en validateRegistry() |
|---|---|---|
key | UPPER_SNAKE estable | regex ^[A-Z0-9_]+$; duplicado o inválido = error |
tier · priority · status | primary/secondary/curated · P0/P1/P2 · wired/ready/blocked/research | el tick ingest:due del daemon drena filas ready |
engine | uno de los 10 AdapterEngine | motor desconocido = error |
license | kind (open/cc-by/non-commercial/share-alike/cite-only) + redistribute + aggregates_only? | la compuerta de licencia (abajo); aggregates_only + motor microdata = warn recordatorio: el adaptador agrega, nunca vuelca filas |
auth | none/env_key/registration/approval + env | env = variable de entorno que lee el adaptador |
endpoint | patrón URL con placeholders {iso3}/{code} | faltante = error |
deep_link | patrón URL por-registro | es lo que se estampa en citations.json |
cadence_days | número | el ledger de frescura compara contra esto |
geos · geo_level | ISO3[] o "ALL" · country/adm1/adm2/point/supranational | geo fuera del set de 26 (25 países + LATAM) = warn |
pillars | slugs de dimensión | vacío con motor ≠ provider = warn |
expected_volume | número ≥ 0 | negativo o no-numérico = error; dimensiona el centinela ±50% |
verify | sample_n + method (refetch-compare/liveness-only/checksum) | bloque faltante o sample_n < 0 = error |
Los adaptadores están aislados por fallo: un endpoint muerto devuelve [] en vez de romper la ingesta. La cadencia (cada cuántos meses se refresca cada indicador) vive junto a la fuente y se materializa en el ledger de frescura.
La compuerta de licencia (en compilación)
La regla más importante del registro no es de runtime: es de compilación. validateRegistry() rechaza cualquier fuente cuya licencia diga redistribute: false pero esté cableada a un motor que no sea provider. La condición exacta es triple: redistribute === false y motor ≠ provider y status === "wired" — una fuente cite-only puede quedar declarada como research o blocked sin romper el build; lo que no puede es estar cableada a un motor que produce valores. Republicar valores de una fuente que solo permite citar es un error de compilación (assertRegistryValid() lanza con la lista de claves ofensoras), no un aviso que alguien podría ignorar. La postura de licencia de una fuente decide si sus datos aparecen como indicador republicado, solo como agregado, o solo como cita — y lo hace antes de que el build produzca un solo archivo.
Esta misma compuerta aplica a los datos aportados por terceros vía /contribuir: nada se redistribuye más allá de lo que su licencia permite. Es el mecanismo que mantiene honesto al Fideicomiso de Datos.
Las etapas de ingesta
scripts/ingest-all.ts orquesta tres etapas, cada una aislada por fallo (una etapa que falla no arrastra a las otras):
runMultisource()→ celdas deparameter-cache/+ citas. Es el grueso: el valor de cada indicador por país, con sucitation_id.runDocuments()→documents/ynews/: documentos y noticias por eje y país.runSignals()→ señales citadas, extraídas por LLM, ensignals/.
Al terminar, escribe refresh-meta.json. Hay ingestores de dominio adicionales para presupuesto abierto, libertad económica, libertad digital, deriva autoritaria, OGP, libertad humana, libertad de prensa, regulación, observatorio y el nivel subnacional ADM1.
Existe además una cuarta vía cadenciada: el pulso social. ingest-social.ts (eventos GDELT + GKG + RSS social) y fetch-trends.ts alimentan las familias social/, social-trends/, attention/ y civic/, con sus propios horneados (bake-trends, bake-civic, bake-attention). Aparece en refresh-meta.json como la fuente social:gdelt, con conteo de posts.
Y una quinta vía: alta frecuencia (HF). ingest-hf.ts corre NASA POWER diario (un punto capital por ISO3) y, en la misma corrida, adapters de FIRMS, ClimateSERV CHIRPS, Ember mensual, Wikimedia, GHS, FAO ASIS, GFW y Meta Movement. La regla de publicación es estricta: solo nasa-power.json se commitea y se sirve. Un almacén vacío (observations: []) o un feed sin filas publicables no se escribe bajo public/data/hf-observations/ — un 200 hueco es un defecto, no un “próximamente”. check-hf-publish.ts bloquea el build si aparece cualquier JSON extra o un POWER vacío. POWER no entra en parameter-cache ni en observations.csv (ese CSV ya rebasa el tope de 100 MB de GitHub). El lane hf no mueve generated_at. No se listan en la superficie pública de Fuentes: el mismo criterio que las olas E/F. No se adaptan Advan, Veraset, Spectus, rasters de luz nocturna, Open-Meteo, rastros de dispositivos, GDB, GIRAI ni el lakehouse B0.5.
Cadencia recurrente
Desde agosto de 2026 el refresco ya no depende de corridas manuales: tres workflows de GitHub Actions ejecutan la ingesta en cadencia y publican por el mismo camino verificado (compuerta verde con el prebuild completo → PR rodante → squash-merge automático; nunca un push directo a main):
| Workflow / tier | Cadencia | Qué refresca |
|---|---|---|
social-ingest.yml | cada 2 horas | pulso social, cívico y de atención (GDELT + Wikimedia + RSS, sin clave) |
data-refresh.yml · docs | lun/mié/vie | corpus de noticias y documentos (GDELT + RSS + gacetas sin clave; Exa si su secret existe) + extracción de señales citadas (si ANTHROPIC_API_KEY está configurada; si no, ingesta de docs sin señales LLM) + poda de señales huérfanas + snapshot Polymarket |
data-refresh.yml · derived | martes | sonda de frontera WB (recheck-wb-freshness) + horneados derivados que rezagan de sus insumos: puntajes compuestos, resiliencia democrática, screener de mercados, brief de pulso, manifiesto _index.json |
data-refresh.yml · corpus | día 1 de cada mes | corpus numérico completo: Banco Mundial/WGI (refresh-from-live-apis) + series multisource (ingest-all --skip-docs --skip-signals) + shards de citas + todos los derivados + ingesta y horneado del observatorio regulatorio (ingest-regulation → bake-regulation) |
data-refresh-hf.yml · daily | cada día 06:17 UTC | NASA POWER (punto capital) + FIRMS (bbox; FIRMS_MAP_KEY solo si el secret existe) + CHIRPS (aislado; HF_SKIP_CHIRPS=1 lo sostiene) + GFW alertas integradas (GFW_API_KEY opcional; sin clave usa download/csv) |
data-refresh-hf.yml · ember | días 1 y 15 | Ember mensual, PASS-PARTIAL (12 ISO3 en el CSV; no se inventan celdas) |
data-refresh-hf.yml · wiki | días 3–5 de cada mes | Wikimedia pageviews top-by-country (ISO2→ISO3; CU/HN/NI/VE ausentes en 2026-07 se omiten) |
data-refresh-hf.yml · ghs | día 1 | JRC GHS-COUNTRY-STATS (superficie construida km², épocas 1975–2030; el ZIP no se commitea) |
data-refresh-hf.yml · asis | día 1 | FAO ASIS anual nacional (PROVINCE=ALL; 403 a UA de Python, 200 con UA de navegador) |
data-refresh-hf.yml · meta | días 1 y 15 | Meta Movement Distribution (HDX; rollup país×día; CSV crudo 48–195 MB se descarta) |
Las corridas del tier corpus van envueltas en el guardián de frescura (snapshot → repair) y en INGEST_GUARD=1, de modo que un adapter estrangulado no puede borrar celdas en silencio. Los lanes con clave de pago (Exa, Voyage, señales LLM vía ANTHROPIC_API_KEY) y los índices anuales que requieren un bump de URL por edición (V-Dem, RSF, HFI, WEO oct/abr) siguen siendo corridas manuales deliberadas cuando la clave falta — el detalle vive en la auditoría DATA_AUDIT_2026-08-22.md del repo.
Ampliaciones de corpus (2026-08-24). Sobre los adaptadores de las olas E/F ya cableados, se hornearon series nuevas y verificadas en vivo: expansión CEPALSTAT (femicidios en número, maternidad adolescente, participación laboral, ocupación carcelaria, primaria completa 15–19); la tasa de política monetaria del BPI (BIS_CBPOL / WS_CBPOL) para los seis bancos centrales de ALC que publican la serie; profundidad WHO GHO (MCV2, hipertensión, cobertura ART) y UNESCO UIS (finalización secundaria, alfabetización adulta); RSF y ND-GAIN llevados a parameter-cache (libertad de prensa 2013–2026 e índice/vulnerabilidad/preparación climática 1995–2024); DesInventar/Sendai (DESINVENTAR) con el conteo anual de eventos de desastre reportados para 22 geografías (BRA/CUB/SUR sin perfil publicado, omitidos); Freedom House FIW (FIW) con Total/PR/CL 2013-2024 para las 25 geografías; lactancia materna exclusiva UNICEF (NT_BF_EXBF) sobre el adaptador SDMX ya cableado; inseguridad alimentaria severa OWID/FAO; anomalía térmica CMIP6 SSP2-4.5 vía CCKP (cuatro ventanas 2020-2099); e iniciativas STI iniciadas (OCDE STIP) para siete geografías del panel. Cada cifra lleva cita con deep-link al registro, CSV o ZIP concreto; los países sin serie se omiten, nunca se imputan.
El horneado (bake)
scripts/bake-all.ts transforma el cache crudo en los datasets derivados que consumen las páginas: narrativas → indicadores de frontera → subnacional → briefs y contradicciones de pulso → pronósticos → shadows → grafo de personas → puntajes compuestos → índice de mercados → _index.json. Son 44 scripts bake-*.ts más 10 generadores build-* — 54 scripts de horneado y generación: puntajes, mercados, V-Dem, exploración, insights, salud del dato, señales, coaliciones, ontología, tendencias, cívica, atención, feeds, contribuciones, soberanía de cómputo, convergencia regional, nowcast de inestabilidad, triangulación, narrativas y grafo de personas, más generadores como build-democracy-index y build-search-index.
Dos generadores de dominio indexan familias que ya viste en la lista de datasets: build-cases-index.ts regenera el _index.json de cases/ (los precedentes y aprendizajes por país, autorados archivo a archivo) y gen-elections-index.mjs el de elections/ (el calendario electoral regional).
Ningún hecho se "hornea" dentro de un modelo: los hechos viven en los datos con su cita determinista; el horneado solo precalcula lo derivable de ellos.
La API pública horneada
El pipeline tiene un cuarto producto además de los datos de la SPA: una API estática de solo lectura en public/api/v1, re-horneada en cada despliegue por bake-api.ts — citations.csv/.json, freshness.json, contributions.json, futuros-data.xlsx y registros por país y por indicador con interoperabilidad M49/SDMX. Tiene su propia compuerta: check-api.ts corre después del horneado y verifica que la API espeja los datos actuales (por ejemplo, que el conteo de citas de su índice coincide con citations.json). Por construcción, la API nunca queda desfasada del corpus. Referencia completa en Public API.
Las familias de datasets (public/data/**)
El resultado son 68 familias de datos estáticos. Entre las principales:
parameters.json(los 10 ejes),geographies.json(25 países + LATAM, con ISO3; los códigos M49 no viven aquí — se unen desde un mapeo ISO3→M49 propio debake-api.tsal hornear la API pública).parameter-cache/(celdas indicador×país) y las tres capas subnacionales:parameter-cache-adm1/(miles de celdas ADM1),parameter-cache-adm1-series/(sus series temporales) yparameter-cache-adm1-universal/(la superposición universal GDL — esperanza de vida, INB per cápita, escolaridad esperada — que cubre unidades donde los ejes no llegan).hf-observations/— el único JSON publicado esnasa-power.json(NASA POWER, punto capital, no vacío). El resto de feeds HF se ingiere pero no se commitea ni se sirve; un archivo hueco es un defecto.citations.json— el registro central de citas (15.098 registros, varios MB) ycitations/— sus shards, para que un consumidor no tenga que traer el registro entero.documents/,news/,signals/,social/,social-trends/,attention/,shadows/.personas/(perfiles +_index+_graph),state-history/,country-history/.scores.json(compuesto),data-health.json,frontier-indicators/,compute-sovereignty/,agi-economy/,dependency/,climate-action/.pulse/,regional-convergence/,subnational-convergence/,instability-nowcast/,triangulation/,uncertainty/,insights/.pilots/,pilot-archetypes/,financing-instruments/,coalitions/,positions/,cases/.regulation/(índice horneado +_instruments.json),regulation-raw/(lo ingerido antes de hornear),legislation/,policies/,constitutional/,elections/,democracy/,transparency/,governance/,ai-governance/.markets/,metrics/,ontology/,civic/,gender-gap/,trends/,explore/(el banco de 215 métricas de /explorar),polymarket/(snapshot de mercados de predicción),biotech/.colombia/— el corpus de la respuesta de Colombia, cuyo nivel de persona y operación es sintético y va con banner de demostración (ver Colombia).search-index.json(índice Voyage para el asistente),catalog.json,sdg-crosswalk.json,falsifications.json,ai-runs.json,personas-images.json.
Frescura
public/data/refresh-meta.json es el ledger de frescura, con dos granularidades: el mapa indicators rastrea por-indicador el subconjunto Banco Mundial / WGI (49 códigos); todo lo demás (884 series en el ledger de fuentes horneado) se rastrea a nivel de feed en el mapa sources (197 entradas como social:gdelt o doc:exa, con kind, last_refreshed, next_refresh_due y conteos como posts). El campo de nivel superior generated_at significa el último ingest no-social (series / documentos / pull del Banco Mundial): el pulso social de cada 2 h estampa sources.social:* y lanes.social, y no mueve generated_at — si lo hiciera, un corpus de noticias rancio de 25 días se leería como actual. El mapa lanes (docs / social / hf / derived) es la lectura honesta por tier. Junto a ellos, el nivel superior lleva total_indicators, scope, vintage_note, generated_at_note, wave_f_refreshed_at y los tres sellos que estampan las compuertas y los refrescos (source_ledger_baked_at, source_ledger_series, wb_freshness_checked_at). La forma de una entrada por-indicador:
| Campo | Ejemplo (SI.POV.GINI) | Qué responde |
|---|---|---|
source / source_url | World Bank Open Data | ¿de dónde salió? |
cadence_months / next_refresh_due | 12 / 2027-03-02 | ¿cuándo toca refrescar? |
last_refreshed | 2026-07-28 | ¿cuándo lo pedimos por última vez? |
last_observation_year | 2025 | ¿qué tan nuevo es el dato en sí? |
countries_with_data / total_countries | 22 / 25 | ¿cuánta cobertura? |
source_frontier_year / source_frontier_checked_at | 2025 / 2026-07-23 | ¿existe algo más nuevo en el upstream? |
freshness_status / freshness_note | current | veredicto: current, source_not_updated o no_data |
La distinción que carga el peso es last_refreshed vs last_observation_year: un pull de ayer con dato de 2023 no es un fallo del pipeline sino rezago del proveedor — y source_frontier_year lo demuestra, porque registra el año más nuevo que el upstream publica. Es lo que hace posible mostrar el vintage de una cifra en vez de fingir que todo está al día. La superficie /confianza lee este ledger; /incertidumbre es otra cosa — expone bandas de confianza de V-Dem desde uncertainty/vdem-ci.json, no este archivo.
El ledger tiene además un guardián activo, no solo una pantalla. El runner multisource poda y reescribe celdas al ingerir, así que una corrida con un adaptador caído o limitado podría borrar en silencio celdas que existían — el defecto "mi fetch falló, escrito a disco como no hay nada". freshness-guard.py lo impide con tres subcomandos sobre parameter-cache/:
snapshot <out.json> # antes de la corrida
por cada parameter-cache/<pilar>__<ISO3>.json, por cada indicador:
snap[id][celda] = { vintage: vintage_year, value, obj: <indicador completo> }
compare <before.json> # después de la corrida
por indicador: gone (celdas que desaparecieron)
regressed (vintage retrocedió: after < before)
advanced (vintage avanzó) · added (celdas nuevas)
reporta el total de celdas perdidas + retrocedidas
repair <before.json> --apply
restaura SOLO gone ∪ regressed, desde el obj guardado en el snapshot
advanced y added se dejan intactos — un avance genuino nunca se revierte
sin --apply es dry run: lista qué restauraría, no toca discoInvariante sutil del filtro --only=<substr>: el guardián filtra la línea base con los mismos términos que el snapshot vivo. Sin eso, todo indicador fuera del filtro parecería gone y repair restauraría la plataforma entera a la línea base — incluidas correcciones deliberadas posteriores. Las corridas de refresco son no destructivas por construcción.
En el prebuild, bake-source-ledger.ts hornea el ledger de fuentes que alimenta /confianza y estampa en refresh-meta.json los campos de nivel superior source_ledger_baked_at y source_ledger_series; en los refrescos de datos, recheck-wb-freshness.ts re-verifica los vintages del Banco Mundial contra el upstream y estampa wb_freshness_checked_at.
Las compuertas del prebuild
El prebuild es una cadena de compuertas encadenadas con && en package.json; si cualquiera falla, el build falla y nada se despliega:
* = consultiva (--warn): reporta y sale 0.
Primero el terreno: dos typechecks (tsc -p tsconfig.scripts.json para los scripts de ingesta y horneado, tsc -p tsconfig.server.json para el servidor del asistente y las funciones), la suite completa de tests (bun test --timeout 15000 — 2.076 tests en 175 archivos, que incluyen el servidor del asistente, el handler MCP y los horneadores) y build-positions-index --check. Después, veintiuna comprobaciones check-* con nombre propio, más dos gates en modo --check sobre scripts que normalmente escriben (build-positions-index, bake-trends-index). Tres son consultivas (--warn: reportan y salen 0 — check-search-index, check-vintage, check-adm1-coverage); las demás hacen fallar el build.
Procedencia y coherencia del corpus:
check-traced— toda cifra tiene su rastro a fuente.check-signals— las señales están bien formadas y citadas.check-source-links— los enlaces de cita apuntan al registro específico de país + indicador. La única excepción es la listaLANDING_OKdentro de la propia compuerta: un opt-out documentado por fuente (capas curadas e internas que resuelven a /metodologia, capas documentales EXA/GDELT/RSS/gacetas/señales y unas pocas fuentes sin URL estable por registro cuyo destino honesto es su propia página de dataset), cada uno con su justificación en el código. Todo lo demás lleva deep link o el build falla.check-pilots— coherencia de los pilotos.check-persona-scope— ningún perfil puede quedar inalcanzable. El<select>de país de /personas se construye desde los 25 canónicos, así que un perfil archivado bajo uniso3fuera de ese conjunto era una ficha completa y con fuentes que ningún visitante podía filtrar (la auditoría D-5 encontró 50 de 1.384 así: Barbados, Bahamas y un columnista radicado en Miami bajoUSA). No se borraron, se escalonaron por tier; esta compuerta hace vinculante el escalonamiento.check-citations— integridad del registro de citas.check-parameter-cache-citations— cadacitation_iddeparameter-cache/resuelve encitations.json, sin recurso a una cita sintética de respaldo. La API pública ya estaba limpia; esta compuerta mantiene limpia la caché desde la que se hornea.check-scores— coherencia de los puntajes compuestos (ver El Índice Futuros).check-provenance— rechaza hashes o digests fabricados (stubs de sha256 truncados) en cualquier parte depublic/data; nació de la auditoría de precisión de datos de julio de 2026.colombia/check-real-data— separa lo real de lo sintético en el corpus de Colombia, para que ninguna cifra de demostración se sirva sin su banner.
Estilo, taxonomía y cobertura:
check-emdash-drift— la deriva de estilo (guiones largos) no reaparece en el contenido ya barrido.check-landing-stats— cada número impreso enpublic/landing.htmlsigue siendo el número que dicen los datos. La landing es un artefacto editado a mano, la página de más tráfico y la única sin componente, diccionario ni test detrás; sus estadísticas eran libres de pudrirse y lo hicieron (la auditoría del 13 de agosto de 2026 la encontró anunciando 6.726 citas contra un corpus de 6.855, y "300+ pilotos" contra un índice de 337). La compuerta recomputa cada cifra desdepublic/data/**del mismo modo que la superficie correspondiente, e imprime el literal que hay que pegar. Hoy verifica seis: fuentes primarias distintas, países, ejes, citas, personas e instrumentos.check-catalog— el catálogo de datasets está completo y consistente.check-search-index(consultiva) — el índice semántico cubre el corpus.check-law-term-index— el índice de términos legales cubre el 100% del corpus regulatorio (instrument_countderegulation/_index.json). Índice local: Voyage sigue siendo opt-in y falla cerrado.bake-trends-index --check— el índice de tendencias horneado coincide con sus insumos.check-vintage(consultiva) — cuántas cifras publicadas rompen la regla de vintage de dos años. El umbral y el orden de resolución de año salen desrc/lib/vintage.ts, el mismo módulo desde el que renderizan las tiras de titular, así que la compuerta mide lo que el visitante ve. Existe porquebake-data-healthpuntúa una celda por la edad mediana de sus indicadores: la plataforma reportaba 2 celdas rancias de 260 mientras el 27% de las cifras individuales violaba la regla.check-adm1-coverage(consultiva) — la compuerta de honestidad de la capa subnacional. "432 de 456 unidades ADM1 con dato real" era cierto solo de la superposición universal GDL; a nivel de eje ninguna unidad llevaba más de 5 de los 10 ejes medidos, cinco ejes estaban 100% modelados y 26 unidades tenían sus celdas escritas bajo slugs que la app nunca puede pedir. Nada lo detectaba, así que derivó en silencio durante meses.check-i18n— la cobertura ES/EN/PT no retrocede.check-taxonomy— la copia de interfaz no vuelve a los sustantivos retirados para las diez medidas sectoriales (diez parámetros,ten dimensions,dez dimensões) ni apunta la navegación a/governance. Los diez son ejes / axes / eixos.check-hf-publish— bajopublic/data/hf-observations/solo puede vivirnasa-power.json, y debe ser un almacén NASA POWER no vacío. Cascarones vacíos (observations: []) y feeds no publicados (CHIRPS, FIRMS, Ember…) son un defecto: sirven un 200 que parece vivo.check-api— corre al final, después de los cuatro horneados de prebuild (bake-data-health,bake-source-ledger,bake-api,bake-feeds), y verifica con siete grupos de aserciones que la API pública estática espeja los datos actuales.
Las compuertas de procedencia en sentido estricto son el subconjunto check-traced, check-signals, check-source-links, check-citations, check-parameter-cache-citations y check-provenance — pero todas comparten la misma consecuencia: son la razón por la que "toda cifra con su fuente" no es un eslogan sino una invariante de compilación. Cómo se resuelve una cita en pantalla se explica en Procedencia y citas.
Un patrón se repite en las compuertas más nuevas (check-landing-stats, check-vintage, check-adm1-coverage, check-persona-scope): cada una nació de una auditoría que encontró una afirmación cierta cuando se escribió y falsa cuando se leyó, sin que nada la conectara con el dato. La compuerta es esa conexión. Es la misma razón por la que existe esta documentación y por la que se audita contra el código.