Picando Código

Anunciado Mega Man: Dual Override

diciembre 12, 2025 11:30

Capcom anunció la duodécima entrega principal de la serie Mega Man durante The Game Awards: Mega Man: Dual Override. El bombardero azul regresa en 2027 en Nintendo Switch 2, Nintendo Switch, PlayStation 5, PlayStation 4, Xbox Series X|S, Xbox One y PC a través de Steam.

Mega Man Dual Overrida

El último título en la serie fue Mega Man 11 en 2018. Parece que Capcom nos va a dar un Mega Man nuevo por década aproximadamente 😅 En 2027 se celebran 40 años de Mega Man, así que es posible que hayan más anuncios al respecto durante el año.

En el mundo de los cómics, después de una muy buena serie Mega Man Timelines durante 2025, vamos a tener cómics de Mega Man X. El primer número, Mega Man X #0, va a estar disponible en Enero de 2026, seguido de una mini-serie de 5 números. Esperemos ver algo de Mega Man X en el terreno de los videojuegos también...

El tráiler del anuncio muestra que sigue el estilo gráfico 2.5D de Mega Man 11. De repente se notan algunos detalles más en lo gráfico. Vuelve la clásica deslizada y el disparo cargado con el mega buster. Hacia el final del tráiler vemos una animación como de expansión de elementos en la armadura de Mega Man. ¿Cómo estará relacionado eso al juego? Habrá que ver también qué protagonismo tiene Proto Man, del que escuchamos el silbido característico al final del tráiler. Por el momento no hay mucho más información, pero seguro sepamos más el año que viene.

Mega Man: Dual Override

Jugué Mega Man 11 en su momento en Nintendo Switch y lo disfruté mucho. Hace poco lo empecé de nuevo, a ver si lo vuelvo a terminar. Es entretenido y desafiante, como nos tiene acostumbrados la saga. En ese título se introdujo una mecánica nueva, el sistema "Double Gear". Este incluye dos habilidades, Speed Gear que permite enlentecer el tiempo, permitiendo atacar y esquivar ataques más fácilmente. La otra es Power Gear, que permite disparar ataques más potentes. Ni idea si esto vuelve en Mega Man 12.

Si todavía no han probado Mega Man 11, en los próximos días va a recibir un descuento en tiendas digitales. Va a estar de oferta en la PlayStation Store, Nintendo eShop, Xbox Store y Steam. También hoy recibió una actualización que expande las opciones de idioma a portugués brasileño y español latinoamericano.

Siguiendo con Mega Man: Dual Override, este mes Capcom organiza un concurso especial de diseño de jefes. Como hizo en alguna entrega anterior hace muchos años, tendremos la oportunidad de crear un nuevo robot que aparecerá en el juego:

Periodo de inscripción: 11 de diciembre de 2025, 22:00 h - 1 de enero de 2026, 01:59 h CST
Convocatoria: Estamos aceptando inscripciones en X (antes Twitter) para el concurso de diseño de Robot Maestro para Mega Man: Dual Override. ¡Utiliza el modelo de abajo como base para crear tu propio diseño único de un Robot maestro con un brazo de succión gigante!
Aceptaremos diseños de las siguientes cuatro regiones: Japón, las Américas, Europa y Asia.
Buscamos diseños de un Robot maestro que tenga un brazo derecho con inmensos poderes de succión!
Premios: Elegiremos los 6 mejores diseños y seleccionaremos un ganador entre ellos.
- Gran premio: 1 ganador
Tu diseño de Robot maestro se usará en Mega Man: Dual Override
- Premios a la excelencia: 6 ganadores
Tu nombre aparecerá en los créditos de Mega Man: Dual Override

Voy a ver si mando algo... Una lástima que el concurso se haga a través de ese sitio web al que las marcas ya deberían haber abandonado hace rato.

Todavía falta al menos un año entero para poder jugar Mega Man Dual Override. Pero es bueno ver que Capcom no se ha olvidado de Mega Man.

Otro título interesante de Capcom que recibió noticias durante el evento fue Pragmata. Cuando vi el primer tráiler me resultó un juego "como un Mega Man más adulto". Es una IP nueva de Capcom, aventura de acción y ciencia ficción con elementos de hackeo. ¡Se ve muy bien! Mediante un tráiler nuevo, se anunció su disponibilidad también para Nintendo Switch 2. Además está disponible una demo gratuita en Steam, y próximamente va a haber demo para Nintendo Switch 2, PlayStation 5 y Xbox Series X|S. El juego va a estar disponible el 24 de abril de 2026 en todas las plataformas, algo para ir jugando mientras esperamos Mega Man 12...

YouTube Video

El post Anunciado Mega Man: Dual Override fue publicado originalmente en Picando Código.

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

Picando Código

Piques Firefox: Contar cantidad de pestañas abiertas en Firefox 2025

diciembre 11, 2025 11:30

Hace unos años publiqué el pique para contar las pestañas abiertas en Firefox. Me sirve para convencerme de que tengo demasiadas pestañas abiertas y que los 16GB de Ram de mi laptop no son suficientes para aguantar tanta cosa. Pero desde hace un tiempo esto no me venía funcionando. Al abrir la consola del navegador con Ctrl Shift J, no me aparecía la línea de comando. Así que no podía ejecutar comandos.

Resulta que desde hace algunas versiones de Firefox la línea de comando de la consola del navegador está deshabilitada por defecto. Para habilitarla, tenemos que ir a about:config en la barra de dirección de Firefox y cambiar el valor de devtools.chrome.enabled de false a true. Tambien se puede habilitar seteando la opción "Enable browser chrome and add-on debugging toolboxes" en las preferencias de las Web Developer Tools.

Así que pude volver a contar mis pestañas abiertas en Firefox:

>> gBrowser.tabs.length
← 445

Debería seguir cerrando algunas 🙈

De esa misma página de documentación de Firefox que enlacé: La consola del navegador es como la consola web, pero aplicado a todo el navegador en vez de al contenido de una pestaña única. Loguea un montón de información como pedidos de red, JavaScript, CSS, seguridad, errores advertencias y más pero de todo el contenido abierto. Podemos ejecutar JavaScript, y tenemos acceso a todas las pestañas a través de la variable global gBrowser. También podemos modificar la interfaz del navegador al tener acceso al objeto window, cerrar pestañas con código y mucho más.

Tendría que buscar algo divertido para programar con este conocimiento. Pero por ahora me limito a contar las pestañas abiertas a mano cada vez que pienso que tengo demasiadas. Hay un montón de extensiones de Firefox diseñadas con este mismo propósito, pero para qué instalar una extensión más si se puede hacer en pocos pasos.

El post Piques Firefox: Contar cantidad de pestañas abiertas en Firefox 2025 fue publicado originalmente en Picando Código.

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

Picando Código

[Libro] Mike Drucker - Good Game No Rematch

diciembre 04, 2025 11:15

Mike Drucker - Good Game No Rematch

Mike Drucker es un escritor y comediante. Escribió para programas de televisión como Bill Nye Saves the World, The Tonight Show y Full Frontal with Samantha Bee. Ha contribuido a The Onion y Saturday Night Live (el actor de Scrubs hizo de él en un sketch de SNL). Pero más importante (para mí) que todo eso, trabajó en Nintendo y escribió la localización al inglés de Kid Icarus Uprising, entre otras cosas. Ah, ¡también escribió un cómic de Spider-Man!

Solía seguirlo en Twitter, y como muchos hicimos, abandonó el barco cuando se destapó como antro fascista. Pero se abrió un newsletter al que me suscribí desde el primer día: At The Mountains of Sadness, está alojado en Substack. Substack es otro sitio web que se destapó como antro fascista, y Mike Drucker está al tanto y tiene planeado migrar a Ghost. Pero estas migraciones son más complicadas de lo que parecen y requieren un montón de tiempo y esfuerzo...

Hace un buen tiempo ya, anunció en su newsletter que había vendido la idea del libro. No sé cómo funciona eso, supongo que uno presenta parte de lo que tiene escrito a una empresa que se compromete a pagar para publicarlo. En fin, el tema es que desde entonces lo esperé con ansias. Habiendo disfrutado mucho de lo que escribía tanto en su newsletter como en artículos que enlazaba, tenía muchas ganas de leerlo.

Lo describía como una colección de ensayos sobre videojuegos. Y si bien es cierto en parte, los capítulos tienen una cohesión cronológica que van contando experiencias en distintas etapas. El elemento que conecta todo son los videojuegos, con referencias y chistes, pero han sido algo muy presente en su vida. El libro está dividido en cuatro secciones principales que agrupan capítulos de las distintas etapas de las que escribe. Algunos capítulos son más cortos con contenido más al estilo lista como Manuales de videojuegos a través de las décadas. Éstos son como experimentos literarios que me recuerdan al trabajo de mi amigo Nacho Alcuri, por ejemplo:

What your favorite classic arcade game says about you (Lo que tu juego clásico de maquinitas favorito dice de tí)

  • Street Fighter 2: You're already a little mad I didn't specify which Street Fighter 2.
    Street Fighter 2: Ya estás un poco enojado que no especifiqué cuál Street Fighter 2.

Si bien es un libro gracioso, también tiene historias muy personales e identificables. No recuerdo la última vez que un libro me hizo lagrimear como éste, encima más de una vez. No sólo son historias emocionantes (y devastadoras), algunas experiencias resultan dolorosamente identificables: I'm a man who plays fighting games alone without considering the depths of sadness that represents (soy un hombre que juega juegos de pelea sólo sin considerar las profundidades de tristeza que eso representa... ¡OUCH! No sé si necesitaba que un libro me dijera lo triste que soy).

