la privacidad es una lista, no un adjetivo: lo que envía realmente este sitio

dev · Oct 4, 2026 · 7 min read

iba a escribir que este sitio no envía nada a ninguna parte. sin analíticas, sin rastreadores, sin cookies, todo de primera parte. la frase ya estaba escrita en mi cabeza y se sentía genial.

entonces abrí el panel de red y conté, porque una afirmación así es o verdad o mentira y no hay tercera opción.

envió tres peticiones a tres orígenes distintos en una carga en frío, y el código fuente contiene dos más que le entregan mi dirección ip a un desconocido.

qué sale realmente del navegador

este es el registro de red completo de una carga en frío de la portada:

destino qué es qué aprenden de ti
fonts.googleapis.com css de JetBrains Mono que estás en este sitio
fonts.gstatic.com el woff2 de JetBrains Mono lo mismo
api.open-meteo.com el widget del tiempo mi latitud y longitud, en la cadena de consulta

hay una cuarta fila, y es la única que elegí poner:

destination what it is who they learn
www.googletagmanager.com Google Analytics 4 que visitaste una página aquí, y cuándo

esa sí es real, la añadí a propósito, y no se dispara hasta que pulsas un botón. src/lib/analytics.ts guarda la respuesta en localStorage, y gtag.js se inyecta solo después de granted. si rechazas, el script nunca se descarga y un no queda recordado; volver a preguntar en cada visita hace que alguien lo lea como presión, así que se pregunta una vez. antes de que elijas, esta tabla tiene tres filas, no cuatro.

esa última fila es de la que tengo que hablar. la petición completa era:

/v1/forecast?latitude=44.4949&longitude=11.3426&current=temperature_2m,...

mis coordenadas, en texto plano, en una url, para mostrarme la temperatura. y aquí está de dónde salieron — o mejor dicho, de dónde pueden salir, porque en esta carga concreta no hizo falta. detectUserLocation() en HomePage.tsx prueba get.geojs.io/v1/ip/geo.json y, si falla, ipwho.is. ambos son servicios de geolocalización por ip, lo que significa que a ambos se les entrega tu dirección ip como precio de un código postal. no se activaron aquí porque el widget ya tenía una ubicación; cuando no la tiene, la ip de un visitante va a dos desconocidos en secuencia para poder saltarse un desplegable.

así que el recuento honesto es tres peticiones en una carga en frío y cinco endpoints en el código fuente, y cuatro de ellos existen para servir un solo widget. lo construí sin pensarlo ni un momento.

lo que no está sigue siendo la parte más interesante, incluso después de las analíticas. sin tag manager. sin reporte de errores. sin reproducción de sesión. sin píxel, sin beacon, sin document.cookie en ningún sitio del código. los ajustes viven en localStorage y no van a ninguna parte. la app, los estilos, las imágenes, las dos fuentes de interfaz: todo desde mi propio origen, un solo hostname, sin terceros.

la parte que realmente está bien, y que nadie captura

el favicon cuesta cero peticiones. este sitio repinta el icono de su pestaña en tiempo de ejecución, con el color del tema actual, a partir del trazo del logo sigma incluido en el bundle:

const href = `data:image/svg+xml;utf8,${encodeURIComponent(buildSigmaFavicon(color))}`;
document.querySelectorAll<HTMLLinkElement>('link[rel~="icon"]').forEach((link) => {
  link.href = href;
});

cambias el acento y el icono de la pestaña sigue, porque es una data uri, así que no hay nada que descargar. no hay ninguna entrada /favicon.ico en el registro de red. el .ico estático, el apple-touch-icon y los png del manifiesto siguen ahí, y tienen que estar: son lo que obtienes antes de que monte react y en los clientes que no pueden ejecutar javascript ni renderizar svg. el último <link> gana donde se soportan ambos, así que el vectorial va al final.

es una cosa pequeña, y es el ejemplo más claro de privacidad-por-construcción que tengo en el código: la funcionalidad no es «no te rastreamos», es «esta petición concreta no existe».

sin sourcemaps. una línea en vite.config.ts:

build: {
  sourcemap: false,
}

