Picando Código
Mi 2025 en Nintendo Switch y Nintendo Switch 2
enero 14, 2026 12:30
Nintendo esperó hasta enero de 2026 para enviar a sus usuarios el resumen del año 2025 con Nintendo. Este año jugué casi el doble de horas que el año anterior. En 2024 jugué 270 horas de Nintendo Switch, mientras que en 2025 entre Switch y Switch 2 alcancé 499 horas 
Por suerte los Game Boys, Nintendo 3DS, Famicom, NES, Analogue 3D y GameCube no registran las horas que juego también. Sino estaríamos hablando de unas cuantas horas más. Aprovechando que Nintendo nos manda una página web con un montón de estadísticas y datos de lo que jugamos en 2025, dejo acá registro de esta información y aprovecho para repasar mi año con Nintendo.
2025 fue el año de Nintendo Switch 2

Antes de mostrarme las estadísticas, el sitio web hace una pausa simpática con un "oh espera, ¿qué es esto?" seguido de la imagen del Nintendo Switch 2. Celebra que empecé a jugar Nintendo Switch 2 este año y espera que la esté pasando bien (lo estoy pasando bien en lo que respecta a videojuegos, gracias).
En el gran esquema de las cosas, no importa mucho, pero el Nintendo Switch 2 tuvo algo de especial para mí. Fue la primera vez que obtuve una consola Nintendo el mismo día que salió. Me lo compré con Mario Kart World, y esa noche me quedé hasta tarde jugando Mario Kart. Creo que esa misma noche jugué todas las copas. También pasé bastante tiempo paseando en el modo Free Roam, recorriendo los distintos mundos en kart.
Mario Kart World es un bueno juego, el problema que tuvo es que le siguió a Mario Kart 8, el mejor Mario Kart hasta ahora. Hay muy poco que se le podría "mejorar" a Mario Kart 8. Lo vengo jugando desde el Wii U y lo volví a comprar en Switch. Es de los juegos que más he jugado online, y de los que más he jugado en general desde que tengo un Nintendo Switch. Si tengo ganas de jugar Mario Kart, voy a por 8 Deluxe en vez de Mario Kart World.
World es un buen juego. De repente los gráficos no hacen pensar "es un juego de próxima generación", pero el hecho de tener todo un mundo abierto para recorrer en el kart, sin pantallas de carga, es bastante impresionante. La impresión que me dio recorriendo en Free Roam fue de que era una demencia, pero habían hecho un Mario Kart Open World. Capaz deberían haberle dado más importancia a este modo durante el desarrollo (calculo que no se imaginaban lo popular que iba a ser).
La música está muy buena, mucho reversionado de temas clásicos de la saga, y hasta metieron un ska. Por alguna razón que no entiendo, no tienen esta música en su aplicación Nintendo Music. Pero nadie entiende qué hace Nintendo... Las carreras soportan hasta 24 corredores, lo que hace que las partidas online sean una experiencia sumamente caótica.
El agua en este juego está tremenda. Podemos correr por el océano o ríos, el kart se transforma automáticamente para navegar. Pero el agua es fascinante, y la física con las olas está muy bien hecha. Básicamente implementaron Wave Race en Mario Kart. Me cuelgo bastante a mirar los renders de agua en este juego tambien...
De los juegos exclusivos de Nintendo Switch 2, creo que Mario Kart World es el que menos he jugado hasta ahora. Capaz me hace falta jugarlo con alguien en el mismo sillón como hice tantas veces con amigos con Mario Kart 8 en Wii U. De repente con algunas actualizaciones más le ajustan cosas que hacen que me resulte más divertido. O capaz que Mario Kart 8 Deluxe es el mejor Mario Kart hasta ahora y no hay forma de superarlo. Veremos si este año anuncian contenido extra descargable.
El Nintendo Switch 2 es compatible con toda mi biblioteca de juegos para Nintendo Switch. Así que cuando se me pasó la novedad con Mario Kart World, seguí jugando Hyrule Warriors: Age of Calamity. Empecé este juego en Nintendo Switch, pero el Switch 2 me encontró en medio de la historia. A lo que el Switch 2 tiene más capacidad de hardware, algunos juegos de Nintendo Switch obtienen mejoras en rendimiento por defecto. Es el caso de Hyrule Warriors, donde noté una mejora importante. Particularmente en escenas donde manejamos a las Bestias Divinas, construcciones mecánicas gigantes. En Nintendo Switch estos niveles se manejan de manera bastante torpe y son medio lentos, pero con el Switch 2 la animación y movimiento es fluido y cómodo.
Después también volví a The Legend Of Zelda: Tears of the Kingdom (TotK). Lo había empezado en Nintendo Switch, pero le di un recreo. The Legend Of Zelda: Breath of the Wild (BotW) salió en 2017, y hasta que obtuve la precuela, lo seguía jugando de vez en cuando. Sabía que TotK iba a ser de estos, así que cada tanto lo dejaba en pausa para jugar otras cosas. También tengo el problema con estos juegos de Zelda que me vicio mucho y paso muchas horas compenetrado en esos mundos. Así que dada tanto me obligo a dejarlos.
Con el anuncio de las ediciones mejoradas de estos juegos para Nintendo Switch 2, decidí que iba a terminar TotK en Switch 2. Y así fue. En el resumen del año Nintendo cuenta la versión original y la versión mejorada para Switch 2 como títulos distintos. Así que tengo 143 horas de la edición de Switch y 32 horas de la edición de Switch 2. Jugar a éstos juegos con gráficos mejorados y 60fps hace la diferencia. ¡Qué bueno va a estar el primer Zelda exclusivo para Nintendo Switch 2!
El juego que más jugué este año fue Hyrule Warriors: Age of Imprisonment. Ya escribí bastante en el post enlazado, así que no tengo mucho para agregar. Es un excelente juego, anda de maravilla, y divierte mucho. Todavía no he jugado después de la última actualización, donde volvieron a agregar más misiones y contenido. I'm not just a traveller, I can fight too...
Estadísticas por mes
- Enero - 23 horas - El título más jugado fue Donkey Kong Country Returns HD, por 7 horas.
- Febrero - 27 horas - Ídem que enero, 7 horas también. Todavía no lo terminé, pero hay mejores Donkey Kong para jugar ahora...
- Marzo - 21 horas - El juego que más jugué fue Shadow of the Orient, también por 7 horas.
- Abril - 41 horas - Más jugado Star Overdrive.
- Mayo - 54 horas - Empieza a levantar la cantidad de horas con el Nintendo. El título más jugado con 24 horas fue Hyrule Warriors - Age of Calamity.
- Junio - 57 horas - Mismo título, 17 horas. Acá empiezo también a jugar al Nintendo Switch 2.
- Julio - 55 horas - El título más jugado es por primera vez un exclusivo de Nintendo Switch 2: Donkey Kong Bananza, 31 horas. Muy divertido, excelentes gráficos y muy buena música.
- Agosto - 59 horas - En agosto me compré la edición física de Cyberpunk 2077 y jugué ese mes por 28 horas. Es un juegazo, y la versión de Switch 2 se ve y funciona muy bien. Fue el primer juego que me hizo ver "esta es una consola de nueva generación". Lo sigo jugando en 2026, todavía no terminé todas las misiones, y es uno de los 3 títulos que más jugué en 2025.
- Setiembre - 36 horas - El título que más jugue fue SpongeBob SquarePants: The Cosmic Shake por 11 horas. Lo compré después de terminar SpongeBob SquarePants: Battle for Bikini Bottom Rehydrated. El primero me entretuvo, este último tiene algunas mejoras. Son juegos de plataforma 3D relativamente sencillos, pero muy divertidos particularmente para seguidores del dibujo animado.
- Octubre - 21 horas - Jugué 18 horas a Super Mario Galaxy. Este es de los mejores juegos de Mario en 3D. En mi opinión hasta mejor que Super Mario Odyssey. No sé qué tiene Odyssey, pero no me termina de convencer. He vuelto varias veces a probarlo y hay algo que se siente distinto. Galaxy y Super Mario 3D World siguen siendo de lo mejorcito que hay en materia de 3D para Mario en mi opinión. Obtuve el cartucho nuevo que incluye Mario Galaxy 1 y 2 para Nintendo Switch. Tengo pendiente en 2026 terminar Super Mario Galaxy 2.
- Noviembre - 54 horas - En noviembre salió Hyrule Warriors: Age of Imprisonment, y ya comenté cómo me fue con eso... 47 de las 54 horas fueron dedicadas a ese juegazo.
- Diciembre - 51 horas - En diciembre salió Metroid Prime 4: Beyond al que le dediqué 28 horas en el mes. Otro excelente juego para Nintendo Switch 2 (y Nintendo Switch también). Escribí bastante al respecto en el post enlazado, no mucho para agregar por ahora.
Nintendo también agrega un detalle del estilo de juego que jugamos:
- Acción 45%
- Aventura 21%
- Disparos 13%
- Plataformas 11%
- Supervivencia 6%
- RPG 4%
En total fueron entonces 499 horas jugando al Nintendo en 2025. Jugué un total de 87 juegos. Muchos de esos juegos jugué menos de una hora o dos. Tengo un montón de cosas para jugar más este año, y los clásicos a los que siempre vuelvo. De lo que viene anunciado hasta ahora para Nintendo Switch 2, no hay nada que me llame particularmente la atención. Con suerte eso me va a dar tiempo para ponerme al día con varios de los juegos que tengo pendientes.
Casi 500 horas de videojuegos en un año me parece un montón, particularmente comparado con otros años. Pero seguramente Fernando niño estaría orgulloso de que a mi edad siga disfrutando tanto de los videojuegos. En 2025 también obtuve mi libreta de conducir, empecé a ir a clases de Italiano, leí muchos cómics y algunos libros, logré varias cosas en el trabajo y paseé bastante. Así que en retrospectiva creo que dentro de todo fue un año bastante balanceado.
Además del resumen de 2025, Nintendo incluyó un montón de estadísticas de años anteriores, hasta 2017. Pero esa información será digerida más adelante, de repente para otro post en el futuro. Como dice el crá de Victor Lucas de EPN, Play forever!

Picando Código
Versión 1.1 de ci_uy: La gema Ruby para validar números de cédula de identidad
enero 12, 2026 10:24
Acabo de publicar la versión 1.1.0 de la gema Ruby ci_uy para validar números de cédula de identidad uruguaya. La versión anterior, 1.0.1, la publiqué en julio de 2019. No es que desde entonces haya abandonado el desarrollo del proyecto, es que realmente no necesita muchos cambios. Podría considerarse "software estable", hace una sóla cosa y la hace suficientemente bien (para mí).
El código Ruby implementa el algoritmo de validación de los números de cédula de identidad uruguaya, y provee algunos métodos de ayuda. Pero a menos que algo cambie en el documento de identidad uruguayo, no va a necesitar actualizaciones grandes. O sería interesante si alguna versión de Ruby se la juega y hace cambios que rompan compatibilidad con la sintaxis actual. Ahí sí que tendría que cambiar más, pero dudo que pase.
A lo largo de los años suelo volver al proyecto al menos una vez después de diciembre, cuando sale una versión nueva de Ruby. Voy agregando las versiones nuevas de Ruby a la matriz de versiones bajo las que corre los tests. Así me aseguro compatibilidad 100%.
Esta actualización además del mantenimiento habitual, simplifica una línea de código y poco más. Subí el código a Codeberg y cambié la metadata de la gema para que apunte a ese repositorio en vez de GitHub. Ya de paso también mandé el repositorio a mi instancia de Forgejo. De a poquito me voy alejando más de GitHub y el slop de Microslop.
¡La versión 1.0.1 tuvo 9.812 descargas! Para un total de 42.872 al momento de escribir esto. Hasta donde sé, la versión web es mucho mas popular. Por lo menos cuando me llega algún comentario es de alguien que usó esa web. Pero a lo mejor con esta versión en unos años alcanza las 50.000 descargas. Ahí veremos de hacer un festejo acorde...