Su humor es autocrítico al extremo. Mientras leía pensaba "qué forma de darse palo, pero sabemos que es hipérbole, al final del día es una persona que dentro de todo hace las cosas bien y tiene relativo éxito. Pero tanto autocrítica al final le puede jugar en contra porque... ¿Dónde escuché esto antes?" Y ahí me pegó como si me hubiera chocado contra un muro y recordé las tantas veces que escuché ese discurso dirigido a mi persona, particularmente en el ámbito profesional. También es que me gusta mucho el humor autocrítico y cínico. Otro ejemplo de su newsletter: from 1977, long before my parents cursed me with life (de 1977, mucho antes que mis padres me maldijeran con la vida).

Con eso y otras tantas experiencias generé esa conexión con el libro que está escrito por Mike Drucker pero al momento de liberarlo al mundo se vuelve la experiencia única de cada individuo que lo lee y el artista pierde el control. Uno trae a un libro todo de sí mismo y saca de él enriquecimiento diverso. Como comenté antes, me generó diversas emociones, más que nada risa e identificación. También disfruté mucho de las anécdotas relacionadas a los videojuegos, ese conocimiento anecdótico de la historia de esto que tanto me apasiona es mi adicción.

Tras leerlo me fui a fijar algo que sospechaba y sí, Mike Drucker y yo somos de la misma generación. Nació un año antes que yo. Salvando las diferencias geográficas y sociales, hay mucho en común. Es un libro gracioso y entretenido, pero si nacieron en las décadas de 1980 o 1990 y crecieron jugando videojuegos, probablemente encuentren mucho en común también.

¿Quiero jugar Nier: Automata después de leer este libro? Sí. ¿Va a empeorar mi depresión? Probablemente. ¿Este párrafo está inspirado en el estilo del libro? Gracias por usar "inspirado en el" y no "robando del", voz imaginaria en mi cabeza que se hace preguntas retóricas a mí mismo.

Es un libro que me gustó mucho, recomiendo y me hizo reír, llorar, y querer estar en una cadena de texto con Mike Drucker por alguna razón. Es un muy buen regalo para gente aficionada a los videojuegos, se ve muy lindo en la biblioteca y seguramente vuelva a leer. El autor es un tipo famoso por las cosas que ha escrito (programas importantes de televisión y más), pero a mí me llega porque es terrible nerd. Y si ves el término nerd como algo distinto al mayor de los cumplidos, estás leyendo el blog equivocado.

El post [Libro] Mike Drucker - Good Game No Rematch fue publicado originalmente en Picando Código.

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

Picando Código

Mi solución RSS con software libre y autogestionado

diciembre 02, 2025 09:00

FreshRSS Capy Reader

Por fín resolví un tema pendiente que tenía con mis feeds RSS. Por mucho tiempo venía usando una aplicación en mi computadora y otra en mi teléfono. Pero cada una tenía sus feeds, sincronizadas a mano, y las que leía en un lado me volvían a aparecer como nuevas en el otro. Finalmente tengo todos mis feeds en un repositorio centralizado al que puedo acceder desde distintos dispositivos, todo sincronizado. Algo así como las épocas donde usaba Google Reader, y accedía desde la computadora o la aplicación en el teléfono, pero todo basado en software libre y alojado en mi computadora.

En algún momento usé TinyRSS, pero se rompió. Con eso di mis primeros pasos hacia centralizar mi consumo de feeds RSS con al exportar y procesar los Feeds RSS de TinyRSS para consumo OPML. Desde entonces venía usando Akregator en Linux y Feeder desde F-Droid en GrapheneOS. Pero finalmente me puse a resolver este tema.

FreshRSS

Hace rato que sabía de FreshRSS, pero como siempre, es cuestión de dedicarle un tiempo para aprender cómo se usa. Y ese tiempo se demoró un poco. Pero una vez que quedó todo armado, valió la pena. Estoy tendiendo a alojar y autogestionar más cosas por mi cuenta, y esto fue una de las soluciones ideales.

¿Qué es FreshRSS? Un agregador de feeds gratuito y libre (AGPLv3), que se lo puede alojar uno mismo. Es liviano, fácil de gestionar, poderoso y personalizable.

Tiene un montón de características de personalización, ideal para ajustarlo como nos quede cómodo. Personalmente toqué un poco al principio, pero desde entonces vengo usándolo para leer feeds y nada más (¡como debe ser!). Podemos instalarlo en nuestro servidor local, un servidor en la web, o incluso usarlo desde una instancia ya instalada en un servidor de confianza. En mi caso lo instalé en un Raspberry Pi 4 Model B. Es la misma computadora donde tengo corriendo Navidrome, entre otras cosas.

Estoy muy contento con la cantidad de cosas que tengo alojadas en la Raspberry Pi, y sigue funcionando sin el más mínimo problema por meses y meses. Tampoco es que esté usando nada muy exigente, accedo a los servicios por demanda, y soy su único usuario. Pero igual, está muy bueno lo que rinden.

Para la instalación, opté por correrlo con Docker y está usando su soporte nativo para SQLite. No tengo cientos de miles de feeds, así que no creo que tenga que migrar a un servidor de base de datos más grande en el futuro.

Capy Reader

Una vez instalado el servidor, necesitaba una aplicación para leer los RSS desde mi teléfono. Es gracioso cómo éstos días cuando uno dice "teléfono", no tiene que aclarar "teléfono móvil", porque ya nadie se imagina los teléfonos de línea...

FreshRSS soporta acceso desde aplicaciones nativas o móviles. Hay varias opciones en Linux y los otros sistemas operativos populares menos respetuosos con sus usuarios. Para el uso con aplicaciones soporta dos APIs: la API de Google Reader (de mejor soporte) y Fever API (características limitadas, menos eficiente, menos seguro). Hay una lista de aplicaciones compatibles que podemos ver para ir probando la que más nos guste.

Esta parte es entretenida, ir probando distintas aplicaciones y ver cómo se comparan. Pero en mi caso la aventura fue corta, porque probé Capy Reader de primera y no vi razón para probar ninguna otra. En primer lugar, la mascota es un carpincho, sumó muchos puntos. Usando la API de Google Reader, logré conectarme con FreshRSS de manera sencilla. Me gustó y me resultó cómoda la interfaz, un poco familiar comparada con Feeder que era lo que venía usando. Así que quedé contento con eso y no probé más alternativas.

Pensamientos finales

Vengo usando FreshRSS y Capy Reader como solución para la lectura sincronizada a través de dispositivos de fuentes RSS desde hace un tiempo. Desde la computadora no tengo la necesidad de usar ninguna aplicación en particular. FreshRSS provee una interfaz web bastante cómoda. Cuando algo queda como leído en la interfaz web, aparece como leído en Capy Reader y viceversa.

Una vez que tenemos todo funcionando, está el paso extra de configurar el port-forwarding en el router si queremos acceder a éstos servicios desde afuera de nuestra red local. Ya sea con IP estática, DNS dinámico, o alguna otra solución de la que no tengo conocimiento. Pero eso depende de cada caso, está fuera del alcance de este post, y es de esos temas en los que no tengo suficiente conocimiento para hablar con confianza.

Pero en mi caso tengo todo configurado para poder leer mis feeds y escuchar música desde cualquier lado, corriendo en mi Raspberry Pi. Por ahora me quedo con estas dos soluciones, y con suerte no tenga que cambiarlas por mucho tiempo. ¡Gracias software libre!

El post Mi solución RSS con software libre y autogestionado fue publicado originalmente en Picando Código.

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

Picando Código

Recomendando blogs en español - Parte II

noviembre 27, 2025 10:00

Recomendaciones de blogs

Hoy tengo un par de blogs en español más para recomendar. Ver Recomendando blogs en español parte 1

Interlan

Interlan es el sitio personal y profesional de Drk0027:

Soy un desarrollador web y he trabajado con múltiples herramientas, lenguajes de programación y Frameworks. Aquí les comparto mis capacidades y experiencia.

Este blog lo conocí gracias a un pingback (¿o trackback?) en el post Aventuras de resaltado de sintaxis en el blog. En ese post escribí sobre mis experiencias mostrando código con resaltado de sintaxis en Picando Código. Después de usar un plugin por años, decidí usar HTML puro pasando el código por un resaltador de sintaxis.

El pingback vino desde Articulo: Resaltado de Sintaxis de Código en WordPress.

En el artículo en cuestión comenta su propia experiencia, viviendo el mismo problema: WordPress no tiene soporte nativo para resaltado de código, plugins que se quedan obsoletos y el trabajo de hacerlo todo a mano. Al final creo que ambos nos quedamos con la misma solución de usar HTML directo, y no enfocarnos tanto en actualizar el código fuente de posts viejos que usaban un plugin para esto.

Gracias Drk0027 por el pingback, espero que con esta entrada te llegue uno también en respuesta a tu artículo. Qué bueno seguir usando estas tecnologías como el pingback que permiten la interacción entre distintos sitios.

También pueden seguir a Drk0027 en Mastodon.

Sidiostedalimones

Sidiostedalimones es el blog de Chuck:

Soy un diseñador gráfico que aprendió a maquetar, y 27 años después soy un desarrollador front con mucho cuidado por el detalle y muy pendiente de la semántica y la accesibilidad. Sin embargo, aquí me gustaría escribir sobre temas más del día a día, series o pelis que haya visto, ese videojuego en el que ando atascado y algún que otro dibujo.

Este blog lo agregué a mi lista de feeds RSS hace un tiempo por medio de la serie de entrevistas de tecnolocuras. Chuck escribe sobre varias cosas como dice la descripción. Comparte experiencias y publica reseñas de películas, el fediverso, programación y más. Entre sus varios artículos interesantes se encuentra por ejemplo este sobre VS Codium que empieza con "Cada vez es más difícil tener cosas bonitas, a veces parece que ya es imposible", y comparto el cinismo...

