devAlice
← Mac

Mantenimiento semanal del entorno de desarrollo Mac: actualiza brew, toolchains y SDKs con un comando

Un único script semanal de shell que actualiza Homebrew, Mac App Store, npm globals, rustup, binarios de cargo, pipx, Flutter, CocoaPods y oh-my-zsh.

Dedicas tiempo a configurar una máquina nueva, luego te olvidas de mantenerla actualizada. Dos meses después, un simple brew install arrastra 30 dependencias transitivas, las compilaciones nativas fallan por incompatibilidades de SDK, y tus certificados de seguridad (ca-certificates) se desfasan silenciosamente hacia la expiración.

Pienso que el mantenimiento semanal no es una tarea de administración, sino parte del flujo de desarrollo. Antes lo ignoraba porque parecía opcional; ahora lo veo como una inversión: diez minutos a la semana evitan horas de depuración de entornos rotos.

Este artículo es el script integrado de 9 pasos que lo previene. Ejecútalo una vez a la semana — listo.

TL;DR

  • Ejecuta brew + mas + npm -g + rustup + cargo install-update + pipx + flutter + pod + omz en orden
  • Cada paso se omite automáticamente si la herramienta no está instalada, y un fallo en uno no detiene el resto
  • Termina con una verificación de actualizaciones del sistema macOS solo de aviso (sin instalación automática — los reinicios son demasiado arriesgados)
  • ~100 líneas de bash simple

Por qué vale la pena

1. Caducidad de certificados

ca-certificates distribuye un nuevo paquete cada trimestre. Sáltate dos trimestres y algunas llamadas HTTPS empezarán a fallar. Puede que creas que no te afecta, pero el registro de npm, el propio Homebrew y la API de GitHub son ejemplos concretos.

2. Desfase del SDK de compilación móvil

Especialmente arriesgado en máquinas con Flutter/iOS. Flutter stable se publica 3–4 veces al mes; el repositorio de specs de CocoaPods, si no se sincroniza durante una semana, empieza a lanzar «spec not found» durante pod install. Un pod repo update semanal por sí solo elimina el 80% de ese problema.

3. Acumulación en los toolchains de lenguaje

Los paquetes globales instalados vía rustup, pipx o cargo requieren su propia actualización. Si lo olvidas, un día descubres que cargo install no puede obtener la última versión por incompatibilidad de rust-version.

Requisitos previos

  • macOS 12+ + Homebrew (configuración inicial de Mac)
  • Para los 9 pasos completos: mas, pipx, cargo-update, flutter, cocoapods, oh-my-zsh preinstalados
  • Que falten algunas herramientas está bien — esos pasos se OMITEN sin problemas

Preparación única

brew install mas pipx cocoapods
brew install --cask flutter
cargo install cargo-update
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
pipx ensurepath

cargo install cargo-update compila, así que la primera ejecución tarda 2–3 minutos. Flutter es ~1GB de descarga.

9 pasos de actualización

#PasoComandoNotas
1Homebrewbrew update && brew upgrade && brew cleanupfórmulas + casks
2Mac App Storemas upgradeApps de la App Store (Xcode etc.)
3npm globalsnpm update -gpaquetes npm globales
4Toolchain de Rustrustup updatecanal stable
5Binarios de Cargocargo install-update -arequiere extensión cargo-update
6pipxpipx upgrade-allherramientas globales de Python
7Flutter SDKflutter upgradela última del canal actual
8Repo de CocoaPodspod repo updatesincronización de specs para compilaciones iOS
9oh-my-zsh~/.oh-my-zsh/tools/upgrade.shframework zsh

El script integrado

Guárdalo como ~/bin/update-system.sh, dale permisos con chmod +x y ejecútalo cuando lo necesites.

#!/usr/bin/env bash
#
# Actualización masiva de herramientas del sistema — macOS
# Uso: ./update-system.sh
#
# Alcance:
#   1. Homebrew (fórmulas + casks)
#   2. Mac App Store (mas, si está instalado)
#   3. npm globals
#   4. Toolchain de Rust (rustup)
#   5. Binarios de Cargo (cargo install-update -a, necesita cargo-update)
#   6. pipx (herramientas globales de Python)
#   7. Flutter SDK (flutter upgrade)
#   8. Repo de CocoaPods (pod repo update — dependencia de compilación iOS)
#   9. oh-my-zsh (omz update)
#
# Termina con aviso de `softwareupdate -l` (sin instalación automática — riesgo de reinicio)
#
 