Cómo se ve Rubygems en 2026 tras publicar la gema
El post Versión 1.1 de ci_uy: La gema Ruby para validar números de cédula de identidad fue publicado originalmente en Picando Código.
Picando Código
Empezando con Docker - Imágenes y contenedores
enero 12, 2026 09:00
Docker es un servicio de contenedores de software que se ha vuelto bastante popular a la hora de publicar aplicaciones en producción. Básicamente, permite empaquetar software de una forma que es conceptualmente similar a una máquina virtual. A diferencia de una máquina virtual, los contenedores no cuentan con software de plataforma, por lo que son bastante livianos y portables. El software empaquetado se puede ejecutar igual en cualquier plataforma que ejecute Docker.
La tecnología introduce algunos términos nuevos, que resultan un poco confusos al empezar a usar Docker. ¿Qué es una Imagen Docker? ¿Qué es un contenedor Docker? ¿Cómo lo ejecuto? Este post intenta explicar todos éstos términos de la forma más directa y con un ejemplo bien básico.
El primer paso es descargar e instalar Docker. Se puede obtener para varias distribuciones GNU/Linux (CentOS, Debian, Fedora, Ubuntu, y más), Mac OS y Windows.
Una vez instalado Docker, necesitamos crear un archivo Dockerfile. El Dockerfile es un set de instrucciones para que Docker construya una imagen. Se puede pensar como el código fuente o un plano. Por defecto, Docker va a buscar un archivo de nombre "Dockerfile" sin extensión. Se puede usar el parámetro -f para especificar un archivo distinto, pero es buena práctica usar el nombre por defecto y tener un Dockerfile por directorio si necesitas más de una imagen en un proyecto.
Empecemos por escribir nuestro Dockerfile:
WORKDIR /picandocodigo
Lo que estamos diciendo con éstas dos líneas es "quiero basar mi imagen en ruby:alpine", esto va a construir sobre dicha imagen. Podemos explorar Docker Hub, un repositorio de imágenes Docker, para encontrar más imágenes en las que basarnos. Estamos usando ruby:alpine, porque vamos a ejecutar código Ruby. Esta imagen nos provee una distribución Linux liviana con Ruby instalado. La imagen alpine es un punto de partida popular debido a ser muy pequeña (~5MB) y minimalista.
También estamos diciendo "usa /picandocodigo como nuestro directorio de trabajo". Luego iremos agregando algunas cosas al Dockerfile para que haga algo además de descargar la imagen y definir el directorio de trabajo:
WORKDIR /picandocodigo
EXPOSE 4000
RUN apk add --no-cache bash wget
RUN wget https://picandocodigo.net/img/diver.gif -O diver.gif
RUN gem install webrick
CMD ruby -run -e httpd . -p 4000
Le estamos pidiendo a Docker que exponga el puerto 4000 e instale bash y wget con "apk add", porque así de minimalista es Alpine Linux. Ejecutamos wget para descargar un archivo gif y lo guardamos a 'tiburon.gif'. Después instalamos la gema webrick para servir contenido por HTTP. Por último, usamos CMD, una instrucción que define el comando a ser ejecutado cuando corramos un contenedor a partir de nuestra imagen. En este caso ejecutamos un comando Ruby que sirve el directorio actual ('/picandocodigo', el que definimos en WORKDIR) a la web en el puerto 4000.
Es un buen momento para hablar sobre imágenes Docker ("Docker images") y contenedores Docker ("Docker containers"). Una vez que tenemos escrito nuestro Dockerfile, generamos una Imagen Docker con él. Algo que me ayudó a entender qué es una imagen, fue compararlo con una imagen ISO (de cuando quemas un CD o pendrive USB). No es exactamente lo mismo, pero fue la primera cosa que me vino a la mente al pensar en una "imagen". Si vienen del paradigma de Programación Orientada a Objetos, una imagen sería una "clase".
Con una imagen, podemos ejecutar contenedores Docker. Éstos serían los "objetos", si seguimos el paradigma de Programación Orientada a Objetos: instanciamos contenedores (objetos) a partir de una imagen (clase). Podemos ejecutar cuantos contenedores Docker queramos con la misma imagen. También podemos construir una imagen sobre otra imagen, como estamos haciendo en el ejemplo sobre la imagen ruby:alpine.
Ahora que tenemos nuestro Dockerfile armado, podemos construirlo:
Necesitamos decirle a "docker build" dónde está el archivo Dockerfile, así que estamos pasando . como parámetro para decir que está en el directorio actual. El parámetro -t nos permite darle un nombre y opcionalmente una etiqueta (con el formato name:etiqueta) a nuestra imagen, la cual ha sido construida ahora:
$ docker images IMAGE ID DISK USAGE CONTENT SIZE picandocodigo/diver:latest df94147085f5 158MB 52.2MB
Así que ahora podemos ejecutar un contenedor con esta imagen usando su etiqueta:
Para procesos interactivos (como un shell), usamos el parámetro -i para mantener STDIN abierto incluso si no está atado, y -t para alocar una pseudo-TTY al proceso del contenedor. Podemos escribirlo también como -it. Le estamos diciendo a Docker que mapee el puerto 4242 de nuestro sistema al puerto 4000 del contenedor (con -p).
Si visitamos http://localhost:4242/diver.gif en nuestro navegador, vamos a ver una animación submrina. Podemos usar cualquier puerto, como el puerto 80 o 3000, pero hay que fijarse que éstos puertos pueden estar en uso (en particular el puerto 80 es el puerto por defecto para servidores web). Ya que éstos son contenedores, basados en nuestra imagen, podemos correr otra instancia, por ejemplo en el puerto 9090:
La misma imagen va a estar disponible en http://localhost:4242/diver.gif.
Hice este diagrama que espero ayude a ilustrar la idea y sea más fácil interpretar los conceptos:

Podemos encontrar más información sobre Docker en su Guía Inicial o visitar su documentación oficial. Algunos comandos importantes más que nos pueden servir de referencia:
docker image rm- Nos permite eliminar una imagen. Le podemos pasar el ID o la etiqueta de la imagen. Podemos ver las imágenes condocker imagescomo hicimos más arriba.docker container rm- A veces queremos borrar una imagen, y no podemos porque hay un contenedor que la está usando. O simplemente queremos eliminar un contenedor. Este es el comando para eso.docker exec- Ejecuta un comando en un contenedor en ejecución. Por ejemplo en mi post sobre Forgejo, ejecutédocker exec -it forgejo /bin/shpara tener una terminal sh dentro del contenedor.
La documentación es bastante buena y todos estos comandos y más están documentados ahí. Espero que haya servidor
Este post fue originalmente publicado en inglés en noviembre de 2017 en el blog de Cultivate (mi antiguo trabajo). Lo traduje al español, actualicé, revisé y agregué un poco de contenido extra. En su momento tuvo muy buen recibimiento, espero que todavía sea de ayuda. Cuando todavía existía Twitter, ¡Docker compartió el post en su cuenta oficial! Muy buen feedback 

He visto hablar bastante sobre Podman recientemente. No me he metido en el tema todavía como para saber bien cómo funciona, pero tengo entendido que tiene una API compatible con Docker. Así que debería ser relativamente fácil aprenderlo viniendo de Docker. Pero eso es un tema para otro día... (pero qué simpáticas las mascotas).
El post Empezando con Docker - Imágenes y contenedores fue publicado originalmente en Picando Código.Variable not found
Top 10 posts 2025 en Variable not found
enero 12, 2026 07:05
Desde hace ya casi veinte años, reservo el primer post de enero para compartir con vosotros los contenidos más leídos del año anterior, sobre todo para asegurarme de que no os habéis perdido ninguno de ellos 😊
Así que, como no podía ser de otra forma, aquí tenéis los top posts de 2025, una lista prácticamente copada por artículos relacionados con .NET y C#, especialmente en sus últimas versiones, que han sido los grandes protagonistas del año en el blog.
Top 10 posts 2025 en Variable not found
Empezando por el final de la lista, en el puesto número 10 encontramos el post Extension members en C# 14 (.NET 10), donde hablamos de una de las novedades más interesantes del lenguaje C# en su versión 14: los bloques extension y la nueva capacidad para crear propiedades extensoras o extender tipos estáticos.
Justo a continuación, en la posición número 9 vemos que a la hora de buscar valores en el interior de un conjunto de datos en memoria, podemos hacer como siempre: recorrerlo manualmente hasta detectar lo que buscamos... o bien exprimir al máximo las capacidades del hardware más moderno y su soporte en .NET 10. Explicamos cómo hacerlo en el artículo Dispara la eficiencia de tus búsquedas en memoria con .NET usando SearchValues.
En octavo lugar, el post Simulando enumerados de texto en C# muestra cómo, abusando un poco de conversores implícitos y algunos otros truquillos, podemos crear enumerados de texto, algo que encontramos en otros lenguajes pero que no está soportado de forma nativa en C#.
Seguimos ascendiendo en la lista, y encontramos algo que, a tenor de las visitas recibidas, parece preocuparnos a muchos: cómo gestionar de forma centralizada las dependencias NuGet en soluciones de gran tamaño. El post Gestión centralizada de paquetes NuGet en soluciones y proyectos .NET describe el CPM (Central Package Management) de .NET y muestra diferentes escenarios y posibilidades de uso.
Reconozcamos que los desarrolladores .NET siempre hemos mirado con envidia a otros lenguajes o tecnologías que permitían escribir scripts ejecutables directamente desde la línea de comandos, como Python o Node.js. Pues, como mostramos en el post Ejecutar directamente archivos C# desde línea de comandos con "dotnet run", esto ya es posible en .NET 10 y C# 14, gracias a la nueva capacidad de ejecutar archivos .cs directamente con dotnet run <filename.cs>, sin necesidad de crear proyectos o soluciones, ni de compilar nada previamente.
Y llegamos al ecuador de la lista, con el post 8 cosas que (quizás) no conocías sobre la clase Random de .NET, donde repasamos algunas curiosidades y funcionalidades poco conocidas de esta clase tan utilizada en multitud de aplicaciones, y cuya potencia y posibilidades suelen pasar desapercibidas.
Ya en el cuarto puesto, rondando el podio, hablamos de los peligros de no gestionar correctamente las excepciones no controladas en servicios en segundo plano (BackgroundService) en aplicaciones ASP.NET Core. En el post ¡Cuidado con las excepciones no controladas de servicios en segundo plano (BackgroundService) en ASP.NET Core! explicamos el problema, que incluso puede llegar a detener por completo aplicaciones en producción, y cómo podemos solucionarlo para hacer nuestras aplicaciones más robustas y fiables.
En tercera posición, el post Aprovecha al máximo los archivos .http en Visual Studio usando variables dinámicas describe las mejoras introducidas en Visual Studio para trabajar con archivos .http, que nos permiten crear y ejecutar peticiones HTTP directamente desde el editor, y cómo podemos usar variables dinámicas para hacerlos aún más potentes y flexibles. Personalmente, me encantan estos archivos para probar y documentar APIs RESTful de forma rápida y sencilla, y parece que no soy el único, ya que este post ha tenido una gran acogida.
El segundo puesto es para el post Obtener información sobre el origen de la invocación de un método con los atributos "caller info" de .NET, donde explicamos cómo usar atributos como CallerMemberName, CallerFilePath, CallerLineNumber y CallerArgumentExpression para obtener información sobre quién llamó a un método, desde qué archivo, en qué línea, y qué expresiones se utilizaron como argumentos de la llamada, lo que puede ser muy útil para depuración, logging y otras tareas similares.
Y por fin en el puesto número uno, el post más leído del año 2025 en Variable not found ha sido JavaScriptizando C#: chequeo de nulos usando conversores implícitos, donde mostramos que la extensibilidad de C# nos permite lograr chequear nulos usando una sintaxis similar a la habitual de JavaScript.
Menciones especiales
Por último, creo que vale la pena destacar dos posts que prácticamente empataban con la posición número 10, y que finalmente tuve que dejar fuera del top ten. Unos aplausos también para ellos 😉
-
Comparar strings ha sido siempre bastante sencillo, pero no cuando se trata de ordenar o comparar valores numéricos incluidos en el interior de cadenas de texto. El post Comparación natural de cadenas en .NET 10 trata de la interesante novedad de .NET 10 que hará posible que "1.2" por fin pueda considerarse mayor que "1.10" sin necesidad de recurrir a complicadas funciones personalizadas.
-
Los archivos de solución de .NET ".sln" siempre han sido extensos, crípticos y difícilmente editables manualmente, pero esto se acabó. En el artículo .SLNX: El nuevo formato de soluciones .NET hablamos del nuevo formato de soluciones ".slnx" introducido en .NET 10, abierto, más simple, conciso y ligero.
Publicado en Variable not found.
Picando Código
Nuclear Throne - Steam
enero 09, 2026 08:00

Nuclear Throne es un juego roguelite de disparos con cámara cenital. Está disponible en Steam, con versión nativa para Linux. Controlamos un personaje visto desde arriba, y avanzamos a travésde escenarios generados donde tenemos que matar a cuanto enemigo encontremos. Es una ambientación post-apocalíptica donde los humanos ya no existen y el mundo está habitado por mutantes y monstruos.
El juego se publicó originalmente en 2015 y tuvo mucho éxito. Fue uno de los primeros títulos de acceso anticipado de Steam con más de un millón de jugadores. Está hecho en GameMaker y el equipo hacía retransmisiones en directo cada semana de su proceso desarrollo. Se terminó publicando tras 99 actualizaciones semanales, el 5 de diciembre de 2015. Lo obtuve en 2016 con el Humble Indie Bundle 17 allá por agosto de 2016, ¿se acuerdan de esos? Lo estoy jugando porque exactamente 10 años después de su publicación, el equipo decidió lanzar la mega actualización número 100.
La historia del juego es que hay que alcanzar el Trono Nuclear, pero no llegué tan lejos, ¡es difícil! Elegimos un personaje y tenemos que ir atacando y recogiendo radiación para mutar y adquirir habilidades nuevas. Podemos usar el mouse y teclado, moviendo al personaje con las teclas W, A, S, y D y apuntamos la mira y disparamos con el mouse. También podemos usar un gamepad moderno con dos palanquitas. Ahí controlamos al personaje con la palanquita izquierda y la mira con la derecha, al estilo "twin-stick shooter".
Cada personaje tiene habilidades o características distintas. Creo que inicialmente sólo estaban Fish y Crystal, un equivalente mutante a monstruo de la laguna y un monstruo de cristales. Fish tiene la habilidad de poder rodar para esquivar ataques y enemigos, y Crystal se vuelve super sólida y se hace temporalmente invulnerable. A medida que avanzamos, vamos desbloqueando personajes nuevos.
A lo que es difícil, de repente convendría quedarse con un personaje, aprenderlo bien y usar siempre ese hasta que dominemos el juego. Pero lo caótico de elegir un personaje aleatorio también está divertido. Después de varias tandas donde elegía un personaje aleatorio (y encontraba que moría muy rápido), empecé a usar siempre los mismos. Mis dos preferidos hasta ahora son Fish, porque puede rodar para esquivar ataques y enemigos, y Eyes porque atrae los ítems con telepatía. Aparte Eyes es un monstruo azul con muchos ojos en la cabeza que se ve divertido.
Los gráficos son pixelados y recuerdan a la época 16/32bit. Los escenarios son generados al azar cada vez, y están plagados de monstruos, mutantes y cofres donde conseguimos más armas, municines y botiquines para recuperar vida.
No soy expero en las diferencias entre Roguelike y Roguelite (tampoco me parece una discusión tan importante). Pero mi idea general del género es:
- Cuentan con escenarios generados con procedimientos.
- Al morir hay que empezar de nuevo y repetir. Generan un loop donde seguimos intentando a ver cuán lejos llegamos esta vez.
- Vamos obteniendo ítems (distintos en cada vuelta) que modifican las habilidades de nuestro personaje.
Esto tiene sus pro y sus contra. Por un lado, si el juego está bueno, tienen un aspecto adictivo. Perdemos mucho, pero es rápido, así que no cuesta nada volver a empezar. Y es difícil, pero cada nueva partida mejoramos un poquito o aprendemos algo nuevo, o llegamos más lejos que la anterior y queremos volver a probar. A lo que los niveles no son diseñados manualmente, sino que toca uno al azar cada vez, no podemos aprender o acostumbrarnos. Esto agrega más al desafío. Se podría hablar de de no tener niveles "curados" por un diseñador en particular, y que siempre tocan al azar, como desventaja. Pero seguramente los procedimientos para generar los niveles estén bastante estudiados.
Nuclear Throne tiene todo esto, y me resultó bastante difícil pero me enganchó. También al ser rápido, una partida dura poco y volvemos a jugar enseguida. Es ideal para jugar partidas cortas cada tanto, dejarlo y volver en otro momento. Es entretenido y como decía, un poco adictivo. Las pantallas de carga tienen bastante humor y el estilo del arte pixelado está bueno.
Inicialmente jugué con mouse y teclado. Después probe jugar con el control de XBox y me gustó un poco más. Creo que lo hace más difícil, pero por costumbre prefiero jugar así. No sé si el control de XBox es ideal para este juego, particularmente la palanca derecha me resultó un poco incómoda. Ya que estaba probé con el Nintendo Switch Pro Controller, y me resultó mucho mejor. Tanto la palanquita como el orden de los botones A, B, Y, X. A lo que XBox tiene invertidos A con B y X con Y, cada vez que juego Steam con ese control y me dicen que presione un botón, tengo que pensar unos milisegundos antes de apretar. Jugué Nintendo demasiado tiempo para estar invirtiendo el orden de los botones a esta edad.
Finalmente volví a jugar más en el escritorio con el mouse y teclado. Sigo prefiriendo la tele con el control, pero el juego es difícil. Para llegar lejos hay que usar todo lo que podamos a nuestro favor.
Hay un modo cooperativo local para hasta 4 jugadores. Debe ser muy divertido jugar con alguien más, pero no he tenido la oportunidad todavía. En esta nueva actualización rediseñaron algunas de las mecánicas cooperativas, así que debe estar interesante. Lamentablemente no tiene para jugar online. Le propuse a grilix con quien alguna vez jugamos algún juego del estilo "twin-stick shooter", (no me acuerdo el nombre del juego). También él me ha recomendado juegos roguelike antes, así que de repente hubiera estado bueno unas partidas. Quedará para alguna jugada presencial.
Por ahora sólo llevo desbloqueados algunos de los personajes como el robot y la planta. Hay 16 en total y skins desbloqueables, así que tengo mucho por ver. Uno de los personajes, Cuz, fue agregado en esta última actualización. Tiene la habilidad Cry que atraviesa enemigos y proyectiles y puede llevar hasta 3 armas.
Como mencionaba, es un juego que está bueno para jugar algunas partidas de a rato y volver después. Pero siento que me queda todavía muchísimo por descubrir. Hay niveles secretos, entré a uno de casualidad una vez por ahora, pero debería investigar a ver si descubro más. Ni que hablar de desbloquear más personajes, y llegar al Trono Nuclear. La actualización también agrega una modalidad personalizada, donde definimos las reglas de las partidas. Podemos desbloquear todo y hacerlo muy fácil. Por ahora prefiero seguir jugando e ir descubriendo las cosa, pero no descarto que si me canso o me frustro termine usando este modo para ver todo 
Entre las mejoras técnicas de la actualización, se agregó soporte para 60 frames por segundo, soporte para pantalla ancha y un montón de detalles visuales y de usabilidad más. En Steam se encuentra el detalle de todo lo que se agregó.
En los últimos años se siente como que el mercado se inundó de juegos del estilo. Capaz por eso cada vez que veo un anuncio de un juego nuevo (indie o no), al leer "roguelike" me genera un poco de leve rechazo (muy suave). De repente agregarle las mecánicas "roguelike" hace que tenga un aspecto adictivo y con eso se aseguran de enganchar a los jugadores por más tiempo y compensan otras carencias. También como pasa con todo, cuando algo se pone de moda y le va bien, mucha gente intenta replicar ese éxito. Así como hace un tiempo estaba la tendencia que todo juego 3D "tenía que ser open-world", surgió la tendencia "todo juego 2D tiene que tener elementos roguelike". Como todo, pasará y vendrá otra cosa, y quedarán los ejemplos exitosos por su mérito.
Sólo desvaríos de un viejo cínico, no me hagan caso. Me gustan los juegos que mencioné y me divierto jugándolos, ¡a disfrutarlos!
Si les interesa ver más de cómo es Nuclear Throne, visiten su página de Steam y dejo el tráiler porque un video vale más que mil fotos. Técnicamente el video son miles de fotos una atrás de la otra, creando la ilusión de que se mueven, ¿no? Así que técnicamente siguen siendo mil o más fotos, pero se presentan en un formato más amigable que navegar una página web con miles de fotos... En fin, el tráiler!