Además, el blog tiene un blogroll, que me hizo visitar más blogs en español y me hizo actualizar mi propio blogroll acá en mi blog sacando algunos enlaces que ya no se actualizan o están dados de baja.

También pueden seguir a Chuck en Mastodon.

Fuente de la imagen original: Hall of Quiet Study, Anónimo, siglo 19 China. Bajo dominio público.

El post Recomendando blogs en español - Parte II fue publicado originalmente en Picando Código.

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

Picando Código

Hyrule Warriors: Age of Imprisonment - Nintendo Switch 2

noviembre 25, 2025 12:00

Hyrule Warriors: Age of Imprisonment es el primer título de la saga Hyrule Warriors exclusivo para Nintendo Switch 2. Fue desarrollado por Koei Tecmo bajo la supervisión de Eiji Aonuma y Nintendo. El estilo de juego está basado en Dynasty Warriors, otra serie creada por Koei Tecmo. El concepto básico es manejar un personaje en un mundo 3D con vista en tercera persona enfrentando hordas de enemigos.

Hyrule Warriors: Age of Imprisonment

En base a eso, se construye todo un género parte de la familia hack and slash. Contamos con distintos personajes, combos, armas diversas y enemigos de todo tipo, incluyendo jefes que exigen más daño y esfuerzo. También dentro de cada mapa tenemos que implementar un poco de estrategia con más de un personaje a la vez para lograr distintos objetivos. El juego puede aparentar ser bastante sencillo o monótono, pero tiene varias características que hacen que uno se enganche y vuelva a jugar varias veces.

Un aspecto que hace bastante especial a Hyrule Warriors es la historia. Ya con el primer Hyrule Warriors (originalmente en Wii U, 3DS y eventualmente una versión definitiva en Nintendo Switch) Koei Tecmo logró contar una historia muy interesante y entretenida para seguidores de The Legend of Zelda. Si bien ese primer título está fuera de la línea de tiempo "canon" de Zelda, en general hacen mejor trabajo que Nintendo en ese aspecto.

Después vino Hyrule Warriors: Age of Calamity. Eiji Aonuma se acercó a Koei Tecmo para usar este formato para contar los eventos de La Gran Calamidad, que conocimos a través de flashbacks en The Legend Of Zelda: Breath of the Wild. En esa ocasión trabajaron en conjunto con el equipo Zelda en Nintendo, usando los mismos gráficos, voces y personajes. La historia estaba directamente conectada a otro título.

Empecé Hyrule Warriors: Age of Calamity en Nintendo Switch. Después de mucho tiempo sin jugarlo volví, pero con el anuncio del Nintendo Switch 2, su mayor capacidad y compatibilidad con juegos de Nintendo Switch original, lo volví a dejar en pausa. Finalmente lo terminé en Nintendo Switch 2, donde noté una mejora importante en el rendimiento. Al ser más potente la consola y poder jugar los juegos del Switch original, se nota un aumento del rendimiento, con mejores frames por segundo y demás.

Terminé la historia y muchas de las misiones opcionales. No llegué a terminarlo 100%, pero eso es mucho trabajo. Estos juegos tienen abundante contenido, y terminarlos por completo es una odisea que sólo los más dedicados van a poder completar. Hay contenido para rato.

Hyrule Warriors: Age of Imprisonment

Tenía mucha expectativa por este juego después de haber visto los trailers para Nintendo Switch 2. La fluidez de los gráficos y la cantidad de enemigos en pantalla a la vez, lo hacían ver muy bien.

Mis primeras impresiones fueron muy positivas. Estos juegos están hechos para jugar en sistemas con hardware de más alto rendimiento como el Switch 2. Los 60 frames por segundo hacen una experiencia totalmente distinta, y hace difícil volver a los anteriores (que se fijan en 30 frames por segundo). Además se ve tan bien como prometían los tráilers. La sensación que tuve en las primeras escenas donde manejamos a Zelda recién llegada al pasado, con los modelos de los personajes mas "grandes" en pantalla, me hicieron pensar "esto es un preview del juego de Zelda que podemos llegar a tener en Nintendo Switch 2".

Hyrule Warriors: Age of Imprisonment - Zelda

Hyrule Warriors: Age of Imprisonment - Zelda con Rauru y Sonia -antiguos reyes de Hyrule- de fondo

El juego se ve y se juega espectacular. En las más de 30 horas iniciales del juego donde terminé la historia principal, noté una baja en la cantidad de frames apenas 2 o 3 veces en momentos muy intensos (muchos enemigos, varios personajes, muchas partículas y efectos de luz en pantalla), pero no duraron más de uno o dos segundos. Es sumamente fluido, los tiempos de carga son rápidos, y todo se ve muy lindo.

Los paisajes son familiares si ya jugamos Breath Of The Wild, Age of Calamity o Tears of the Kingdom. El mapa cuenta con las mismas tres capas que vimos en Tears of the Kingdom: El cielo, la tierra y las profundidades. Así que hay misiones en esos distintos ambientes y los varios biomas de la superficie. También vuelven los elementos conocidos como los constructs, el gloom y dispositivos Zonai.

La cantidad de enemigos en pantalla sumada a las escenas cinematográficas dan una verdadera sensación de estar participando de una batalla épica contra el mal. La ambientación que logran con las capacidades del Switch 2, sumado a la música e historia, hacen sentir mucho más la magnitud de la guerra.

El juego encontró archivos guardados de The Legend Of Zelda: Tears of the Kingdom y Hyrule Warriors: Age of Calamity en mi Switch. Con eso nos regalan un par de armas de yapa. Está bueno cuando los desarrolladores hacen estas cosas, agrega a esa sensación de que estos títulos están conectados.

Personajes

La protagonista es Zelda. Ya en versiones anteriores se le daba bastante importancia y está bueno poder jugar como ella y ver el protagonismo que tuvo en la historia previa a Tears of the Kingdom. Tiene poderes mágicos de luz y su tradicional arco y flecha. Es uno de los personajes que estuve usando a lo largo de toda la historia, y sus poderes son particularmente fuertes contra enemigos con gloom, que viene bien hacia el final.

Hay que señalar que igual el juego sufre un poco del "no podemos hacer un Zelda sin Link", al igual que pasa con The Legend Of Zelda: Echoes of Wisdom. Si bien Zelda es el personaje principal, y Link no es parte de la historia directamente, tenemos un Link "por las dudas", para no salir del todo de la fórmula. En Echoes, Zelda tiene un poder especial (limitado) que básicamente la transforma en Link (además de jugar con Link en unas pocas partes). En esta ocasión, el Construct se juega como una versión actualizada del Link del Hyrule Warriors anterior. Sus poderes son bastante similares, y se maneja parecido, tanto usando una espada como una lanza. Y no digo más para no contar parte de la historia.

Por cierto, Echoes of Wisdom es otro juego que obtuvo una actualización para Switch 2, ¡debería probarla!

Otro de los personajes que me gustan es Calamo. Es un guerrero korok que lucha junto al Construct y su arco emula el de una película (buddy picture) de pareja dispareja que terminan siendo los mejores amigos. Calamo empieza su aventura queriendo viajar por Hyrule para encontra un lugar donde literalmente "echar raíces" para convertirse en el árbol más grande del reino y crear un bosque nuevo. Es un personaje bastante simpático, con diálogos divertidos, y no entiendo cómo Nintendo no lo usó para crear un amiibo nuevo.

Vuelven los Goron, Zoras, Rito, Koroks, Hylians, Zonai, y están presentes los antiguos reyes de Hyrule Rauru y Sonia. También podemos jugar como Mineru, la hermana de Rauru que se encarga de investigar a los constructs y los usa como armas en diversas formas que recuerdan a la creación de dispositivos con Ultrahand. Cada especie tiene un estilo único y armas específicas. Me pareció un poco mucho la cantidad de personajes disponibles. Intenté mantener en un nivel alto a un equipo variado, pero tenemos dos personajes de cada especie, así que subir de nivel a todos llevaría cientos de horas y no sé si no se volvería demasiado repetitivo. Podemos subir de nivel entrenando a los personajes, pero eso exige rupíes que no crecen en los árboles (pero sí se encuentran escondidos al azar en cajas y jarros por el reino). 

Durante las batallas tenemos oportunidades para que los personajes interactúen entre sí, lo que sube un medidor de afinidad que permite combinar dos personajes en un ataque especial. Esto está muy bueno, y combinar nuestro guerrero con otros nos da distintos resultados divertidos de ver. Un aspecto más de lo intenso que es el juego en cuanto a contenidos.

El gameplay puede aparentar ser repetitivo, y de repente un poco lo es. ¡Pero es divertido! Y está la sensación de progreso al ir destrabando cosas en el mapa y cumplir con misiones y objetivos. Para el ojo no entrenado se puede ver el gameplay como un "button mashing" donde simplemente apretamos todos los botones hasta ganar. Pero esto desacredita la profundidad de los estilos de juego sumado a la estrategia y diversion que aporta la diversidad de escenarios, personajes y misiones. Algo que recuerdo repetitivo del título anterior fue la cantidad de Moblins que tuve que vencer. En este título se siente un poco más variado, como que se equilibra más la diversidad de los jefes con Moblins, Bokoblins, Hynoxes y Lynels.

YouTube Video

Star Fox

A veces otros nos proveen lo que Nintendon't. El último título en la saga de Star Fox fue Star Fox Zero para Wii U. Como con otros títulos de esta serie, Nintendo lo usó como demo técnico para una tecnología nueva, en este caso el gamepad del Wii U. Resultó que jugar un juego con dos pantallas con distintas vistas a la vez, y controles por movimiento, no era tan cómodo o divertido. Fracasó tanto comercialmente como en la crítica. Tampoco le pusieron demasiado esfuerzo a la historia, básicamente reciclaron Star Fox 64.