set -uo pipefail
 
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
GRAY='\033[0;90m'
NC='\033[0m'
 
TOTAL=9
STEP=0
 
step() {
    STEP=$((STEP + 1))
    printf "\n${CYAN}[%d/%d] %s${NC}\n" "$STEP" "$TOTAL" "$1"
}
 
ok()   { printf "  ${GREEN}OK${NC} %s\n" "$1"; }
skip() { printf "  ${GRAY}SKIP${NC} %s\n" "$1"; }
warn() { printf "  ${YELLOW}WARN${NC} %s\n" "$1"; }
fail() { printf "  ${RED}FAIL${NC} %s\n" "$1"; }
 
printf "${CYAN}━━━ Actualización del sistema (macOS) ━━━${NC}\n"
 
# Inicializa fnm para que el paso de npm pueda encontrar Node
if command -v fnm >/dev/null 2>&1; then
    eval "$(fnm env --use-on-cd --shell bash 2>/dev/null)" 2>/dev/null || true
fi
 
# 1. Homebrew
step "Homebrew (fórmulas + casks)"
if command -v brew >/dev/null 2>&1; then
    if brew update && brew upgrade && brew cleanup; then
        ok "brew update / upgrade / cleanup"
    else
        fail "El paso de brew falló parcialmente — revisa el log anterior"
    fi
else
    skip "brew no está instalado"
fi
 
# 2. Mac App Store
step "Mac App Store (mas)"
if command -v mas >/dev/null 2>&1; then
    mas upgrade && ok "mas upgrade" || fail "mas upgrade"
else
    skip "mas no está instalado (brew install mas)"
fi
 
# 3. npm globals
step "npm globals"
if command -v npm >/dev/null 2>&1; then
    npm update -g && ok "npm update -g" || fail "npm update -g"
else
    skip "npm no está instalado"
fi
 
# 4. Toolchain de Rust
step "Toolchain de Rust (rustup)"
if command -v rustup >/dev/null 2>&1; then
    rustup update && ok "rustup update" || fail "rustup update"
else
    skip "rustup no está instalado"
fi
 
# 5. Binarios de Cargo
step "Binarios de Cargo (cargo install-update -a)"
if command -v cargo >/dev/null 2>&1; then
    if cargo install-update --version >/dev/null 2>&1; then
        cargo install-update -a && ok "cargo install-update -a" || fail "cargo install-update -a"
    else
        warn "cargo-update faltante (instalar: cargo install cargo-update)"
    fi
else
    skip "cargo no está instalado"
fi
 
# 6. pipx
step "pipx (herramientas globales de Python)"
if command -v pipx >/dev/null 2>&1; then
    pipx upgrade-all && ok "pipx upgrade-all" || fail "pipx upgrade-all"
else
    skip "pipx no está instalado (brew install pipx)"
fi
 
# 7. Flutter SDK
step "Flutter SDK (flutter upgrade)"
if command -v flutter >/dev/null 2>&1; then
    flutter upgrade && ok "flutter upgrade" || fail "flutter upgrade"
else
    skip "flutter no está instalado"
fi
 
# 8. Repo de CocoaPods
step "Repo de CocoaPods (pod repo update)"
if command -v pod >/dev/null 2>&1; then
    pod repo update && ok "pod repo update" || fail "pod repo update"
else
    skip "pod no está instalado (brew install cocoapods)"
fi
 
# 9. oh-my-zsh
step "oh-my-zsh (omz update)"
if [ -d "$HOME/.oh-my-zsh" ]; then
    OMZ_UPDATER="$HOME/.oh-my-zsh/tools/upgrade.sh"
    if [ -x "$OMZ_UPDATER" ]; then
        zsh "$OMZ_UPDATER" && ok "oh-my-zsh actualizado" || fail "oh-my-zsh"
    else
        skip "oh-my-zsh upgrade.sh faltante"
    fi
else
    skip "oh-my-zsh no está instalado"
fi
 