El post Nuclear Throne - Steam fue publicado originalmente en Picando Código.
Picando Código
Alojando mi propio servidor git con Forgejo
enero 08, 2026 11:00
Forgejo es una plataforma de software web colaborativa para alojar repositorios git. Es software libre y gratuito, publicado bajo la licencia GPLv3. Una de las plataformas más populares actuales de alojamiento git es GitHub. Forgejo es similar, con la ventaja de ser software libre y permitirnos alojarla donde, cómo y cuándo queramos. Codeberg.org es una instancia de Forgejo, una plataforma equivalente a GitHub, pero con otro montón de ventajas: es un esfuerzo sin fines de lucro, no yanqui, liderado por la comunidad.
Ambos proyectos están bajo el paraguas de Codeberg e.V., una organización democrática sin fines de lucro en Alemania.
Forgejo se jacta de ser "fácil de instalar y de bajo mantenimiento, simplemente hace el trabajo". Y debo decir que en mi experiencia hasta ahora, esto se cumple. Además su documentación da gusto. No es que haya necesitado buscar demasiada cosa, una vez que quedó funcionando no le cambié mucho. Pero siempre que necesité algo lo encontré ahí.
Instalando Forgejo
Para mi instancia local de Forgejo, estoy usando mi leal Raspberry Pi 4 donde también tengo alojado Navidrome, FreshRSS y alguna cosa más de la que escribiré más adelante:

