Programar tareas en el Mac con launchd (y por qué olvidarte de cron)
Aprende a programar tareas en macOS con launchd y LaunchAgents: anatomía del plist, ejemplos reales de limpieza y backup, launchctl y alternativas sin Terminal.
Llevas años haciendo la misma limpieza del escritorio cada lunes a mano, y un día se te ocurre lo sensato: «esto debería hacerlo el Mac solo». Si vienes de Linux, tu primer impulso será escribir crontab -e. En macOS funciona… pero chirría: el camino nativo es launchd.
Para programar tareas en el Mac, la respuesta corta es: usa launchd mediante un plist de LaunchAgent en ~/Library/LaunchAgents, no cron. Los pasos básicos son tres:
- Escribe el script que quieres ejecutar y dale permisos (
chmod +x). - Crea un fichero
.plistcon la claveStartCalendarIntervalque indique cuándo correr. - Cárgalo con
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/tu.plist.
A partir de ahí, macOS se encarga de todo: lanza la tarea a la hora prevista, la reintenta si el Mac estaba apagado y registra los errores. Veamos el porqué y el cómo con ejemplos reales.
Por qué launchd y no cron en macOS
cron sigue presente en macOS 26 Tahoe, pero Apple lo considera tecnología heredada: su propia documentación de programación de tareas, la guía de Daemons and Services de Apple Developer, indica que el mecanismo recomendado es launchd. No es una cuestión de gusto, hay diferencias prácticas:
- Si el Mac está dormido o apagado a la hora programada, cron se salta la ejecución. launchd, en cambio, ejecuta la tarea nada más despertar el equipo.
- launchd es el sistema de arranque completo de macOS: gestiona servicios, respuestas a eventos, reinicios tras un fallo y, de paso, tareas programadas. cron solo sabe de calendarios.
- Los permisos modernos van contra cron: el daemon
crondno tiene acceso total al disco, así que muchos scripts de cron fallan silenciosamente al tocar carpetas protegidas. Con un LaunchAgent de usuario no tienes ese problema. - Registro integrado: puedes redirigir la salida estándar y de error a ficheros de log desde el propio plist, sin trucos.
La contrapartida: launchd es más verboso. Donde cron cabe en una línea, launchd exige un plist XML. El tutorial de launchd.info es la referencia clásica para entender su modelo; con el ejemplo de abajo tendrás el 90 % de lo que necesitas.
Anatomía de un plist de LaunchAgent
Un LaunchAgent es un fichero XML que vive en ~/Library/LaunchAgents (tareas del usuario) y se activa al iniciar sesión. Las claves mínimas son:
| Clave | Para qué sirve |
|---|---|
Label | Nombre único, por convención dominio inverso: com.tuusuario.limpieza |
ProgramArguments | El comando y sus argumentos, como array |
StartCalendarInterval | Cuándo ejecutarlo: día de la semana, hora, minuto |
StandardOutPath / StandardErrorPath | Dónde volcar el log de salida y errores |
StartCalendarInterval admite Weekday (0 = domingo, 1 = lunes…), Hour, Minute, Day y Month. A diferencia de cron, si quieres dos horarios distintos pasas un array de diccionarios en vez de una cadena críptica de cinco campos.
Ejemplo real: limpieza semanal de Descargas
Supón que quieres que cada lunes a las 9:30 el Mac mueva a la Papelera los ficheros de Descargas con más de 30 días (si lo que buscas es ordenar Descargas por tipo y fecha con reglas, hay opciones más cómodas sin código en cómo organizar la carpeta Descargas automáticamente; esto es la versión «quiero que se haga solo, sea como sea»).
Primero, el script ~/bin/limpieza-descargas.sh:
#!/bin/zsh
# Mueve a la Papelera ficheros de Descargas con más de 30 días
find ~/Downloads -type f -mtime +30 -maxdepth 1 -exec mv {} ~/.Trash/ \;
echo "Limpieza ejecutada: $(date)"
Dale permiso de ejecución con chmod +x ~/bin/limpieza-descargas.sh. Ahora el plist ~/Library/LaunchAgents/com.mac.limpieza.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.mac.limpieza</string>
<key>ProgramArguments</key>
<array>
<string>/bin/zsh</string>
<string>/Users/tuusuario/bin/limpieza-descargas.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key><integer>1</integer>
<key>Hour</key><integer>9</integer>
<key>Minute</key><integer>30</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/tuusuario/logs/limpieza.log</string>
<key>StandardErrorPath</key>
<string>/Users/tuusuario/logs/limpieza.err</string>
</dict>
</plist>
Ojo a dos detalles que causan el 80 % de los fallos: usa rutas absolutas en todas partes (launchd no expande ~) y no te olvides de crear antes la carpeta de logs (mkdir -p ~/logs).
Ejemplo real: copia de seguridad programada
El mismo patrón sirve para un backup nocturno con rsync hacia un disco externo, por ejemplo a las 3:00 de la madrugada. El script ~/bin/backup.sh:
#!/bin/zsh
rsync -a --delete ~/Documents/ /Volumes/Backup/Documents/
echo "Backup terminado: $(date)"
En el plist solo cambia el Label, la ruta del script y el intervalo (Hour 3, Minute 0, sin Weekday para que sea diario). Es un buen complemento a una estrategia seria de copias como la que se detalla en Time Machine: la estrategia de copias para tu Mac: Time Machine cubre el historial, y este rsync te da un clon espejo de lo crítico.
launchctl: cargar, descargar y listar
Los comandos históricos launchctl load y launchctl unload siguen funcionando, pero Apple los marcó como heredados en la página de manual de launchctl. La sintaxis actual es:
# Cargar (activar) el agente
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.mac.limpieza.plist
# Descargar (desactivar)
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.mac.limpieza.plist
# Ver si está activo y su último estado
launchctl list | grep limpieza
# Forzar una ejecución inmediata para probar
launchctl kickstart gui/$(id -u)/com.mac.limpieza
En la salida de launchctl list, la primera columna es el PID (un guion si no está corriendo, lo normal en una tarea programada) y la segunda el código de salida de la última ejecución: un 0 significa éxito. Si ves un 1 o un 127, revisa el fichero de StandardErrorPath; casi siempre es una ruta mal escrita o un permiso que falta.
Si editas el plist, haz bootout y vuelve a hacer bootstrap: launchd no relee el fichero por sí solo. Y valida la sintaxis antes con plutil -lint tu.plist: ahorra muchos quebraderos de cabeza con el XML. Si aún te manejas poco con la línea de comandos, la guía Terminal en macOS y Homebrew te pone al día con lo esencial.
La alternativa sin Terminal: Atajos y Calendario
Si el XML te echa para atrás, hay una vía nativa y visual. La app Atajos de macOS permite crear automatizaciones que se disparan a una hora concreta cada día o cada semana (pestaña Automatización → «Hora del día»). Dentro del atajo puedes usar acciones como «Buscar archivos» filtrando por fecha, «Mover archivo» o «Ejecutar script de shell», que cubre la mayoría de tareas de limpieza y copia. La guía Atajos: la app para automatizar tu Mac explica cómo montarlas.
El truco clásico con Calendario (un evento repetitivo cuya alerta «abre un fichero» apunta a un script o atajo) sigue funcionando en macOS 26, aunque es más frágil: no deja ningún log. Úsalo como apaño, no como fundación.
¿Cuándo elegir cada opción? Atajos para tareas personales sencillas; launchd cuando necesites fiabilidad, logs, horarios complejos o que la tarea se recupere tras un apagón. Y cron, en 2026, solo si estás manteniendo un sistema viejo que ya lo usa.
Preguntas frecuentes
¿cron está eliminado en macOS 26 Tahoe? No, crontab sigue instalado y funciona, pero Apple lo documenta como tecnología heredada y sus permisos están limitados por la protección del sistema. Para cualquier tarea nueva, launchd es la vía recomendada.
¿Dónde van los LaunchAgents y los LaunchDaemons? Los agentes de usuario en ~/Library/LaunchAgents (se ejecutan al iniciar sesión, con tus permisos). Los daemons de sistema en /Library/LaunchDaemons (corren como root desde el arranque, sin sesión gráfica). Para tus tareas personales usa siempre el primero.
¿Se ejecuta la tarea si el Mac está apagado a esa hora? Con launchd, sí: si el equipo estaba dormido, la tarea corre al despertar; si estaba apagado, se ejecuta en el siguiente arranque. Es una de sus grandes ventajas sobre cron.
¿Cómo depuro un LaunchAgent que no se ejecuta? Comprueba el estado con launchctl list | grep tu-label, revisa el fichero de StandardErrorPath, valida el XML con plutil -lint y fuerza una ejecución de prueba con launchctl kickstart.
Programar tareas en el Mac ya no pasa por memorizar la sintaxis de cron: un plist bien hecho y dos comandos de launchctl te dan un sistema fiable, con logs y recuperación automática. Empieza por una tarea pequeña —la limpieza de Descargas es perfecta— y en un mes querrás automatizar media vida digital.