un sourcemap es un mapa completo de tu código fuente, enviado a todo el que carga la página, normalmente por accidente porque era útil una vez en las devtools. es el ajuste más privado de un empaquetador y el más fácil de olvidar. dist/ no tiene ningún archivo .map.

los nombres de archivo con hash no son privacidad

cada recurso de este sitio tiene un hash de contenido en su nombre: a/ y c/ para los chunks de js, s/ para el css. deliberadamente no pego ninguno aquí, porque cambia en cada compilación y este párrafo quedaría mal mañana.

la gente lista los nombres con hash en las entradas sobre privacidad. no es privacidad, es caché: el hash está ahí para que un despliegue no invalide un año de caché del navegador. el hash no tiene nada que ver con la dirección de nadie.

lo escribo aquí porque mezclar esas dos cosas es exactamente lo que hace inútiles las entradas «sitio web privado». un bundle con un hash de contenido en el nombre no es privado. un script de terceros de 10 MB tampoco, tenga hash en la url o no.

el problema de JetBrains Mono, que es vergonzoso

todo lo anterior sobre autoalojar fuentes es cierto, y luego está esto, en la línea 1 de src/index.css:

@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap");

y la variable justo debajo:

--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;

dos problemas, y el segundo es el interesante.

el primero es que todos los demás tipos de letra de este sitio se sirven desde mi propio origen — GoogleSansFlex-Variable.woff2 y MaterialSymbolsRounded.woff2, ambos en public/fonts/, ambos precargados — y un solo @import mete a google de vuelta en la habitación. el navegador de un visitante ahora le dice a google en qué página están y más o menos desde dónde, a cambio de una fuente monoespaciada que la mayoría nunca verá renderizada.

el segundo es que la fuente está detrás de un ajuste. hay un interruptor de «fuente de desarrollador» en el diálogo de ajustes que cambia toda la interfaz a JetBrains Mono. casi nadie lo activa. y al @import no le importa: un @import de css al principio de una hoja de estilos es incondicional, así que se descarga en cada carga en frío abras ese interruptor o no. la ruta de código relevante para la privacidad y la ruta de código que ve el usuario no son la misma, y la de la comodidad es la que siempre se ejecuta.

la solución es una línea. la pila ya tiene ui-monospace, SFMono-Regular y monospace detrás — tres fuentes que cuestan cero peticiones — así que borrar el import no degrada nada salvo las formas de los glifos.

las fuentes que envío y nadie carga

esto no es privacidad, es honestidad sobre mi propio repositorio:

archivo bytes referenciado por
MaterialSymbolsRounded.woff2 5.345.304 index.html, index.css
GoogleSansFlex-Variable.woff2 1.945.520 index.html
RobotoFlex-Variable.ttf 1.787.292 nada
GoogleSansFlex-Latin.woff2 20.076 nada

9.098.192 bytes en dist/fonts/, de los cuales 1.807.368 no los pide ninguna página. están en public/, así que se copian en cada despliegue, cualquiera que adivine la url puede descargarlos, y seguirán ahí en tres años porque nada te avisa de un archivo que nunca se solicita.

y el de 20.076 bytes es vergonzoso de una manera concreta: es el subconjunto latino de 20 kb del que escribí una entrada entera. lo medí, publiqué el archivo de 1.945.520 bytes en su lugar porque decidí que el completo era lo correcto, y luego nunca borré el subconjunto. el archivo sobrevivió a la decisión que lo contradijo. el número de mi entrada sigue siendo correcto y el archivo sigue sin usarse.

lo que «privacidad como decisión de diseño» tiene que significar

no un descargo. no un banner de cookies donde no hay nada que consentir. la prueba es una pregunta:

¿puedes afirmar, sin rodeos, qué sale del navegador?

si no puedes, tienes una política de privacidad. una política es un documento que escribiste una vez. si puedes — y la lista es corta, y cada entrada es una funcionalidad que elegiste a propósito — entonces tienes una decisión de diseño. la mía son cuatro peticiones, y a partir de hoy puedo nombrar las cuatro y decidir sobre cada una.

esa es toda la disciplina. no «respetamos tu privacidad». una lista, contrastada con el panel de red, con las coordenadas dentro.