Procedencia y citas
La procedencia es el foso competitivo de Futuros. Cualquiera puede mostrar cifras; lo raro es que cada cifra se remonte a un registro de fuente primaria específico en dos clics, y que el sistema falle en compilación si no puede. Este documento explica la maquinaria que sostiene ese contrato. Sirve tanto al Pilar 1 — Fideicomiso de Datos (la procedencia es lo que hace verificable el corpus) como al Pilar 2 — Modelo Soberano (los hechos viven en las citas, nunca horneados en pesos).
El contrato de dos clics
Toda cifra en pantalla lleva su cita en la propia cifra: un subrayado ligero, no un chip [WB] después del número. Un clic abre la referencia (título, editor, año); un segundo clic te lleva al registro exacto de la fuente — el país y el indicador precisos, no una landing genérica. Esa especificidad es una regla de build: la compuerta check-source-links hace fallar el build si un enlace apunta a una fuente genérica en vez del registro país + indicador.
La excepción es explícita, no tácita: la propia compuerta lleva una lista LANDING_OK de fuentes cuyas citas resuelven legítimamente a una página de landing o metodología — capas curadas e internas (gov-, event-, calc-, agg-, idx-), capas documentales (EXA, GDELT, RSS, gacetas, señales) y unas pocas fuentes sin URL por registro (OCDE benchmarks, IDEA, UNODC, V-Dem, FSI, INFORM, IMF PortWatch, FAOSTAT) — cada una con su justificación comentada en el código. Es un opt-out consciente y auditable, no un agujero: todo lo que no está en esa lista debe llevar deep link o el build no compila. Si un dato no puede rastrearse así, no se publica como valor.
Dos redes más cierran los resquicios. GENERIC_URLS enumera páginas "genéricas de dataset" que difieren de la raíz solo por un path o un fragmento (el dashboard de FSI, el formulario de búsqueda vacío de USGS…): una cita no exenta sobre una de ellas es violación igual que una landing desnuda. Y los agregados regionales (-latam, -world, -subregion, -oecd en el id) quedan exentos porque no existe un registro país+indicador para una media supranacional. El diseño es fail-closed: una fuente nueva sin plantilla de deep link cae a su url_root y el build muere en prebuild; el mensaje de error imprime las dos salidas válidas — escribir la url_template y correr fix-source-deeplinks.ts, o documentar la exención en LANDING_OK con justificación de una línea.
Convenciones de citation_id
El registro central de citas es public/data/citations.json — 15.098 registros con la forma {id, source, title, url, retrieved_at, year}, sobre 336 fuentes primarias distintas. Las celdas de datos y los componentes apuntan a un registro por ese id (el campo que las celdas llaman citation_id). El id lleva un prefijo que codifica su origen: wb- (Banco Mundial, el más numeroso), oecd-, who-, gov- (fuente gubernamental), event-, web- (búsqueda web externa), etc.; corr- (correlación) existe como convención reservada en el código, hoy sin citas vivas. El prefijo no es cosmético: es lo que permite resolver una cita a su fuente aunque la cita no esté en el registro en memoria (ver reconstrucción sintética).
La gramática del id es mecánica, y por eso parseable: <prefijo>-<geo>-<año>, consumida de derecha a izquierda por parseMultiId() — si el último token son 4 dígitos, es el año; si el siguiente es un ISO3 en minúsculas (o latam, la media regional derivada por Futuros), es la geografía; todo lo que queda es el prefijo. extractYear() aplica la misma convención con la regex -(\d{4})(?:[-_]|$). Ejemplo real del registro: wb-si-pov-gini-arg-2024 → fuente WB, indicador SI.POV.GINI, Argentina, 2024.
El registro de fuentes
src/lib/source-registry.ts mapea más de 200 SourceKey (más los que registran las olas de fuentes en módulos aparte) (acrónimos como WB, WHO, IMF) a su SourceMeta: acrónimo, nombre completo, agencia, jurisdicción, raíz de URL, plantilla de URL, cadencia, metodología (con variantes _en / _pt), tier y licencia. Las piezas clave:
inferSourceKey()resuelve uncitation_ida su fuente por el prefijo.deepLinkFor()construye el enlace al registro específico.- 43 funciones
url_templatereconstruyen el deep link por-indicador y por-país a partir del id solo — es decir, dado uncitation_idel sistema puede regenerar la URL exacta de la fuente sin depender de que alguien la haya guardado a mano.
Esto es lo que garantiza el segundo clic del contrato: el deep link no se almacena, se deriva, y por eso no puede quedar desactualizado respecto al id. Dos detalles del resolutor importan en la práctica:
- El orden de los prefijos es semántico.
inferSourceKey()es una cadena de reglasstartsWithdonde gana la primera coincidencia, así que los prefijos más largos van antes (mepyd-debe preceder amep-; el comentario en el código lo advierte). Los bancos centrales ilustran la disciplina de nombres:bcb-es Brasil,bcbol-Bolivia,bch-Honduras,bcch-Chile,bccr-Costa Rica — tokens únicos y terminados en guion para que ninguno ensombrezca a otro. Un prefijo no mapeado devuelveUNKNOWN, que deliberadamente no está exento en la compuerta de deep links: un prefijo nuevo sin registrar es una regresión, no un caso permitido. - La licencia viaja en la fuente.
SourceMeta.license(open,cc-by,non-commercial,share-alike,cite-only; ausente ⇒ abierta/permisiva) compuertea el horneado: una fuentecite-onlypuede citarse sin redistribuir sus valores. - Las olas de fuentes registran aparte. Un frente de ingesta que añade decenas de fuentes no edita las ~5.000 líneas de
source-registry.ts— declara sus entradas en un módulo propio (src/lib/gap-sources.tspara la ola de brechas; a partir de la ola E, un archivo por fuente bajosrc/lib/gap-sources-e/y, en la ola F, bajosrc/lib/gap-sources-f/, cada uno exportando su propioSOURCESyPREFIXES) y el registro central lo fusiona:GAP_PREFIX_RULESse consulta antes que la cadena de prefijos incorporada, ylookupSourceMeta()busca en ambos registros. Las reglas de prefijo se ordenan de más largo a más corto en el momento de la fusión, no a mano: así un prefijo corto nuevo no puede tapar en silencio a uno largo ya existente, que es el modo de fallo que un orden manual invita. Así dos streams paralelos no compiten por las mismas líneas, ymetaForKey()garantiza que ninguna clave nueva devuelvaundefineden los chips. - No toda fuente puede tener
url_template. Cuando el editor no publica un permalink por registro (PAHO, IDEA), el adaptador estampa comosource_urlel archivo versionado exacto que contiene la fila — un CSV o XLSX descargable, no la portada del portal. La compuertacheck-source-linkssigue satisfecha porque esa URL difiere delurl_root, y la cita sigue siendo verificable: quien la abre obtiene el dato citado.
Marcadores CiteRef
src/components/citations/CiteRef.tsx es el ancla de cita de toda la plataforma: la cifra o el sintagma citado es el enlace, con un subrayado ligero. No hay chips [CEPAL] / [INEC] / [WB] después del número. Al pasar el cursor (o al enfocar / tocar) muestra el mismo popover de siempre (título, editor, año); al hacer clic lleva a la fuente externa. Si un tramo tiene varias fuentes, el popover las lista. La resolución sigue este orden:
- Registro de citas en memoria (
citations.json). - Si no está, reconstrucción sintética desde el registro de fuentes.
Hay un tercer estado, deliberado: el modo strict. Las superficies que saben que sus citas fueron registradas (por ejemplo, la página de un indicador) pasan strict; ahí, un id que no resuelve se renderiza como un marcador suelto y sin enlace, en vez de fabricarle un título, una URL o un vintage sintéticos. Es el contrapeso exacto de syntheticCitation(): la reconstrucción existe para embeds que nunca cargaron el registro, nunca para maquillar un id roto donde el registro sí estaba disponible. La condición es doble por diseño: un marcador solo se considera dangling cuando hay un registro montado en la página y el llamador pasó strict — un miss simple en una superficie que nunca registra (embeds, chat, mini-tarjetas) es normal y cae a la sintética. Y cuando un id resuelve a fuente pero no a URL externa útil, el clic no navega a ninguna parte muerta: abre el cajón SourceTrace con la metodología. Cada clic sobre un marcador emite además el evento de telemetría source_click con {cita, fuente, año} — el uso de la procedencia también se mide.
Alrededor de CiteRef viven CitationChip (un envoltorio que exige el texto a subrayar), CitationDrawer y SourceTrace (un cajón de respaldo con metodología y el log de falsificaciones). La marca de evidencia externa no es un componente aparte: vive dentro del propio CiteRef, que pinta el subrayado en oro discontinuo cuando el citation_id lleva el prefijo web-. El recorrido de extremo a extremo es: valor horneado → citation_id → CiteRef → registro → deep link.
Reconstrucción sintética para embeds
Las superficies incrustables (/embed/stat, /embed/chart, /embed/board) no siempre cargan el registro completo de citas — pesaría demasiado. Para eso existe syntheticCitation(): reconstruye la cita a partir del citation_id y el registro de fuentes (prefijo → SourceKey → url_template). Resultado: una tarjeta incrustada en un sitio de terceros conserva su cita y su deep link, sin cargar los megabytes del registro. La procedencia viaja con el dato, no con la página.
Tres invariantes de honestidad en la sintética: devuelve null si el prefijo resuelve a UNKNOWN — nunca inventa una institución; retrieved_at queda vacío — no fabrica una fecha de recuperación que no ocurrió; y el año solo se incluye si es derivable del propio id.
Badging de citas web externas
No todo hecho viene del corpus horneado. Cuando el asistente usa búsqueda web como último recurso (ver El asistente), esas citas llevan el prefijo web- y se marcan visiblemente como externas: el subrayado de la cifra es oro y discontinuo, y la tarjeta de hover antepone la etiqueta de dato no verificado. La razón es honestidad: un hecho traído de la web abierta nunca debe confundirse con un dato del corpus verificado. El badging separa las dos clases de evidencia en la propia interfaz, para que el lector sepa siempre qué tan firme es el piso bajo cada cifra.
Log de falsificaciones
public/data/falsifications.json es el registro de afirmaciones que fueron puestas a prueba — el reverso de "cada cita es una fuente": cada falsificación es un compromiso auditable de que la plataforma corrige lo que resulta no sostenerse. SourceTrace lo expone junto a la metodología de la fuente. Es parte del contrato de honestidad que se detalla en Metodología y honestidad.
En resumen, la procedencia en Futuros es derivable, no prometida: el deep link se reconstruye del id, la cita sintética viaja al embed, y el build se niega a compilar si algún enlace fuera del opt-out documentado (LANDING_OK) apunta a una fuente genérica. Sigue con El asistente para ver cómo el chat hereda esta misma disciplina — cada figura anclada a un [[cite:id]]. Y para la siguiente frontera — de procedencia derivable a procedencia criptográficamente verificable por terceros — ver la hoja de ruta técnica de optimización.