# Actualización del sistema macOS — solo aviso
printf "\n${CYAN}━━━ Verificación de actualización del sistema macOS (solo aviso) ━━━${NC}\n"
if command -v softwareupdate >/dev/null 2>&1; then
    SU_OUT=$(softwareupdate -l 2>&1)
    if echo "$SU_OUT" | grep -qi "no new software\|No updates"; then
        printf "${GRAY}  Al día — sin actualizaciones del sistema${NC}\n"
    else
        printf "${YELLOW}  ⚠ Actualizaciones del sistema disponibles:${NC}\n"
        echo "$SU_OUT" | sed 's/^/    /'
        printf "${GRAY}  Instalar con: sudo softwareupdate -ia --restart  (reiniciará)${NC}\n"
    fi
fi
 
printf "\n${CYAN}━━━ Listo ━━━${NC}\n"
printf "${GRAY}Comprobaciones: brew doctor / brew outdated / npm outdated -g / flutter doctor${NC}\n"

Ejemplo de salida

La primera ejecución es larga (5 minutos si brew tiene 13 actualizaciones pendientes). Las siguientes son de 1–2 minutos.

━━━ Actualización del sistema (macOS) ━━━

[1/9] Homebrew (fórmulas + casks)
==> Upgrading 13 outdated packages:
ca-certificates 2026-03-19 -> 2026-05-14
ruby 4.0.3 -> 4.0.4
sqlite 3.53.0 -> 3.53.1
python@3.14 3.14.4_1 -> 3.14.5
...
==> This operation has freed approximately 18.8MB of disk space.
  OK brew update / upgrade / cleanup

[2/9] Mac App Store (mas)
  OK mas upgrade

[3/9] npm globals
  OK npm update -g

[4/9] Toolchain de Rust (rustup)
  stable-aarch64-apple-darwin unchanged - rustc 1.95.0
  OK rustup update

...

[9/9] oh-my-zsh (omz update)
Hooray! Oh My Zsh has been updated!
  OK oh-my-zsh actualizado

━━━ Verificación de actualización del sistema macOS (solo aviso) ━━━
  Al día — sin actualizaciones del sistema

━━━ Listo ━━━

Automatización: ¿hasta qué punto?

No se recomienda la automatización completa. Las actualizaciones mayores de brew (p.ej., vercel-cli 53→54) incluyen a veces cambios incompatibles que conviene detectar en el momento.

Un punto intermedio razonable es un recordatorio, no una ejecución automática:

~/Library/LaunchAgents/local.update-reminder.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>local.update-reminder</string>
  <key>ProgramArguments</key>
  <array>
    <string>osascript</string>
    <string>-e</string>
    <string>display notification "Weekly update-system check" with title "Dev Maintenance"</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Weekday</key><integer>5</integer>
    <key>Hour</key><integer>10</integer>
    <key>Minute</key><integer>0</integer>
  </dict>
</dict>
</plist>

Activar:

launchctl load -w ~/Library/LaunchAgents/local.update-reminder.plist

Cada viernes a las 10:00, una única notificación de escritorio. Tú decides cuándo ejecutarlo.

Advertencias

  • Sin instalación automática de actualizaciones del sistema macOS: softwareupdate -ia --restart fuerza un reinicio. Tu IDE, terminales y contenedores Docker se perderán. El script solo lista las actualizaciones disponibles; las instalas cuando tú decidas.
  • pod repo update es lento (puede superar 5 minutos): el repositorio trunk es enorme. Más aún si han pasado varias semanas.
  • oh-my-zsh tiene su propia opción de actualización automática: ajusta zstyle ':omz:update' mode auto en .zshrc. La superposición con este script es inofensiva.
  • Cargo compila: cargo install-update -a compila las nuevas versiones de los binarios instalados; la primera ejecución puede tardar varios minutos.

¿Usas Windows?

La misma idea en PowerShell, cubriendo MSYS2 + winget + toolchains de lenguaje: Mantenimiento semanal del entorno de desarrollo Windows.

Resumen

  • Actualizar herramienta por herramienta conduce al olvido.
  • Un único script integrado: ejecútalo cuando quieras sin perder el hilo.
  • Automatización solo como recordatorio, nunca como ejecución automática.
  • Una vez a la semana previene las tormentas de dependencias de brew, el desfase de SDK y la caducidad de certificados.