
Hasta ahora, con esto de la inteligencia artificial, hemos estado jugando con agentes, con herramientas sueltas como opencode, open-webui, con skills, con MCPs… en fin, una cantidad abrumadora de opciones que a veces abruma más que ayuda. Pero si lo piensas un momento, te darás cuenta de que por un lado estamos usando herramientas de terceros de las que no sabemos exactamente qué flujo realizan ni qué comparten con terceros, y por otro lado hay operaciones que directamente no necesitan IA para nada. La cuestión es que puedes montar un proceso donde solo una parte, por ejemplo la del informe o la de la alerta, use IA. Delegarlo todo a un LLM es malgastar tokens y recursos, y además le quitas predictibilidad a tu sistema. Así que en este episodio te he preparado cuatro workflows que puedes implementar tú mismo, o modificar a partir de lo que yo he hecho, para exprimir tu día a día sin dejarte los tokens en ello.
Del caos de herramientas a los workflows de IA
En los episodios anteriores, el 829 sobre skills, el 831 sobre MCPs, y otros donde te he hablado de ollama o de opencode, te he presentado piezas sueltas. Tienes ollama, tienes claude, tienes skills, tienes MCPs. Pero todo esto suelto no te vale para nada. Lo tienes que montar, lo tienes que combinar, lo tienes que unir para que tenga sentido. Y ahí es donde entran los workflows.
Un workflow de IA no es ni más ni menos que la combinación de varias herramientas, un modelo de lenguaje, un sistema de notificaciones, una base de datos, un programador de tareas, coordinadas para resolver un problema concreto. No es una herramienta, es un proceso. Y el Linuxero con IA tiene procesos, no solo herramientas sueltas.
Te puedo asegurar que hasta que no empecé a pensar en términos de workflows, todo era un caos. Tenía scripts por aquí, prompts por allá, modelos descargados a medias… un desastre. Cuando empecé a estructurarlo todo como procesos, con entrada, transformación y salida, todo cobró sentido. De repente, cada pieza tenía un propósito claro y un lugar definido en la cadena.
La diferencia entre tener herramientas sueltas y tener workflows es la misma que hay entre tener un montón de ladrillos y tener una casa construida. Los ladrillos son necesarios, pero sin un plano, sin una estructura, no son más que un montón de barro cocido. Con la IA pasa exactamente igual: puedes tener el mejor modelo del mundo, la mejor herramienta de prompts, el mejor sistema de notificaciones, pero si no los coordinas en un proceso, no estás aprovechando su potencial.
Y esto no es solo una cuestión de productividad, sino también de privacidad y control. Cuando usas herramientas de terceros para cada tarea, estás delegando datos y decisiones a servicios que no controlas. Con workflows locales, todo queda en tu máquina. Tus noticias, tus tareas, tus logs, tus investigaciones, todo procesado localmente sin pasar por servidores externos.
La arquitectura: Rust, Ollama y Docker como base del sistema
Antes de meterme en los cuatro workflows, déjame contarte por qué he elegido este stack. Porque no es casualidad, te lo aseguro. Llevo años experimentando con diferentes combinaciones y esta es la que mejor me ha funcionado.
Rust como lenguaje de implementación. ¿Por qué Rust y no Python o Bash? Pues porque los workflows que te voy a enseñar son servicios persistentes, no scripts que se ejecutan una vez al día. Necesitan estar funcionando 24/7, gestionar concurrencia real, manejar errores con gracia y, sobre todo, no depender de un entorno de ejecución en el servidor de producción. Los binarios compilados con cargo build --release pesan entre 5 y 8 MB, son estáticos, no necesitan ninguna librería del sistema. Te lo copias con scp a cualquier máquina Linux con la misma arquitectura y funciona. Punto. Sin Python, sin Node, sin JVM, sin nada.
Ollama como motor de inferencia local. Uso dos modelos: llama3.2:3b para generación de texto, resúmenes, clasificación, análisis de logs, respuestas RAG, y bge-m3 para embeddings. El llama3.2:3b pesa unos 2 GB, necesita unos 3 GB de RAM, y tiene 128K tokens de contexto. El bge-m3 son 567 millones de parámetros, 1.2 GB en disco, soporta más de 100 idiomas, incluyendo español, y genera embeddings de 1024 dimensiones. No lo confundas con nomic-embed-text, que solo funciona en inglés y tiene 768 dimensiones. Para español, bge-m3 es muy superior, y además soporta 8K tokens de contexto frente a los 2K de nomic-embed-text.
Docker como orquestador de contenedores. O Podman, que para el caso es lo mismo. Los workflows Rust van contenerizados con compose.yml, se conectan a la red de ollama, y se despliegan con docker compose up -d. También te mostraré la alternativa con servicios systemd, que para binarios Rust estáticos funciona de maravilla.
El stack mínimo para tener todo funcionando son unos 3.2 GB en disco y unos 4 GB de RAM. Funciona en cualquier CPU con 8 GB. Si tienes menos memoria, puedes usar qwen2.5:1.5b como alternativa más ligera, que pesa solo 1 GB y necesita unos 2 GB de RAM.
Antes de ponerte a instalar nada, te recomiendo que ejecutes el script de test que he preparado para verificar que todo funciona. El test-stack.sh hace cinco pruebas que cubren exactamente lo que necesitan los cuatro workflows: clasificación de mensajes en JSON, resumen de texto, análisis de logs, generación de embeddings con bge-m3, y un RAG simulado. Es como un smoke test para todo el stack. Si pasa las cinco pruebas, puedes estar tranquilo de que todo está listo.
El script está en el directorio 00-test-stack y se ejecuta con un simple bash test-stack.sh. Te recomiendo que lo tengas siempre a mano, sobre todo cuando actualices los modelos o cambies de versión de Ollama. Te lo dejo aquí para que lo tengas a mano:
#!/bin/bash
set -euo pipefail
MODEL="llama3.2:3b"
EMBED="bge-m3"
# Test 1: Clasificación en JSON
ollama run $MODEL 'Eres un clasificador. Responde SOLO en JSON.
Clasifica este mensaje: "Revisa los logs del servidor que están petando"
Devuelve: {"es_tarea": bool, "prioridad": "alta|media|baja", "contexto": "..."}'
# Test 2: Resumen
ollama run $MODEL 'Resume este artículo en 1-2 líneas en español:
"Meta ha lanzado Llama 3.2, una nueva versión de su modelo de lenguaje..."'
# Test 3: Análisis de logs
ollama run $MODEL 'Clasifica este log como CRITICO, ADVERTENCIA o INFO.
Log: "kernel: critical error - I/O error reading superblock on /dev/sda1"'
# Test 4: Embeddings con bge-m3
curl -s http://localhost:11434/api/embeddings \
-d '{"model": "bge-m3", "prompt": "Los workflows de IA permiten automatizar tareas"}' \
| jq '.embedding | length'
# Test 5: RAG simulado
ollama run $MODEL 'Contexto: Ollama permite ejecutar modelos localmente.
Pregunta: ¿Cómo se descargan modelos en Ollama?
Responde basándote SOLO en el contexto.'
Si todo esto funciona, tienes el stack listo para los cuatro workflows.
Los cuatro workflows que cambiarán tu día a día
Vamos al turrón. Te voy a presentar cuatro workflows que he implementado, con sus versiones en Bash como punto de partida didáctico y sus versiones en Rust como servicio persistente. Porque no todo tiene que ser Rust, y no todo tiene que ser Bash. Cada herramienta tiene su momento y su propósito.
Workflow 1: noticias-bot, RSS filtrado a Telegram
Este fue el primero que monté y el que más satisfacción me ha dado. El pipeline es sencillo: monitoreas varios feeds RSS, cuando aparece un artículo nuevo lo resumes con Ollama y lo envías a Telegram. Parece simple, pero los detalles marcan la diferencia entre un script cutre y un servicio profesional.
Empecé con una versión en Bash, porque era rápida de prototipar. El script fetch-news.sh descarga los feeds con curl y los parsea con xmlstarlet. La versión Bash tiene tres scripts que se encadenan: fetch-news.sh descarga los artículos, process-news.sh los resume con Ollama, y send-telegram.sh los envía a Telegram. Te pongo el process-news.sh porque es donde está la chicha:
#!/bin/bash
set -euo pipefail
INPUT="/tmp/raw-news.txt"
OUTPUT="/tmp/summarized-news.txt"
MODEL="llama3.2:3b"
TIMEOUT=30
if [ ! -f "$INPUT" ] || [ ! -s "$INPUT" ]; then
echo "[ERROR] No hay artículos para procesar." >&2
exit 1
fi
if ! ollama list 2>/dev/null | grep -q "$MODEL"; then
echo "[ERROR] Modelo $MODEL no encontrado. Ejecuta: ollama pull $MODEL" >&2
exit 1
fi
> "$OUTPUT"
while IFS='|' read -r title link date; do
[ -z "$title" ] && continue
summary=$(timeout "$TIMEOUT" ollama run "$MODEL" \
"Resume este artículo de tecnología en menos de 20 palabras. \
Sé conciso y directo: $title" 2>/dev/null) || \
summary="[No se pudo generar resumen]"
if [ -z "$summary" ] || [ ${#summary} -lt 5 ]; then
summary="[Resumen no disponible]"
fi
{
echo "📰 $title"
echo "📝 $summary"
echo "🔗 $link"
echo "---"
} >> "$OUTPUT"
done < "$INPUT"
Todo muy bonito, pero con limitaciones importantes: no hay deduplicación, si ejecutas el script dos veces recibes dos veces las mismas noticias. No hay filtrado por palabras clave, te llega todo. No hay control de errores fino, y todo es secuencial.
La versión en Rust soluciona todo esto. Te voy a contar cómo está organizada, porque la estructura es interesante y puede servirte de plantilla para tus propios workflows.
El config.toml se carga con serde y define los feeds, cada uno con su propio intervalo de actualización y sus palabras clave para filtrar. Fíjate en los detalles: el feed de Hacker News se actualiza cada 15 minutos, mientras que el de LWN solo cada 30 y además solo me interesan los artículos que mencionan Linux, kernel, security o Rust. Eso se traduce en un filtro por palabras clave antes de llamar a Ollama, ahorrándote tokens innecesarios.
La deduplicación se hace con SHA256 del enlace y una tabla SQLite con UNIQUE constraint. Cada vez que aparece un artículo nuevo, se calcula su hash, se comprueba si ya existe en la base de datos, y si no, se procesa y se marca como visto. Así nunca recibes duplicados, aunque el feed devuelva los mismos artículos en varias consultas.
El resumen con Ollama usa un prompt muy específico para que quepa en un mensaje de Telegram, con un timeout de 30 segundos. Esto es crítico: si el LLM se cuelga, y te aseguro que pasa, la llamada falla con timeout y el workflow continúa con el siguiente artículo. No se bloquea todo el proceso.
El despliegue es trivial con Docker o con systemd. Si usas systemd, el servicio se configura con Restart=on-failure y RestartSec=10, y lo activas con sudo systemctl enable --now noticias-bot. Así de simple.
Workflow 2: tareas-bot, GTD vía Telegram
Este es mi favorito con diferencia. Se trata de un bot de Telegram que recibe mensajes, los clasifica con Ollama para determinar si son tareas accionables, los persiste en SQLite y confirma en el chat. Es mi sistema GTD particular, pero sin complicaciones, sin metodologías imposibles, sin herramientas sobreingenierizadas.
La versión en Bash que tenía antes funcionaba con email: mbsync + mu + Ollama a TODO.md. Sin confirmación, sin tiempo real, sin SQLite. Un poco cutre, la verdad. Funcionaba, pero no era fiable.
La versión en Rust es un servicio persistente que hace long polling a la API de Telegram. Cada mensaje que llega se pasa por Ollama con un prompt de sistema muy trabajado que le dice al modelo cómo clasificar. Te pongo el prompt porque es un buen ejemplo de cómo poner guardarraíles a un LLM, que es una de las cosas más importantes cuando trabajas con inteligencia artificial local:
Eres un sistema de clasificación de texto especializado en la detección y extracción de tareas accionables a partir de mensajes en lenguaje natural.
Una tarea accionable es cualquier solicitud, recordatorio, orden o intención que implique ejecutar una acción concreta en el mundo real o digital.
Ejemplos de tareas: «Hay que revisar los logs del servidor», «Comprar el dominio nuevo», «Sacar a pasear al perro», «Prepara la presentación para el viernes».
NO son tareas: saludos, chistes o conversación informal como «Hola, ¿cómo estás?», preguntas puramente informativas como «¿A qué hora empieza la charla?», notificaciones pasivas como «El servidor está funcionando».
Y lo más importante: el prompt fuerza al modelo a responder solo en JSON, con un formato estricto. Si es una tarea, devuelve algo como:
{
"is_task": true,
"task_description": "Revisar logs del servidor",
"priority": "alta",
"context": "infraestructura"
}
Si no es una tarea, devuelve {"is_task": false}. El bot entonces decide si guarda la tarea en SQLite o la ignora.
La clasificación en JSON es clave porque te permite procesar la respuesta de manera determinista. No tienes que andar parseando texto libre, sino que serde_json te da la estructura directamente. Y si Ollama se desvía del formato, que a veces pasa, el código tiene un extractor que busca el primer { y el último } en la respuesta para extraer el JSON, ignorando cualquier texto circundante.
El bot responde con un mensaje de confirmación al mensaje original, usando reply_to_message_id. Así sabes que tu tarea se ha registrado correctamente. Y lo mejor es que el offset de Telegram se guarda en SQLite, así que si el bot se reinicia por actualización, por caída del sistema, lo que sea, retoma exactamente donde lo dejó, sin perder ni un mensaje.
La base de datos SQLite tiene tres tablas: bot_state para el offset, tasks para las tareas con toda su metadata, y processed_messages para llevar la cuenta de qué mensajes se han procesado ya. Todo con PRAGMA journal_mode=WAL para mejor concurrencia.
Workflow 3: monitor-bot, monitorización híbrida del sistema
Este es el ejemplo perfecto de lo que llamo IA híbrida: usar IA solo donde realmente aporta valor. El monitor-bot ejecuta cinco tipos de check configurables en config.toml. Los tres primeros checks son puramente deterministas: comprobar el disco con df -h, comprobar servicios con systemctl --failed, comprobar memoria con free -m. No necesitas un LLM para saber que el disco está al 95%. Pero el cuarto y el quinto, journalctl y docker, sí se benefician de la IA, porque un log de error puede ser crítico o puede ser un falso positivo, y un contenedor caído puede tener múltiples causas.
La versión en Bash del monitor es más limitada, pero te puede servir como punto de partida. El script monitor-logs.sh captura los logs del sistema con journalctl --since "1 hour ago" --priority err --output=json, y el analyze-logs.sh los pasa por Ollama para clasificarlos. Te pongo el analyze-logs.sh porque el prompt es otro ejemplo de guardarraíles bien puestos:
#!/bin/bash
set -euo pipefail
INPUT="/tmp/recent-errors.json"
MODEL="llama3.2:3b"
TIMEOUT=60
LOG_SAMPLE=$(jq -r '.[:20] | .[] | "\(.MESSAGE)"' "$INPUT" 2>/dev/null | head -30)
analysis=$(timeout "$TIMEOUT" ollama run "$MODEL" "
Eres un administrador de sistemas experto con 20 años de experiencia.
Analiza estos errores de log y clasifícalos:
🔴 CRÍTICO: pérdida de datos, servicio caído, disco lleno, kernel panic
🟡 ADVERTENCIA: alta carga, reintentos, timeouts, conexiones rechazadas
🟢 INFORMATIVO: esperado, config, debug, errores recuperables
Para cada 🔴 CRÍTICO, propón un comando de mitigación EXACTO.
Logs:
$LOG_SAMPLE
" 2>/dev/null) || analysis="⚠️ El análisis de logs falló por timeout."
La versión en Rust va mucho más allá. Tiene una arquitectura basada en un trait Check que todos los tipos de check implementan:
#[async_trait]
trait Check {
async fn run(&self, client: &Client) -> Result<CheckResult>;
}
Cada tipo de check implementa su propio run, y el dispatcher create_check() crea la instancia adecuada según el tipo. Limpio, extensible, testable.
El bot tiene remediación automática: para logs críticos de disco, ejecuta journalctl --vacuum-time=7d; para contenedores, docker system prune; para paquetes, apt clean. Todo configurable, y con modo dry-run para probar antes de ejecutar acciones reales.
Además, tiene rate limiting: SQLite guarda un historial de alertas, y no se envía más de una alerta cada 15 minutos por cada tipo de check. Así evitas el spam de notificaciones cuando algo se repite, que es una de las cosas más molestas de los sistemas de monitorización mal configurados.
Workflow 4: investigacion-bot, búsqueda web con RAG local
Este es el más potente y el que más me ha costado, te lo aseguro. Se trata de un asistente de investigación que, dada una pregunta, busca en la web, extrae el contenido de las páginas más relevantes, construye un RAG (Retrieval Augmented Generation) en memoria con embeddings, y responde citando las fuentes. Es como tener tu propio asistente de investigación privado, sin depender de Google, sin trackers, sin perder tu privacidad.
La versión original en Bash+Python era un monstruo de 242 líneas de Python para el RAG, usaba html2text para convertir HTML a texto, sqlite-vec como base de datos vectorial, y las descargas eran secuenciales. Además, tenía problemas con UTF-8 multi-byte que me volvieron loco durante semanas. Cada vez que una página contenía un carácter Unicode fuera del rango básico, el chunking fallaba y el embedding se generaba mal. Un desastre.
La versión en Rust: cero dependencias del sistema, descargas paralelas con tokio::join_all, UTF-8 safe, todo en un solo binario. La diferencia es abismal. Te pongo el pipeline completo para que veas la elegancia de la solución:
cargo run -- "¿pregunta?"
│
├─► Paso 1: Búsqueda web
│ ├─► Ollama genera 3 consultas de búsqueda diferentes
│ └─► SearXNG busca cada consulta y deduplica URLs
│
├─► Paso 2: Extracción de contenido
│ └─► tokio::join_all descarga todas las URLs en paralelo
│ → scraper extrae HTML a texto plano
│
└─► Paso 3: RAG + Respuesta
├─► chunk_text(): divide en fragmentos de 1024 chars con 128 de overlap
├─► get_embedding(): bge-m3 vía Ollama para cada chunk
├─► cosine_similarity(): encuentra los top-5 chunks más relevantes
└─► answer(): Ollama genera respuesta con contexto + fuentes
La generación de consultas es interesante: le pides a Ollama que, dada una pregunta, genere tres consultas de búsqueda diferentes. Por ejemplo, para «¿Qué novedades trae Podman 5?», el modelo podría generar:
- «Podman 5 new features release notes»
- «Podman v5 changelog breaking changes»
- «Podman 5 vs Docker compatibility»
Luego cada consulta se lanza contra SearXNG, que es un metabuscador auto-hosteado que agrega resultados de 269 servicios de búsqueda. No trackea, no perfila, es privado. También puedes usar la API de Brave Search como alternativa, que te da 1000 consultas gratis al mes.
La extracción de contenido usa el crate scraper para parsear HTML con selectores CSS. Nada de llamar a html2text externo, nada de depender de Python. Todo dentro del binario.
El RAG en memoria es la parte más chula. Se divide cada página en chunks de 1024 caracteres con 128 de overlap, para que no se pierda contexto en los bordes. Se genera el embedding de cada chunk con bge-m3, y se almacena todo en un vector en memoria. Luego, cuando llega la pregunta, se genera su embedding y se calcula la cosine similarity con todos los chunks. Los cinco más cercanos se pasan como contexto a Ollama para que genere la respuesta.
El resultado es una respuesta con fuentes, algo como:
Podman 5 introduce soporte para pods como unidad de primera clase, mejoras en el manejo de redes con netavark 2.0, y la capacidad de ejecutar contenedores rootless sin problemas de montaje tmpfs. [Fuente: docs.podman.io] [Fuente: redhat.com]
Cada afirmación incluye su fuente original. Esto es clave para la credibilidad: no es un LLM alucinando, es información verificable.
Orquestación: systemd, Docker y just
Tener cuatro workflows está muy bien, pero si no los orquestas, tienes cuatro procesos sueltos. Aquí es donde entra la orquestación, que es lo que convierte un conjunto de herramientas en un sistema.
Systemd es mi opción preferida para los binarios Rust. Cada workflow tiene su propio servicio systemd con Restart=on-failure y RestartSec=10. Así, si el proceso cae por un error no controlado, por falta de memoria, lo que sea, systemd lo levanta automáticamente. Además, con After=network-online.target ollama.service te aseguras de que el bot no arranque antes de que la red y Ollama estén disponibles.
Docker Compose es la alternativa si prefieres contenerización. Cada workflow tiene su propio compose.yml que lo conecta a la red de Ollama. La ventaja es que aíslas completamente cada servicio y puedes actualizarlos independientemente. El compose.yml típico es muy simple:
services:
noticias-bot:
build: .
restart: unless-stopped
depends_on:
- ollama
volumes:
- ./config.toml:/app/config.toml:ro
- data:/app/data
networks:
- ollama
ollama:
image: ollama/ollama:latest
restart: unless-stopped
volumes:
- ollama_data:/root/.ollama
volumes:
data:
ollama_data:
networks:
ollama:
external: true
Just es el command runner que uso para los workflows en Bash. Es una herramienta escrita en Rust, cómo no, que te permite definir recetas como si fueran targets de Makefile, pero mucho más limpias y con sintaxis moderna. El justfile del episodio tiene recetas para cada workflow y para combinaciones:
# Resumen diario de noticias
workflow-news:
@echo "📰 Workflow: Resumen de noticias"
cd {{noticias_dir}} && ./fetch-news.sh
cd {{noticias_dir}} && ./process-news.sh
cd {{noticias_dir}} && ./send-telegram.sh
# Investigar un tema
workflow-research question:
cd {{investigacion_dir}} && ./run-research.sh "{{question}}"
# Workflow matutino completo
workflow-morning: workflow-news workflow-tasks
@echo "☕ Buenos días! Todo listo."
# Verificar dependencias
check-deps:
@command -v ollama >/dev/null && echo "✅ ollama" || echo "❌ ollama"
@ollama list 2>/dev/null | grep -q "llama3.2" && echo "✅ llama3.2:3b" || echo "❌ llama3.2:3b"
Ejecutas just workflow-news y se lanza la cadena completa. O just workflow-morning y tienes noticias y tareas en un solo comando.
Buenas prácticas que aprendí por las malas
Después de meses iterando sobre estos workflows, he aprendido algunas lecciones que te pueden ahorrar más de un dolor de cabeza. Porque te aseguro que yo me he llevado más de un disgusto hasta llegar a una configuración estable.
Siempre usa timeouts en las llamadas a Ollama. El LLM se puede colgar, puede tardar más de lo esperado, o puede simplemente no responder. Si no pones un timeout, tu workflow se queda bloqueado esperando indefinidamente. En Rust, cada llamada HTTP lleva su timeout(). En Bash, usas timeout antes de ollama run. Nunca, bajo ningún concepto, hagas una llamada a un LLM sin timeout.
Validación de entrada en los prompts. El LLM puede desviarse del formato esperado. Por eso siempre incluyo un extractor de JSON que busca {...} en la respuesta, por si el modelo decide añadir texto explicativo antes o después del JSON. En el tareas-bot, por ejemplo, el extractor busca el primer { y el último } en la respuesta, y parsea solo esa sección. Así, aunque el modelo añada «Aquí tienes el JSON solicitado:» antes del JSON, el código sigue funcionando.
Graceful shutdown con ctrl_c y AtomicBool. En Rust, cada workflow tiene un manejador de SIGINT que pone una bandera atómica a true, y los loops de los workers comprueban esa bandera en cada iteración. Así, cuando pulsas Ctrl+C, todos los workers terminan ordenadamente, cerrando conexiones y guardando estado. Sin esto, si matas el proceso con SIGKILL, puedes corromper la base de datos SQLite.
Logging estructurado con tracing. Nada de println! o eprintln!. Uso tracing con EnvFilter para poder activar o desactivar niveles de log sin recompilar. En producción, RUST_LOG=noticias_bot=info. Para depurar, RUST_LOG=noticias_bot=debug. Y todo con formato que journalctl puede parsear y filtrar. Cuando algo falla a las 3 de la madrugada, tener logs estructurados es la diferencia entre saber qué pasó y no tener ni idea.
Errores con contexto con anyhow. En lugar de unwrap() por todas partes, uso anyhow::Context para añadir contexto a los errores. Cuando algo falla, el mensaje de error te dice exactamente qué estaba haciendo el programa en ese momento. Por ejemplo, si falla la lectura del config.toml, el error dice «No se pudo leer /app/config.toml», no un críptico «error reading file».
Idempotencia con SQLite UNIQUE constraints. Todas las operaciones de escritura son idempotentes gracias a INSERT OR IGNORE y ON CONFLICT DO UPDATE. Puedes ejecutar el mismo artículo, la misma tarea, la misma alerta múltiples veces y nunca tendrás duplicados. Esto es especialmente importante en los workflows que se ejecutan en bucle, donde un mismo evento podría procesarse dos veces si el timing no es perfecto.
Verificación de dependencias al arrancar. Cada workflow debería comprobar al arrancar que Ollama está disponible y que los modelos necesarios están descargados. El script check-deps.sh que te he preparado hace exactamente eso: verifica que curl, jq, xmlstarlet, ollama están instalados, que llama3.2:3b y bge-m3 están descargados, y que SearXNG responde si lo necesitas. Es mejor fallar rápido al arrancar que descubrir a mitad de la ejecución que falta una dependencia.
Bash vs Rust: cuándo usar cada uno
Llegados a este punto, igual te estás preguntando: vale, Lorenzo, pero ¿siempre tengo que usar Rust? Pues no, claro que no. Cada herramienta tiene su momento.
Usa Bash cuando tengas un pipeline lineal y simple, cuando uses comandos del sistema como journalctl, df o systemctl, cuando no necesites estado entre ejecuciones, y cuando la concurrencia no sea relevante. Para un script que una vez al día descarga un RSS, lo resume y lo envía a Telegram, Bash es perfecto. Rápido de escribir, fácil de modificar, y con herramientas que ya conoces.
Usa Rust cuando necesites un servicio persistente que funcione 24/7, cuando tengas estado complejo con SQLite, sesiones, deduplicación, cuando necesites concurrencia real con tokio, cuando quieras cero dependencias del sistema en producción, y cuando el deploy sea crítico, un solo binario que copias y ejecutas.
La comparativa es reveladora: los cuatro workflows en Bash suman unas 520 líneas en 11 scripts, dependen de curl, jq, xmlstarlet, html2text, python3 y pip. Los cuatro workflows en Rust suman unas 1750 líneas en 38 archivos, pero dependen de cero herramientas del sistema. Y tienen 15 tests unitarios, logging estructurado con niveles, graceful shutdown con ctrl_c y AtomicBool, y configuración externa con config.toml.
El Linuxero con IA tiene procesos
Te puedo asegurar que desde que empecé a pensar en términos de workflows, en lugar de herramientas sueltas, mi productividad ha mejorado notablemente. Ya no tengo que estar recordando qué script ejecutar, ni dónde dejé aquel prompt que funcionaba tan bien. Todo está organizado, todo tiene su lugar, todo es reproducible.
La clave de todo esto es la IA híbrida: no uses un LLM para todo. Usa la IA solo donde aporta valor, interpretar logs, clasificar mensajes, resumir artículos, responder preguntas con contexto, y deja las tareas deterministas para código determinista. Comprobar si el disco está lleno no necesita un modelo de 3B parámetros. Pero interpretar por qué se llenó el disco a las 3 de la madrugada, eso sí se beneficia de la IA. Esta distinción marca la diferencia y es lo que separa un sistema bien diseñado de un juguete que gasta tokens innecesariamente.
Y los guardarraíles en los prompts son fundamentales. Un prompt bien escrito, con formato de salida estricto, con ejemplos, con restricciones claras, convierte un LLM impredecible en una herramienta fiable. No es magia, es ingeniería de prompts. Cada uno de los cuatro workflows tiene prompts cuidadosamente diseñados para que el modelo se comporte de manera predecible. Y cuando el modelo se desvía, el código tiene mecanismos de recuperación: extractores de JSON, timeouts, reintentos, valores por defecto.
El stack que te he presentado, Rust, Ollama, Docker, SQLite, SearXNG, funciona en cualquier Linux con 8 GB de RAM, sin GPU, sin conexión a internet, sin depender de servicios externos. Todo corre en tu máquina, todo es privado, todo es controlable. Y lo mejor es que puedes empezar poco a poco: primero el noticias-bot, que es el más sencillo, luego el tareas-bot, que te cambia la forma de organizarte, y según te vayas sintiendo cómodo, añades el monitor y el investigador. No hace falta montarlo todo de golpe.
Si te quedas con una sola idea de este episodio, que sea esta: el Linuxero con IA tiene procesos, no solo herramientas sueltas. Un workflow bien diseñado, aunque sea simple, vale más que diez herramientas increíbles que no se coordinan entre sí.
¿Y tú? ¿Has pensado en qué procesos de tu día a día podrían beneficiarse de un workflow de IA? ¿Sigues usando herramientas sueltas o ya tienes procesos montados? Te leo en los comentarios 😊
Más información
- Ollama – Llama 3.2 — modelo multimodal 1B/3B/11B/90B para CPU y GPU
- Ollama – bge-m3 — modelo de embeddings multilingüe con 1024 dimensiones, soporta 100+ idiomas
- SearXNG — metabuscador privado auto-hosteado que agrega 269 servicios de búsqueda
- Telegram Bot API — documentación oficial de la API de bots de Telegram
- just – command runner — herramienta Rust para ejecutar recetas tipo Makefile con sintaxis moderna
- tokio – runtime asíncrono Rust — runtime asíncrono con epoll/kqueue/IOCP para concurrencia real
- rusqlite – SQLite para Rust — SQLite embebido con feature bundled, sin dependencias externas
- scraper – parseo HTML en Rust — parseo HTML con selectores CSS, sin depender de herramientas externas
- reqwest – cliente HTTP Rust — cliente HTTP con connection pooling y rustls-tls
- bge-m3 paper (arXiv 2402.03216) — paper original de BAAI sobre M3-Embedding multilingüe
- systemd (ArchWiki) — documentación completa de systemd en la ArchWiki
- systemd timers (ArchWiki) — cómo programar tareas con timers systemd
- ntfy.sh — alternativa simple a Telegram para notificaciones push, self-hostable
- Apprise — unificador de 90+ servicios de notificación en una sola API
- Brave Search API — alternativa a SearXNG con 1000 consultas gratis al mes