Picando Código

Nuclear Throne - Steam

enero 09, 2026 08:00

Nuclear Throne

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.

Nuclear Throne

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ó.

No sé qué tanto me gustan el tipo de juego rogue(like|lite). Me hacen acordar a Diablo III, que no sé si entra perfectamente en la categoría, pero es de los juegos del estilo que más jugué. Recientemente también empecé a jugar Absolum y Teenage Mutant Ninja Turtles: Splintered Fate en Nintendo Switch. Siguen las mecánicas que describí más arriba del género que me dan la idea de buscar ser adictivos. Pero si no hay un gameplay divertido e interesante, o algo más que atrape, pueden ser uno más del montón. Nuclear Throne tiene su humor, estilo y personajes que me gustan. 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. Ssó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!

YouTube Video

El post Nuclear Throne - Steam fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

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:

Neofetch Raspberry Pi 4 - enero 2026

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!

forgejo @picandocodigo

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:

docker exec -it forgejo /bin/sh

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.

» Leer más, comentarios, etc...

Picando Código

Actualización de mullvadrb - versión 0.0.9

enero 05, 2026 10:40

mullvadrb-0.0.9

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.

» Leer más, comentarios, etc...

Picando Código

River City Ransom: Underground a ser quitado de Steam

enero 02, 2026 03:09

River City Ransom: Underground

En una actualización el 30 de diciembre, quien quiera que queda responsable de River City Ransom: Underground anuncia que va a ser quitado de Steam (traducción al español por mí):

Hola todo el mundo,

Queríamos compartir una actualización rápida.

Debido al vencimiento de un acuerdo de licenciamiento, este juego va a ser quitado de la venta en Steam. El proceso está actualmente en marcha.

Si ya tienes el juego, nada cambia - va a seguir estando disponible en tu biblioteca de Steam, y puedes continuar descargándolo y jugando como siempre.

Gracias a todas las personas que han apoyado el juego a lo largo de los años. Realmente lo apreciamos.

El juego está a la venta con un 85% de descuento actualmente. La oferta termina el 5 de enero de 2026. Visto que ya empezó el proceso de quitarlo de Steam, imagino que después de ese día el juego ya no va a estar disponible para comprar. Así que si alguna vez consideraron comprarlo, esta seguramente será la última oportunidad.

Una lástima, pero es un final no muy sorpresivo para esta saga. No llegó a ser portado a consolas, algo que los desarrolladores tenían en mente en su momento. De repente le hubiera ido bien en el Nintendo Switch por ejemplo, donde el Nintendo Online permite jugar River City Ransom original para NES. Tuvieron problemas financieros y legales, que seguramente afectaron cualquier trabajo extra que podría haberse volcado en portar a consolas o mejorar la versión existente.

Salvo que alguna empresa grande (como Arc System Works) decida comprar los derechos y resolver con plata los problemas de licencias, es posible que el juego sea un título más en la lista de juegos imposibles de obtener (al menos de manera legal) en el futuro.

En su momento lo apoyé en Kickstarter, y lo tengo en mi biblioteca de Steam, así que la situación no me debería afectar. Quería compartir la noticia en el blog para que más gente esté al tanto por si alguna vez consideraron comprarlo. Escribí un poco sobre el juego en A las piñas y patadas: Jugando beat 'em ups, al estar en la computadora no juego tanto como con juegos en consola. Pero de repente esta noticia me da la excusa para volver a jugarlo y terminar la historia principal.

La serie de Kunio-Kun sigue vigente y recientemente han publicado varios títulos nuevos. Por un lado están las River City Girls, con sus juegos desarrollados por WayForward. Continúan la historia del River City Ransom original pero modernizados. Arc System Works (actuales dueños de los derechos) ha publicado varias compilaciones de juegos tanto de consolas como maquinitas con Super Technos World: River City & Technos Arcade Classics y Double Dragon & Kunio-kun Retro Brawler Bundle. Y la saga sigue en una reinterpretación del "Romance de los Tres Reinos" con River City Saga: Three Kingdoms y River City Saga: Three Kingdoms Next.

Así que hay bastante Kunio-Kun para jugar. River City Ransom: Underground era una versión distinta e interesante. Es una lástima que no haya seguido su desarrollo o tenidos continuaciones debido a problemas de licenciamiento y legales. Pero la saga está vigente desde hace décadas y seguro haya más en el futuro.

Me enteré gracias a GamingOnLinux.

El post River City Ransom: Underground a ser quitado de Steam fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Picando Código

Objetivo: abandonar WordPress y Firefox

enero 02, 2026 12:39

En el software libre hay dos proyectos con los que creí que siempre iba a poder contar: WordPress y Firefox. Para este año nuevo tengo el objetivo de dejar de usar ambos. Con el paso del tiempo tanto Mozilla como Automattic han ido adoptando posturas y tomando decisiones que me resultan indefendibles. Vengo usando ambos proyectos desde hace años, prácticamente desde sus inicios. Es lamentable que con el tiempo las cosas se vayan corrompiendo y volviendo en contra de los usuarios.