Personalmente disfruté del juego porque me encanta Star Fox. Pero reconozco que el control es horrible, recuerdo particular frustración en los últimos jefes donde se ponía difícil la cosa. Si sacaran una versión actualizada para jugar con controles tradicionales, no estaría mal. Pero para eso tuvimos Star Fox 64 3D en 3DS.

Después de eso, tuvimos Starlink: Battle for Atlas. Un juego que pre-ordené para Nintendo Switch ni bien salió, y pocos meses después se encontraba por un 10% del precio en todos lados porque no lo compraba nadie. El tema es que la versión para Nintendo traía una nave Arwing con Fox McCloud y misiones con Star Fox. Jugué bastante a ese juego, y las misiones de Star Fox estaban muy bien. Es más, escribiendo esto me dieron ganas de volver a jugarlo...

El tema es que desde entonces no hemos tenido nada nuevo de Star Fox por Nintendo. Pero Koei Tecmo encontró una forma de básicamente reimplementar Star Fox en el universo de Zelda. Tenemos varias misiones con Calamo y Knight Construct en las que Calamo pilotea el Construct en forma de nave/avión/Arwing. Estas misiones son muy buenas y muy similares a Star Fox. Podemos hacer un barrel roll para esquivar ataques, fijar la mira a enemigos con un ataque más fuerte, y más. A veces cambia de cámara y lo hace como un shooter 2D, y encima agrega otras mecánicas tradicionales del juego. Muy buenas las pantallas de este estilo, pero me dejaron ansiando más un Star Fox nuevo. Nintendo, ¡queremos Star Fox Armada!

La historia

La historia del juego relata los acontecimientos que llevaron a los eventos de The Legend Of Zelda: Tears of the Kingdom. Zelda viaja al pasado y conoce a los reyes antiguos de Hyrule. Si bien mi memoria no funciona tan bien, por lo que no recuerdo la historia de cada uno. Pero de todas formas la historia de los Zelda no es necesariamente algo en lo que Nintendo invierta demasiado tiempo. Son el tipo de equipo que empieza con la forma de jugar y va armando la historia alrededor de eso en base a las necesidades del juego. Y la línea temporal de tiempo de Zelda es un despelote. Nintendo no tiene vergüenza de rebootear y abrir otra rama de leyenda en cada título nuevo.

No voy a contar muchos detalles, porque creo que gran parte del disfrute de los Hyrule Warriors es ver las escenas cinematográficas y ver cómo se va desarrollando. Si bien más o menos tenemos una idea del desenlace, está muy bueno cómo se llega a eso. Se sintió más conectada a Tears of the Kingdom que Age of Calamity a Breath of the Wild. Esto puede que tenga que ver con los tiempos en los que jugué cada uno (Tears of the Kingdom lo terminé hace no mucho), pero también sin entrar en terreno de spoilers, este juego es más "fiel" al desenlace.

Al ver los créditos tras terminar la historia principal, descubrí que el juego está disponible en Español de Latinoamérica. Ni siquiera me había fijado al empezarlo, así que decidí probarlo. Están los textos en español, pero también las voces de los personajes. Probé jugar un par de misiones en español, pero volví al inglés, no me gustaron mucho las voces. Creo que el problema que tengo con los doblajes al español es que intentan hacer un idioma neutro que no me suena natural. Latinoamérica es muy grande, y tenemos tantas variedades de español como de personas. Y tratar de hacer algo neutral termina sonando raro. De repente estaría mejor que cada personaje tuviera un acento natural, ahí de repente sonaría mejor. En fin, bueno saber que está disponible, pero ya acostumbrado a jugarlo en inglés me quedé con el inglés.

Conclusión

Hyrule Warriors: Age of Imprisonment es mi título favorito de la saga hasta ahora. Le vino muy bien su salto a una plataforma nueva con mejor hardware. Así es como se deben jugar estos juegos. Es muy entretenido e incluso después de terminar la historia principal se descubre suficiente contenido nuevo para mantenernos enganchados por un buen tiempo. La mecánica de juego es muy entretenida y vencer a cientos de enemigos a la vez puede resultar catártico y hasta un poco adictivo. Siendo seguidor de la serie, está buenísimo jugar como distintos personajes introducidos en este y otros juegos. Lo recomiendo ampliamente y seguiré jugando un buen tiempo. Lo que tengo en el debe por probar es jugar en modo cooperativo.

Entré a nueva generación de Leyenda de Zelda en 2017 con Breath of the Wild. Ese mundo se expandió (se podría decir que de manera lateral, en una rama de tiempo paralela si nos ponemos exquisitos) con Hyrule Warriors: Age of Calamity. Volví por más con The Legend Of Zelda: Tears of the Kingdom, otro juego que disfruté inmensamente y terminé en Nintendo Switch 2 con la edición especial con mejores gráficos y 60 fps. Ahora lo estoy disfrutando nuevamente  gracias a Hyrule Warrios: Age of Imprisonment.

Después de más de 8 años y cientos de horas jugadas, todavía sigo disfrutando volver a este mundo. Muy conforme hasta ahora, pero también me genera expectativa imaginar cómo llegará a ser el próximo juego de Zelda. Breath of the Wild fue revolucionario tanto para ser un Zelda como en lo que respecta a juegos Open World. Habrá que esperar y ver qué se traen con el hardware más potente del Nintendo Switch 2 y la experiencia adquirida. Mientras tanto, seguiré teniendo aventuras en Hyrule en sus versiones disponibles...

El post Hyrule Warriors: Age of Imprisonment - Nintendo Switch 2 fue publicado originalmente en Picando Código.

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

Variable not found

Novedades de C# 14

noviembre 20, 2025 11:52

C# 14 ya está aquí

Hace unos días se lanzó .NET 10 y, con él, C# 14, una nueva versión del lenguaje que viene con varias novedades interesantes que mejoran la productividad y la experiencia de desarrollo.

Las más destacables son:

Les damos un vistazo rápido a continuación.

Miembros extensores

Hasta C# 13, los métodos extensores (extension methods) eran la única forma de añadir funcionalidad o características a tipos existentes sin necesidad de modificar su código fuente original. Sin embargo, esta técnica tenía limitaciones, como la imposibilidad de añadir propiedades o eventos, o definirlas sobre clases estáticas. Además, la forma en la que los implementábamos no favorecía especialmente la legibilidad del código.

En C# 14, los métodos extensores siguen siendo válidos y retrocompatibles a nivel de código, pero el concepto ha sido ampliado significativamente. A partir de esta versión, se han introducido los bloques extension como delimitadores para definir miembros extensores de un mismo tipo de una forma más estructurada y legible. Observa el siguiente código, donde podemos ver la comparativa entre la sintaxis clásica y la nueva:

// C# 13 y anteriores
public static class StringExtensions
{
    public static bool IsCapitalized(this string str) 
    {
        return !string.IsNullOrEmpty(str) && char.IsUpper(str[0]);
    }
    public static bool IsInteger(this string str) 
    {
        return int.TryParse(str, out _);
    }
}

// C# 14:
public static class StringExtensions
{
    extension(string str)
    {
        public bool IsCapitalized()
        {
            return !string.IsNullOrEmpty(str) && char.IsUpper(str[0]);
        }
        public bool IsInteger()
        {
            return int.TryParse(str, out _);
        }
    }
}

Pero la cosa no queda ahí 🙂. Además del bloque extension, C# 14 introduce la posibilidad de definir otros tipos de miembros extensores, tanto de instancia como estáticos:

public static class StringExtensions
{
    extension(string str)
    {
        // Propiedad extensora:
        public char? FirstCharacter
        {
            get => string.IsNullOrEmpty(str) ? null : str[0];
        }
    }

    extension(Math)
    {
        // Extensión de clase estática:
        public static int Factorial(int n)
        {
            if (n < 0) throw new ArgumentException("Invalid negative value", nameof(n));
            return n == 0 ? 1 : n * Factorial(n - 1);
        }
    }
}

Podéis leer más sobre miembros extensores en este post de hace unas semanas.

Asignaciones condicionales

Llevamos tiempo usando operadores como null coalescing (??), null coalescing assignment (??=) o safe navigation (?.) para lidiar con valores nulos de una forma más concisa y legible. En C# 14 se ha extendido este último para permitir su aplicación en el lado izquierdo de una asignación, permitiéndonos asignar valores a propiedades o campos de objetos que podrían ser nulos, sin necesidad de realizar comprobaciones explícitas.

// C# 13 y anteriores:
var friend = GetFriendOrNull(1);
if (friend != null)
{
    friend.Name = "John Doe";
}

// C# 14:
var friend = GetFriendOrNull(1);
friend?.Name = "John Doe"; // Si friend es nulo, no se lanza excepción y no se asigna valor

Si queréis profundizar algo más, hace algún tiempo hablamos de esta característica en este otro post.

Propiedades semi-automáticas

Las propiedades semiautomáticas son una mezcla entre las propiedades automáticas y las tradicionales con backing field, pero tomando lo mejor de ambos mundos. Gracias a esta característica podemos definir propiedades con un campo de respaldo generado automáticamente, pero al mismo tiempo nos dan la flexibilidad de acceder a él mediante la palabra clave contextual field, lo que nos permite personalizar los accesores get y set según nuestras necesidades.

// C# 13 y anteriores:
private int _age;
public int Age
{
    get { return _age; }
    set {
        if (value < 0) throw new ArgumentException("Age cannot be negative");
        _age = value;
    }
}

// C# 14:
public int Age
{
    get; // Retorna el valor del backing field generado automáticamente
    set
    {
        if (value < 0) throw new ArgumentException("Age cannot be negative");
        field = value; // 'field' es el backing field
    }
}

Para ver esta característica en mayor detalle, podéis consultar el post Propiedades semi-automáticas en C# que publiqué por aquí hace ya casi un año.