Forgejo publica una versión estable cada tres meses y una versión de soporte a largo plazo una vez por año. Se publican versiones parches con arreglos de bugs y vulnerabilidades de seguridad más frecuentemente. Tenemos un montón de opciones para instalarlo: descargar un binario, usar el gestor de paquetes de nuestro sistema o con Docker.
En mi caso elegí usar Docker con un archivo docker-compose. Docker Compose nos permite definir y ejecutar aplicaciones con varios contenedores Docker. En este caso por un lado necesitamos el contenedor para forgejo y opcionalmente el contenedor para la base de datos. Forgejo nos provee ejemplos para armar una versión a la medida. También funciona con Podman, pero no tengo experiencia con esta herramienta así que por ahora me quedo con Docker.
Si no usamos uno de los ejemplos de docker-compose con base de datos (MySQL o PostgreSQL), va a usar SQLite por defecto. En mi caso creé un directorio forgejo en /home/fernandoy ahí adentro creé el archivo compose.yml. Copie y pegué el ejemplo con PostgreSQL de la documentación oficial, seteando algunas variables a medida. Al ejecutar docker compose up, tenemos Forgejo corriendo en el puerto 3000 (por defecto) de nuestro sistema. Y así de fácil fue.
Al entrar al sitio web (e.g. http://raspberrypi:3000) tenemos que seguir algunas configuraciones y registrar un primer usuario. Éste usuario va a tener permisos de administración. Y así queda todo armado. ¡Es momento de empezar a crear repositorios y subir código!

De a poco voy a ir agregando más de mis proyectos. Lo que vengo haciendo es cada vez que modifico o actualizo algo en un proyecto personal, agrego el repositorio remoto nuevo y lo publico en Codeberg y Forgejo. Como la mayoría ya tienen un remoto origin que dirige a GitHub, estoy agregando local y codeberg para los nuevos remotos. La escasez de proyectos muestra lo poco que he estado trabajando en mis proyectos personales los últimos meses... Pero eso cambia este año 
Algunas cosas que aprendí
Forgejo tiene una herramienta forgejo cli para administrar nuestra instancia. Para entrar al contenedor y usarla tenemos que ejecutar:
Al ejecutar un comando con forgejo, me saltó el error:
Forgejo is not supposed to be run as root.
Mirando el archivo /etc/group del contenedor, está listado el usuario git, así que ejecuté su - git para cambiarme al usuario git y se resuelve ese error.
Una vez estuve un tiempo sin entrar a la web y me olvidé el nombre de usuario y contraseña. Soy viejo, perdón... Pero con la línea de comando me resultó bastante fácil ver los usuarios y actualizar la contraseña. Con el comando forgejo admin user list veo la lista de usuarios, en este caso uno sólo picandcodigo, y con el siguiente comando puedo cambiarle la contraseña:
forgejo admin user change-password --username picandocodigo --password nuevoPassword
Inicialmente estaba usando la versión 12 de Forgejo. Pero publicada la versión 13, decidí actualizarme. Edité el archivo compose.yml y le cambié la versión a la imagen en image: codeberg.org/forgejo/forgejo:13. Para actualizar la imagen Docker de forgejo, ejecuté docker compose pull, y quedó ejecutando Forgejo 13. Tuve suerte, todo siguió funcionando bien, sin ningún problema. En algún momento debería escribir un script que se fije y me avise si hay una versión más nueva para actualizar. Pero mientras tanto me da algo de mantenimiento para hacer en mi Raspberry Pi.
Pensamientos finales
Otro aspecto muy interesante de Forgejo es que se está trabajando activamente en la federación. Esto implicaría que distintas instancias de Forgejo podrían conectarse entre sí y no estaría todo centralizado en Codeberg y otras asociaciones que decidieran alojar instancias de Forgejo. Esa es la web autónoma, sustentable y abierta que queremos
(Yo y mis otros yos, por lo menos). Ya hay algunos plugins que proveen esta funcionalidad, pero en el futuro va a estar integrada nativamente en el código.
Gentoo es un proyecto software libre grande que recientemente migró de GitHub a Codeberg.: "Más que nada por los intentos contínuos de forzar el uso de Copilot (nota de Fernando: el clippy de código de Microslop/GitHub) para nuestros repositorios, Gentoo está actualmente considerando y planea la migración de nuestros espejos de repositorio y contribuciones a través de pull requests a Codeberg". Seguramente no sea el último.
Dejar de usar GitHub del todo es muy difícil por varias razones. Microslop tiene que terminar de romperlo para que la gente siga migrando, y más proyectos grandes tienen que acompañar la migración. Pero de a poco seguro va a ir ocurriendo. Mi teoría es que de acá a un tiempo, GitHub va a pasar a ser algo así como Jira. Una herramienta que en teoría "hace su trabajo", pero sólamente la usan las empresas corporativas porque alguien les vendió la idea de que así se hace el desarrollo. Microslop lo va a seguir arruinando y llenando de lo que llaman AI éstos días, y seguirá empeorando.
Por ahora no voy a quitar mis proyectos de GitHub, pero proyectos personales nuevos ya van a ir sólo a Codeberg y mi instancia de Forgejo. Espero que este artículo ayuda a inspirar a más gente a probar Codeberg y Forgejo, y con suerte alojar su propia instancia.
El post Alojando mi propio servidor git con Forgejo fue publicado originalmente en Picando Código.Picando Código
Actualización de mullvadrb - versión 0.0.9
enero 05, 2026 10:40

Publiqué la versión 0.0.9 de la gema Ruby mullvadrb, la interfaz de usuario de terminal para Mullvad VPN en Ruby. Esta actualización fue bastante chica. Desde agosto tenía código actualizado localmente, que venía usando casi a diario. Pero no lo había ni subido a git, ni que hablar de hacerlo disponible en una actualización de la gema.
Modifiqué un poco el código que lista los servidores. En parte para no mostrar como opciones las conexiones a través de OpenVPN. Mullvad anunció ya hace bastante que iban a dejar de soportar OpenVPN para usar sólo WireGuard. El soporte termina el 15 de enero de 2026. Creo que no debería cambiar nada si tenemos una lista de servidores actualizados. Pero puede quedar una versión vieja en el archivo de configuración que incluya conexiones con OpenVPN. Así que para acelerar el tema, la aplicación ni siquiera considera las conexiones OpenVPN.
Lo otro que hice fue un cambio en el código que detecta y muestra el nombre del país de la conexión. Usándola en local, me dio un error con "Czech Republic", porque la lógica que escribí inicialmente no consideraba el espacio en el nombre. ¡Por eso escribimos tests! Que en esta aplicación el test es que la uso casi a diario y me funciona...
Desde ahora el código fuente está disponible también en Codeberg. Hace tiempo que quiero salirme de GitHub. Si bien lo voy a seguir usando en el ámbito laboral, lo que sean mis proyectos personales los voy a mantener en Codeberg, GitHub y mi servidor Forgejo autohosteado en local. Que para eso git es descentralizado...
Por último, empecé a usar la gema con Ruby 4.0 que se publicó el pasado 25 de diciembre. Así que agregué un mensaje a la lista de cambios que está probada en Ruby 4.
Si bien la gema tiene más de 3.000 descargas, no tengo ni idea si alguien más la usa, o las descargas son sólo de bots. Pero a mí me viene resultando súper útil. Así que la seguiré actualizando mientras siga usando Mullvad como solución VPN.
El post Actualización de mullvadrb - versión 0.0.9 fue publicado originalmente en Picando Código.Navegapolis
Spec-Driven Development
diciembre 24, 2025 08:53
El contexto: los problemas del vibe coding
Para entender Spec-Driven Developmen (SDD) o, hablando en nuestro idioma: desarrollo guiado por especificaciones, es bueno empezar conociendo el problema que intenta solucionar.
En febrero de 2025, Andrej Karpathy (cofundador de OpenAI y exdirector de IA en Tesla) acuñó el término vibe coding en un tweet que se hizo viral(1)
«Hay un nuevo tipo de programación que llamo ‘vibe coding’, donde te dejas llevar completamente por las vibraciones, abrazas los exponenciales, y olvidas que el código siquiera existe. […] Acepto todo siempre, ya no leo los diffs. Cuando recibo mensajes de error, simplemente los copio y pego sin comentarios, y normalmente eso lo arregla.»
Para proyectos de fin de semana o prototipos rápidos, dejarse llevar por la IA puede funcionar, pero tiene limitaciones: código aparentemente correcto, pero con bugs sutiles, arquitecturas inconsistentes, vulnerabilidades de seguridad, y la incomodidad de no conocer la lógica que implementa el código generado.
Los datos son reveladores: según Y Combinator, el 25% de las startups de su cohorte de invierno 2025 tenían bases de código generadas en un 95% por IA(2). Es impresionante, pero: ¿quién entiende y mantiene ese código?
Qué es Spec Driven Development
Spec Driven Development es, en esencia, un enfoque en el que la especificación precede y guía al código. No es un framework ni una metodología prescriptiva como scrum. Es una filosofía de trabajo que propone:
- Escribir primero una especificación clara de lo que se desea construir: objetivos, reglas de negocio, criterios de aceptación, restricciones técnicas.
- Usar esa especificación como fuente tanto para humanos como para agentes de IA.
- Generar código a partir de la spec, no de prompts improvisados.
Como lo resume GitHub en su documentación de Spec Kit: «En este nuevo mundo, mantener software significa evolucionar especificaciones. […] El código es el enfoque de última milla.»(3)
Los tres niveles de SDD
Birgitta Böckeler, de Thoughtworks, propone una taxonomía útil para entender las diferentes implementaciones(4):
- Spec-first: Escribes la spec antes de codificar, la usas para la tarea en curso, y luego la descartas. Es el nivel más básico.
- Spec-anchored: La spec se mantiene después de completar la tarea y se usa para evolución y mantenimiento del feature.
- Spec-as-source: La spec es el artefacto principal. Solo editas la spec, nunca tocas el código directamente. El código se regenera desde la especificación.
La mayoría de herramientas actuales operan en el nivel spec-first, algunas aspiran a spec-anchored, y solo unas pocas experimentan con spec-as-source (como Tessl Framework).
La relación con IA: el catalizador del resurgimiento
Aunque la idea de «especificar antes de codificar» está en el origen de la ingeniería de software, SDD ha resurgido ahora por una razón específica: los LLMs necesitan contexto estructurado para generar código coherente.
Piénsalo así: cuando le das a un agente de código un prompt vago como «hazme un login», está adivinando arquitectura, patrones, validaciones y flujos de error. Cuando le das una especificación detallada que define exactamente qué debe pasar, el código resultante tiene muchas más probabilidades de hacer lo que necesitas.
La spec funciona como un «super-prompt» persistente y versionable. Como describe el equipo de Kiro (AWS): «Una especificación es una especie de super-prompt (versionado, legible por humanos).»(5)
Herramientas del ecosistema
El ecosistema de herramientas SDD está creciendo rápidamente(6):
- GitHub Spec Kit: Toolkit open source que proporciona un flujo estructurado: Constitution → Specify → Plan → Tasks → Implement. Funciona con Copilot, Claude Code y otros.
- Kiro (AWS): IDE basado en VS Code con flujo integrado de Requirements → Design → Tasks.
- Tessl Framework: Explora el nivel spec-as-source con mapeo 1:1 entre specs y archivos de código.
- BMAD Method: Usa agentes virtuales (Analyst, Product Manager, Architect) para generar PRDs y specs de arquitectura.
SDD, TDD y BDD: primos cercanos, no competidores
Si trabajas con metodologías ágiles, probablemente te preguntarás cómo encaja SDD con prácticas que ya conoces. La buena noticia: no son excluyentes, son complementarias(7).
- TDD (Test Driven Development): Tests primero. Escribes un test que falla, luego el código para pasarlo. Excelente para calidad a nivel de unidad, pero no captura el intent de producto completo.
- BDD (Behavior Driven Development): Comportamiento primero. Escenarios en formato Given-When-Then que describen cómo se comporta el sistema. Fuerte para alineación con stakeholders, pero deja decisiones técnicas abiertas.
- SDD (Spec Driven Development): Especificación primero. Define el qué y el por qué, añade un plan técnico, y descompone en tareas. La spec es el ancla que mantiene a todos (humanos y agentes) alineados.
En la práctica, puedes usar SDD para definir el feature completo, TDD para cada tarea individual, y BDD para validaciones end-to-end. Como dice Beam.ai: «SDD te dice qué y por qué. BDD verifica comportamiento en todo el sistema. TDD asegura corrección a nivel de código. Estas prácticas funcionan bien juntas.»
La tensión con Agile (y cómo resolverla)
La pregunta incómoda surge porque el Manifiesto Ágil dice «software funcionando sobre documentación extensiva», y esto contradice SDD ese principio al pedir especificaciones detalladas.
La respuesta corta es que depende de cómo lo implementes; y la respuesta larga es que SDD no propone documentación extensiva estilo waterfall. Propone especificaciones vivas, ejecutables y versionadas que evolucionan con el código. Como GitHub lo describe: «Spec-Driven Development no se trata de escribir documentos de requisitos exhaustivos que nadie lee. Tampoco se trata de planificación waterfall.» (3)
En la práctica, SDD funciona bien a nivel de feature o sprint. No estás especificando todo el producto de antemano; estás especificando la siguiente funcionalidad con suficiente detalle para que un agente de IA pueda implementarla correctamente.
Consideraciones críticas y limitaciones
Pero SDD puede no ser una bala de plata. Estos son los puntos de crítica más relevantes en la comunidad profesional:
El problema humano
Como señala Daniel Sogl en DEV Community: «El problema principal no es la IA, es el factor humano. SDD requiere que los desarrolladores especifiquen sus intenciones con precisión. […] Después de más de 10 años en desarrollo de software, rara vez he experimentado proyectos donde los requisitos estuvieran completamente formulados antes de la implementación.»(8)
Escribir buenas specs es difícil. Requiere claridad de pensamiento, conocimiento del dominio, y la disciplina de pensar antes de actuar.
Overhead vs. beneficio
Birgitta Böckeler de Thoughtworks probó varias herramientas SDD y encontró que para tareas pequeñas, el proceso era desproporcionado: «Cuando pedí a Kiro que arreglara un pequeño bug, el documento de requisitos lo convirtió en 4 ‘user stories’ con 16 criterios de aceptación […] Era como usar un mazo para romper una nuez.»(4)
SDD tiene más sentido para features medianos a grandes donde la inversión upfront se amortiza en menos correcciones posteriores.
Falsa sensación de control
Las ventanas de contexto son más grandes, pero eso no significa que la IA siga todas las instrucciones. Böckeler observó: «Frecuentemente vi al agente no seguir todas las instrucciones. […] También vi al agente excederse porque seguía instrucciones demasiado ávidamente.»(4)
Las specs ayudan, pero no eliminan el no-determinismo de los LLMs. Todavía necesitas revisar y validar.
¿Revisar markdown o código?
Algunas herramientas generan cantidades masivas de archivos markdown. Como comenta Böckeler: «Honestamente, preferiría revisar código que todos estos archivos markdown. […] Eran repetitivos y tediosos de revisar.»(4)
Una buena herramienta SDD debería proporcionar una experiencia de revisión de specs genuinamente mejor que revisar código, no peor.
Entonces, ¿merece la pena?
Como con cualquier práctica emergente, la respuesta honesta es: depende.
SDD probablemente te beneficie si:
- Trabajas regularmente con agentes de código IA
- Tus proyectos tienen complejidad media-alta
- Valoras la trazabilidad y la documentación viva
- Tu equipo puede invertir tiempo en aprender un nuevo flujo de trabajo
Probablemente no lo necesites si:
- Solo haces prototipos rápidos o proyectos personales
- Tu trabajo consiste principalmente en fixes pequeños y mantenimiento
- Prefieres un control directo y granular sobre cada línea de código
Si decides probarlo, el consejo del equipo de AI Native Dev es pragmático: «Empieza con un feature. No especifiques toda tu aplicación. Elige una funcionalidad bien entendida y escribe una spec detallada. Ve cómo rinde tu agente de IA.»(9)
Como resume el equipo de AI Native Dev: «Estamos todos descubriendo esto juntos, y los patrones que emerjan vendrán de experiencias compartidas entre cientos de equipos.»(9)
Fuentes citadas
[1]: Andrej Karpathy en X (Twitter), 2 de febrero de 2025: x.com/karpathy/status/1886192184808149383
[2]: «Vibe coding» – Wikipedia. Datos de Y Combinator: es.wikipedia.org/wiki/Vibe_coding
[3]: GitHub Blog – Spec-driven development with AI: github.blog/ai-and-ml/generative-ai/spec-driven-development-with-ai
[4]: Birgitta Böckeler, Thoughtworks – Understanding Spec-Driven-Development: Kiro, spec-kit, and Tessl: martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html
[5]: Kiro – The future of AI spec-driven software development: kiro.dev/blog/kiro-and-the-future-of-software-development
[6]: Microsoft for Developers – Diving Into Spec-Driven Development With GitHub Spec Kit: developer.microsoft.com/blog/spec-driven-development-spec-kit
[7]: Beam.ai – Spec Driven Development: The Future of Building with AI: beam.ai/agentic-insights/spec-driven-development
[8]: Daniel Sogl – Spec Driven Development: A initial review, DEV Community: dev.to/danielsogl/spec-driven-development-sdd-a-initial-review-2llp
[9]: AI Native Dev – Spec-Driven Development: 10 things you need to know about specs: ainativedev.io/news/spec-driven-development-10-things-you-need-to-know-about-specs
La entrada Spec-Driven Development se publicó primero en Navegápolis.
Variable not found
Enlaces interesantes 632
diciembre 22, 2025 07:05
Mucho contenido interesante en la última recopilación del año 🙂
Me ha alegrado verde nuevo a Juan María Hernández que, tras algunos años de parón, vuelve con una reflexión bastante certera sobre por qué el proceso de desarrollo es cada vez más lento y costoso conforme los productos van creciendo en complejidad. Os recomiendo su lectura.
También me ha parecido interesante el artículo de Alexey Fedorov sobre el devastador efecto de una estampida en la caché de nuestras aplicaciones, junto con algunas estrategias para evitarlo.
El proyecto MinimalWorker, de Joshua Jesper Krægpøth Ryder, puede resultar útil para reducir significativamente el boilerplate necesario para registrar workers en aplicaciones ASP.NET Core y .NET.
Vladan Petrovic nos muestra TUnit, un nuevo framework de testing para .NET que promete ser una alternativa interesante a los ya existentes.
Y, como siempre, vale la pena leer a Derek Comartin, esta vez reflexionando sobre por qué los debates sobre si debemos usar o no microservicios suelen perder el foco del verdadero problema que se intenta resolver: el acoplamiento.
Por último, como este será el último recopilatorio hasta la vuelta de las fiestas navideñas, aprovecho para desearos a todos unas felices fiestas y un próspero año nuevo 2026.
Por si te lo perdiste...
- Otras formas de obtener dependencias en controladores ASP.NET Core MVC
José M. Aguilar - Propiedades parciales en C# 13 y .NET 9
José M. Aguilar
.NET
- Custom Scripting for Web Applications
Nevio Medancic, Enzo Grubisa & Vassili Kaplan - Dynamically Changing Decimal & Thousand Separators At Runtime
Conrad Akunga - TUnit: The New Sheriff in Town for .NET Testing
Vladan Petrovic - Refactoring Legacy C# Codebases: Modernizing Async, Collections, and Architecture for Safe, Maintainable .NET
Sudhir Mangla - C# Tip How to create and access custom C# Attributes by using Reflection
Davide Bellone - Creating a .NET CLR profiler using C# and NativeAOT with Silhouette
Andrew Lock - .NET Job Scheduling — Choosing the Right Framework
Martin Stühmer - New in .NET 10 and C# 14: Optimizations in log aggregation jobs
Ali Hamza Ansari - Angular Signals, But Better Now for .NET
I Looked Inside .git and You Won't Believe What I Found - How to Analyse Large CSV Files with Local LLMs in C# (English)
Scott Galloway - Stop Typing: The .NET CLI Tab Completion You've Been Missing
Martin Stühmer - Flatten your models with Facet .NET
Tim Maes - Fetching and Analysing Web Content with LLMs in C#
Scott Galloway - Streams Record Truth. Queues Do Work: A .NET Way To Keep Them Straight
Christopher Johnson - Your cache is not protected from cache stampede
Alexey Fedorov - The New Features and Enhancements in .NET 10
Joydip Kanjilal
ASP.NET Core / ASP.NET / Blazor
- .NET 10 en el desarrollo de APIs REST modernas: Qué aporta realmente en la industria
Gerson Azabache Martínez - MinimalWorker: simplify background worker registration in ASP.NET Core and .NET applications
Joshua Jesper Krægpøth Ryder - Showing custom metrics in Aspire
Bart Wullems - Blazor SaaS Starter Kits Compared: When to Choose Brick Starter for Full‑Stack C#
Brick Starter Team - Clean Architecture for Blazor with DDD & CQRS
Aliaksandr Marozka - MVVM vs MVU in Blazor for Enterprise Apps
Aliaksandr Marozka - Deploy Aspire to Azure Container Apps using the Aspire CLI
Aspire Team - Managing Content Security in Telerik ASP.NET Core Applications
Peter Vogel - Blazor WebAssembly Using Local Storage in Offline Scenarios
Claudio Bernasconi
Azure / Cloud
- JSON Web Token (JWT) Validation in Azure Application Gateway: Secure Your APIs at the Gate
Pierre Roman
Conceptos / Patrones / Buenas prácticas
- Why “Microservices” Debates Miss the Point
Derek Comartin
Data
- One Minute Knowledge: Is ToArrayAsync or ToListAsync faster for Entity Framework?
Steven Giesel - Introducing Kepler.Core — Smart Field Selection for EF Core APIs
Mohammad Ali Ebrahimzadeh - EF Core Bulk Data Retrieval: 5 Methods You Should Know
Anton Martyniuk - EF Core 10 Turns PostgreSQL into a Hybrid Relational-Document DB
Stefan Mitic
Machine learning / IA
- Introducing GPT-5.2-Codex
OpenAI Team - What Building AI Agents in .NET Taught Me in 2025
Bruno Capuano - Prompt Noise Is Killing Your AI Accuracy: How to Optimize Context for Grounded Output
Chris Pietschmann - GPT-5.2 Prompting Guide
Mandeep Singh & Emre Okcular - Local AI Power: Vision and Function Calling with Microsoft Agent Framework and Ollama
Bruno Capuano - Understanding RAG (Retrieval-Augmented Generation)
Hassan Djirdeh
Web / HTML / CSS / Javascript
- Responsive List of Avatars Using Modern CSS, Part 1 & Part 2
Temani Afif - What’s !important #1: Advent Calendars, CSS Wrapped, Web Platform Updates, and More
Daniel Schwarz - New to the web platform in December
Rachel Andrew - Forced Colors Mode
Chris Ferdinandi - JWT Authentication in React: Secure Routes, Context, and Token Handling
Prashant Yadav - Smashing Animations Part 7: Recreating Toon Text With CSS And SVG
Andy Clarke - Add Some Class to Your JavaScript
Paul Sheriff - CSS Layout: How To 'Center a Div,' the Old School Way
Steven J. Vaugha - My HTML Web Component boilerplate for 2026
Chris Ferdinandi - Vite vs. Webpack for react apps in 2025: A senior engineer’s perspective
Peter Aideloje - Masonry Layout is Now grid-lanes
Sunkanmi Fafowora
Visual Studio / Complementos / Herramientas
- Productividad avanzada con GitHub Copilot en el desarrollo backend con .NET
Gerson Azabache Martínez - 16 Tips for Writing AI-Ready C# Code
Matt Eland - A visual editor for the Cursor Browser
Cursor Team - Microsoft.Testing.Platform Now Fully Supported in Azure DevOps
Jeff Bowman - Visual Studio 2026: Debugging with Copilot
David Kahler - Behind the scenes of the Visual Studio feedback system
Mads Kristensen - Microsoft Quietly Kills IntelliCode as AI Strategy Shifts to Subscription Copilot
David Ramel - Investigating a deadlock in Visual Studio
Kevin Gosse
.NET MAUI
- How to Build iOS Widgets with .NET MAUI
Toine de Boer - .NET 10 for .NET MAUI: Exploring New Diagnostics and Metrics
Leomaris Reyes
Otros
- Sobre velocidad y coste de desarrollo
Juan María Hernández - A los desarrolladores de software se les fue de las manos cómo lo bautizan. Y ahora todos pagamos un 'impuesto cognitivo' por ello
Marcos Merino
Publicado en Variable not found.
Koalite
Sobre velocidad y coste de desarrollo
diciembre 19, 2025 09:19
Al desarrollar software, y más cuando se trata de un producto maduro, es habitual que surga la sensación de que «antes sacábamos cambios rápido y ahora todo va más lento» y, por tanto, se proponga «contratar más gente para volver a ir rápido».
Desde fuera puede parecer que el equipo simplemente se ha vuelto menos eficiente. Desde dentro, normalmente lo que está pasando es bastante más mecánico y predecible:
- A medida que el software crece, su coste mínimo de mantenimiento y evolución sube.
- A medida que crece el equipo, la capacidad productiva media por persona decrece.
Esto, que puede parecer algo exclusivo del mundo del desarrollo, es algo que pasa en más ámbitos, como el nivel de vida de una persona o algo tan básico como organizar una habitación.
La complejidad tiene un coste
Cuando un software es pequeño, tiene pocas piezas, pocas reglas, pocas integraciones y pocos casos especiales. Cambiar algo es parecido a mover un mueble en una habitación vacía: lo haces rápido y rara vez rompes nada.
Con el tiempo el producto crece: más funcionalidades, más pantallas, más configuraciones, más clientes con necesidades distintas, más integraciones externas, más datos históricos, más restricciones.
El sistema se convierte en un conjunto de piezas que encajan entre sí. Y ese encaje tiene un precio. Ya no estamos moviendo un mueble en una habitación vacía; estamos intentando cambiar de sitio un armario ropero en una habitación llena de muebles.
En la práctica, esto se nota en tres cosas:
- Entender lo que hay cuesta más: antes podías leer una parte del código y ya. Ahora entender un cambio requiere saber cómo afecta a otras piezas.
- Hacer un cambio correctamente cuesta más : hay más validaciones, más tests, más revisiones, más compatibilidad hacia atrás, más escenarios que no puedes romper.
- Los errores se vuelven más probables y más caros: no porque la gente programe peor, sino porque hay más interacciones posibles. Además, cuando aparece un bug, cuesta más investigar su origen.
Si lo llevamos al nivel de vida de una persona, pasa algo parecido.
Si vives con tus padres, llevas una vida sencilla y no necesitas muchas cosas, tu coste fijo es bajo y puedes llegar a fin de mes sin problemas con poco dinero. A medida que tu vida se va complicado, y te compras una casa, tienes un hijo o te compras un coche, tu coste fijo va aumentando y cada vez necesitas más dinero para llegar fin de mes.
Esto es lo que pasa en software: cuando es sencillo, un cambio puede costar un par de días, pero cuando va ganando en complejidad, ese mismo cambio puede requerir un par de semanas para poder tener en cuenta todas las interacciones con el resto del sistema.
Es una consecuencia natural de tener más cosas funcionando y más cosas que no puedes romper.
Escalar el equipo ayuda, pero con rendimientos decrecientes
Cuando el coste sube la reacción típica es: «contratemos más gente». Es algo que ayuda y tiene un impacto positivo. Pero hasta cierto punto.
Hay quien piensa que si 2 personas producen 10, aumentando a 4 producirán 20. O producirán 10, pero en la mitad de tiempo. Por desgracia, no es tan sencillo, y, en general, al aumentar el número de personas, la productividad media por persona irá disminuyendo.
¿Por qué disminuye la productividad por persona al aumentar el número de personas?
Hay dos motivos principales.
La falta de contexto. Una persona nueva puede ser muy buena, pero empieza con desventaja:
- No conoce el producto en detalle.
- No conoce los “porqués” históricos (decisiones pasadas, limitaciones, clientes clave).
- No conoce los riesgos escondidos (lo que no se debe tocar sin cuidado).
Al principio, esa persona necesita tiempo de formación y, además, tiempo de otras personas para ayudarle: explicaciones, revisiones, acompañamiento, correcciones. Con el tiempo se convierte en productiva, pero no es inmediato.
El coste de coordinación interno del equipo.
Cuando un equipo es pequeño pasa mucho tiempo junto, hay pocas tareas abiertas en paralelo, y todo el mundo está al tanto de todo. Al ir creciendo esto cambia: aparecen más reuniones, más tareas en paralelo que dependen unas de otras, más necesidad de alinear criterios (qué se hace, cómo, etc.).
Aunque se pueda trabajar en más frentes en paralelo, cada iniciativa individual suele avanzar más lento porque necesita más coordinación y aumenta el coste de comunicación.
Si volvemos a compararlo con el nivel de vida, es similar a lo que ocurre con los impuestos. Supongamos que cuando tenías una vida sencilla, con 12.000€ brutos al año conseguías 1.000€ netos al mes y te podías apañar. Podrías pensar que cuando la complejidad de tu vida aumenta te basta con aumenta linealmente tus ingresos, pero no es así: si duplicas tu ingresos y ganas 24.000€ brutos al año, mensualmente sólo recibes 1.600€. Los siguientes 12.000€ no han multiplicado por dos tu capacidad de asumir costes, sino sólo por 1.6.
Algo parecido ocurre al incorporar personas a un equipo. Duplicar el tamaño del equipo no multiplica su capacidad de producción por 2, sino por un factor menor.
Conclusiones
Es necesario entender que cuando aumenta la complejidad del software los cambios cuestan más, no porque el equipo sea menos competetente, sino porque, sencillamente, los cambios son más difíciles y costosos de hacer.
Contratar más gente ayuda, pero es necesario asumir un coste inicial de formación y un coste de coordinación creciente, por lo que no cabe esperar un crecimiento de la productividad lineal con respecto al número de personas.
Para recuperar la velocidad hay que tener en cuenta otros factores, como reducir la complejidad simplificando el software, eliminando casos especiales, automatizando procesos y, sobre todo, teniendo unas prioridades claras para evitar que unas tareas acaben compitiendo con otras.
Si entendemos que el software tiene un coste fijo creciente y que los equipos tienen rendimientos decrecientes al crecer, el foco de la conversación cambia. Ya no es «¿por qué somos más lentos?», sino «¿qué complejidad estamos acumulado? ¿la necesitamos? ¿cómo la vamos a gestionar?».
Variable not found
¿Estás usando tus expresiones regulares en .NET de forma óptima?
diciembre 19, 2025 07:47
La relación de muchos desarrolladores con las expresiones regulares es de amor-odio. Aunque indudablemente son una herramienta muy potente, su uso puede ser complicado y a menudo se convierten en un dolor de cabeza.
Pero hoy no vamos a hablar de su (oscura) sintaxis, ni de lo difícil que es depurarlas, ni de cómo utilizarlas en .NET, sino de distintas técnicas que pueden ayudarnos a disparar su velocidad de proceso, algo bastante importante si las utilizamos en los procesos críticos o hot paths de nuestra aplicación.
En este artículo vamos comparar el rendimiento de distintos escenarios de uso de expresiones regulares, y cómo podemos optimizar su uso en .NET.
Chequear direcciones de email usando expresiones regulares
Como punto de partida, echemos un vistazo al siguiente código, un ejemplo donde definimos la clase estática EmailValidator, con un método IsValid() que utiliza la clase RegEx para validar el email que recibe como parámetro:
Console.WriteLine(EmailValidator.IsValid("john@server.com")); // true
Console.WriteLine(EmailValidator.IsValid("john@smith@server.com")); // false
public static class EmailValidator
{
public static bool IsValid(string email)
{
string emailPattern = @"^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|""(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*"")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$";
var regex = new Regex(emailPattern, RegexOptions.IgnoreCase);
return regex.IsMatch(email);
}
}
No vamos a entrar en el debate de si la expresión regular que hemos utilizado es la mejor para validar un email. Simplemente es la recomendación de un LLM asegurando que cumple la RFC 5322, y, para la prueba que queremos hacer es totalmente válida porque tiene una cierta complejidad.
Si ejecutamos el código anterior, veremos que la expresión regular funciona correctamente y el método IsMatch() nos devuelve true o false dependiendo de si el email es válido o no. Y además, aparentemente la ejecución es bastante rápida, suficiente si no es algo que se vaya a ejecutar con mucha frecuencia.
Sin embargo, internamente, cada vez que llamamos a ese método estático IsValid(), estamos instanciando la clase Regex suministrándole el patrón de la expresión regular, que es parseado, verificado, optimizado, compilado y posteriormente ejecutado por un intérprete para realizar la validación que le estamos solicitando. Todo este proceso puede ser costoso en términos de rendimiento, sobre todo si esa parte del código se ejecuta con mucha frecuencia.
Seguro que podemos mejorar esto...
Primera mejora: reutilización de Regex
La primera optimización que podemos aplicar en este punto es reutilizar la instancia de Regex. De esta forma, evitaremos la sobrecarga de crear una nueva instancia cada vez que llamamos al método IsValid() y evitaremos el proceso de verificación y compilación de la expresión regular.
Esto podríamos conseguirlo fácilmente insertando en la clase anterior el siguiente código:
public static class EmailValidator
{
private const string EmailPattern = @"^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|""(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*"")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$";
private static Regex SharedInstance = new Regex(EmailPattern, RegexOptions.IgnoreCase);
public static bool IsValid_Shared(string email)
{
return SharedInstance.IsMatch(email);
}
}
Si ejecutamos de nuevo la aplicación, veremos que el funcionamiento es exactamente el mismo, y que aparentemente sigue siendo igual de rápido. Pero si usamos BenchmarkDotNet para medir el rendimiento de las dos implementaciones, nos llevaremos una sorpresa:
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|-----------------|-----------:|----------:|----------:|--------:|----------:|
| IsValid | 206.716 us | 1.5089 us | 1.2600 us | 27.3438 | 233969 B |
| IsValid_Shared | 2.742 us | 0.0312 us | 0.0276 us | - | - |
Esta segunda implementación se ejecuta casi 80 veces más rápido que la primera, sin consumo de memoria adicional. Impresionante, ¿verdad? Realmente se trata de una mejora brutal a cambio de muy poco esfuerzo de implementación.
Hay que tener en cuenta que las cifras no son siempre tan espectaculares, y que el rendimiento de la primera implementación puede variar dependiendo de la complejidad del patrón de la expresión regular. En expresiones más simples, la diferencia de rendimiento puede ser mucho menor, pero en cualquier caso habrá mejoras.
Pero... ¿aún podemos hacerlo mejor?
Segunda mejora: compilación de la expresión regular
Por defecto, las expresiones regulares se compilan a una serie de instrucciones de alto nivel que indican las operaciones que deben realizarse para comprobar si la cadena de texto suministrada coincide con el patrón de la expresión regular. Luego, en cada llamada a IsMatch() o métodos similares, un intérprete ejecuta esas instrucciones para realizar la validación.
Sin embargo, la clase Regex también permite compilar la expresión regular a código IL, por lo que el runtime de .NET puede ejecutarlo directamente e incluso, gracias al JIT, generar y ejecutar el código máquina nativo para la plataforma donde corre la aplicación, a cambio, eso sí, de consumir un poco más de memoria y tiempo durante su inicialización.
Esto lo conseguimos de nuevo con muy poco esfuerzo, simplemente añadiendo el RegexOptions.Compiled a la llamada al constructor de la clase Regex:
private static Regex SharedCompiledInstance
= new Regex(EmailPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
Si volvemos a llevarnos las tres opciones a BenchmarkDotNet, y medimos su rendimiento, veremos que en este último caso hemos mejorado algo más el rendimiento:
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|------------------ |-----------:|----------:|----------:|--------:|----------:|
| IsValid | 202.007 us | 2.4068 us | 2.2513 us | 27.3438 | 233969 B |
| IsValid_Shared | 2.606 us | 0.0276 us | 0.0258 us | - | - |
| IsValid_Compiled | 2.570 us | 0.0141 us | 0.0132 us | - | - |
En este caso la diferencia es mínima, pero es algo que también depende de la complejidad de las operaciones que hay que realizar para validar los valores contra la expresión regular. Por ejemplo, si en lugar de usar la expresión regular que hemos visto anteriormente para detectar emails, ejecutamos el mismo benchmark para un patrón aparentemente simple como "(\d+)*\1" y hacemos que se compruebe un string numérico muy largo (unos 100.000 dígitos), la diferencia de rendimiento es mucho más notable:
| Method | Mean | Error | StdDev | Allocated |
|----------------- |---------:|---------:|---------:|----------:|
| IsValid | 65.70 ms | 1.107 ms | 1.088 ms | 5401 B |
| IsValid_Shared | 63.74 ms | 0.925 ms | 0.772 ms | 57 B |
| IsValid_compiled | 19.52 ms | 0.147 ms | 0.130 ms | 12 B |
La expresión regular
"(\d+)*\1"permite buscar cadenas que contengan un número seguido de un número repetido, como por ejemplo123123,456456,789789, etc. Esta expresión regular es ejemplo conocido por dar lugar al llamado catastrophic backtracking, un problema que puede dar lugar a un rendimiento muy bajo en ciertas expresiones regulares, que incluso puede ser explotado en ataques de denegación de servicio (DoS) en aplicaciones web.
Estos resultados son fácilmente explicables: la diferencia de rendimiento entre la primera y segunda opción es pequeña, porque la expresión regular es muy simple y, por tanto, el coste de su compilación es bajo. Pero el rendimiento se multiplica por tres en la tercera opción porque la ejecución de la expresión regular se beneficia de la compilación a código IL.
Hasta aquí, hemos comprobado cómo realizando un par de modificaciones simples en el código podemos lograr mejorar considerablemente el rendimiento de las expresiones regulares en .NET. Pero aún hay más...
Tercera mejora: compilar la expresión regular en tiempo de diseño
La compilación de la expresión regular a código IL es una mejora muy interesante, pero tiene un coste adicional el términos de memoria y proceso, que se produce en el momento de la inicialización de la expresión regular, es decir, en tiempo de ejecución.
De hecho, también podemos realizar un benchmark del tiempo de creación de la instancia de Regex con y sin compilación, y veremos que la diferencia es prácticamente del triple, tanto en tiempo de proceso como en consumo de memoria:
| Method | Mean | Error | StdDev | Gen0 | Gen1 | Allocated |
|-------------------- |---------:|---------:|---------:|--------:|-------:|----------:|
| CreateRegex | 28.64 us | 0.204 us | 0.170 us | 3.4180 | - | 29.27 KB |
| CreateRegexCompiled | 99.51 us | 0.973 us | 0.863 us | 10.7422 | 1.4648 | 90 KB |
Si queremos evitar este sobrecoste, a partir de .NET 7 podemos compilar la expresión regular en tiempo de diseño usando source generators. De esta forma, el compilador de C# generará el código C# necesario para ejecutar la expresión regular, y lo incluirá en el ensamblado de la aplicación, por lo que no pagaremos ningún coste adicional en tiempo de ejecución. Pero además, como veremos algo más adelante, el código generado será mucho más eficiente que la versión compilada en tiempo de ejecución 🙂
Para conseguirlo, en una clase cualquiera debemos un método parcial de tipo Regex y asignarle el atributo [GeneratedRegex] especificando el patrón de la expresión regular y las opciones que queramos utilizar. Por ejemplo, en el siguiente código podemos ver el método al que hemos llamado GeneratedEmailRegex() sobre la clase estática parcial EmailRegex (ambos nombres son arbitrarios):
public static partial class EmailRegex
{
[GeneratedRegex(@"^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|""(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*"")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$", RegexOptions.IgnoreCase)]
public static partial Regex GeneratedEmailRegex();
}
Podéis ver fácilmente el código generado ejecutando la aplicación y, en el explorador de soluciones de Visual Studio, desplegando la carpeta "External Sources", el ensamblado de la aplicación, y abriendo el archivo
RegexGenerator.g.cs, o bien, siguiendo estos pasos.
Una vez tenemos este método disponible, para utilizar la expresión regular simplemente debemos usar la instancia de Regex retornada por el mismo, por ejemplo así:
public static class EmailValidator
{
public static bool IsValid(string email)
{
return EmailRegex.GeneratedEmailRegex().IsMatch(email);
}
}
Y si de nuevo nos llevamos estos cambios a BenchmarkDotNet, y medimos el rendimiento de las distintas implementaciones, de nuevo nos llevaremos una alegría:
| Method | Mean | Error | StdDev | Gen0 | Allocated |
|-------------------------- |-------------:|------------:|------------:|--------:|----------:|
| IsValid_Initial | 213,564.0 ns | 1,532.91 ns | 1,280.05 ns | 27.3438 | 233969 B |
| IsValid_Shared_Instance | 2,667.7 ns | 39.86 ns | 35.33 ns | - | - |
| IsValid_Compiled_Instance | 2,745.5 ns | 37.81 ns | 35.37 ns | - | - |
| IsValid_UsingGenerators | 788.3 ns | 7.91 ns | 7.40 ns | - | - |
¡Uau! De nuevo hemos conseguido dividir por tres el tiempo de ejecución de la expresión regular respecto a la versión compilada en tiempo de ejecución. Y bueno, si lo comparamos con la versión inicial, la que implementamos sin pensar en ninguna optimización, es cerca de 300 veces más eficiente.
Conclusiones
A veces, el código que escribimos puede no ser el más óptimo: a veces por costumbre, a veces por comodidad, u otras simplemente porque no conocemos fórmulas mejores. En algunos casos no importará demasiado porque quizás nuestros requisitos de rendimiento no son excesivamente exigentes, pero en otros muchos escenarios sí debemos prestar atención a este tipo de detalles.
Lo que hemos visto en este post es un claro ejemplo de cómo las mejoras que se van introduciendo en el framework y el SDK de .NET pueden ayudarnos a mejorar el rendimiento de nuestras aplicaciones con muy poco esfuerzo.
Publicado en: www.variablenotfound.com.Variable not found
Enlaces interesantes 631
diciembre 15, 2025 07:42
La semana pasada estuve unos días fuera y no publiqué la habitual recopilación de los lunes. Pero ya me he puesto al día, y, como se me acumuló el trabajo, en esta entrega tenemos más de ¡100! enlaces a contenidos a los que vale la pena echar un vistazo.
Por destacar algunos, en primer lugar el profundo análisis que está llevando a cabo Martin Stühmer sobre soluciones de scheduling en .NET, muy interesantes para estar al tanto de las opciones disponibles.
También Gerson Azabache ha publicado varios artículos interesantes sobre ASP.NET Core, como la comparativa entre Minimal APIs y controladores, resultados tipados y algunas buenas prácticas en el desarrollo de APIs empresariales.
Braulio Díez comparte sus reflexiones y experiencias sobre el impacto de la IA en la programación y el futuro de los desarrolladores de software, que igual no es tan malo como algunos pintan.
Y en la misma línea, José Manuel Alarcón habla sobre cómo los juniors deben enfocar su proceso de aprendizaje, integrando la IA como una aliada imprescindible.
Muchos más enlaces, a continuación.
Por si te lo perdiste...
- Mi controlador tiene muchos parámetros en el constructor, ¿estoy haciendo algo mal?
José M. Aguilar - HybridCache, la nueva caché híbrida de .NET 9
José M. Aguilar
.NET
- C# 14 New Feature: Field-Backed Properties
Ian Griffiths - Recent updates to NetEscapades.EnumGenerators: [EnumMember] support, analyzers, and bug fixes
Andrew Lock - “Classic” .NET Domain Events with Wolverine and EF Core
Jeremy D. Miller - .NET Job Scheduling: Quartz.NET for Enterprise Scale & Coravel and Fluent Simplicity & NCronJob and Native Minimalism & TickerQ and Modern Architecture
Martin Stühmer - How to Build a Production-Ready Invoice Builder in .NET Using IronPDF
Anton Martyniuk - How can my process read its own standard output?
Raymond Chen - Mime Type Helper in .NET 11
Steven Giesel - Single File Test Suites in Dotnet Csharp
Steve Smith - Create Types on Demand and Cecilifier
Roman Stoffel - Creating a custom MSBuild SDK to reduce boilerplate in .NET projects
Gérald Barré - Debug Dumps in Visual Studio
Stephen Cleary - Building Modular .NET Applications with CShells
Sipke Schoorstra - .NET 10 Networking Improvements
Marie Píchová - What the heck is a
\\.\nulpath and why is it breaking my Directory Files Lookup?
Rick Strahl - .NET 10 Validation
Ricardo Peres - .NET Performance: Efficient Async Code
Nick Kovalenko - .NET 10 and Memory: Less Heap, Smarter GC, Faster Apps
Hazem Ali - Immutable Collection Add() Trap: Don’t Get Burned — Use a Builder Instead
David McCarter - How .NET 10.0 boosted AIS.NET performance by 7%
Ian Griffiths - Resolving Overload Ambiguity with Collection Expressions
Gérald Barré - Typemock Architecture: Inside the .NET Isolator Engine (Part 2)
Eli Lopian - Cleaner Code: C# 14 Null-Conditional Assignment Operator
Dave Brock
<!--more-->
ASP.NET Core / ASP.NET / Blazor
- Minimal APIs vs Controllers en .NET 10: la guía corta y honesta & Por qué tus APIs en .NET deberían usar Result Contracts (Typed Results) & Las 5 mejores prácticas modernas para APIs empresariales en .NET (2026)
Gerson Azabache Martínez - Enterprise Patterns for ASP.NET Core Minimal API: Transaction Script Pattern – The Shortcut That Quietly Reshapes Your System
Chris Woodruff - How to upload files in an ASP.NET Core Web API
David Grace - Stop Letting Your Controllers Talk to SQL: Layered Architecture in ASP.NET Core
Chris Woodruff - Best Practices For Building REST APIs
Anton Martyniuk - How to Choose the Best Blazor Dropdown Component for Your Web App
Prince Oliver - mostlylucid.MinimalBlog - How Simple Can an ASP.NET Blog Really Be? (English)
Scott Galloway - Enterprise Patterns for ASP.NET Core Minimal API: Domain Model Pattern – When Your Core Rules Deserve Their Own Gravity
Chris Woodruff - Load Testing ASP.NET Core Applications with k6: Introduction & Practical Implementation
Scott Galloway - How Aspire composes itself: an overview of Aspire's Docker Compose integration
Safia Abdalla - Migrating from Bootstrap Blazor or MudBlazor to Blazorise
Mladen Macanović - Using Strategy Pattern with Dependency Injection in ASP.NET Core
Ali Hamza Ansari - Getting Started with the Aspire CLI - A Complete Guide
Chris Ayers
Azure / Cloud
- You Can't Use Azure Migrate to Move Between Tenants (Even if you Try to Run it in Azure)
Shannon B. Kuehn - How to Deploy a Spreadsheet Server on Azure App Service Using Visual Studio and Docker
Parthasarathy Ranjan
Conceptos / Patrones / Buenas prácticas
- Domain-Driven Design Misconceptions
Derek Comartin - Treat test code like production code
Mark Seemann - Aggregates in DDD: Model Rules, Not Relationships
Derek Comartin - 10 Habits That Make You a Great Programmer
Shalitha Suranga - 12 Essential Distributed System Design Patterns Every Architect Should Know
Anton Martyniuk - Power of Ten Rules: More Relevant Than Ever for .NET
Martin Stühmer - Stop Naming Your Variables "Flag": The Art of Boolean Prefixes
Christopher Johnson
Data
- Data Access in .NET: Comparing ORMs and Mapping Strategies (Part 1 & * Comparing ORMs and Mapping Strategies (Part 2
Scott Galloway - Debugging Entity Framework Core: 8 Real-World Query Anti‑Patterns (and How to Fix Them)
Chris Woodruff - Named global query filters in Entity Framework Core 10
Tim Deschryver - DbContext is Not Thread-Safe: Parallelizing EF Core Queries the Right Way
Milan Jovanović - Why your EF Core queries are slow and how to fix them
David Grace
Machine learning / IA
- Meet EuroLLM: Large language model made in Europe built to support all official 24 EU languages
EuroLLM - Introducing: Devstral 2 and Mistral Vibe CLI
Mistral - Introducing GPT-5.2
OpenAI - Introducing the Microsoft Agent Framework – A Dev-Friendly Recap
Bruno Capuano - Introducing Data Ingestion Building Blocks (Preview)
Luis Quintanilla - Anomaly Detection Using K-Means Clustering with JavaScript
James McCaffrey
Web / HTML / CSS / Javascript
- Cómo probar tu aplicación con Vue en GitHub Pages
Gisela Torres - Prevent a page from scrolling while a dialog is open
Geoff Graham - Getting Creative With “The Measure”
Andy Clarke - Scrollytelling on Steroids With Scroll-State Queries
Lee Meyer - Masonry: Things You Won’t Need A Library For Anymore
Patrick Brosset - Frederik Braun: Why the Sanitizer API is just
setHTML()
Frederik Braun - Creating Scroll-Based Animations in Full view()
Preethi - CSS Wrapped 2025
Chrome DevRel Team - Fit width text in 1 line of CSS
Geoff Graham - That Time I Tried Explaining HTML and CSS to My 5-Year Old Niece
Kevine Nzapdi - ::target-text: An easy way to style text fragments
Saron Yitbarek - Stop using JavaScript to solve CSS problems
Chizaram Ken - Simulating the Enigma Machine
Andrew S. Erwin - Tailwind CSS: Targeting Child Elements (when you have to)
Christian Ekrem - How to Prevent XSS Attacks in React Rich Text Editor
Thangavel E. - What's wrong with this HTML, and is it valid?
Patrick Brosset - How to create Liquid Glass effects with CSS and SVG
Rahul Chhodde - What Else Could Container Queries... Query?
Daniel Schwarz - Denial of Service and Source Code Exposure in React Server Components – React
The React Team
Visual Studio / Complementos / Herramientas
- Why changing keyboard shortcuts in Visual Studio isn’t as simple as it seems
Steven Miller - Unlock GitHub Copilot’s Full Potential: Why Every Repo Needs an AGENTS.md File
Chris Pietschmann - Progress on TypeScript 7 - December 2025
Daniel Rosenwasser - Introducing Stack Overflow AI Assist—a tool for the modern developer
Stack Overflow Team - Microsoft Learn MCP Server Elevates Development
Wendy Breiding - Trying out the Zed editor on Windows for .NET and Markdown
Andrew Lock - Previewing the JavaScript/TypeScript Modernizer for VS Code Insiders
Sayed Ibrahim Hashimi - DarkGPT: Malicious Visual Studio Code Extension Targeting Developers — Real-time Open Source Software Supply Chain Security
SafeDep Team - Streamlining your Git workflow with Visual Studio 2026
Mads Kristensen - Making Windows Terminal awesome with GitHub Copilot CLI
Linda Berns
.NET MAUI / Cross-Platform
- Cross-Platform Age Verification in .NET MAUI Applications
Gerald Versluis - Securing Sensitive Mobile Operations with Device-Bound Request Signing
Fernando de Oliveira - Mastering Popups in .NET MAUI: Alerts, Action Sheets, Overlays & More
Jayaleshwari N. - Implementing Cross-Platform In-App Billing in .NET MAUI Applications
Gerald Versluis - How to Add and Remove Digital Signatures in PDF Using .NET MAUI
Rangarajan Ashokan - How to Integrate Google Maps in .NET MAUI: A Cross-Platform Guide Using the Google Maps Tile API
Jeyasri Murugan - Avalonia MAUI Progress Update
Tim Miller - .NET 10: Quick UI Changes Worth Noticing in .NET MAUI
Leomaris Reyes
Otros
- El picar se va acabar
Braulio Díez - Aprende a programar en 2026: guía práctica para juniors (que tienen miedo a la IA, o no)
José Manuel Alarcón
Publicado en Variable not found.
Variable not found
Soporte para el TLD .localhost en ASP.NET Core 10
diciembre 02, 2025 07:05
Al desarrollar aplicaciones ASP.NET Core (APIs, MVC, Razor Pages o Blazor) y ejecutarlas en local, ya sabes que todas ellas comparten el mismo nombre de host: localhost, y lo único que las diferencia es el puerto en el que se encuentran a la escucha.
Esto puede dar lugar a ciertos problemas. Aparte de no ser sencillo identificarlas a partir de la URL mostrada en el navegador, hay ciertos recursos web que pueden mezclarse entre proyectos (por ejemplo, las cookies) y pueden dar lugar a comportamientos inesperados.
En ASP.NET Core 10 se ha puesto solución a esto añadiendo el soporte para el TLD (Top Level Domain) .localhost, lo que permite que cada aplicación pueda tener su propio nombre de host único.
Lo vemos a continuación.
Soporte para TLD .localhost en ASP.NET Core 10
El nombre .localhost es un dominio de nivel superior (TLD, Top Level Domain) reservado por las RFC 2606 y 6761 para ser utilizado en entornos de desarrollo y pruebas, y siempre está asociado a la dirección de loopback, es decir, a la dirección local del propio equipo.
Por tanto, cualquier nombre de host que termine en .localhost (por ejemplo, miapp.localhost, api.localhost, MyApp.dev.localhost, etc.) debería resolverse siempre a la dirección IP 127.0.0.1 o ::1, dependiendo de si se utiliza IPv4 o IPv6.
A partir de ASP.NET Core 10, Kestrel entenderá que todos los nombres de host que terminen en .localhost son alias válidos para localhost, por lo que podemos utilizarlos para configurar el servidor, definir las URLs de nuestras aplicaciones (tanto en launchSettings.json como en variables de entorno), etc. Esto nos permitirá usar distintos nombres de host para cada aplicación, evitando los problemas mencionados anteriormente.
Otro tema que han actualizado es el certificado HTTPS utilizado en desarrollo. Dado que el antiguo certificado solo era válido para localhost, al utilizar otros nombres de host el navegador mostraría advertencias de seguridad. Por esta razón, el certificado que se registra al instalar .NET 10 es un wildcard del dominio *.dev.localhost.
Observad que no han podido hacerlo directamente para soportar
*.localhost. Han tenido que introducir el subdominio.devpor delante porque no es posible crear wildcards sobre top level domains comolocalhost.
En la práctica, esto implica que podremos asignar a nuestras aplicaciones ASP.NET Core nombres de host como miapp.dev.localhost, api.dev.localhost, etc., aunque no es algo que esté habilitado por defecto.
Para activar esta característica, debemos seleccionar la opción "Use the .dev.localhost TLD in the application URL" al crear una nueva aplicación, como se muestra en la siguiente imagen:
Al hacerlo, se configurará el launchsettings.json para que utilice el nombre de proyecto como subdominio. Por ejemplo, a continuación se muestra la configuración generada para el proyecto MyWebApplication, donde podemos ver el uso del host mywebapplication.dev.localhost:
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://mywebapplication.dev.localhost:5244",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://mywebapplication.dev.localhost:7279;
http://mywebapplication.dev.localhost:5244",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
También podemos conseguir lo mismo si utilizamos la línea de comandos de .NET para crear los proyectos. Por ejemplo, el siguiente comando crea una aplicación MVC que utiliza el TLD .dev.localhost:
C:\MyProjects\MyWebApp>dotnet new web --localhost-tld
The template "ASP.NET Core Empty" was created successfully.
Processing post-creation actions...
Restoring C:\MyProjects\MyWebApp\MyWebApp.csproj:
Restore succeeded.
C:\MyProjects\MyWebApp>type .\Properties\launchSettings.json
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://mywebapp.dev.localhost:5024",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://mywebapp.dev.localhost:7125;
http://mywebapp.dev.localhost:5024",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
C:\MyProjects\MyWebApp>_
Con estas configuraciones, al lanzar el proyecto se utilizarán estos nombres de host exclusivos para el proyecto, evitando los problemas de compartir el mismo localhost entre varias aplicaciones.
C:\MyProjects\MyWebApp> dotnet run
Using launch settings from C:\MyProjects\MyWebApp\Properties\launchSettings.json...
Building...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://mywebapp.dev.localhost:5024
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5024
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\MyProjects\MyWebApp
_
Un último detalle importante: podremos utilizar los nombres de host personalizados cuando accedamos a las aplicaciones desde el navegador web, pero probablemente no desde otras herramientas como curl, Postman, etc., o al menos de directamente.
Esto se debe a que, aunque el TLD .localhost está reservado para este propósito, no todas las aplicaciones cliente o los sistemas operativos resuelven automáticamente estos nombres a la dirección de loopback. Por esta razón, en estos casos tendríamos que añadir entradas manuales en el archivo hosts de nuestro sistema operativo para que los nombres personalizados funcionen correctamente.
Afortunadamente, los navegadores web modernos implementan esta resolución de forma automática, por lo que funcionará directamente 🙂
Publicado en Variable not found.
Una sinfonía en C#
¿Cómo conectar Kakfa-UI con Event Hubs de Azure?
diciembre 02, 2025 12:00
“Introducción”
Si usamos Eventhubs de Azure como broker Kafka, y queremos usar Kafka-UI para monitorizar los topics, tenemos que tener en cuenta algunas cosas para que funcione. Hablando siempre de autenticación utilizando SASL_SSL con mecanismo PLAIN. (Esto no funciona para Managed Identity).
En principio sabemos que podemos definir un connection string SAS para conectarnos a Event Hubs, pero Kafka-UI no soporta directamente este formato, por lo que tenemos que hacer algunos ajustes.
KAfka en generar no soporta este formado, sino que hay que hacer un mapeo de los valores del connection string a las propiedades que Kafka espera, más específicamente en el usuario y password, además de protocolo de comunicación.
Configuración de Client Kafka
Para conectar un cliente Kafka a Event Hubs, tenemos que mapear los valores del connection string a las propiedades de usuario y password. Pero primero hay que configurar:
- Protocol: SASL_SSL
- Mechanism: PLAIN
Y luego en las propiedades de autenticación:
- Username: $ConnectionString
- Password: Endpoint=sb://.servicebus.windows.net/;SharedAccessKeyName=;SharedAccessKey=;EntityPath=
El EntityPath es opcional, y solo si queremos conectarnos a un Event Hub específico. Si no se especifica, se puede acceder a todos los Event Hubs dentro del namespace.
Básicamente el username es el literal $ConnectionString (con el signo $) y el password es el connection string completo.
Conectar Kafka-UI
En el caso de Kafka-UI tenemos un truco adicional, que sería agregar al user name un $ adicional al inicio, quedando así:
- Username: $$ConnectionString
- Password: Endpoint=sb://.servicebus.windows.net/;SharedAccess
Esto en la propiedad KAFKA_CLUSTERS_0_PROPERTIES_SASL_JAAS_CONFIG (reemplazar el 0 por el índice del cluster que estemos configurando).
En el caso de un docker-compose.yml, quedaría algo así:
version: '2'
services:
kafka-ui:
image: provectuslabs/kafka-ui:latest
ports:
- 9999:8080
environment:
- KAFKA_CLUSTERS_0_NAME=azure
- KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=xxxx.servicebus.windows.net:9093
- KAFKA_CLUSTERS_0_PROPERTIES_SECURITY_PROTOCOL=SASL_SSL
- KAFKA_CLUSTERS_0_PROPERTIES_SASL_MECHANISM=PLAIN
- KAFKA_CLUSTERS_0_PROPERTIES_SASL_JAAS_CONFIG=org.apache.kafka.common.security.plain.PlainLoginModule required username='$$ConnectionString' password='Endpoint=sb://<NAMESPACE>.servicebus.windows.net/;SharedAccessKeyName=<KEY_NAME>;SharedAccessKey=<KEY_VALUE>';
El connection string tiene que tener scope del namespace (lo que sería el broker de Kafka).
Especial cuidado al doble $ en el username y al ; final
Nos leemos.
Variable not found
Enlaces interesantes 630
diciembre 01, 2025 07:01
Esta semana me gustaría destacar en primer lugar el detallado análisis de Andrew Lock sobre el proceso de arranque de una aplicación .NET, muy interesante para saber cómo funcionan las cosas por dentro.
Bipin Joshi da un gran repaso a Kestrel, el motor de todas las aplicaciones ASP.NET Core: cómo configurarlo, tunearlo, ejecutarlo y buenas prácticas de uso.
Milan Jovanović nos recuerda que el happy path no es el único camino en nuestras aplicaciones, y nos muestra técnicas para desacoplar servicios para hacerlos más robustos.
Por último, Ricardo Peres nos habla de la relación entre tipos anulables y miembros requeridos en C#, algo que, cuando empezamos a trabajar con ellos, puede resultar algo confuso.
El resto de enlaces, a continuación.
Por si te lo perdiste...
- Soporte para colecciones en parámetros
paramde .NET 9
José M. Aguilar - Establecer textos por defecto y localizados en validaciones de ASP.NET Core MVC
José M. Aguilar
.NET
- TUnit — Why I Spent 2 Years Building a New .NET Testing Framework
Tom Longhurst - C# 14: User-Defined Compound Assignment Operators
Anthony Giretti - Under Pressure: How Queueing Systems Handle Backpressure with Examples in C#
Scott Galloway - Precision Matters in C#: Correctly Handling Money, Time Zones, and Date Ranges
Sudhir Mangla - Exploring the .NET boot process via host tracing
Andrew Lock - .NET Job Scheduling — The Landscape
Martin Stühmer - Why Do You Need To Write Architecture Tests in .NET
Anton Martyniuk - Fetching GitHub content from C#
Thomas Ardal - Using CSX Scripts for Quick C# Testing (English)
Scott Galloway - The Worst Security Vulnerability in Akka.NET
Aaron Stannard - IDistributedCache (Redis)
Josef Ottosson - Adding Intelligence to Blazor with Telerik Smart Components
Héctor Pérez - How to ensure your expert C# knowledge doesn't make you a TypeScript noob
Lewis Cianci - Ix.NET v7.0: .NET 10 and LINQ for IAsyncEnumerable<T>
Ian Griffiths - Real-Time Recommendation Engines in .NET: Hybrid Retrieval, Deep Learning, and Vector Search
Sudhir Mangla - One shot tool execution in .NET 10
Bart Wullems - .NET Job Scheduling — Hangfire and Persistent Reliability
Martin Stühmer - Nullable and Required Types
Ricardo Peres - Unit Testing HttpClient WITHOUT Mocks
Scott Galloway
ASP.NET Core / ASP.NET / Blazor
- Multitenancy Techniques for the UI in ASP.NET Core
Ricardo Peres - Why you must use Minimal APIs over Controllers for new apps
David Grace - Mastering Kestrel: From Configuration to Cloud
Bipin Joshi - Securing Apps with the Telerik UI for ASP.NET Core OTP Control
Héctor Pérez - Optimizing Rendering and Reconciliation in Large Blazor Apps
Mladen Macanović - Performance Tuning in ASP.NET Core: Best Practices for 2025
Arulraj Aboorvasamy - HTMX with ASP.NET Core Partials: The Server-Side Renaissance
Scott Galloway - Announcing ASP.NET Core OData 10.0.0 Preview 1
Samuel Wanjohi
Azure / Cloud
- Azure MCP Server II - Configuración y primeros pasos
Juan Irigoyen
Conceptos / Patrones / Buenas prácticas
- Minimal APIs, CQRS, DDD… Or Just Use Controllers?
Derek Comartin - WebSockets vs HTTP: Key Differences Explained
The Postman Team - The False Comfort of the "Happy Path": Decoupling Your Services
Milan Jovanović
Data
- Announcing OData .NET (ODL) 9 Preview 3 Release
John Gathogo - Using sortable UUID / GUIDs in Entity Framework
Steven Giesel - You're Probably Doing EF Migrations wrong...
Scott Galloway - MSSQL Extension for VS Code: Introducing Edit Data (Public Preview)
Yo-Lei Chen
Machine learning / IA
- Introducing Claude Opus 4.5 \ Anthropic
Anthopic - RAG in Practice: Building Real-World Applications
Scott Galloway - Building our first MCP Resources enabled MCP Server with C#
Bart Wullems
Web / HTML / CSS / Javascript
- Angular 21: desarrollo web empresarial más rápido, inteligente y accesible
José Manuel Alarcón - Todo lo que el navegador web revela sin que se sepa
Álvaro Ibáñez - How to Create Interactive Fillable PDF Forms Using JavaScript PDF Viewer
Arun Kumar Chandrakesan - On Inheriting and Sharing Property Values
Daniel Schwarz - Shuffling a CSS grid using custom properties
Christian Heilmann - CSS Backgrounds Module Level 4 - Working Draft
Elika J. Etemad & Lea Verou & Sebastian Zartner - How to Simplify Your React Components with Derived State
Olaleye Blessing - Angular 21: My Favorite New Features, Demo, What’s Next
Alyssa Nicoll - You're doing JavaScript testing wrong
Chris Ferdinandi - Securing Apps with the Telerik UI for ASP.NET Core OTP Control
Héctor Pérez - Managing Side Effects: A JavaScript Effect System in 30 Lines or Less
Aycan Gulez - Angular 21 Is Here: How Syncfusion Delivers Compatibility Support
Sabari Anand S. - How to Hallucinate using Web Components—zachleat.com
Zach Leatherman - How Closures Work in JavaScript: A Handbook for Developers
Sumit Saha - New to the web platform in November
Rachel Andrew - WebGPU is now supported in major browsers
François Beaufort
Visual Studio / Complementos / Herramientas
- Visual Studio – Built for the Speed of Modern Development
Paul Chapman - Manage Containers the Easy Way: Copilot + VS Code
Matt Hernandez - Automatically Signing a Windows EXE with Azure Trusted Signing, dotnet sign, and GitHub Actions
Scott Hanselman - How to Create an MSI Installer with Visual Studio 2026
Renato Ivanescu
.NET MAUI
- Visualize Monthly Weather Forecasts with .NET MAUI Scheduler
Jeyasri Murugan - Replicating a Christmas UI in .NET MAUI
Leomaris Reyes
Otros
- Scaling culture in a scale-up
Rob Nicholson
Publicado en Variable not found.
Arragonán
Viviendo en el péndulo. Hasta la vista Genially
octubre 13, 2025 12:00
Tras casi 4 años, esta fue mi última semana en Genially. Una decisión que me costó mucho tomar tanto por motivos personales, con un ambiente de compañerismo en la compañía que creo que no es habitual en otros lugares que tienen cierta escala y haber construido lazos con un buen puñado de personas; como por motivos profesionales, con unos cuantos desafíos que se venían a corto y medio plazo en los que sé que hubiera disfrutado y aprendido.
En este tiempo, mi rol en la empresa se fue transformando poco a poco casi sin darnos cuenta, primero influenciando solo a nivel de tecnología y terminando haciéndolo a nivel de producto, negocio y operaciones. Hubo algo que fue una constante: vivir en el péndulo entre individual/team contributor y el management.
Incorporación
Entré casi por casualidad. Conocí a Chema, CTO de Genially, unos años antes en una Pamplona Crafters y mantuvimos el contacto vía redes sociales (cuando tuiter aún molaba). Tiempo después, hablamos por otro tema y me propuso explorar si tenía sentido incorporarme. La compañía venía de crecer mucho y vieron que podía asumir un rol que resultase complementario.
Justo era un momento en el que empezaba a sentir que necesitaba un cambio profesional, pero quería estar seguro de mi siguiente paso. Así que tras haber hecho el proceso de selección, nos tomamos un par de meses y tuvimos varias conversaciones hasta que finalmente me incorporé a la compañía.
Plataforma y organización
Entré con un rol de Platform Lead, por lo que mi responsabilidad iba a ser ayudar a los equipos de producto a que pudieran enfocarse en tener impacto, tratando de mejorar la experiencia de desarrollo y la efectividad de los equipos. Como bonus, también iba a echar una mano en cuestiones más organizativas.
Aunque este era un rol de liderazgo, no había un equipo dedicado, de modo que iba a tener que influir, colaborar con distintos equipos y empujar algunas iniciativas por mi cuenta.
Sobre el trabajo de plataforma
Una vez hecho el onboarding, empezar a tener algunas iniciativas ya lanzadas y conociendo más la compañía, traté de aterrizar una serie de metas:
Las iniciativas y actividades de plataforma deben facilitar la Developer Experience y ayudar a los equipos en distintos aspectos:
- Reducir su carga cognitiva, que se enfoquen en complejidad intrínseca y no en la extrínseca, potenciando la germana (generando aprendizaje, construyendo esquemas o automatizando su uso)
- Habilitar actitudes data-informed en la toma de decisiones
- Aportar la mayor autonomía y empoderamiento posibles
- Tratar de ser lo más eficientes en cuestiones de costes económicos
- Dar fiabilidad, que las herramientas apenas fallen
Metas que traté de tener en cuenta, definiendo e implantando prácticas, procesos y herramientas, automatizando tareas, creando documentación… Y darles seguimiento unas veces de manera cuantitativa y otras de forma cualitativa.
Algunas de las iniciativas más relevantes fueron estas:
- Actuar como puente entre el equipo de infraestructura y los equipos de producto para acelerar la implantación del stack de observabilidad de Grafana (Loki, Mimir, Tempo).
- Formalizar la redacción de postmortems blameless tras incidencias que afecten al servicio junto a CTO y VP de ingeniería, para fomentar el aprendizaje sistémico.
- Apoyar al equipo de Design System con distintas actividades.
- Ayudar en la introducción de Snowplow + Amplitude para la instrumentación de producto colaborando con el equipo de data y los primeros equipos de producto involucrados.
- Introducir el uso de Turborepo en el monorepo para simplificar el proceso de build.
- …
A nivel organizativo
Durante esos primeros meses se estaba planteando una reorganización del área de desarrollo de producto bastante importante. Básicamente era pasar de una estructura de squads que se organizaba de forma recurrente a una basada en líneas de trabajo que tuvieran continuidad en el tiempo.
Esto fue algo en el que me involucraron aún sin tener mucho contexto junto al resto de managers de tech y producto, en parte venía bien tener a alguien con mirada limpia aunque en ocasiones pudiera pecar de ingenuo, para cuestionar supuestos y detectar disonancias.
En aquel entonces me resultó muy útil que alguna de la gente involucrada en la reorganización estuviera hablando en términos de Team Topologies, porque nos ayudaba a tener un lenguaje común. Eso lo terminamos enfrentando con un ejercicio de Context Mapping (en un par de eventos charlé sobre ello), donde representamos buena parte del funcionamiento de la compañía para ver el encaje de equipos respecto a la situación actual.
Este ejercicio sirvió para completar la foto real a la que íbamos, e incluso permitió detectar potenciales problemas de bounded contexts en los que había muchas manos y algunas en las que había muy pocas. Así que cuando con el paso del tiempo surgieron algunos problemas relacionados, no nos pilló tan por sorpresa.
Además, institucionalizamos el dar seguimiento a lo que surgía de las retros de los equipos, ya que se estableció como práctica que se documentase por escrito al menos lo más destacado de ellas y las acciones que surgieran. Esta práctica se mantiene a día de hoy, y resulta muy útil a la capa de management como complemento a los 1:1s para detectar fricciones y puntos de mejora.
Mucho de esto y algunas cosas más, las compartí en la charla Desarrollo de producto en una Scale-Up precisamente en la Pamplona Crafters 2024.

Volviendo a hacer producto (interno)
Tras un año, mi foco cambió a ser algo más todoterreno. Por un lado, ya tenía bastante controladas las dinámicas en el área de desarrollo de producto y a nivel de plataforma había aún puntos de mejora en los que iba trabajando, pero estábamos ya más en una fase de mejora continua y constante, además el equipo de infraestructura seguía introduciendo grandes mejoras. Mientras tanto, en otras áreas de la compañía se identificaban problemas mayores con los que creíamos que podía aportar más.
Ventas
Uno que terminó surgiendo fue en el equipo de ventas, al que había que buscarle solución de forma prioritaria, ya que ninguna de las líneas de trabajo a priori tenía foco en ese tema. Teníamos un proceso interno ineficiente y propenso a errores que terminaban sufriendo clientes High Touch de gran tamaño: muchos emails y llamadas de coordinación, uso de spreadsheets de gestión, tiempos de espera por parte de clientes y account managers, etc.
Para solventar el problema se decidió montar un squad específico moviendo a algunos perfiles técnicos desde algunas líneas de trabajo, y como no queríamos sacar de foco a product managers terminé involucrado en la fase de discovery para abordarlo.
Así que tocó entender los problemas que el equipo de ventas se estaba encontrando y, junto a compañeras de customer experience, entrevistarnos con clientes y gente del área de ventas. A partir de ahí, documentar el impacto que estaba teniendo, definir un primer alcance del MVP usando un user story map y preparar un kick-off para tener tanto al squad, stakeholders y el resto de la compañía alineados.
El squad trabajó en una herramienta que diera mayor autonomía a esos clientes de gran tamaño, permitiendo a account managers mantener el control y darles seguimiento. Y en mi caso, aunque no tiré ni una línea de código y que luego me quedase acompañando al squad de lejos casi como un stakeholder más, me lo pasé como un enano volviendo a participar en hacer producto en fases iniciales.
Creativo
Tiempo más tarde, nos encontrarnos otro problema importante en el área de creativo. Uno de los valores que ofrece Genially a las personas usuarias son las plantillas diseñadas por este equipo. Nos encontramos con un problema de atasco en la publicación de nuevas plantillas y cambios en las existentes que antaño no ocurría, el producto había evolucionado de un modo en el que terminó afectando en la operativa diaria de este equipo.
Esto es porque una vez diseñadas, existía un cuello de botella en el proceso de publicación tanto en el producto como en la web pública. Esta ineficiencia en el Go to Market provocaba tardar más tiempo en recuperar la inversión, era propenso a errores y frustraba a las personas de ese equipo.
Al final era un problema para el que la perspectiva Lean encajaba como anillo al dedo: Identificar el valor, mapear el flujo de trabajo, mantener un flujo continuo, aplicar sistema pull y buscar la mejora continua. Para lo cual se decidió crear de nuevo un squad que se enfocara en esta área.
Una vez analizado y mapeado el journey principal que queríamos resolver, habiendo identificado los distintos hand-offs y limitaciones de las herramientas, planteamos crear un nuevo backoffice diseñado para habilitar mayor autonomía y que simplificase su proceso. De ese modo podríamos sustituir de forma incremental el backoffice legacy, un CMS y un par de spreadsheets de gestión.
Para acelerar el proceso de publicación introdujimos: soporte i18n, gestión de estados, uso de IA generativa, mejoras en las validaciones… Además de crear un servicio que pudiera consumirse desde el producto y la web, cosa que evitaba el uso de herramientas externas y, a nivel técnico, simplificaba la infraestructura y la mantenibilidad futura.
Una vez eliminado ese cuello de botella, que con la combinación del trabajo del squad con el equipo creativo pasó de un retraso de 4 meses a estar al día, nos centramos en mover y mejorar el resto de procesos que se soportaban aún en el legacy en esta nueva herramienta, y en el camino colaborar con uno de los equipos de producto para introducir algunas mejoras conjuntamente.
Operaciones y tech
Aproximadamente el último año en Genially mi rol terminó pivotando de manera oficial a trabajar con foco en las operaciones internas de la compañía. Esto significaba estar en un punto medio entre tecnología, producto, negocio, organización y su potencial impacto en la operativa diaria de cualquier área de la compañía. Esto implicaba mucha amplitud y normalmente menor profundidad, mucha comunicación, intentar promover iniciativas que tuvieran sentido o frenar las que aparentemente necesitasen reflexionarse más, identificar posibles problemas entre áreas de forma prematura, moverme todavía más habitualmente entre diferentes grados de abstracción, etc.
Siempre con una perspectiva de desarrollo de producto, durante ese tiempo tuve oportunidad de trabajar de nuevo y esta vez mucho más involucrado con el área de ventas y desarrollo de negocio; además de empezar a hacerlo también con las de financiero, personas, soporte y comunidad.
Trabajando con más áreas
Con el área de ventas empezamos a trabajar en hacer crecer el MVP que arrancamos antaño, soportando nuevas casuísticas primero y luego acercando e integrando esta herramienta con el CRM que se usa en la compañía. En mi caso había estado involucrado en el día a día de la línea de trabajo que lo evoluciona, con un rol de facilitador y ocasionalmente de desatascador.
Junto a las áreas de financiero y personas hicimos pequeñas iniciativas coordinadas con líneas de trabajo de producto, aunque algunas más ambiciosas se quedaron en el tintero porque el coste de llevarlas a cabo las hacían inviables al menos en el medio plazo.
Con soporte empecé a trabajar muy de cerca, ya que había una tormenta perfecta de cambios en el producto, iteraciones de negocio, atasco en los tiempos de respuesta y degradación de la experiencia de cliente.
Lanzamos varias acciones para solventar la situación: mejorar flujos en el chatbot, rehacer la integración con la herramienta de soporte para enriquecer la información de clientes y mejorar la gestión de colas, introducir automatizaciones, contratar e integrar a un proveedor para pasar a tener un chat basado en agentes de IA que escalase sólo casos complejos, etc. Una vez recuperada una situación de normalidad pudimos entrar en un modo de mejora continua en soporte, y poder dedicar más tiempo a iniciativas relacionadas con comunidad.
Y en medio de todo esto apoyar en ajustes organizacionales, tanto a nivel de desarrollo de producto como en el resto de áreas; y en iniciativas transversales, por ejemplo y para sorpresa de nadie, últimamente con foco en un par relacionadas con IA generativa.

Conclusión
Aunque al inicio costó un poco ver cómo gestionar ese tipo de rol pendular en el que a veces era difícil manejar la cantidad de cambios de foco y de niveles de abstracción, se me dio siempre confianza y autonomía. Finalmente me sentí muy cómodo con ese tipo de responsabilidades.
Me permitió poder influir a nivel organizativo como un manager primero a nivel de área y luego tratar de hacerlo a nivel de compañía, aunque sin las responsabilidades de gestión de personas. Y de vez en cuando poder ejecutar trabajo directamente por ejemplo programando, documentando o investigando de forma autónoma o colaborando con más personas en equipo.
Tras tener la oportunidad de trabajar con tanta gente diferente y variedad de áreas en el contexto de una scale-up, cierro esta etapa en Genially habiendo crecido mucho profesionalmente y con un montón de aprendizajes: técnicos, organizativos, de personas, de negocio… y siendo un fan de la marca y la compañía.
Y ahora ¿qué?
Toca arrancar una aventura nueva que no esperaba, uno de esos trenes que sientes que son una gran oportunidad que tienes que tomar, pero eso lo cuento un poco más adelante en otro post.
Juanjo Navarro
LinkBridge, gestor de enlaces
septiembre 25, 2025 05:46
¡He creado una extensión para navegador!
Este verano, aprovechando las vacaciones, he creado una extensión para navegador que puedes utilizar si te apetece.
Se llama LinkBridge y es un gestor de enlaces para que puedas acceder fácilmente a las webs que utilizas más frecuentemente.
Te dejo un vídeo de su uso y los enlaces de descarga y después te cuento algo más sobre ella.
Algunos otros navegadores pueden usar las extensiones de Chrome o Firefox.
De dónde viene la idea
Hace tiempo que venía usando como gestor de enlaces frecuentes en el navegador el software Flame.
Software de este tipo hay mucho, algunos muy populares, pero a mi me gusta Flame por su estilo minimalista tanto en lo visual como en lo funcional.
El problema de Flame es que es una aplicación que tienes que instalar en tu propio servidor, lo cual obviamente no está al alcance de muchas personas.
Pensé que estas funciones (especialmente las que yo usaba, que no son todas) se podrían cubrir perfectamente desde una extensión del navegador que no requiriese montar nada en nuestro propio servidor.
Funciones de la extensión
Las funciones que he implementado son las principales (y las que yo uso) del software Flame:
- Tus enlaces aparecen cada vez que abres una nueva pestaña del navegador
- Puedes agrupar los enlaces en grupos temáticos con dos niveles: Aplicaciones (de mayor tamaño) y Bookmarks.
- Soporte de temas, tanto claros como oscuros
- Exportación e importación de los enlaces
Acceso al código fuente
El código fuente (por si te interesan estas cosas) está disponible en GitHub: LinkBridge en GitHub
¡Espero que te guste y te sirva si te animas a probarla!
Blog Bitix
Novedades y nuevas características de Java 25
septiembre 19, 2025 07:00
Habitualmente publicaba un artículo por cada versión de Java, pero teniendo en cuenta que para producción es recomendable usar preferentemente las versiones LTS estoy prefiriendo publicar uno en cada versión LTS con el acumulado de características de la versión anterior. En este caso de Java 25 como nueva LTS que sucede a Java 21.
Meta-Info
¿Que es?
Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.
Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
![]()
![]()
Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.
Idea: Juanjo Navarro
Diseño: Albin
Fuentes
- Arragonán
- Bitácora de Javier Gutiérrez Chamorro (Guti)
- Blog Bitix
- Blog de Diego Gómez Deck
- Blog de Federico Varela
- Blog de Julio César Pérez Arques
- Bloggingg
- Buayacorp
- Coding Potions
- DGG
- Es intuitivo...
- Fixed Buffer
- Header Files
- IOKode
- Infectogroovalistic
- Ingenieria de Software / Software Engineering / Project Management / Business Process Management
- Juanjo Navarro
- Koalite
- La luna ilumina por igual a culpables e inocentes
- Made In Flex
- Mal Código
- Mascando Bits
- Metodologías ágiles. De lo racional a la inspiración.
- Navegapolis
- PHP Senior
- Pensamientos ágiles
- Picando Código
- Poesía Binaria
- Preparando SCJP
- Pwned's blog - Desarrollo de Tecnologia
- Rubí Sobre Rieles
- Spejman's Blog
- Thefull
- USANDO C# (C SHARP)
- Una sinfonía en C#
- Variable not found
- Yet Another Programming Weblog
- design-nation.blog/es
- info.xailer.com
- proyectos Ágiles
- psé
- vnsjava