Mozilla viene pisándose el palito constantemente desde hace un buen tiempo. Cada vez son menos los usuarios que usan Firefox, y Mozilla parece querer hacer todo lo posible para perder más de los usuarios como uno que creían en su misión. Ya a principios del año pasado Mozilla me inspiró a dibujar un cómic. Lo aprolijé un poco en GIMP con mi tableta Wacom y lo traduje a español para acompañar este post:

Cómic: Cómo imagino que se toman las decisiones sobre Firefox en las reuniones de líderes de Mozilla

Cómic: Cómo imagino que se toman las decisiones sobre Firefox en las reuniones de líderes de Mozilla

Si bien no soy muy de planear o ponerme metas "por año", el año nuevo me da una excusa para ponerle tiempo límite a este tipo de objetivos.

En el caso de Firefox me resulta relativamente sencillo migrar y ya empecé a trabajar en eso el año pasado. Uso varios navegadores web en mi computadora para distintas tareas. Así que de repente haré más cosas en esos otros navegadores. Pero también hay varios forks actuales de Firefox que mantienen versiones bastante a la par en cuanto a funcionalidad. Pero le quitan las porquerías de telemetría, ajustan mejor la privacidad y no van a incluir la tecnología que un montón de cocainómanos billonarios nos vienen presionando a usar y vendiendo como Inteligencia Artificial.

Próximamente escribiré más al respecto, pero una vez que desinstale Firefox, voy a dar por completado el objetivo. Creo que vengo bastante bien, seguramente lo cumpla durante Enero.

En el caso de Automattic en particular, su CEO resultó ser otro caso aislado de CEO millonario psicópata egocéntrico narcisista. Abandonar WordPress es un poco más complicado con todo el contenido que he ido poniendo en el blog a lo largo de sus más de 18 años de vida. Escribí al respecto en WordPress en 2024, hace poco más de un año. Me falta ponerme a trabajar en lo que mencionaba en ese post:

Pero todavía hay un paso más que quiero dar antes de proceder con la migración. Como buen webmaster responsable, hago respaldos regulares de todo el contenido del blog. Pero nunca levanté el blog de uno de estos respaldos. Me gustaría aprovechar esta migración como excusa para probar levantarlo en local. De esta forma seguro aprendo un poco y me puedo armar un plan organizado en el caso de que un día dependa de ese respaldo porque algo salió mal. Ya contaré al respecto.

Una vez hecho eso, tendría un poco más de confianza y migraría el blog. También es posible que un día me de el ataque, instale el plugin para migrar a ClassicPress, y migre todo cruzando los dedos esperando que no se rompa nada. Veremos...

Es muy lamentable que tenga que estar pensando y dedicando tiempo a estas cosas. Pero así estamos. Lo bueno es que el ecosistema del software libre nos provee un montón de opciones con distintas características y muchas alternativas.

El post Objetivo: abandonar WordPress y Firefox fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Picando Código

[Libro] Fernanda Trías - Mugre Rosa

diciembre 30, 2025 05:00

Fernanda Trías - Mugre Rosa

Fernanda Trías es una autora uruguaya cuyos libros se han publicado en Bolivia, Chile, Colombia, España, Estados Unidos, Francia, México, Uruguay y otros países de Europa. En mis paseos por tiendas de libros de Escocia, desde hace un tiempo vengo notando una buena cantidad de libros de autoras latinoamericanas traducidos al inglés. Mariana Enríquez es particularmente popular, y por suerte hay mucha variedad de autores y países. "Pink Slime" (su título en inglés) es uno de esos libros que me encontré por acá.

Me traje la versión uruguaya de Uruguay cuando fui en Marzo, pero recién lo leí ahora en Diciembre.

En una ciudad portuaria con reminiscencias de Montevideo y asolada por una peste misteriosa, una mujer intenta descifrar por qué su mundo se desmorona. No es sólo el acecho de la enfermedad y la muerte, las algas y los vientos envenenados o la pasta color rosa que es ya lo único que se puede comer, sino el colapso de todos sus vínculos afectivos, la incertidumbre, la eclosión de una soledad radical.

Con una prosa inquietante y rica en hallazgos poéticos, Fernanda Trías construye un universo extraordinario que descubre la inmensa belleza y el inmenso dolor que hay en la fragilidad de las relaciones humanas.

Como dice la descripción, la ciudad no es específicamente Montevideo, pero lo leí asumiendo que sí lo era. Esto hizo que tuviera una experiencia medio rara. Mientras leía me venían recuerdos al azar de cosas que pasaron allá. Ya sea paralelismos, visitando la rambla y el puerto, o cómo viví cosas en lugares familiares. Al estar situado en Montevideo dentro de la novela, por ejemplo un ascensor me hacía acordar al ascensor del primer edificio donde alquilé cuando me fui a vivir ahí.

