[{"data":1,"prerenderedAt":442},["ShallowReactive",2],{"blog-list-es-ES-all-any":3,"blog-tags-es-ES":435},[4,108,195],{"id":5,"title":6,"author":7,"body":8,"date":94,"description":95,"draft":96,"extension":97,"meta":98,"minutes":99,"navigation":100,"path":101,"seo":102,"stem":103,"tags":104,"__hash__":107},"blog_es\u002Fblog\u002Fwhy-there-will-never-be-ai-in-numori.md","Por qué nunca habrá IA en Numori","Erik Bjerke",{"type":9,"value":10,"toc":85},"minimark",[11,15,20,23,30,36,42,46,49,52,56,68,71,75,78],[12,13,14],"p",{},"Cada pocas semanas alguien pregunta cuándo tendrá Numori Notes un botón de\nresumir. La respuesta es nunca, y merece la pena explicar por qué, porque «sin\nIA» suena a preferencia estética y no lo es.",[16,17,19],"h2",{"id":18},"los-tres-costes-que-nunca-aparecen-en-la-lista-de-funciones","Los tres costes que nunca aparecen en la lista de funciones",[12,21,22],{},"Una función generativa dentro de una aplicación de notas trae tres costes que\nsuelen quedarse fuera del anuncio.",[12,24,25,29],{},[26,27,28],"strong",{},"Tus datos tienen que ir a algún sitio donde puedan procesarse."," Numori Notes\nguarda tus notas en local y las sincroniza a través de un relé que no tiene\nclaves. Un modelo no puede resumir algo que no puede leer. Añadir un botón de\nresumir significa o enviar tus notas a un servidor capaz de descifrarlas, o\ndescargar en tu portátil un modelo lo bastante grande para ser útil. Lo primero\nrompe la arquitectura. Lo segundo son 4 GB de descarga para ahorrarte leer tu\npropia lista de la compra.",[12,31,32,35],{},[26,33,34],{},"Alguien tiene que pagar la inferencia."," La inferencia no es gratis y no se\nabarata por usuario a medida que creces: escala con el uso, al contrario que el\nalmacenamiento, que cuesta menos por gigabyte cada año. Una empresa que añade\nfunciones de IA a una suscripción con un precio como el nuestro tiene tres opciones: subir el precio,\nlimitar la función hasta hacerla inútil, o encontrar otra forma de ganar dinero\ncon quien la usa. Las tres son peores que no tener la función.",[12,37,38,41],{},[26,39,40],{},"Cambia para qué sirve el producto."," En cuanto una función es caro mantenerla,\nhay que promocionarla. Consigue un sitio en la barra de herramientas, luego un\naviso, luego un mensaje sugiriéndote que la pruebes. El software que tiene que\njustificar un coste operativo deja de ser una herramienta y se convierte en un\nembudo.",[16,43,45],{"id":44},"pero-es-opcional","«Pero es opcional»",[12,47,48],{},"Que sea opcional es una distinción real y no quiero despacharla. Pero se degrada.\nLa función que se lanzó detrás de un interruptor en la versión 1 viene activada\npor defecto en la versión 4, porque las métricas decían que la adopción era baja y\nla adopción era lo que se estaba midiendo.",[12,50,51],{},"Y lo que es más importante: ser opcional no revierte el cambio arquitectónico. Si\nel servidor puede descifrar tus notas cuando pulsas el botón, entonces el\nservidor puede descifrar tus notas. Lo que importa es la capacidad, no la política\nactual sobre cuándo se usa. Es el mismo argumento que damos sobre las políticas de\nprivacidad en general: preferimos ser incapaces de leer tus datos antes que\nprometer que no lo haremos.",[16,53,55],{"id":54},"qué-hacemos-en-su-lugar","Qué hacemos en su lugar",[12,57,58,59,63,64,67],{},"La calculadora de Numori Notes ilustra bastante bien la alternativa. Interpreta\n",[60,61,62],"code",{},"48 GBP en EUR"," y ",[60,65,66],{},"12% de 340"," y te da una respuesta. Es un analizador sintáctico\ny un grafo de unidades. Se ejecuta en una fracción de milisegundo, enteramente en\ntu dispositivo, da la misma respuesta siempre y, cuando no entiende algo, lo dice\nen vez de inventarse un número plausible.",[12,69,70],{},"Esa última propiedad es la que merece atención. Una calculadora que se equivoca el\n2% de las veces no es una calculadora. Buena parte de lo que se publica como\nfunción de IA es una función que antes era exacta y ahora es aproximada, con un\ntono de voz más simpático.",[16,72,74],{"id":73},"el-compromiso","El compromiso",[12,76,77],{},"Sin asistentes, sin resúmenes, sin generación, sin clasificación de tu contenido,\nsin reordenaciones «inteligentes» y sin modelos de embeddings recorriendo tus\narchivos para construir un índice que podamos consultar.",[12,79,80,81,84],{},"Es una promesa fácil de verificar, y para eso publicamos todo: ",[60,82,83],{},"Numori-Notes"," no\ntiene ninguna dependencia de inferencia en su fichero de bloqueo, y nunca la\ntendrá. Si eso cambia, lo verás en un pull request antes de verlo en una versión, y\ntienes todo el derecho a decirlo bien alto.",{"title":86,"searchDepth":87,"depth":87,"links":88},"",3,[89,91,92,93],{"id":18,"depth":90,"text":19},2,{"id":44,"depth":90,"text":45},{"id":54,"depth":90,"text":55},{"id":73,"depth":90,"text":74},"2026-08-18","Renunciar a las funciones de IA no es nostalgia ni una estrategia de marketing. Es la única postura coherente con el resto de promesas que hemos hecho.",false,"md",{},6,true,"\u002Fblog\u002Fwhy-there-will-never-be-ai-in-numori",{"title":6,"description":95},"blog\u002Fwhy-there-will-never-be-ai-in-numori",[105,106],"principios","privacidad","XgjzH1sa5qtIWuS9KDH1PGvK5gslUtnIZVdQEbo-9hM",{"id":109,"title":110,"author":7,"body":111,"date":185,"description":186,"draft":96,"extension":97,"meta":187,"minutes":188,"navigation":100,"path":189,"seo":190,"stem":191,"tags":192,"__hash__":194},"blog_es\u002Fblog\u002Fopen-source-has-to-include-the-servers.md","El código abierto tiene que incluir los servidores",{"type":9,"value":112,"toc":178},[113,116,120,123,127,130,133,136,140,143,146,150,161,164,168,175],[12,114,115],{},"Muchas empresas centradas en la privacidad se describen como de código abierto.\nNormalmente eso significa que las aplicaciones son públicas y el backend no. Es\nmejor que nada, y es bastante menos de lo que parece.",[16,117,119],{"id":118},"qué-demuestra-un-cliente-abierto","Qué demuestra un cliente abierto",[12,121,122],{},"Bastante, en realidad. Si el cliente es abierto y su compilación es reproducible,\npuedes verificar que cifra antes de enviar, que las claves se derivan donde dice\nla documentación y que no contiene un SDK de analítica de terceros. Para contenido\ncifrado de extremo a extremo, eso es casi toda la historia: si el texto cifrado\nsale correctamente de tu dispositivo, la honestidad del servidor importa mucho\nmenos.",[16,124,126],{"id":125},"qué-no-demuestra","Qué no demuestra",[12,128,129],{},"Todo lo relativo a las partes que no van cifradas de extremo a extremo, que\nsiempre son más de las que el marketing sugiere.",[12,131,132],{},"Los metadatos son el caso evidente. A quién escribes, cuándo, con qué frecuencia,\ndesde qué IP y de qué tamaño era el mensaje. Nada de eso está en el modelo de\namenazas del cliente, y todo eso lo conserva o no lo conserva un código que no\npuedes ver. «No registramos direcciones IP» es una frase sobre un fichero de\nconfiguración que no te dejan leer.",[12,134,135],{},"Luego está todo lo operativo: los periodos de retención, la vida de las copias de\nseguridad, si el borrado es real, a qué terceros llama el servicio y qué ocurre\ncon una cuenta marcada por abuso. Todo decidido en un repositorio al que no tienes\nacceso.",[16,137,139],{"id":138},"la-versión-incómoda","La versión incómoda",[12,141,142],{},"El motivo por el que el código del servidor sigue cerrado casi nunca es que\ncontenga secretos. Los secretos van en un gestor de secretos, no en el código\nfuente. Sigue cerrado porque es donde viven las concesiones: la analítica que iba\na ser temporal, el proveedor que necesitaba texto en claro, el periodo de\nretención más largo de lo que insinúa la página de política.",[12,144,145],{},"No estoy acusando a nadie en particular. Digo que «confía en nosotros, el servidor\nestá bien» es la única afirmación de un discurso sobre privacidad que no se puede\ncomprobar, y es sistemáticamente la afirmación que se deja sin comprobar.",[16,147,149],{"id":148},"qué-estamos-haciendo-al-respecto","Qué estamos haciendo al respecto",[12,151,152,153,156,157,160],{},"Todos los servidores de Numori son públicos bajo la AGPL-3.0. ",[60,154,155],{},"Numori-CRDT"," es el\nrelé de sincronización, ",[60,158,159],{},"Numori-Auth"," es el servicio de identidad, y hoy mismo se\npueden leer los dos. Aquí importa que sea AGPL y no una licencia permisiva:\nsignifica que cualquiera puede ofrecer Numori como servicio, pero tiene que\npublicar también sus modificaciones. Si algún día nos compran y alguien decide que\nel relé de sincronización debería empezar a guardar un poco más, la licencia lo\nhace visible.",[12,162,163],{},"Esto no nos vuelve fiables. Nos vuelve comprobables, que es una propiedad más\nútil, porque no depende de que nuestras intenciones sigan siendo buenas.",[16,165,167],{"id":166},"la-pega-dicha-claramente","La pega, dicha claramente",[12,169,170,171,174],{},"Publicar el servidor no demuestra que el código que publicamos sea el código que\nestamos ejecutando. Esa brecha es real y no voy a fingir lo contrario. Cerrarla\nbien requiere compilaciones reproducibles y atestación, que está en la hoja de ruta\nde ",[60,172,173],{},"Numori-Updater"," y es genuinamente difícil.",[12,176,177],{},"Lo que sí te da publicarlo es una especificación lo bastante precisa para\nexigirnos que la cumplamos, una implementación independiente que puedes ejecutar tú\ny la posibilidad de marcharte a tu propio servidor sin perder nada. Es una posición\nmucho mejor que dar por buena una página de política.",{"title":86,"searchDepth":87,"depth":87,"links":179},[180,181,182,183,184],{"id":118,"depth":90,"text":119},{"id":125,"depth":90,"text":126},{"id":138,"depth":90,"text":139},{"id":148,"depth":90,"text":149},{"id":166,"depth":90,"text":167},"2026-08-04","Publicar un cliente y dejar el servidor cerrado demuestra muy poco. Esto es lo que un cliente abierto sí te dice, y lo que no.",{},5,"\u002Fblog\u002Fopen-source-has-to-include-the-servers",{"title":110,"description":186},"blog\u002Fopen-source-has-to-include-the-servers",[105,193],"código-abierto","OSqlAZapM4QZWsq-RPxM4T4PNJSPVKSLSW4cLa6mGH4",{"id":196,"title":197,"author":7,"body":198,"date":423,"description":424,"draft":96,"extension":97,"meta":425,"minutes":426,"navigation":100,"path":427,"seo":428,"stem":429,"tags":430,"__hash__":434},"blog_es\u002Fblog\u002Fbuilding-eighteen-apps-with-one-design-system.md","Construir dieciocho aplicaciones con un solo sistema de diseño",{"type":9,"value":199,"toc":417},[200,207,214,218,229,235,239,242,245,287,304,308,319,340,400,403,407,410,413],[12,201,202,203,206],{},"Un ecosistema de dieciocho aplicaciones hecho por un equipo muy pequeño solo\nfunciona si casi nada de la interfaz se escribe dos veces. ",[60,204,205],{},"Numori-UI"," es cómo lo\nconseguimos: una biblioteca de componentes en Tailwind 4 y Vue 3 con la que se\nmonta cada interfaz de Numori, incluida esta web.",[12,208,209,210,213],{},"No empezó siendo una biblioteca. Empezó como ",[60,211,212],{},"components\u002Fui\u002F"," dentro de Numori\nNotes, y ahí se quedó un año antes de que la sacáramos.",[16,215,217],{"id":216},"extraerla-consistió-sobre-todo-en-borrar-cosas","Extraerla consistió sobre todo en borrar cosas",[12,219,220,221,224,225,228],{},"La aplicación de notas tenía veintiocho componentes en esa carpeta.\nAproximadamente un tercio tenía alguna propiedad que existía para exactamente un\npunto de uso. Un ",[60,222,223],{},"Modal"," con un booleano\n",[60,226,227],{},"hideCloseButtonOnMobileWhenScrolled"," no es un sistema de diseño, es un registro\nde cambios.",[12,230,231,232,234],{},"La regla con la que nos quedamos: una propiedad se gana su sitio si dos pantallas\nsin relación la necesitan, o si quitarla obliga a quien la consume a reimplementar\nel componente. Todo lo demás pasó a ser un slot. ",[60,233,223],{}," bajó de once propiedades\na seis más un slot, y los puntos de uso se acortaron en lugar de alargarse.",[16,236,238],{"id":237},"la-accesibilidad-es-la-razón-para-extraerla-no-un-extra","La accesibilidad es la razón para extraerla, no un extra",[12,240,241],{},"Este fue el resultado realmente útil y no era el que buscábamos.",[12,243,244],{},"Cuando el manejo del teclado vive en cada pantalla, se hace bien en las pantallas\nen las que alguien estaba pensando en ello y se olvida en todas las demás. Nuestra\naplicación de notas tenía un modal que atrapaba el foco y un diálogo de ajustes que\nno, porque se escribieron con cuatro meses de diferencia.",[12,246,247,248,251,252,255,256,259,260,263,264,267,268,271,272,275,276,63,279,282,283,286],{},"Mover ese comportamiento al componente lo vuelve estructural. ",[60,249,250],{},"Toggle"," renderiza un\n",[60,253,254],{},"\u003Cbutton role=\"switch\">"," de verdad con ",[60,257,258],{},"aria-checked",", así que la activación por\nteclado viene gratis de la plataforma en lugar de venir de un manejador\n",[60,261,262],{},"@keydown.space"," que alguien recordó poner. ",[60,265,266],{},"Button"," acepta ",[60,269,270],{},"ariaLabel",",\n",[60,273,274],{},"ariaChecked",", ",[60,277,278],{},"role",[60,280,281],{},"tabindex"," como propiedades de primera clase, porque la\nalternativa (bajar a un ",[60,284,285],{},"\u003Cbutton>"," en crudo cuando necesitas un atributo ARIA) es\nexactamente cómo se acaba esquivando un sistema de diseño.",[12,288,289,290,292,293,63,296,299,300,303],{},"La parte honesta: no está terminado. ",[60,291,223],{}," todavía no atrapa el foco ni define\n",[60,294,295],{},"role=\"dialog\"",[60,297,298],{},"aria-modal",", y no se cierra con Escape. Son carencias reales,\nestán anotadas, y esta web deliberadamente no usa ",[60,301,302],{},"UiModal"," para nada importante\nhasta que se resuelvan.",[16,305,307],{"id":306},"tree-shaking-porque-una-web-de-marketing-no-debería-cargar-una-biblioteca-de-formularios","Tree-shaking, porque una web de marketing no debería cargar una biblioteca de formularios",[12,309,310,311,314,315,318],{},"La biblioteca no publica CSS precompilado ni un fichero barril que arrastre con\ntodo. Importa ",[60,312,313],{},"UiKbd"," y te llevas 0,4 kB. Importa ",[60,316,317],{},"UiButton"," y te llevas 16 kB, de\nlos cuales unos 7 kB son la tabla de rutas de iconos.",[12,320,321,322,325,326,275,329,332,333,335,336,339],{},"Ese último número es la razón por la que esta web delega los iconos en\n",[60,323,324],{},"@nuxt\u002Ficon",". La biblioteca incluye los treinta y seis glifos de MDI que necesitan\nsus propios componentes; una web necesita ",[60,327,328],{},"mdi:github",[60,330,331],{},"mdi:robot-off-outline"," y\ntreinta más que ningún componente usa. Así que el módulo de Nuxt de ",[60,334,205],{},"\nacepta una opción ",[60,337,338],{},"icons.component"," y entrega el renderizado de iconos a lo que la\naplicación anfitriona ya tenga:",[341,342,346],"pre",{"className":343,"code":344,"language":345,"meta":86,"style":86},"language-js shiki shiki-themes github-light github-dark","numoriUi: {\n  prefix: 'Ui',\n  icons: { component: 'Icon' },\n}\n","js",[60,347,348,361,375,394],{"__ignoreMap":86},[349,350,353,357],"span",{"class":351,"line":352},"line",1,[349,354,356],{"class":355},"sScJk","numoriUi",[349,358,360],{"class":359},"sVt8B",": {\n",[349,362,363,366,369,373],{"class":351,"line":90},[349,364,365],{"class":355},"  prefix",[349,367,368],{"class":359},": ",[349,370,372],{"class":371},"sZZnC","'Ui'",[349,374,271],{"class":359},[349,376,377,380,383,386,388,391],{"class":351,"line":87},[349,378,379],{"class":355},"  icons",[349,381,382],{"class":359},": { ",[349,384,385],{"class":355},"component",[349,387,368],{"class":359},[349,389,390],{"class":371},"'Icon'",[349,392,393],{"class":359}," },\n",[349,395,397],{"class":351,"line":396},4,[349,398,399],{"class":359},"}\n",[12,401,402],{},"Los sistemas de diseño suelen hacer esto al revés e insisten en ser los dueños de\nla capa de iconos.",[16,404,406],{"id":405},"qué-costó","Qué costó",[12,408,409],{},"Dos semanas y una clase de error genuinamente molesta. Como los componentes se\nconsumen como ficheros Vue en crudo y no como un paquete precompilado, hay que\ndecirle a Vite que no los preempaquete ni los externalice. Ahora son tres líneas en\nun plugin de Vite, y primero fueron una tarde de desconcierto.",[12,411,412],{},"Mereció la pena. La siguiente aplicación arranca con una interfaz funcional,\naccesible y traducida desde el primer día, y una corrección de accesibilidad llega\na dieciocho aplicaciones a la vez en lugar de solo a aquella sobre la que alguien\nabrió una incidencia.",[414,415,416],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":86,"searchDepth":87,"depth":87,"links":418},[419,420,421,422],{"id":216,"depth":90,"text":217},{"id":237,"depth":90,"text":238},{"id":306,"depth":90,"text":307},{"id":405,"depth":90,"text":406},"2026-07-21","Numori UI empezó como una carpeta de componentes dentro de la aplicación de notas. Extraerla nos enseñó más sobre accesibilidad que sobre APIs de componentes.",{},7,"\u002Fblog\u002Fbuilding-eighteen-apps-with-one-design-system",{"title":197,"description":424},"blog\u002Fbuilding-eighteen-apps-with-one-design-system",[431,432,433],"ingeniería","sistema-de-diseño","accesibilidad","xoVS2YyB62PVAhbdHc1X-hv6C9vqqXSi6QfdOqWTKQg",[436,437,438,439,440,441],{"tag":105,"count":90},{"tag":433,"count":352},{"tag":193,"count":352},{"tag":431,"count":352},{"tag":106,"count":352},{"tag":432,"count":352},1788170792385]