Pair Programming Asíncrono con Agentes: Sentinel, Bolt y Palette en Acción
Índice de contenidos
Ya tienes la teoría y tienes la configuración. Ahora vamos a ver la magia en acción. Vamos a simular escenarios de desarrollo Android reales y ver cómo Sentinel, Bolt y Palette resuelven problemas que un “chatbot” generalista probablemente ignoraría o solucionaría mal.
Pero antes de entrar en los casos de éxito, quiero adoptar un enfoque de Building in Public y compartir con ustedes no solo lo bonito, sino también los dolores de cabeza, los errores de configuración y las frustraciones reales que he vivido implementando estos agentes en mis proyectos a lo largo de los últimos meses de 2026. La inteligencia artificial no es magia, es una herramienta, y como cualquier herramienta, necesitas aprender a usarla, afilarla y, a veces, cortarte un poco para entender sus límites.
Building in Public: Mi Viaje (y Tropiezos) con Agentes Autónomos
Cuando empecé a integrar agentes autónomos en mi flujo de trabajo diario de Android, mi expectativa era ingenua: “Le voy a dar a la IA acceso a mi repositorio, le diré “haz la app más rápida” y me iré a tomar un café”. La realidad fue un golpe directo al ego.
El primer intento con Sentinel (mi agente de seguridad) fue un desastre monumental. Lo configuré con permisos demasiado amplios en mi pipeline de CI/CD. ¿El resultado? En su primera ejecución autónoma, Sentinel analizó mi base de código, encontró una dependencia legacy de Google Play Services que consideró “vulnerable” (aunque era necesaria para soportar Android 9) y decidió unilateralmente eliminarla de mi build.gradle.kts. Esto rompió la compilación de producción durante dos horas mientras yo estaba trabajando en otro proyecto, y tuve que hacer un hotfix de emergencia.
Ahí aprendí mi primera gran lección de Building in Public: Los agentes necesitan límites estrictos y un contexto hiper-específico. No son desarrolladores senior que entienden las decisiones comerciales detrás del código técnico; son máquinas de ejecución que siguen sus system prompts a rajatabla.
Con Bolt (mi agente de rendimiento), la historia fue diferente pero igualmente frustrante al principio. Le pedí que optimizara el uso de memoria de una aplicación de mensajería que estaba desarrollando. Bolt, en su afán por reducir milisegundos de recolección de basura, reescribió toda mi arquitectura basada en Kotlin Flows a callbacks tradicionales en Java, argumentando que la instanciación de corrutinas tenía un overhead inaceptable. Técnicamente tenía razón en un entorno de micro-benchmarking, pero arruinó la legibilidad del proyecto. Tuve que revertir 45 commits y redefinir sus reglas en el archivo AGENTS.md, especificándole explícitamente: “Cualquier optimización de rendimiento DEBE respetar el uso idiomático de Kotlin y mantener la arquitectura basada en Coroutines/Flows intacta”.
Y finalmente Palette (mi agente de UI/UX). Al principio, Palette era demasiado “creativo”. Le daba un diseño de Figma y le pedía que creara los Composables. Su código era funcional, pero tenía la mala costumbre de hardcodear colores (Color(0xFFE5E5E5)) en lugar de usar mi MaterialTheme.colorScheme. Pasé semanas peleando con PRs rechazados hasta que aprendí a alimentarlo con mis tokens de diseño como contexto inicial en cada prompt.
Hoy, estos tres agentes son parte fundamental de mi equipo. Nos entendemos. Yo les doy contexto preciso, y ellos ejecutan análisis profundos. Ya no espero magia; espero rigor computacional. Y eso es exactamente lo que me están dando. Veamos entonces, con esta configuración afinada, cómo se comportan en casos del mundo real.
Caso 1: Sentinel vs. El AndroidManifest Inseguro 🛡️
Escenario: Tienes una aplicación que requiere subir fotos a un servidor. En la fase inicial de desarrollo, has añadido permisos “por si acaso” y configuraciones de red rápidas para saltarte las restricciones del entorno local y centrarte en la funcionalidad. Este es un pecado clásico que todos hemos cometido, y que en un equipo sin revisiones estrictas puede terminar en la Play Store.
En un flujo tradicional, podrías pedirle a una IA generalista como el viejo ChatGPT o a las versiones anteriores de Gemini: “Revisa mi código para ver si hay errores”. Generalmente, te dirán que la sintaxis es correcta y te felicitarán por tu indentación. Pero un agente especializado con un contexto de seguridad profunda y configurado para actuar como un “Red Teamer” hace algo muy distinto.
Prompt al Agente Sentinel:
“Sentinel, asume el rol de Auditor de Seguridad. Revisa los archivos de configuración de Android en el directorio
/app/src/main/enfocándote en vulnerabilidades de red, permisos excesivos y exportación indebida de componentes de cara a un paso a producción para la versión 1.2.0.”
Respuesta de Sentinel (basada en su System Prompt de Seguridad Estricta):
**ANÁLISIS DE SEGURIDAD - SENTINEL v2.4 (Modo Auditor)**
He escaneado el directorio especificado. He detectado 3 vulnerabilidades que violan nuestras políticas definidas en `AGENTS.md` y las guías de seguridad de Android 16.
**[CRÍTICO] Tráfico de Texto Plano Habilitado**
* **Archivo:** `AndroidManifest.xml` (Línea 14)
* **Problema:** `android:usesCleartextTraffic="true"` detectado en la etiqueta `<application>`.
* **Riesgo:** Permite tráfico HTTP no cifrado. Cualquier atacante en una red Wi-Fi pública (ataque Man-in-the-Middle) puede interceptar tokens de sesión, credenciales o datos de usuario.
* **Solución Obligatoria:** Eliminar el atributo (el valor por defecto es `false` desde Android 9) o establecerlo explícitamente en `false`. Si requieres HTTP para pruebas locales o en debug, debes usar un `network_security_config.xml` configurado exclusivamente para el variante de debug. ¿Deseas que genere este archivo por ti?
**[ALTO] Exceso de Privilegios de Almacenamiento**
* **Archivo:** `AndroidManifest.xml` (Líneas 5 y 6)
* **Problema:** Permisos `READ_EXTERNAL_STORAGE` y `WRITE_EXTERNAL_STORAGE` solicitados sin condicionales de versión.
* **Riesgo:** Exceso masivo de privilegios. En Android 13+ (API 33+), estos permisos están obsoletos para medios. En Android 14+ puedes tener rechazos directos en Google Play si no justificas el uso.
* **Solución Obligatoria:** Tu aplicación solo necesita fotos. Debes migrar al **Photo Picker** de Android, que no requiere permisos. Si debes mantener retrocompatibilidad o acceso directo, debes usar `READ_MEDIA_IMAGES` condicionado a `android:maxSdkVersion`.
**[MEDIO] Superficie de Ataque Abierta en Activities**
* **Archivo:** `AndroidManifest.xml` (Línea 42)
* **Problema:** Activity `DeepLinkHandlerActivity` exportada explícitamente (`android:exported="true"`) sin restricciones ni permisos.
* **Riesgo:** Vulnerabilidad de Intent Spoofing. Cualquier aplicación maliciosa instalada en el dispositivo del usuario puede lanzar esta actividad, pasándole extras manipulados y potencialmente engañando a la app para ejecutar acciones no autorizadas.
* **Solución Obligatoria:** Si esta actividad solo debe ser lanzada por notificaciones o componentes internos, cambia a `android:exported="false"`. Si requiere ser lanzada externamente, valida rigurosamente el Intent en el método `onCreate` o protégela con un permiso de nivel `signature`.
¿La diferencia? Sentinel no se limita a validar XML; aplica un modelo de amenazas real. Actúa proactivamente, no reactivamente.
Caso 2: Bolt vs. El Jank en el RecyclerView (y Compose LazyColumn) ⚡
Escenario: Tienes una lista compleja de elementos (productos en un catálogo) que hace “tirones” (jank) al hacer scroll. Ya sea que estés usando el viejo y confiable RecyclerView o el moderno LazyColumn de Jetpack Compose, estás perdiendo frames y la aplicación se siente pesada, especialmente en dispositivos de gama media-baja.
Este es un escenario donde los chatbots fallan miserablemente. Si les dices “mi lista es lenta”, te darán una lista genérica de 10 consejos de Google Developers de hace cuatro años. Pero Bolt, al tener acceso a todo tu código fuente y a las herramientas de profiling (si se las proporcionas en su pipeline), busca micro-optimizaciones que destruyen el rendimiento en bucles de renderizado.
Prompt al Agente Bolt:
“Bolt, el
ProductCatalogListestá reportando 15% de dropped frames en los logs de Firebase Performance. Analiza el código de renderizado de los ítems y propón optimizaciones de nivel de micro-arquitectura. El objetivo es mantener 60fps constantes.”
Respuesta de Bolt (tras clonar y analizar el repositorio):
**ANÁLISIS DE RENDIMIENTO - BOLT v1.8 (Modo Optimización de Renderizado)**
He analizado el archivo `ProductItem.kt` y he identificado múltiples causas de "GC Churn" (asignación y recolección rápida de basura) y recomposiciones innecesarias.
**Problema 1: Asignación de Objetos en Ciclos de Renderizado (Crítico)**
En tu `LazyColumn`, dentro del bloque de contenido de cada ítem, estás haciendo esto:
```kotlin
val formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")
val displayDate = item.creationDate.format(formatter)
Impacto: DateTimeFormatter.ofPattern() es extremadamente costoso de inicializar. Al colocarlo dentro del ámbito del Composable de cada ítem, estás forzando a la JVM a instanciar este objeto cientos de veces por segundo durante un scroll rápido. Esto llena la memoria rápidamente (Heap), activando el Garbage Collector, el cual detiene la ejecución del hilo principal (Main Thread) y causa el temido jank.
Solución Implementada por Bolt (PR #142): He movido la inicialización a un nivel estático o al menos fuera de la fase de composición.
// Fuera del Composable, como una constante estática de alto nivel
private val DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")
@Composable
fun ProductItem(item: Product) {
// ...
// Solo se llama al método format, usando el singleton en memoria
val displayDate = remember(item.creationDate) { item.creationDate.format(DATE_FORMATTER) }
// ...
}
Problema 2: Inestabilidad de Modelos en Compose (Alto)
He detectado que tu clase de datos Product contiene un List<String> para las categorías:
data class Product(val id: String, val categories: List<String>)
Impacto: En Jetpack Compose, List (la interfaz estándar de Kotlin) es considerada “inestable” (Unstable) porque el compilador no puede garantizar que su contenido no cambie mutando la lista original por debajo. Como resultado, Compose no puede saltarse (Skip) la recomposición de los ítems de la lista aunque los datos no hayan cambiado, destruyendo el rendimiento de la LazyColumn.
Solución Implementada por Bolt (PR #142):
He modificado el modelo de datos para usar ImmutableList de la librería kotlinx.collections.immutable.
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
// Ahora Compose considera este parámetro "Estable"
data class Product(val id: String, val categories: ImmutableList<String>)
*¿La diferencia?* Bolt comprende cómo funciona la máquina virtual de Android y el compilador de Compose a nivel íntimo.
## Caso 3: Palette vs. El Botón "Invisible" y la Accesibilidad Olvidada 🎨
**Escenario:** Has creado un componente personalizado precioso en Jetpack Compose. Visualmente es perfecto, encaja con el diseño y la paleta de colores. Pero cuando lo subes a un entorno de pruebas, los QA y los usuarios beta se quejan de que "el botón no funciona" o que "cuesta mucho darle". Además, no has pensado en TalkBack.
**Prompt al Agente Palette:**
> "Palette, revisa los componentes en `ui/components/buttons/`. Nuestro reporte de QA indica problemas de usabilidad táctil y advertencias en el escáner de accesibilidad. Adapta los componentes a los estándares de Material Design 3."
**Respuesta de Palette:**
```markdown
**REVISIÓN DE UX/UI Y ACCESIBILIDAD - PALETTE v3.0**
He revisado tu `CustomActionButton.kt`. Visualmente cumple con los tokens de diseño de `AGENTS.md`, pero presenta graves deficiencias estructurales en términos de interacción humana y accesibilidad.
**Observación Estructural:**
Tu `Icon` tiene un tamaño definido de 24.dp y el `Box` que lo envuelve y maneja el evento de clic está ajustado al contenido usando `wrapContentSize`.
**1. Problema de Área Táctil (Touch Target):**
El área interactiva real es de exactamente 24x24dp. Las directrices de accesibilidad de Android y Material Design 3 requieren estrictamente un área táctil mínima de **48x48dp** para cualquier elemento interactivo. Los usuarios con dispositivos pequeños, dedos grandes o discapacidades motoras tendrán enormes dificultades para interactuar con este botón, lo que lleva a la frustración reportada ("no le atinan").
**2. Problema de Semántica (TalkBack):**
El botón utiliza el modificador `clickable` estándar sin proporcionar semántica adicional, y el `Icon` tiene el `contentDescription` establecido en `null`. Un usuario invidente usando TalkBack solo escuchará "Botón, tocar para activar" sin tener idea de qué hace el botón.
**Solución Refactorizada:**
```kotlin
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
@Composable
fun CustomActionButton(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Box(
modifier = modifier
// 1. Ampliamos el área táctil internamente sin afectar el diseño visual
.minimumInteractiveComponentSize()
.clickable(
onClick = onClick,
// 2. Definimos explícitamente el rol para los servicios de accesibilidad
role = Role.Button
)
// 3. Añadimos descripción clara para TalkBack
.semantics {
contentDescription = "Añadir un nuevo producto al carrito de compras"
},
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Default.AddShoppingCart,
contentDescription = null, // La descripción ya está en el contenedor
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp) // El tamaño visual sigue siendo 24dp
)
}
}
Palette no solo arregla el código; enseña sobre empatía digital y cumplimiento de estándares que muchas veces pasamos por alto en las prisas de un sprint.
## Caso 4: Refactorización de Arquitectura Legacy con Sentinel y Bolt en Equipo 🔄
**Escenario:** Tienes un proyecto de 5 años de antigüedad. En el núcleo de la aplicación, hay un mamotreto llamado `UserManager.java`, un singleton de 3500 líneas de código lleno de lógica de negocio, llamadas a la base de datos en hilos secundarios mal gestionados y un acoplamiento extremo. Tocar este archivo da terror. Quieres migrarlo a Kotlin y aplicar Clean Architecture (separar en un Repositorio y un Caso de Uso), pero hacerlo manualmente llevaría semanas de riesgo e interrupciones.
Aquí es donde los flujos de trabajo con agentes brillan con luz propia. En mi experiencia de "Building in Public", descubrí que orquestar agentes es el verdadero superpoder.
**Prompt Orchestrador:**
> "Bolt, Sentinel: Necesitamos desmantelar `UserManager.java`. Bolt, quiero que analices las dependencias del archivo y extraigas la lógica de obtención de datos a un `UserRepository.kt` usando Corrutinas. Sentinel, mientras Bolt hace esto, asegúrate de que ninguna de las lógicas de autenticación o manejo de tokens que se muevan introduzcan vulnerabilidades de exposición de memoria."
El resultado de esta operación asíncrona es asombroso de presenciar en un PR:
1. **Fase 1 (Análisis):** Los agentes leen el archivo y construyen un grafo de dependencias interno. Bolt identifica que `UserManager` habla con Retrofit y Room simultáneamente.
2. **Fase 2 (Refactorización de Bolt):** Bolt genera una interfaz `UserRepository`, crea la implementación `UserRepositoryImpl` inyectando las dependencias mediante Hilt/Dagger, y envuelve las llamadas asíncronas en `suspend functions` con `withContext(Dispatchers.IO)`. Todo en un Kotlin idiomático.
3. **Fase 3 (Auditoría de Sentinel):** Sentinel revisa el PR de Bolt antes de que me llegue a mí. Detecta que al migrar a Kotlin, un token sensible quedó en un `data class` que, por defecto, implementa un método `toString()` que expone todos sus campos. Sentinel inyecta un commit adicional sobre el de Bolt sobrescribiendo el `toString()` para ofuscar el token: `override fun toString() = "UserToken(token=***)"`.
Yo, como desarrollador senior, abro Github a la mañana siguiente, reviso el Pull Request combinado de mis dos agentes, corroboro que los tests pasen y hago *Merge*. Lo que antes era un dolor de cabeza mensual, ahora es una tarea de revisión matutina.
## Caso 5: La Red de Seguridad de los Tests Automatizados 🧪
**Escenario:** El equipo comercial pide una nueva funcionalidad crítica (un nuevo flujo de pago) para el viernes. La desarrollas, funciona perfectamente, pero no has tenido tiempo de escribir ni un solo test unitario. Subir código a producción sin tests es como conducir a ciegas a 200 km/h.
Aquí es donde entra el "Modo Esclavo" de los agentes (y lo digo con el mayor de los respetos por la IA). Crear tests unitarios exhaustivos requiere mucho tiempo y a menudo es repetitivo.
**Prompt a Agente General de Testing:**
> "Agente, lee el nuevo archivo `PaymentProcessorUseCase.kt`. Utiliza JUnit5 y MockK. Genera una suite de tests unitarios que cubra el 100% de los caminos lógicos de esta clase: éxito, fallo de red, saldo insuficiente, y errores de deserialización del servidor. Usa nombres de funciones descriptivos según nuestro estándar GIVEN-WHEN-THEN definido en `AGENTS.md`."
El agente analiza el caso de uso y en minutos genera cientos de líneas de código de testing. Configura los mocks (`mockk<PaymentRepository>()`), define las respuestas falsas (`coEvery { repo.process(any()) } returns Result.success()`), y establece las aserciones precisas (`coVerify(exactly = 1) { analytics.track("payment_success") }`).
Nuevamente, no confío ciegamente. Reviso los tests generados. A veces el agente alucina un camino que no existe o asume un mock incorrecto. Corrijo un par de aserciones, ejecuto `pnpm test` o `./gradlew test` y voilà. Cobertura del 100% en 15 minutos en lugar de 4 horas.
## Tareas Recurrentes: Cómo configurar un "Cron Job" para tu Agente Jules (Gemini) ⏰
Hasta ahora hemos hablado de interactuar con agentes bajo demanda: "Haz esto ahora". Pero el verdadero poder de la automatización llega cuando tu agente trabaja de forma autónoma y recurrente mientras tú duermes. A esto le llamo "Mantenimiento Preventivo de Código".
En 2026, el agente de codificación autónomo de Google para el ecosistema Gemini se llama **Jules**. Jules opera de forma asíncrona: clona tu repositorio en una máquina virtual segura en la nube (VM), instala dependencias, hace el trabajo, y te envía un Pull Request.
Aunque Jules tiene una interfaz web, la magia real ocurre en la terminal usando **Jules Tools** (la CLI de Jules) integrada con la extensión de Gemini.
Para configurar que Jules actúe como un empleado incansable que revisa tu código cada fin de semana de forma recurrente (un Cron Job de IA), este es el flujo de trabajo real que he implementado en mis repositorios:
### Paso 1: Instalación de las Herramientas
Primero, necesitas instalar la extensión de Jules para la CLI de Gemini, o las Jules Tools independientes. Según las últimas actualizaciones de Google Developers, puedes hacerlo así en tu terminal:
```bash
# Opción A: A través de npm (Jules Tools standalone)
npm install -g @google/jules
# Opción B: Como extensión de Gemini CLI
gemini extensions install https://github.com/gemini-cli-extensions/jules --auto-update
Asegúrate de haber conectado tu repositorio de GitHub a tu cuenta en jules.google.com.
Paso 2: Creando el Script de Tarea
Jules Tools permite crear sesiones remotas vía línea de comandos. Vamos a crear un script bash simple llamado jules_weekend_audit.sh en nuestro servidor de CI o en nuestra máquina local:
#!/bin/bash
# jules_weekend_audit.sh
# Nos aseguramos de estar en el directorio correcto
cd /path/to/my/android/repo
echo "Iniciando auditoría de fin de semana con Jules..."
# Comando para Jules Tools (npm)
jules remote new --repo mi-usuario/mi-repo-android --session "Revisa todas las dependencias en los archivos build.gradle.kts. Si hay actualizaciones menores o de parche disponibles, actualízalas. Además, corre un análisis de linting profundo y arregla cualquier advertencia sobre código obsoleto (deprecated). Ejecuta los tests, y si todo pasa, crea un PR con el título "[Jules] Auditoría de Fin de Semana"."
echo "Tarea enviada a la VM de Jules exitosamente."
Paso 3: Configurando el Cron Job (Linux/macOS)
Ahora, solo tenemos que decirle a nuestro sistema operativo (o servidor de GitHub Actions) que ejecute este script periódicamente. Si usas un sistema basado en Unix, abrimos el editor de cron:
crontab -e
Y añadimos la siguiente línea para que se ejecute cada viernes a las 11:00 PM:
0 23 * * 5 /bin/bash /ruta/absoluta/a/tu/script/jules_weekend_audit.sh >> /var/log/jules_cron.log 2>&1
Alternativa Moderna: Usando GitHub Actions como “Cron”
Si no quieres depender de una máquina local, la mejor práctica de Building in Public es usar GitHub Actions para lanzar a Jules. Crea un archivo en tu repositorio: .github/workflows/jules-cron.yml:
name: Jules Weekly Audit
on:
schedule:
# Se ejecuta a las 00:00 todos los sábados
- cron: "0 0 * * 6"
workflow_dispatch: # Permite ejecución manual
jobs:
jules_audit:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Jules
run: npm install -g @google/jules
- name: Trigger Jules Agent
env:
JULES_API_KEY: ${{ secrets.JULES_API_KEY }}
run: |
jules remote new --repo ${{ github.repository }} --session "Auditoría de dependencias y refactorización menor de fin de semana. Actualiza librerías y corrige warnings. Genera un PR."
Con esta configuración, cada lunes por la mañana llego a la oficina, abro GitHub, y Jules (potenciado por Gemini 3) me ha dejado un PR limpio, probado y listo para revisar, con todas las tediosas actualizaciones de dependencias y limpiezas menores ya resueltas. Eso es verdadera delegación asíncrona.
Conclusión Final: La Mentalidad del Ingeniero Aumentado
Integrar agentes de IA como Sentinel, Bolt, Palette y automatizar con Jules no significa que el rol del desarrollador Android vaya a desaparecer. Todo lo contrario. Nuestro rol está mutando.
Ya no somos simplemente “escribidores de código” que traducen requerimientos de negocio a Kotlin. Nos estamos convirtiendo en Directores de Orquesta o Ingenieros de Sistemas Aumentados.
Estos agentes actúan como un equipo de especialistas junior incansables. Nunca se aburren, siempre recuerdan las reglas de seguridad oscuras, están obsesionados con los frames por segundo y conocen de memoria las WCAG de accesibilidad. Pero carecen de contexto comercial, no entienden por qué a veces un “código feo” es preferible si permite llegar al mercado a tiempo, y no tienen empatía real por el usuario final.
Ese es tu trabajo. Tu trabajo es definir la arquitectura general, asegurar la calidad del producto, entender las necesidades del negocio, y ahora, dirigir a tus agentes con precisión quirúrgica.
Construir en público y mostrar estos flujos de trabajo me ha enseñado que el futuro no es la IA reemplazando programadores. El futuro son los desarrolladores que utilizan IA superando abrumadoramente a los desarrolladores que se niegan a usarla. Empieza hoy. Crea tu carpeta agents/, define tu AGENTS.md, configura tu primer Cron Job con Jules, y empieza a liderar tu propio equipo de inteligencia artificial.
También te puede interesar
Adiós al Contexto Estático: Agent Skills y la Divulgación Progresiva
Optimiza el rendimiento de tu IA adoptando Agent Skills. Aprende a usar la divulgación progresiva para cargar contexto especializado solo cuando es necesario, ahorrando tokens y mejorando la precisión.
Clawdbot en Android: Compila e Instala tu Propio Nodo desde el Código Fuente
Guía técnica para compilar la app nativa de Clawdbot para Android, permitiendo a tu asistente acceder a la cámara, ubicación y modo de voz.
Potencia tus Agentes de IA con Skills: De Gemini a Copilot
Descubre cómo transformar tu asistente de IA generalista en un equipo de especialistas usando Agent Skills. Incluye ejemplos prácticos para Android, Kotlin y Conventional Commits.