Lo otro que me generó bastante nostalgia fueron las cosas familiares como "blanquear con Jane", montoncitos de queso y dulce de membrillo y café con leche batido (si habremos tomado ésto en el liceo con los gurises), el mensaje para los niños irse a dormir en la tele, los bombones brasileros. El estilo de Fernanda Trías ya de por sí resulta un poco melancólico, pero sumado a mi situación que hace mucho me fui de Uruguay, me pegó directo mucha nostalgia. Será que me estoy volviendo viejo.

La autora nos translada a esa versión de ciudad capitalina donde se refieren al "adentro" en vez del interior y  esa situación opresiva en la que se vive ahí. Recuerda lo que vivimos cuando estuvimos encerrados durante la pandemia (tengo entendido que el libro fue escrito antes de 2020). También hace pensar con esos textos medio poéticos entre cada capítulo. Da la impresión que son pequeñas conversaciones entre la protagonista y su ex, pero también pueden ser ejercicios para invitar a reflexionar y discutir su significado, y de repente ser protagonistas de esa conversación por nuestro lado en nuestra cabeza.

La trama es interesante, pero creo que el valor está en cómo se desarrolla y se va contrastando "lo que está pasando" con la peste misteriosa y lo que le pasa a la protagonista y sus relaciones con otras personas. El camino es la recompensa, ya que estamos en urugayismos. Es un libro entretenido, lindo de leer y disfrutable. El final deja algunas cosas sin resolver y preguntas sin responder, pero extrañamente me dejó muy conforme. Quedé satisfecho con haber experimentado el viaje. En conclusión es un libro que disfruté, me gustó el estilo de la autora, y me resultó muy uruguayo. ¡Recomendado!

El post [Libro] Fernanda Trías - Mugre Rosa fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

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:

  1. Escribir primero una especificación clara de lo que se desea construir: objetivos, reglas de negocio, criterios de aceptación, restricciones técnicas.
  2. Usar esa especificación como fuente tanto para humanos como para agentes de IA.
  3. 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.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 632

diciembre 22, 2025 07:05

Estampida arrasando una oficina con desarrolladores

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...

.NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

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?».

» Leer más, comentarios, etc...

Variable not found

¿Estás usando tus expresiones regulares en .NET de forma óptima?

diciembre 19, 2025 07:47

Desarrollador viendo como el rendimiento de su aplicación desciende

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 ejemplo 123123, 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.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 631

diciembre 15, 2025 07:42

Puesto de trabajo de desarrollador inquieto por la IA

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...

.NET

<!--more-->

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI / Cross-Platform

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Soporte para el TLD .localhost en ASP.NET Core 10

diciembre 02, 2025 07:05

Desarrollador trabajando en una aplicación web

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 .dev por delante porque no es posible crear wildcards sobre top level domains como localhost.

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:

Pantalla de creación de una aplicación ASP.NET Core, seleccionando el uso del TLD .dev.localhost

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.

» Leer más, comentarios, etc...

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.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 630

diciembre 01, 2025 07:01

Un usuario caminando feliz por el happy path

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...

.NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 629

noviembre 24, 2025 07:05

Programador usando un ordenador Minivac en los años 60

Años atrás, en los inicios de la informática, se programaba de forma bastante diferente: conectando cables y usando paneles de interruptores. Por eso me ha parecido muy curioso el simulador del Minivac 601, un ordenador de los años 60,  donde podemos probar de primera mano cómo era su experiencia de uso.

Y continuando con temas vintage, Microsoft ha anunciado la liberación del código fuente de los juegos Zork I, II y III, la mítica saga de aventuras conversacionales de los años 80. Pura historia del software.

También esta semana encontramos una interesante lectura de Sudhir Mangla donde explica cómo usar patrones modernos y características recientes de C# para construir modelos más expresivos, seguros y mantenibles que los que ofrece la aplicación estricta de SOLID.

El resto de enlaces interesantes recopilados esta semana, entre los que podéis encontrar información sobre .NET 10, ASP.NET Core, IA, desarrollo web y mucho más, los tenéis a continuación.

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

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.

Foto donde hay algo más de 100 personas de pie posando para la foto, fondo con árboles y suelo adoquinado

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.

Foto donde aparecemos 12 personas posando de pie, sucios y con barro en el suelo tras haber estado limpiando una casa en las afueras de Álora, Málaga, tras la DANA 2025 que también afectó a esa zona

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.

» Leer más, comentarios, etc...

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!

» Leer más, comentarios, etc...

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.

Continuar leyendo en Blog Bitix

» Leer más, comentarios, etc...

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.

rss subscripción

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

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