Uso de nameof con tipos genéricos

El operador nameof ha sido mejorado para permitir el uso de tipos genéricos sin especificar sus parámetros de tipo, algo que antes no era posible.

Console.WriteLine(nameof(List<>)); // Imprime "List"

Constructores y eventos parciales

También es interesante comentar que ahora se permite la implementación de constructores y eventos parciales. En la práctica, esto quiere decir que podemos definir este tipo de miembros en una parte de una clase parcial, y completarlos en otra.

Como podemos ver en el siguiente ejemplo, en la primera porción de la clase definimos los elementos como partial y en la segunda los implementamos. Fijaos que en el caso del evento, debemos definir los bloques add y remove para gestionar los suscriptores:

// Definición en una parte de la clase parcial:
public partial class MyClass
{
    public partial MyClass(string something); // Partial constructor declaration
    public partial event EventHandler? OnMyEvent; // Partial event declaration
}

// Implementación en otra parte de la clase parcial:
public partial class MyClass
{
    public partial MyClass(string something)
    {
        // Constructor implementation
    };

    private EventHandler? _onMyEvent;

    public partial event EventHandler? OnMyEvent
    {
        add
        {
            _onMyEvent += value; // Subscriber added
        }

        remove
        {
            _onMyEvent -= value; // Subscriber removed
        }
    }
}

Conversiones implícitas para Span<T> y ReadOnlySpan<T>

C# 14 introduce conversiones implícitas para los tipos Span<T> y ReadOnlySpan<T>, facilitando su uso y manipulación sin necesidad de conversiones explícitas.

Por ejemplo, el bloque de código mostrado a continuación no compila en versiones anteriores a C# 14, porque el método StartsWith() es un extensor del tipo ReadOnlySpan<int> y no existe en la clase Array. Sin embargo, en C# 14, gracias a las conversiones implícitas, este código funciona correctamente:

int[] arr = [1, 2, 3];
Console.WriteLine(arr.StartsWith(1)); // Muestra "True" en C# 14

Modificadores simplificados en parámetros lambda

En C# 14, se pueden usar modificadores de parámetros como scoped, ref, in, out, o ref readonly en expresiones lambda sin necesidad de especificar el tipo del parámetro, haciendo el código algo más conciso y legible.

Por ejemplo, observa la sutil diferencia entre C# 13 y 14 en la siguiente definición de un delegado que intenta parsear un valor:

// Definición del delegado
delegate bool TryParse<T>(string text, out T result);

// C# 13 y anteriores
TryParse<int> parser = (string text, out int result) => int.TryParse(text, out result);

// C# 14
TryParse<int> parser = (string text, out result) => int.TryParse(text, out result);

La segunda opción no compilará en versiones anteriores de C# porque en el parámetro out de la expresión lambda no se especifica el tipo int. En C# 14 es perfectamente válido, porque puede ser inferido del delegado.

Punto extra: ¿podemos usar C# 14 en proyectos .NET 9 o Visual Studio 2022?

Pues sí, es posible porque estamos hablando de características del lenguaje y no del framework, así que podremos aprovechar estas novedades en proyectos dirigidos a versiones anteriores de .NET y con Visual Studio 2022.

Para ello, simplemente debemos asegurarnos de configurar la versión del lenguaje en la propiedad LangVersion del archivo de proyecto (.csproj), especificando 14.0, como se muestra a continuación:

<PropertyGroup>
    <LangVersion>14.0</LangVersion>
</PropertyGroup>

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 628

noviembre 17, 2025 07:03

Desarrolladora rodeada de monitores donde se anuncia la llegada de .NET 10

La semana pasada, sin duda el protagonismo se lo ha llevado el lanzamiento de .NET 10, con multitud de artículos que repasan los cambios y características introducidas en esta versión, aunque hay mucho más.

En primer lugar, destacamos el artículo de CampusMVP: .NET 10 - Informe técnico para desarrolladores: novedades y mejoras importantes, imperdible para ponernos rápidamente al día.

También OpenAI ha publicado interesantes artículos, uno sobre los problemas de seguridad que supone el uso de prompt injection, y otro sobre el uso de una nueva técnica que mejora la interpretabilidad de las redes neuronales, algo difícil de conseguir con los modelos tradicionales. Y bueno, ha aprovechado también presentar su nuevo modelo GPT-5.1.

Por último, destacar el artículo de Sudhir Mangla sobre gestión de memoria en .NET, que repasa conceptos clave como stack, heap, Span<T>, Memory<T>, ArrayPool y otros temas que debemos conocer para crear aplicaciones .NET de alto rendimiento.

El resto de enlaces a contenidos interesantes, 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 627

noviembre 10, 2025 06:50

Desarrollador con dudas sobre la puerta a la que debe dirigirse: React, Angular, Vue o Svelte

La obsesión por los tipos primitivos es un code smell bastante común en el desarrollo de software. Gerson Azabache nos explica cómo los Value Objects en .NET pueden ayudarnos a combatir este problema.

Decidir qué framework de frontend nos conviene aprender siempre es una tarea complicada. Para ayudarnos con esto, José Manuel Alarcón ha preparado una comparativa detallada entre React, Angular, Vue.js y Svelte.

El servidor MCP de Azure permite que agentes o aplicaciones basadas en IA interactúen con los recursos que gestionamos en la nube. Juan Irigoyen da un vistazo a esta herramienta en su artículo.

Daniel Roth nos cuenta cómo Copilot Studio utiliza .NET y WebAssembly para mejorar el rendimiento en este entorno low-code.

Estos, y muchos más enlaces a contenidos interesantes, a continuación.

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI / Cross-platform

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

Header Files

Mejorando los tiempos de compilación

agosto 28, 2025 09:00

Introducción

Como todos los lectores sabrán, C++ es un lenguaje compilado, lo que implica que su código fuente se transforma en un archivo binario ejecutable. No es necesario que el usuario final realice este proceso, lo hace el desarrollador. Generalmente es un proceso bastante rápido, pero a medida que el proyecto comienza a crecer también lo hace el tiempo requerido para su compilación. No es extraño encontrar proyectos de mediano tamaño que tardan 15-20 minutos en compilarse, y personalmente he trabajado con algunos que requerían unas 13 horas partiendo de cero.

Una de las primeras formas de reducir el tiempo de compilación usadas fue la de evitar compilar cosas que no fuesen necesarias: si un fichero ni ninguna de sus dependencias ni parámetros han sido modificados, una nueva compilación devolverá el mismo fichero objeto, por lo que una comparación de los timestamps entre ficheros fuente y objeto ayudará a centrarnos en aquellos que sí requieren ser procesados nuevamente. Este mecanismo es estándar en cualquier sistema de compilación, bien sean Makefiles, proyectos de MSVC, Xcode, etc.

Aún así, esto no suele ser suficiente, siendo frecuente tener que esperar entre 30 segundos y 5 minutos hasta que la compilación termine. Si sumamos el hecho de que tenemos que compilar multitud de veces cada día, en total supone un montón de tiempo dedicado a ver pasar líneas frente a nuestros ojos. Un día normal para mí conlleva entre 30 y 50 compilaciones, con una duración media de 30s, lo que nos da entre 15 y 25 minutos por jornada.

Créditos: https://xkcd.com/303/

Casi todos aprovechamos estos micro-descansos durante las compilaciones para hacer algo en paralelo. Dos minutos apenas da tiempo para ir al baño, en 4 podemos hacernos un café (contando el tiempo de ir a la cocina, calentar la leche, etc.). Podemos aprovechar para responder un email o mirar el estado del equipo, etc. Pero, salvo contadas ocasiones, la experiencia dicta que es mejor no hacer nada, no sólo porque lo que creíamos que era cosa de 1 minuto se extienda, sino porque además hacer muchas cosas al mismo tiempo significa perder el enfoque: el cambio de contexto penaliza la tarea principal. Lo mejor que podemos hacer es tratar de minimizar ese tiempo muerto (obviamente estoy asumiendo que compilamos cuando lo necesitamos y no como tic nervioso).

En este artículo comentaremos algunas estrategias de optimización (todas centradas en reducir la cantidad de código a compilar, ya veremos por qué), cómo aplicarlas eficientemente, y aspectos a tener en cuenta para que no nos salga el tiro por la culata.

Poniendo a dieta al compilador

El proceso de compilación de proyectos en C++ se lleva a cabo, normalmente, en tres etapas: preprocesado, compilación y enlazado. Simplificando mucho, diremos que en la primera el fichero de código fuente es leído y convertido en un fichero de código fuente intermedio completamente autocontenido (es decir, que toda la información necesaria para compilarse está presente en dicho fichero). En la etapa de compilación, cada uno de estos ficheros autocontenidos es analizado y las sentencias C++ son transformadas en código binario intermedio, aunque aún no es ejecutable. Es en la tercera etapa donde todas las unidades de compilación son enlazadas entre sí junto a las dependencias externas, resultando en el fichero ejecutable.

Primero que nada, es importante mencionar que el preprocesado no hace ningún tipo de análisis de código; más bien podríamos verlo como una serie de operaciones de edición de texto. Por ejemplo, las macros son operaciones de sustitución de bloques de código (buscar/reemplazar), la inclusión de ficheros de cabecera es como un copiar/pegar, y la compilación condicional es como eliminación de código. Es durante la compilación donde se determina si el fichero tiene una sintaxis C++ válida y se identifica qué partes de todo el código compilado son realmente necesarias.

Como podemos intuir, la compilación más rápida será aquella que sólo compile lo que realmente estamos necesitando.

Análisis de la compilación

Es posible que muchos managers no entiendan el beneficio de reducir en 3 segundos el proceso de compilación. La forma más sencilla de justificarlo es, claramente, desde el punto de vista económico: 3 segundos por 30 veces diarias por 20 días al mes es media hora que gano al mes para el proyecto por programador. Lo mismo aplica para las pipelines de CI/CD.

De todas formas, si para conseguir esos 3 segundos hemos necesitado 3 días de trabajo, no hemos mejorado el proceso, ya que el ROI (Return on Investment) será muy bajo. Pero si por el contrario lo hemos hecho en una hora, habrá merecido la pena.

Se ve claramente que, como en toda optimización, lo primero que tenemos que hacer es averiguar dónde necesitamos optimizar, para atacar los problemas que nos den el mayor salto posible. Y es que casi siempre los cuellos de botella están centrados en unos pocos lugares.

Centraré la sección de profiling en clang, aunque el modus operandi en general es análogo en otros compiladores. Lo primero será activar la opción -ftime-trace, con la cual obtendremos un análisis de la compilación con el tiempo dedicado a cada etapa de la compilación y fichero (en MSVC sería /timetrace). El fichero generado se ubicará junto a los ficheros objeto intermedios, con el mismo nombre de la unidad de compilación pero extensión JSON. Podemos abrirlo con la herramienta de tracing incluida en cualquier navegador Chromium (por ejemplo, en Edge es edge://tracing/), o con otras como Perfetto (https://ui.perfetto.dev/).

Identificando objetivos

Hay básicamente dos formas que uso para detectar los cuellos de botella. La primera sería analizar los ficheros JSON manualmente, y lo que suelo hacer en estos casos es ordenarlos por tamaño y centrarme en los más grandes; si bien no siguen un orden estricto, en general un fichero JSON grande significa que el timetrace recolectó muchos datos sobre el mismo. La segunda ya es más elaborada, y pasa por un script que lee los JSON y básicamente ensambla estadísticas globales del proyecto, incluyendo ficheros de mayor tiempo (medio), número de inclusiones y tiempo total.

Personalmente uso una combinación de ambas: con el script obtengo una clasificación (el podio), los ficheros que más impactan la compilación. Luego los analizo manualmente, abriendo cualquiera de los JSON referenciados para su análisis.

La siguiente imagen muestra las trazas para la compilación de un fichero .cpp modesto, de 2K líneas y 68KB de peso (es un ejemplo real, por lo que he ocultado algunos datos). Los resultados se muestran como un flame graph: el eje horizontal es el tiempo desde el inicio del procesado del fichero, donde cada bloque es una fase de la compilación y su ancho es el tiempo invertido en dicha fase; el eje vertical es el call stack.

Vista general de un timetrace

En este artículo nos centraremos en los bloques verdes, que representan la lectura, preprocesado y parseo (análisis sintáctico). Los bloques más grandes son nuestros principales cuellos de botella: ficheros que tardan mucho en ser analizados. Estos ficheros son los que tenemos que optimizar.

Ahora bien, debemos saber si, dado un bloque grande, el fichero asociado es pesado en sí mismo o porque otros (ficheros incluidos por el primero) lo engordan. Podemos obtener esta información en dos formas (complementarias entre sí): la primera es observar el eje vertical y el impacto de las dependencias (véase el bloque central: el primer fichero consume mucho, pero es obvio que es culpa de dos dependencias). La otra es observando los detalles de cada bloque (basta con seleccionar el bloque y ver el pánel inferior):

Detalle de un timetrace

Acá podemos ver que si bien el procesado del fichero toma 325ms (wall duration), el fichero como tal solo toma 2ms (self time).

Esta combinación de acciones permite centrar los esfuerzos en los ficheros más importantes, si no podría pasar que el análisis individual sin ningún tipo de guía nos lleve a optimizar ficheros con un impacto muy bajo en la compilación final del proyecto.

Estrategias a seguir

A continuación comentaremos algunas de las acciones que podemos llevar a cabo para mejorar los tiempos de compilación, aunque podrían resumirse en quitar lo que no hace falta. El orden de exposición corresponde al que uso personalmente, siguiendo como criterio el tiempo que lleva aplicarlas.

  • Incluir únicamente los ficheros de cabecera necesarios. Es normal que, a lo largo de la historia del proyecto, el código haya cambiado mucho, por lo que es posible que haya ficheros de cabecera innecesarios, o que estemos incluyendo un súper fichero que contiene muchas cosas cuando sólo necesitamos una. Cualquier limpieza viene bien, y más aún si es de alguno de nuestros objetivos. Esta estrategia puede no salir siempre bien en el caso de que otro fichero de cabecera del mismo documento esté incluyendo al que hemos borrado, con lo que no estamos ganando nada. Esta estrategia paga mejor cuando se aplica a limpiar los propios ficheros de cabecera de includes innecesarios, ya que el impacto se multiplica. Truco: algunos IDEs y analizadores estáticos proveen esta información de forma directa.
  • Utilizar forward-declarations. Si nuestro fichero de cabecera sólo hace uso de una clase como una referencia o puntero (es decir, no necesita saber detalles), podríamos eliminar el fichero de cabecera que la declara y sustituirla por un forward declaration.
  • Separar ficheros con múltiples declaraciones en ficheros individuales. Existe una regla de oro (aunque algo flexible) que dice que cada declaración debe ir en su propio fichero. Esto permite que incluyamos únicamente lo que necesitamos.
  • Extraer declaraciones anidadas. Es una especie de corolario de la estrategia anterior: si una clase define una clase o enumeración dentro de la misma, cualquier referencia a los segundos obligará a incluir a la primera por completo.
  • Utilizar el patrón Pimpl. No entraré en detalles, pero este patrón permite separar mejor la declaración de la implementación. Así no sólo evitamos incluir ficheros de cabecera que sólo son necesarios en la implementación, sino que además cualquier modificación en la misma no impacta a los ficheros que usan esta clase.
  • Refactorizar las clases grandes en clases más pequeñas y con menos responsabilidades. De nuevo, una forma de incluir sólo lo que se necesita, pero que además mejora enormemente el diseño reduciendo el acoplamiento entre módulos.

Pasándonos de la raya

Consideremos ahora algunas posibles desventajas de las estrategias mencionadas:

  • Un forward-declaration es un segundo lugar al que tenemos que prestar atención si hacemos algún cambio a un tipo de dato (especialmente su nombre) aunque normalmente esto implica únicamente un fallo de compilación.
  • Tener decenas o cientos de mini-ficheros para definir enums puede ser tedioso de mantener; en algunas ocasiones bastará con tener unos pocos ficheros de “tipos” básicos, agrupados por componente o función.
  • El uso del patrón Pimpl implica en la práctica una desreferencia de memoria adicional. En los sistemas modernos esto no suele ser un problema, pero convendría tenerlo presente si lo usamos en secciones de código donde el rendimiento es crítico (y el profiler ya nos ha dicho que el pimpl es el problema; no optimicemos prematuramente, primero el diseño).

Conclusión

Optimizar los tiempos de compilación no es sólo una cuestión de comodidad para el desarrollador, sino una inversión que mejora la productividad del equipo y reduce los costes del proyecto. Las estrategias presentadas en esta primera parte se centran en el principio fundamental de “compilar únicamente lo necesario”, atacando el problema en su origen: la cantidad de código que debe procesar el compilador.

La clave del éxito radica en medir antes de optimizar. Herramientas como -ftime-trace nos permiten identificar los verdaderos cuellos de botella y centrar nuestros esfuerzos donde realmente importa. No todos los ficheros tienen el mismo impacto, y una optimización bien dirigida puede resultar en mejoras significativas con un esfuerzo mínimo.

Recordemos que estas optimizaciones deben aplicarse con criterio: aunque las estrategias mencionadas son generalmente beneficiosas, siempre conviene evaluar el coste de mantenimiento frente a la ganancia obtenida.

El tiempo de compilación perdido nunca se recupera, pero el tiempo invertido en optimizarlo se amortiza cada día.

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

Header Files

5 cosas que puedes hacer al migrar a C++ moderno

agosto 14, 2025 06:00

Si bien C++11 y el resto de versiones del bien llamado C++ moderno llevan ya tiempo entre nosotros, muchos programadores siguen usando C++ a la antigua usanza, en detrimento de la legibilidad, flexibilidad de diseño e incluso del rendimiento que nos ofrecen las versiones más recientes. He aquí algunas cosas que puedes hacer fácilmente para comenzar a disfrutar de las ventajas del C++ moderno.

No hagas nada

Sí, como suena, la primera de ellas es “no hagas nada”. Haz un benchmarking de tu código antes y después de migrar y seguramente te sorprenderás, especialmente si haces un uso intensivo de contenedores de la biblioteca estándar.

Una de las principales razones de esto es la introducción de la semántica de movimiento, con la consecuencia añadida de que muchos de los métodos y constructores ya existentes han recibido soporte para argumentos r-value, lo que significa que, sin mover un dedo, disfrutamos ya de sus beneficios. Además, existen otras optimizaciones que pueden destacar dependiendo del compilador que usemos, tales como el return-value-optimization (RVO, obligatorio a partir de C++17).

El siguiente ejemplo muestra un caso no poco común de creación de un vector de objetos (adjunto también el código del benchmark):

std::vector<std::string> create_vector(size_t n, std::string s)
{
    std::vector<std::string> v;

    for (size_t i = 0; i < n; ++i)
    {
        v.push_back(s + s);
    }

    return v;
}

int main(int argc, char* argv[])
{
    const size_t n = atoi(argv[1]);
    const size_t m = atoi(argv[2]);

    int z = 0;
    for (size_t i = 0; i < m; ++i)
    {
        char c = (rand() % 26) + 'a';
        size_t sn = (rand() % 1000) + 10;
        std::vector<std::string> v = create_vector(n + i, std::string(sn, c));

        const int x = v.front().front();
        z += x; // to prevent compiler to remove code
    }

    std::cout << z << std::endl;

    return 0;
}

El compilador usado ha sido Apple clang version 14.0.3 (clang-1403.0.22.14.1), todos con nivel de optimización -O2 y bajo plataforma ARM. Todas las pruebas se han hecho con n=10000 y m=1000, midiendo el tiempo de ejecución con time 5 veces y promediando los resultados:

  • --std=c++03: real 5.586s, user 3.325s, system 2.010s
  • --std=c++11: real 1.856s, user 1.166s, system 0.689s
  • --std=c++14: real 1.875s, user 1.155s, system 0.692s
  • --std=c++17: real 1.829s, user 1.139s, system 0.689s
  • --std=c++20: real 1.839s, user 1.159s, system 0.674s

Sin hacer nada hemos podido triplicar la velocidad de nuestro programa simplemente compilando con una versión más moderna del lenguaje. Esto obviamente no quiere decir que todo nuestro programa se acelere 3x; este ejemplo está preparado específicamente para mostrar esta mejora, pero da una idea clara de los beneficios que implican las nuevas características del lenguaje.

Auto-matiza la deducción de tipos

C++11 introdujo un nuevo significado para la palabra reservada auto (es el único caso que conozco de cambios en este sentido). Se usa en una declaración para deducir el tipo de la variable a partir de su inicialización; y si el compilador no es capaz de hacer una deducción única, la declaración se considera incorrecta y se genera un error.

El uso de auto permite reducir la cantidad de código a escribir (y leer), simplificando el mismo, y moviendo el nivel de abstracción al qué en lugar del cómo (o con qué).

std::vector<int> generate_ids(int n) { ... }

// Before
const std::vector<int> ids_old_way = generate_ids();

// Now
const auto ids_old_way = generate_ids();

Un caso especialmente útil es a la hora de usar iteradores:

std::map<std::string, std::vector<std::string> > synonyms;

// Before
std::map<std::string, std::vector<std::string> >::iterator it = synonyms.find(word);

// Now
auto it = synonyms.find(word);

Además, se facilitan los refactorings y optimizaciones de código al reducir el número de errores de compilación: si se cambia el tipo de un contenedor, auto se deducirá el nuevo iterador y listo (obviamente, si el nuevo tipo de iterador no es compatible con el anterior sí puede haber problemas, por ejemplo si se pasa de un std::vector a un std::unordered_map). Lo mismo sucedería si se cambia el tipo de retorno de una función: con auto tendríamos ya hecha una parte del pastel.

La única pega (que por otra parte tiene su lado positivo), es que el nombre de la variable cobra más peso ya que no tenemos a la mano (¿ojo?) su tipo. Pero como dije, esto puede incluso mejorar el código al obligarnos a poner nombres descriptivos (cardVector podría ser cardCollection o simplemente cards, y esa enigmática DatabaseConnectionController* dcc = DatabaseConnectionController::create() pasar a ser auto dbConnectionController = DatabaseConnectionController::create()).

Pythoniza tu código

El que diga que C++ moderno no ha copiado se ha inspirando en aspectos de otros lenguajes más jóvenes (especialmente Python), pues simplemente está negando lo obvio. Las nuevas sintaxis introducidas no sólo ayudan a hacer un código más compacto, sino que además permiten mejorar la expresividad del código y elevar el nivel de abstracción.

  • Range-for: seguramente la más conocida de estas pythonizaciones, permite recorrer una colección de elementos, sin necesidad de preocuparse del tipo exacto de contenedor. C++ ya disponía de un par de formas de hacerlo (un for desde begin hasta end, y el std::for_each), pero el range-for es más natural en muchos casos donde solamente queremos recorrer los elementos (pero no modificar el contenedor, por ejemplo).

      for (size_t i = 0; i < container.size(); ++i) { // random-access iterators
          foo(container[i]);
      }
    
      for (std::list<int>::iterator it = list.begin(); it != list.end(); ++it) { // basic iterator version
          foo(*it);
      }
    
      for (auto it = container.begin(); it != container.end(); ++it) { // more generic using 'auto'
          foo(*it);
      }
    
      std::for_each(container.begin(), container.end(), foo); // using an algorithm
    
      for (auto&& c : container) { // range-for
          foo(c);
      }
    

    Como detalle curioso, he visto cómo el uso del range-for puede optimizar código en determinados momentos. Un range-for siempre copiará el iterador end(), por lo que si nuestro contenedor hacía uso de un end() costoso, eso que nos ahorramos.

    Por último, una rápida comparación entre std::for_each y los range-for:

    • Los range-for permiten utilizar las instrucciones break y continue para modificar el flujo.
    • std::for_each puede ser paralelizado (C++17, ver más abajo).
  • Utiliza las listas de inicialización, de esta forma puedes inicializar colecciones de datos en la propia declaración, e incluso hacerlas constantes.

      const std::map<int, std::string> numbers = {
          {1, "one"},
          {2, "two"},
          {3, "three"},
      };
    
  • Mejora la expresividad atando variables. Devolver pares o tuplas es una forma común de evitar crear structs específicamente para devolver varios valores en una función. Ahora bien, el problema surge rápidamente cuando no sabemos qué significan el .first o el .second, y peor aún si comparten el mismo tipo de datos.

      std::pair<int, std::string> get_id_and_name();
    
      // Without binding
      const auto id_and_name = get_id_and_name();
      std::cout << "ID: " << id_and_name.first << ", name: " << id_and_name.second << std::endl;
    
      // With binding
      const auto [id, name] = get_id_and_name();
      std::cout << "ID: " << id << ", name: " << name << std::endl;
    

    También puede usarse al iterar sobre mapas:

      std::map<int, std::string> roman_numbers;
    
      // Without binding
      for (const auto& it : roman_numbers) {
          std::cout << "Number " << it.first << " is " << it.second << std::endl;
      }
    
      // With binding
      for (const auto& [decimal, roman] : roman_numbers) {
          std::cout << "Number " << decimal << " is " << roman << std::endl;
      }
    

Haz uso de los nuevos contenedores y métodos

C++11 introduce nuevas estructuras de datos que mejoran drásticamente el rendimiento bajo determinadas condiciones:

  • Todo std::map cuyas claves sean tipos básicos (char, int, float, enumeraciones, punteros, etc.), y con más de unas pocas decenas de elementos, puede ser reemplazado por std::unordered_map. Es el equivalente de una tabla hash y sus operaciones son mucho más eficientes: O(1) de media para la inserción y la búsqueda, dependiendo de las colisiones que puedan generarse. También puede usarse con otros tipos, tales como std::string pero acá el rendimiento va a depender también del tamaño medio de la clave. Cuidado que con mapas de poco tamaño puede no notarse el rendimiento o incluso disminuir (el coste relativo de calcular la función hash respecto a la comparación del tipo bruto aumenta conforme el número de elementos es más pequeño).
  • De forma análoga tenemos a std::unordered_set como alternativa a std::set. En ambos casos es importante hacer notar que, tal y como indica su nombre, las claves no están ordenadas, por lo que hay que tener cuidado si la implementación actual depende de ello. Esto no debe de ser un impedimento por sí mismo para migrar; por ejemplo, si sólo se requieren las claves ordenadas para un proceso de serialización, y el rendimiento del mismo no es crítico, se podrían extraer las claves, ordenarlas y serializarlas en orden, manteniendo así la compatibilidad con el código anterior.
  • Usar std::string_view (C++17) en los argumentos de funciones que no requieren modificar la cadena de texto. std::string_view es básicamente un wrapper al estilo de las cadenas de texto en C (un puntero al primer caracter y un tamaño) pero de forma segura y compatible con std::string donde haga falta. De esta forma, cuando se requiere un subconjunto de la cadena, se evita pasar copias innecesarias.

Gracias a los r-value y a los variadic templates, C++11 introdujo nuevos métodos para añadir elementos a un contenedor de forma más eficiente. Tradicionalmente usamos push_back para añadir elementos a un std::vector o insert para los std::map. Ahora bien, en ambos casos el método primero reserva e inicializa el espacio para el elemento en el contenedor, y luego es que copia (o mueve) el elemento en sí. En la práctica esto significa que tenemos que llamar a un constructor por defecto y a un constructor de copia (o movimiento). En C++11 tenemos std::vector::emplace_back y std::map::emplace que nos permitirán construir in-place el elemento en su zona de memoria reservada, generando un código mucho más eficiente. Desgraciadamente, y por compatibilidad hacia atrás, los métodos anteriores push_back e insert no pudieron ganar esta mejora y la migración tenemos que hacerla a mano (además de cambiar costumbre de los métodos a usar).

struct my_bag {
    my_bag(int32_t a, int32_t b, int32_t c);
};

std::vector<my_bag> bags;

// Before
bags.push_back(my_bag(1, 3, 3));

// Now
bags.emplace_back(1, 2, 3);

Así, vemos que emplace_back se ha de llamar con los mismos argumentos del constructor. Si hiciese my_bag bag{2, 3, 4}; bags.emplace_back(bag); estaría llamando al constructor de copia, pero in-place, que sería una mejora más no la óptima.

Reduce la dependencia de bibliotecas de terceros

Como extensión del punto actual, y como ya se ha visto, C++11 y posteriores han ido extendiendo la biblioteca estándar con nuevos integrantes, muchas veces inspirándose en populares bibliotecas de terceros, especialmente Boost.

  • std::thread, std::mutex, para gestión de hilos y sincronización. boost::thread no es exactamente igual que std::thread, la de Boost tiene un conjunto mayor de funcionalidades, tales como interrupción de un hilo y manejo de colecciones de hilos.
  • std::chrono, para operaciones con unidades de tiempo.
  • std::optional, std::variant, para tipos opcionales y variantes tipo-seguras.
  • std::filesystem (C++17), para gestión del sistema de ficheros (aunque no es 100% equivalente).
  • std::ranges (C++20), inspirándose en ranges-v3.

Mejora la gestión de recursos

Uno de los puntos que muchos desarrolladores critican a C++ es la gestión de memoria (punteros nulos, colgantes, etc). Es cierto que, tal y como comentaba Bjarne Stroustrup, C hace que sea fácil pegarte un tiro en el pie; C++ lo hace más difícil, pero cuando lo haces te vuela toda la pierna. Pero también es cierto que desde C++11 es aún más difícil ya que la biblioteca estándar provee de muchos mecanismos para evitarlo. Los dos principales son std::unique_ptr y std::shared_ptr. el primero permite expresar que un objeto tiene un único dueño, mientras el segundo distribuye, mediante un contador de referencias, la propiedad entre varios.

Un ejemplo común para unique_ptr son las clases manager, que centralizan el acceso a un determinado recurso. Así, esta clase puede tener un unique_ptr y pasar una referencia a todas las demás. Además, los unique_ptr no pueden ser copiados, sólo movidos, por lo que la transferencia de propiedad es explícita. Los shared_ptr, por otro lado, son más comunes en elementos con un ciclo de vida impredecible o donde los actores creadores del objeto pueden desaparecer antes que el objeto en sí.

Un código de C++ moderno no debería usar punteros raw para almacenar objetos. A la hora de pasar un objeto unique_ptr podemos o bien usar una referencia (que además obliga a no pasar un nullptr); si el objeto puede no estar inicializado podríamos pasar un std::optional<Objeto&> (C++17), pero en este caso no hay una ventaja muy clara respecto a pasar un puntero raw ya que se puede usar mal en ambos casos. El acceso a punteros nulos sigue siendo responsabilidad del programador. Así que cuidado en este caso.

Ambos tipos de punteros inteligentes se basan en un principio muy conocido de C++ y del que ya he hablado en otras ocasiones: el RAII (ver RAII 1 y RAII 2). No me extenderé acá en este tema y refiero a dichas páginas para más información

Bonos

Algunos pequeños cambios adicionales que marcan una gran diferencia:

  • Nuevos literales: ""s para std::string, ""ms para std::chrono::milliseconds, 0x1234_u32 para enteros con tipo específico, etc. Hacen el código más expresivo y evitan conversiones implícitas.
  • Paralelización de algoritmos (C++17): simplemente añade std::execution::par a tus std::sort, std::transform y otros algoritmos para aprovechar múltiples núcleos automáticamente.
  • if constexpr (C++17): cambia el complicado SFINAE por código estructurado más legible en templates. Permite escribir código condicional que se evalúa en tiempo de compilación.
  • Designated initializers (C++20): inicializa estructuras de forma más clara con Point{.x = 10, .y = 20} en lugar de Point{10, 20}.
  • std::optional (C++17): expresa explícitamente cuando una función puede no devolver un valor válido, eliminando la ambigüedad de los valores “especiales” como -1 o nullptr.
  • Fold expressions (C++17): simplifica operaciones en parameter packs con expresiones como (args + ...) en lugar de recursión manual.
  • Lambda expressions mejoradas: desde C++11, pero con mejoras constantes. Usa [&] para capturar por referencia, [=] por copia, o mezcla ambas. En C++14 puedes usar generic lambdas con auto en los parámetros.
  • constexpr everywhere: marca funciones como constexpr siempre que sea posible. El compilador las evaluará en tiempo de compilación cuando pueda, mejorando el rendimiento.
  • std::array vs arrays C: reemplaza int arr[10] por std::array<int, 10>. Obtienes los beneficios de los contenedores STL sin coste adicional.
  • Inicialización uniforme: usa {} en lugar de () para la inicialización. Es más segura (previene narrowing conversions) y más consistente.

Conclusión

Migrar a C++ moderno no es solo cambiar el estándar del compilador; es adoptar una mentalidad que prioriza la expresividad, el rendimiento y la seguridad. Como hemos visto, algunas de estas mejoras llegan prácticamente “gratis” con solo recompilar el código, mientras que otras requieren cambios mínimos que pueden transformar drásticamente la calidad del software.

Los cinco puntos que hemos cubierto —aprovechar las optimizaciones automáticas, usar auto para simplificar el código, adoptar las sintaxis “pythonizadas”, migrar a contenedores más eficientes y mejorar la gestión de recursos— representan solo la punta del iceberg de lo que C++ moderno tiene para ofrecer.

La belleza del C++ moderno radica en que permite escribir código más limpio y expresivo sin sacrificar el rendimiento que siempre ha caracterizado al lenguaje. Al contrario, en muchos casos lo mejora. Así que la próxima vez que inicies un proyecto o tengas la oportunidad de refactorizar código existente, no dudes en darle una oportunidad a estas características. Tu código (y tus compañeros de equipo) te lo agradecerán.

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

Navegapolis

IA en empresas ágiles y en las que dicen ser ágiles

junio 27, 2025 10:36

En la comunidad ágil, documentos como el “Scrum Guide Expansion Pack“ intentan guiar el uso de la IA en las organizaciones ágiles, pero la verdad es que pueden ser cuchillos de doble filo: puede ayudar a usar la IA sobre las bases de la agilidad o pueden ser la coartada perfecta para perpetuar viejos hábitos disfrazados de agilidad. En concreto el «Scrum Guide Expansion Pack» dedica gran esfuerzo a recordar que “lo humano es lo primero” y que la IA debe ser sólo un “aumento cognitivo”, pero al mismo tiempo se explaya en describir con detalle la eficiencia que aporta la inteligencia artificial.

Es inevitable preguntarse si en la práctica, la presión por los resultados no inclinará la balanza hacia la optimización y convertirá las buenas intenciones humanistas en discursos de bien quedar.

La encrucijada oculta en el mismo marco

Para la cultura de la empresa, el «Scrum Guide Expansion Pack» es un test de Rorschach, que refleja la que ya tiene. La empresa puede proyectar sus valores sobre el texto y encontrar, la confirmación que necesita para validar su enfoque.

Una organización centrada en las personas, busca en el texto la inspiración para validar o desarrollar su cultura de confianza, creatividad y desarrollo de personas y , por supuesto, la encuentra:

  • Encuentra un liderazgo que sirve, no que manda: Confirma su creencia en un liderazgo de servicio al leer que su función es «…cultivar el entorno para los equipos Scrum autogestionados…«. Identifica el significado de nutrir y remover obstáculos, no dirigir ni controlar.
  • Encuentra en la autogestión un motor de innovación al encontrar que los «Equipos Scrum autogestionados organizados en torno al valor son cruciales para la resolución creativa de problemas y la captura de la emergencia«. Valida que la autogestión es el camino para desatar el potencial colectivo ante la complejidad.
  • Pone a la IA al servicio del talento humano, afianzando su visión de la tecnología como una palanca para las personas, al leer que la IA debe permitir que «…los miembros humanos del equipo Scrum se centren en consideraciones estratégicas, creativas y éticas«. La IA es un asistente que libera, no un sustituto que optimiza.

Pero al mismo tiempo, una organización con una cultura arraigada en la eficiencia, la predictibilidad y el control de procesos, busca en el mismo documento la justificación para perfeccionar y acelerar su maquinaria productiva y, también la encuentra:

  • Encuentra en el Lean Thinking la coartada para la eficiencia extrema: El pensamiento lean «…reduce el desperdicio en el trabajo y en cómo se lleva a cabo…«. Desperdicio es todo lo que no sea producción directa, osea se pueden eliminar debates «innecesarios» y estandarizar procesos para maximizar el flow.
  • Encuentra en la IA el camino hacia la automatización del control: Se entusiasma al leer que la IA puede «…actualizar y repriorizar los elementos del Product Backlog…» o que sus «…análisis basados en IA mejoran la transparencia, la inspección y la adaptación», viendo aquí un consejo para reemplazar el juicio humano por algoritmos eficientes que optimizan el rendimiento a escala.
  • Encuentra en la responsabilidad un mecanismo de conformidad: Define la profesionalidad de forma rígida al encontrar que los desarrolladores son «…colectivamente responsables de: Instaurar la calidad adhiriéndose y mejorando la definición de resultado terminado (Definition of Output Done)«. La responsabilidad se convierte en conformidad con la norma, y la calidad en una checklist que el sistema debe superar.

Para esta organización, la “agilidad” es un marco adecuado para modernizar el taylorismo, con un barniz vanguardista, sin alterar su núcleo de control y eficiencia.

Las dos organizaciones aplican el “Pack”, y en ambos casos el resultado es la intensificación la cultura que ya poseían.

Cuando el marco deja de ser un mapa y se convierte en un espejo

Un marco ambiguo puede funcionar como un espejo que refleja la cultura y las intenciones preexistentes. A una organización centrada en las personas, el marco valida y refuerza su enfoque, pero a una organización centrada en el producto, también.

Puede ser un eco de lo que ya es la organización y el peligro se hace más profundo cuando no se usa como espejo sino como máscara. Porque la ambigüedad permite adoptar la retórica de la agilidad centrada en las personas —»empoderamiento» , «autogestión» «seguridad psicológica», etc. — para construir una fachada sin una realidad consecuente.

Las consecuencias: cinismo, estancamiento y devaluación

Para las personas, el cinismo de las organizaciones que predican el humanismo pero practican el taylorismo es agotador. Mata la confianza, la motivación y con ello: la creatividad.

Para la organización el resultado es una transformación estancada. Se adoptan los rituales, pero sin cambiar el ADN cultural, confundiendo la actividad con el progreso.

Y para la comunidad, el resultado es la devaluación del término «ágil», que pierde su significado para convertirse en un conjunto de herramientas que cualquier cultura, puede adoptar.

La entrada IA en empresas ágiles y en las que dicen ser ágiles se publicó primero en Navegápolis.

» 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