APP_GESTION_PAQUETS

Primer crearem l’archiu de l’script:

touch install-and-uninstall.sh

Despres anirem a dins de l’archiu

nano install-and-uninstall.sh

Dins tindrem que afegir el seguent codi:

#!/bin/bash
# ----------------------------------------------------------------------------
# Script: install-and-uninstall.sh
# Descripción:
#   - Gestiona la instalación y desinstalación de paquetes mediante menús
#     organizados en categorías y subcategorías (usando solo echo/read).
#   - Las listas de paquetes para instalación están extendidas, y al seleccionar
#     un paquete se muestra una descripción breve y se pregunta al usuario si
#     está seguro de proceder.
#   - La categoría "Externos" se maneja con procedimientos especiales en la instalación,
#     mientras que en la desinstalación se descarta.
#   - Incluye registro de operaciones, manejo de errores mejorado, opción de actualización
#     del sistema, búsqueda de paquetes, soporte para Snap/Flatpak y modo de simulación.
# ----------------------------------------------------------------------------

# -------------------------------------
# Definición de colores ANSI (más variedad)
# -------------------------------------
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
MAGENTA='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m'   # Sin color

# -------------------------------------
# Configuración del log
# -------------------------------------
LOG_FILE="/var/log/gestor-paquetes.log"
DRY_RUN=false # Variable para el modo simulación

# -------------------------------------
# Función para registrar actividad
# -------------------------------------
log_message() {
  local type="$1" # INFO, WARNING, ERROR, ACTION, CANCEL
  local message="$2"
  echo "$(date '+%Y-%m-%d %H:%M:%S') [$type] $message" | sudo tee -a "$LOG_FILE" > /dev/null
}

# ----------------------------------------------------------------------------
# Creación del archivo .desktop global y su copia en los escritorios
# ----------------------------------------------------------------------------
DESKTOP_FILE="/usr/share/applications/gestor-paquetes.desktop"
self_path=$(readlink -f "$0")

echo -e "${BLUE}Creando archivo .desktop global...${NC}"
if [ "$(id -u)" -eq 0 ]; then
  cat <<EOF > "$DESKTOP_FILE"
[Desktop Entry]
Version=1.0
Type=Application
Name=Gestor de Paquetes
Comment=Ejecuta el script de instalación/desinstalación de paquetes
Exec=$self_path
Icon=utilities-terminal
Terminal=true
EOF
  log_message INFO "Archivo .desktop creado en $DESKTOP_FILE"
else
  echo -e "${YELLOW}Se requieren permisos de root para crear el .desktop. Solicitando elevación...${NC}"
  log_message WARNING "Intentando crear .desktop con sudo."
  sudo bash -c "cat <<EOF > $DESKTOP_FILE
[Desktop Entry]
Version=1.0
Type=Application
Name=Gestor de Paquetes
Comment=Ejecuta el script de instalación/desinstalación de paquetes
Exec=$self_path
Icon=utilities-terminal
Terminal=true
EOF"
  if [ $? -eq 0 ]; then
    log_message INFO "Archivo .desktop creado en $DESKTOP_FILE (con sudo)."
  else
    log_message ERROR "Fallo al crear el archivo .desktop en $DESKTOP_FILE."
  fi
fi
echo -e "${GREEN}Archivo .desktop creado/actualizado en: $DESKTOP_FILE${NC}"

desktop_dir="$(xdg-user-dir DESKTOP 2>/dev/null)"
if [ -z "$desktop_dir" ]; then
  desktop_dir="$HOME/Desktop"
fi
if [ -d "$desktop_dir" ]; then
  if [ "$DRY_RUN" = false ]; then
    cp "$DESKTOP_FILE" "$desktop_dir/"
    log_message INFO "Acceso directo copiado al escritorio del usuario actual: $desktop_dir"
  else
    echo -e "${YELLOW}Simulación: cp \"$DESKTOP_FILE\" \"$desktop_dir/\"${NC}"
  fi
  echo -e "${GREEN}Acceso directo copiado al escritorio del usuario actual: $desktop_dir${NC}"
else
  echo -e "${YELLOW}No se encontró el directorio de escritorio para el usuario actual.${NC}"
  log_message WARNING "No se encontró el directorio de escritorio para el usuario actual."
fi

for user_home in /home/*; do
  if [ -d "$user_home" ]; then
    if [ -d "$user_home/Desktop" ]; then
      if [ "$DRY_RUN" = false ]; then
        sudo cp "$DESKTOP_FILE" "$user_home/Desktop/"
        log_message INFO "Acceso directo copiado en: $user_home/Desktop"
      else
        echo -e "${YELLOW}Simulación: sudo cp \"$DESKTOP_FILE\" \"$user_home/Desktop/\"${NC}"
      fi
      echo -e "${CYAN}Acceso directo copiado en: $user_home/Desktop${NC}"
    elif [ -d "$user_home/Escritorio" ]; then
      if [ "$DRY_RUN" = false ]; then
        sudo cp "$DESKTOP_FILE" "$user_home/Escritorio/"
        log_message INFO "Acceso directo copiado en: $user_home/Escritorio"
      else
        echo -e "${YELLOW}Simulación: sudo cp \"$DESKTOP_FILE\" \"$user_home/Escritorio/\"${NC}"
      fi
      echo -e "${CYAN}Acceso directo copiado en: $user_home/Escritorio${NC}"
    fi
  fi
done

# ----------------------------------------------------------------------------
# Funciones de apoyo para sugerir correcciones en nombres
# ----------------------------------------------------------------------------
filtrar_paquete() {
  local input="$1"
  shift
  local lista=("$@")
  local suggestions=()
  for pkg in "${lista[@]}"; do
    if [[ "${pkg,,}" == *"${input,,}"* ]]; then
      suggestions+=("$pkg")
    fi
  done
  echo "${suggestions[@]}"
}

verificar_paquete() {
  local input="$1"
  shift
  local lista=("$@")
  local found=0
  for pkg in "${lista[@]}"; do
    if [[ "${pkg,,}" == "${input,,}" ]]; then
      found=1
      break
    fi
  done
  if [ $found -eq 0 ]; then
    suggestions=( $(filtrar_paquete "$input" "${lista[@]}") )
    if [ ${#suggestions[@]} -gt 0 ]; then
      echo -e "${YELLOW}No se encontró \"$input\" exactamente. Quizás quisiste decir:${NC}"
      local index=1
      for s in "${suggestions[@]}"; do
        echo -e "  $index) $s"
        ((index++))
      done
      read -p "Elige la opción (ingresa el número) o escribe nuevamente el nombre: " resp
      if [[ "$resp" =~ ^[0-9]+$ ]]; then
        input=${suggestions[$((resp-1))]}
      else
        input="$resp"
      fi
    else
      echo -e "${YELLOW}No se encontraron sugerencias para '$input'. Se usará la entrada tal cual.${NC}"
    fi
  fi
  echo "$input"
}

# ----------------------------------------------------------------------------
# Configuración de categorías para INSTALACIÓN (listas extendidas)
# ----------------------------------------------------------------------------
declare -A categorias
# Categoría Herramientas
categorias["Herramientas"]="Utilidades de Terminal|nano vim emacs mc htop glances ranger net-tools curl wget rsync tree ncdu inxi neofetch bat ripgrep fd-find
Automatización y Scripting|ansible terraform cron apt-xapian-index build-essential"
# Categoría Desarrollo
categorias["Desarrollo"]="Lenguajes de Programación|python3 python3-pip gcc g++ openjdk-17-jdk nodejs npm ruby-full perl golang rustc cargo swift-lang php php-cli php-mysql php-fpm
Control de Versiones|git subversion mercurial
IDE y Editores|gedit kate geany sublime-text atom codeblocks eclipse netbeans intellijidea
Bases de Datos|mysql-server postgresql sqlite3 redis-server mongodb-org-server"
# Categoría Redes
categorias["Redes"]="Monitoreo y Diagnóstico|nmap wireshark-qt tcpdump traceroute iperf3 netcat openvpn telnet dnsutils whois
Seguridad y Firewall|ufw fail2ban openssh-server gufw clamav rkhunter chkrootkit john"
# Categoría Multimedia
categorias["Multimedia"]="Reproductores y Codecs|vlc totem audacious rhythmbox ffmpeg gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly
Edición de Audio|audacity ardour lmms sox easytag
Edición de Video|kdenlive openshot obs-studio handbrake vokoscreen krita
Edición de Imagen y Diseño|gimp inkscape krita darktable blender digikam pinta imagemagick"
# Categoría Oficina y Productividad
categorias["Oficina y Productividad"]="Suites de Oficina|libreoffice libreoffice-writer libreoffice-calc libreoffice-impress libreoffice-draw
Utilidades PDF|evince okular qpdfview poppler-utils
Gestión de Proyectos|planner projectlibre taskwarrior"
# Categoría Utilidades del Sistema
categorias["Utilidades del Sistema"]="Archivado y Compresión|p7zip-full unrar zip unzip xz-utils bzip2 gzip tar
Backup y Recuperación|grsync timeshift deja-dup rsnapshot testdisk foremost
Virtualización|virtualbox virt-manager qemu-kvm vagrant docker.io docker-compose"
# Categoría Navegadores Web
categorias["Navegadores Web"]="Navegadores|firefox chromium-browser opera-stable brave-browser" # opera-stable and brave-browser usually installed via external sources
# Categoría Juegos y Emuladores
categorias["Juegos y Emuladores"]="Juegos|steam lutris playonlinux minetest openarena
Emuladores|retroarch pcsx2 dolphin-emu mgba-qt mupen64plus"
# Categoría Externos (procedimientos especiales para .deb o PPAs)
categorias["Externos"]="Software Externo|google-chrome-stable code slack spotify zoom discord opera brave-browser teamviewer postman" # Added more examples

# ----------------------------------------------------------------------------
# Array asociativo con descripciones para cada paquete (simplificado para APT, detallado para externos)
# ----------------------------------------------------------------------------
declare -A descripciones

# Descripciones genéricas para paquetes APT (se usará apt-cache show para detalles)
descripciones["nano"]="Editor de texto de terminal."
descripciones["vim"]="Editor de texto programable."
descripciones["emacs"]="Editor de texto extensible."
descripciones["mc"]="Midnight Commander: gestor de archivos."
descripciones["htop"]="Monitor de procesos interactivo."
descripciones["glances"]="Monitor de sistema en tiempo real."
descripciones["ranger"]="Gestor de archivos de terminal."
descripciones["net-tools"]="Herramientas de red básicas."
descripciones["curl"]="Herramienta para transferir datos con URL."
descripciones["wget"]="Descargador de archivos."
descripciones["rsync"]="Herramienta de sincronización y backup."
descripciones["tree"]="Muestra el contenido del directorio en forma de árbol."
descripciones["ncdu"]="Analizador de uso de disco en terminal."
descripciones["inxi"]="Herramienta de información del sistema."
descripciones["neofetch"]="Muestra la información del sistema en un ASCII art."
descripciones["bat"]="Un 'cat' con esteroides, con resaltado de sintaxis."
descripciones["ripgrep"]="Herramienta de búsqueda de texto superrápida."
descripciones["fd-find"]="Una alternativa más rápida y amigable a 'find'."
descripciones["ansible"]="Plataforma de automatización IT."
descripciones["terraform"]="Herramienta de infraestructura como código."
descripciones["cron"]="Daemon para ejecutar comandos programados."
descripciones["apt-xapian-index"]="Índice de búsqueda para paquetes APT."
descripciones["build-essential"]="Paquetes esenciales para compilar software."
descripciones["python3"]="Lenguaje de programación Python 3."
descripciones["python3-pip"]="Gestor de paquetes para Python 3."
descripciones["gcc"]="Compilador de GNU C."
descripciones["g++"]="Compilador de GNU C++."
descripciones["openjdk-17-jdk"]="Kit de desarrollo de Java OpenJDK 17."
descripciones["nodejs"]="Entorno de ejecución de JavaScript."
descripciones["npm"]="Gestor de paquetes para Node.js."
descripciones["ruby-full"]="Entorno completo de Ruby."
descripciones["perl"]="Lenguaje de scripting Perl."
descripciones["golang"]="Lenguaje de programación Go."
descripciones["rustc"]="Compilador de Rust."
descripciones["cargo"]="Gestor de paquetes de Rust."
descripciones["swift-lang"]="Compilador de Swift."
descripciones["php"]="Lenguaje de scripting PHP."
descripciones["php-cli"]="Versión de línea de comandos de PHP."
descripciones["php-mysql"]="Módulo MySQL para PHP."
descripciones["php-fpm"]="FastCGI Process Manager para PHP."
descripciones["git"]="Sistema de control de versiones distribuido."
descripciones["subversion"]="Sistema de control de versiones centralizado."
descripciones["mercurial"]="Sistema de control de versiones distribuido."
descripciones["gedit"]="Editor de texto simple para GNOME."
descripciones["kate"]="Editor de texto avanzado de KDE."
descripciones["geany"]="Entorno de desarrollo ligero."
descripciones["sublime-text"]="Editor de texto y código propietario."
descripciones["atom"]="Editor de texto personalizable."
descripciones["codeblocks"]="IDE para C, C++ y Fortran."
descripciones["eclipse"]="IDE extensible para Java y otros."
descripciones["netbeans"]="IDE para Java, PHP, HTML, etc."
descripciones["intellijidea"]="IDE para desarrollo Java y Kotlin (versión de comunidad)."
descripciones["mysql-server"]="Servidor de base de datos MySQL."
descripciones["postgresql"]="Servidor de base de datos PostgreSQL."
descripciones["sqlite3"]="Motor de base de datos SQL incrustado."
descripciones["redis-server"]="Servidor de base de datos en memoria."
descripciones["mongodb-org-server"]="Servidor de base de datos NoSQL MongoDB."
descripciones["nmap"]="Escáner de red y auditor de seguridad."
descripciones["wireshark-qt"]="Analizador de protocolos de red gráfico."
descripciones["tcpdump"]="Herramienta de captura de tráfico de red."
descripciones["traceroute"]="Utilidad para rastrear rutas de red."
descripciones["iperf3"]="Herramienta de medición de rendimiento de red."
descripciones["openvpn"]="Software de VPN."
descripciones["telnet"]="Cliente de Telnet."
descripciones["dnsutils"]="Utilidades de DNS (dig, nslookup)."
descripciones["whois"]="Cliente Whois."
descripciones["ufw"]="Firewall simple y fácil de usar."
descripciones["fail2ban"]="Previene ataques de fuerza bruta."
descripciones["openssh-server"]="Servidor SSH."
descripciones["gufw"]="Interfaz gráfica para UFW."
descripciones["clamav"]="Antivirus de línea de comandos."
descripciones["rkhunter"]="Escáner de rootkits."
descripciones["chkrootkit"]="Detector de rootkits."
descripciones["john"]="Crackeador de contraseñas (John the Ripper)."
descripciones["vlc"]="Reproductor multimedia versátil."
descripciones["totem"]="Reproductor de video para GNOME."
descripciones["audacious"]="Reproductor de audio ligero."
descripciones["rhythmbox"]="Reproductor de música para GNOME."
descripciones["ffmpeg"]="Marco de trabajo para audio/video."
descripciones["gstreamer1.0-plugins-good"]="Plugins multimedia GStreamer (buenos)."
descripciones["gstreamer1.0-plugins-bad"]="Plugins multimedia GStreamer (malos)."
descripciones["gstreamer1.0-plugins-ugly"]="Plugins multimedia GStreamer (feos)."
descripciones["audacity"]="Editor de audio."
descripciones["ardour"]="Estación de trabajo de audio digital (DAW)."
descripciones["lmms"]="Estación de trabajo musical Linux."
descripciones["sox"]="Navaja suiza de los efectos de sonido."
descripciones["easytag"]="Editor de etiquetas para audio."
descripciones["kdenlive"]="Editor de video no lineal."
descripciones["openshot"]="Editor de video simple."
descripciones["obs-studio"]="Software para streaming y grabación."
descripciones["handbrake"]="Transcodificador de video."
descripciones["vokoscreen"]="Grabador de pantalla."
descripciones["gimp"]="Editor de imágenes."
descripciones["inkscape"]="Editor de gráficos vectoriales."
descripciones["krita"]="Software de pintura digital."
descripciones["darktable"]="Procesador de imágenes RAW."
descripciones["blender"]="Suite de creación 3D."
descripciones["digikam"]="Gestor de fotos."
descripciones["pinta"]="Editor de imágenes similar a Paint.NET."
descripciones["imagemagick"]="Suite de herramientas de manipulación de imágenes."
descripciones["libreoffice"]="Suite ofimática completa."
descripciones["libreoffice-writer"]="Procesador de textos de LibreOffice."
descripciones["libreoffice-calc"]="Hoja de cálculo de LibreOffice."
descripciones["libreoffice-impress"]="Software de presentación de LibreOffice."
descripciones["libreoffice-draw"]="Herramienta de dibujo de LibreOffice."
descripciones["evince"]="Visor de documentos (PDF, PostScript)."
descripciones["okular"]="Visor de documentos universal de KDE."
descripciones["qpdfview"]="Visor de PDF ligero."
descripciones["poppler-utils"]="Utilidades para PDFs."
descripciones["planner"]="Herramienta de gestión de proyectos."
descripciones["projectlibre"]="Software de gestión de proyectos compatible con MS Project."
descripciones["taskwarrior"]="Gestor de tareas basado en línea de comandos."
descripciones["p7zip-full"]="Archivador 7-Zip."
descripciones["unrar"]="Extractor de archivos RAR."
descripciones["zip"]="Archivador Zip."
descripciones["unzip"]="Extractor de archivos Zip."
descripciones["xz-utils"]="Utilidades de compresión XZ."
descripciones["bzip2"]="Compresor de archivos de alta calidad."
descripciones["gzip"]="Compresor de archivos de uso común."
descripciones["tar"]="Utilidad de archivado de cintas."
descripciones["grsync"]="Interfaz gráfica para rsync."
descripciones["timeshift"]="Herramienta de restauración del sistema."
descripciones["deja-dup"]="Herramienta de backup simple."
descripciones["rsnapshot"]="Herramienta de backup basado en rsync."
descripciones["testdisk"]="Herramienta de recuperación de datos."
descripciones["foremost"]="Herramienta de recuperación de archivos."
descripciones["virtualbox"]="Software de virtualización."
descripciones["virt-manager"]="Gestor de máquinas virtuales."
descripciones["qemu-kvm"]="Emulador de virtualización."
descripciones["vagrant"]="Herramienta para construir y gestionar entornos de máquinas virtuales."
descripciones["docker.io"]="Plataforma de contenedores Docker."
descripciones["docker-compose"]="Herramienta para definir y ejecutar aplicaciones Docker multi-contenedor."
descripciones["firefox"]="Navegador web de Mozilla."
descripciones["chromium-browser"]="Navegador web de código abierto."
descripciones["steam"]="Plataforma de distribución digital de videojuegos."
descripciones["lutris"]="Gestor de juegos para Linux."
descripciones["playonlinux"]="Herramienta para ejecutar juegos de Windows en Linux."
descripciones["minetest"]="Juego tipo Minecraft de código abierto."
descripciones["openarena"]="Juego de disparos en primera persona."
descripciones["retroarch"]="Frontend para emuladores de juegos."
descripciones["pcsx2"]="Emulador de PlayStation 2."
descripciones["dolphin-emu"]="Emulador de GameCube y Wii."
descripciones["mgba-qt"]="Emulador de Game Boy Advance."
descripciones["mupen64plus"]="Emulador de Nintendo 64."


# Externos - Software Externo (más detalladas y con enlaces actuales donde sea posible)
descripciones["google-chrome-stable"]="Google Chrome: Navegador web rápido y seguro, de Google."
descripciones["code"]="Visual Studio Code: Editor de código moderno, extensible y multiplataforma (Microsoft)."
descripciones["slack"]="Slack: Herramienta de comunicación y colaboración para equipos."
descripciones["spotify"]="Spotify: Servicio de música en streaming con amplio catálogo y alta calidad."
descripciones["zoom"]="Zoom: Plataforma de videoconferencia que facilita reuniones virtuales."
descripciones["discord"]="Discord: Plataforma con chat de voz y texto para comunidades y gamers."
descripciones["opera"]="Opera: Navegador web innovador con características únicas y VPN integrada."
descripciones["brave-browser"]="Brave Browser: Navegador web enfocado en la privacidad y con bloqueador de anuncios incorporado."
descripciones["teamviewer"]="TeamViewer: Software de acceso remoto y colaboración online."
descripciones["postman"]="Postman: Plataforma de API para construir, probar y documentar APIs."

# ----------------------------------------------------------------------------
# Función para instalar paquetes (APT, Snap, Flatpak)
# ----------------------------------------------------------------------------
install_package() {
  local package_name="$1"
  local install_type="$2" # apt, snap, flatpak, external
  local category="$3" # Para logging

  log_message ACTION "Iniciando instalación de $package_name (tipo: $install_type) en categoría $category."

  echo -e "\n${CYAN}Instalando ${GREEN}$package_name${NC} a través de $install_type..."

  local cmd=""
  local success_message=""
  local error_message=""
  local post_install_info=""
  local pkg_found=false

  # Pre-check if package exists in a sensible way for each type
  if [ "$install_type" == "apt" ]; then
    if apt-cache show "$package_name" &>/dev/null; then
      pkg_found=true
    fi
  elif [ "$install_type" == "snap" ]; then
    if snap find "$package_name" &>/dev/null; then
      pkg_found=true
    fi
  elif [ "$install_type" == "flatpak" ]; then
    if flatpak search "$package_name" &>/dev/null; then # This might return many, a better check is flatpak remote-ls flathub | grep $package_name
      pkg_found=true # Simplistic check for now
    fi
  elif [ "$install_type" == "external" ]; then
      if [[ -n "${descripciones[$package_name]}" ]]; then # If it has a custom description, assume it's "known"
          pkg_found=true
      fi
  fi

  if [ "$pkg_found" = false ]; then
    echo -e "${RED}Error: El paquete '$package_name' no se encontró o no es válido para el tipo de instalación '$install_type'.${NC}"
    log_message ERROR "Paquete '$package_name' no encontrado/válido para $install_type."
    return 1
  fi


  case "$install_type" in
    apt)
      cmd="sudo apt update && sudo apt install -y $package_name"
      success_message="¡Instalación de $package_name (APT) completada!"
      error_message="Error al instalar $package_name (APT). Intenta 'sudo apt update --fix-missing' o 'sudo apt --fix-broken install'."
      ;;
    snap)
      cmd="sudo snap install $package_name"
      success_message="¡Instalación de $package_name (Snap) completada!"
      error_message="Error al instalar $package_name (Snap). Asegúrate de que Snapd esté instalado ('sudo apt install snapd') y que el paquete exista."
      ;;
    flatpak)
      cmd="sudo flatpak install -y flathub $package_name" # Asumimos flathub como remoto
      success_message="¡Instalación de $package_name (Flatpak) completada!"
      error_message="Error al instalar $package_name (Flatpak). Asegúrate de que Flatpak esté instalado y Flathub configurado (ver https://flatpak.org/setup/)."
      ;;
    external)
      # Manejo de paquetes externos (APT personalizado vía .deb, PPA, etc.)
      case "$package_name" in
        google-chrome-stable)
          cmd="wget -O /tmp/google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && sudo dpkg -i /tmp/google-chrome.deb && sudo apt-get -f install -y"
          success_message="¡Instalación de Google Chrome completada!"
          error_message="Error al instalar Google Chrome. Posibles problemas de dependencia. Intenta 'sudo apt-get -f install'."
          ;;
        code)
          cmd="wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/packages.microsoft.gpg && sudo install -o root -g root -m 644 /tmp/packages.microsoft.gpg /etc/apt/trusted.gpg.d/ && sudo sh -c 'echo \"deb [arch=amd64,arm64,armhf] https://packages.microsoft.com/repos/vscode stable main\" > /etc/apt/sources.list.d/vscode.list' && sudo apt update && sudo apt install -y code"
          success_message="¡Instalación de VS Code completada!"
          error_message="Error al instalar VS Code. Revisa las fuentes de paquetes."
          ;;
        slack)
          cmd="wget -O /tmp/slack.deb https://downloads.slack-edge.com/linux_releases/slack-desktop-4.33.73-amd64.deb && sudo dpkg -i /tmp/slack.deb && sudo apt-get -f install -y"
          success_message="¡Instalación de Slack completada!"
          error_message="Error al instalar Slack. Posibles problemas de dependencia. Intenta 'sudo apt-get -f install'."
          ;;
        spotify)
          # Updated Spotify installation from their official guide
          cmd="curl -sS https://download.spotify.com/debian/pubkey_7A3A762FAFD4A51F.gpg | sudo gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/spotify.gpg && echo \"deb http://repository.spotify.com stable non-free\" | sudo tee /etc/apt/sources.list.d/spotify.list > /dev/null && sudo apt update && sudo apt install -y spotify-client"
          success_message="¡Instalación de Spotify completada!"
          error_message="Error al instalar Spotify. Revisa las claves GPG y las fuentes de paquetes."
          ;;
        zoom)
          cmd="wget -O /tmp/zoom.deb https://zoom.us/client/latest/zoom_amd64.deb && sudo dpkg -i /tmp/zoom.deb && sudo apt-get -f install -y"
          success_message="¡Instalación de Zoom completada!"
          error_message="Error al instalar Zoom. Posibles problemas de dependencia. Intenta 'sudo apt-get -f install'."
          ;;
        discord)
          cmd="wget -O /tmp/discord.deb \"https://discordapp.com/api/download?platform=linux&format=deb\" && sudo dpkg -i /tmp/discord.deb && sudo apt-get -f install -y"
          success_message="¡Instalación de Discord completada!"
          error_message="Error al instalar Discord. Posibles problemas de dependencia. Intenta 'sudo apt-get -f install'."
          ;;
        opera)
          # Updated Opera installation from their official guide
          cmd="curl -fsSL https://deb.opera.com/archive.key | sudo gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/opera.gpg && echo \"deb [arch=amd64] https://deb.opera.com/opera-stable/ stable non-free\" | sudo tee /etc/apt/sources.list.d/opera.list > /dev/null && sudo apt update && sudo apt install -y opera-stable"
          success_message="¡Instalación de Opera completada!"
          error_message="Error al instalar Opera. Revisa las claves GPG y las fuentes de paquetes."
          ;;
        brave-browser)
          # Brave Browser installation from their official guide
          cmd="sudo curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg && echo \"deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg] https://brave-browser-apt-release.s3.brave.com/ stable main\"|sudo tee /etc/apt/sources.list.d/brave-browser-release.list > /dev/null && sudo apt update && sudo apt install -y brave-browser"
          success_message="¡Instalación de Brave Browser completada!"
          error_message="Error al instalar Brave Browser. Revisa las claves GPG y las fuentes de paquetes."
          ;;
        teamviewer)
          cmd="wget -O /tmp/teamviewer.deb https://download.teamviewer.com/download/linux/teamviewer_amd64.deb && sudo dpkg -i /tmp/teamviewer.deb && sudo apt-get -f install -y"
          success_message="¡Instalación de TeamViewer completada!"
          error_message="Error al instalar TeamViewer. Posibles problemas de dependencia. Intenta 'sudo apt-get -f install'."
          ;;
        postman)
          # Postman is typically installed from their tar.gz or via snap, providing a snap option here as apt is not direct
          echo -e "${YELLOW}Postman no se instala directamente vía APT. Sugerimos instalarlo como un Snap.${NC}"
          read -p "¿Quieres instalar Postman vía Snap? (S/n): " confirm_snap
          if [[ "$confirm_snap" =~ ^[Ss]$ ]]; then
              install_package "postman" "snap" "Externos (via Snap)"
              return $? # Return result of snap install
          else
              echo -e "${YELLOW}Instalación de Postman cancelada.${NC}"
              log_message CANCEL "Instalación de Postman (externo) cancelada por el usuario."
              return 1
          fi
          ;;
        *)
          echo -e "${YELLOW}Paquete externo '$package_name' no reconocido para instalación automática. Se requiere intervención manual.${NC}"
          log_message ERROR "Intento de instalación de paquete externo no reconocido: $package_name."
          return 1 # Fallo
          ;;
      esac
      ;;
    *)
      echo -e "${RED}Tipo de instalación desconocido: $install_type.${NC}"
      log_message ERROR "Tipo de instalación desconocido: $install_type para $package_name."
      return 1
      ;;
  esac

  if [ "$DRY_RUN" = true ]; then
    echo -e "${YELLOW}Simulación: Comando a ejecutar: $cmd${NC}"
    log_message INFO "Simulación de instalación de $package_name. Comando: '$cmd'"
    echo -e "${GREEN}Simulación de instalación completada para $package_name.${NC}"
    return 0 # En modo simulación, siempre 'exitoso'
  fi

  eval "$cmd"
  if [ $? -eq 0 ]; then
    echo -e "${GREEN}$success_message${NC}"
    log_message INFO "$success_message"
    if [ -n "$post_install_info" ]; then
      echo -e "${YELLOW}$post_install_info${NC}"
      log_message INFO "$post_install_info"
    fi
    return 0
  else
    echo -e "${RED}$error_message${NC}"
    log_message ERROR "$error_message (Comando: $cmd)"
    return 1
  fi
}

# ----------------------------------------------------------------------------
# Función para desinstalar paquetes (APT, Snap, Flatpak)
# ----------------------------------------------------------------------------
uninstall_package() {
  local package_name="$1"
  local install_type="$2" # apt, snap, flatpak
  local category="$3" # Para logging

  log_message ACTION "Iniciando desinstalación de $package_name (tipo: $install_type) en categoría $category."

  echo -e "\n${BLUE}Desinstalando ${RED}$package_name${NC} a través de $install_type..."

  local cmd=""
  local success_message=""
  local error_message=""

  case "$install_type" in
    apt)
      cmd="sudo apt remove -y $package_name"
      success_message="¡Desinstalación de $package_name (APT) completada!"
      error_message="Error al desinstalar $package_name (APT). Intenta 'sudo apt autoremove' o 'sudo apt --fix-broken install'."
      ;;
    snap)
      cmd="sudo snap remove $package_name"
      success_message="¡Desinstalación de $package_name (Snap) completada!"
      error_message="Error al desinstalar $package_name (Snap)."
      ;;
    flatpak)
      cmd="sudo flatpak uninstall -y $package_name"
      success_message="¡Desinstalación de $package_name (Flatpak) completada!"
      error_message="Error al desinstalar $package_name (Flatpak)."
      ;;
    *)
      echo -e "${RED}Tipo de desinstalación desconocido: $install_type.${NC}"
      log_message ERROR "Tipo de desinstalación desconocido: $install_type para $package_name."
      return 1
      ;;
  esac

  if [ "$DRY_RUN" = true ]; then
    echo -e "${YELLOW}Simulación: Comando a ejecutar: $cmd${NC}"
    log_message INFO "Simulación de desinstalación de $package_name. Comando: '$cmd'"
    echo -e "${GREEN}Simulación de desinstalación completada para $package_name.${NC}"
    return 0
  fi

  eval "$cmd"
  if [ $? -eq 0 ]; then
    echo -e "${GREEN}$success_message${NC}"
    log_message INFO "$success_message"
    return 0
  else
    echo -e "${RED}$error_message${NC}"
    log_message ERROR "$error_message (Comando: $cmd)"
    return 1
  fi
}

# ----------------------------------------------------------------------------
# Función para el menú post-operación
# ----------------------------------------------------------------------------
post_op_menu() {
  local menu_choice=""
  while true; do
    echo -e "\n${MAGENTA}=== Operación Completada. ¿Qué quieres hacer ahora? ===${NC}"
    echo -e "${GREEN}1) Actualizar repositorios (apt update)${NC}"
    echo -e "${GREEN}2) Actualizar sistema (apt update && apt upgrade)${NC}"
    echo -e "${YELLOW}3) Instalar paquetes${NC}"
    echo -e "${RED}4) Desinstalar paquetes${NC}"
    echo -e "${BLUE}5) Ver registro de operaciones (log)${NC}"
    echo -e "${BLUE}6) Ver información detallada de un paquete${NC}"
    echo -e "${MAGENTA}7) Configurar opciones personalizadas (próximamente)${NC}" # Placeholder
    echo -e "${BLUE}8) Salir del gestor${NC}"
    read -p "Elige una opción: " menu_choice
    log_message INFO "Opción seleccionada en menú post-operación: $menu_choice"

    case "$menu_choice" in
      1)
        echo -e "${CYAN}Actualizando repositorios (apt update)...${NC}"
        if [ "$DRY_RUN" = true ]; then
          echo -e "${YELLOW}Simulación: sudo apt update${NC}"
          log_message INFO "Simulación: Actualizando repositorios."
        else
          sudo apt update
          if [ $? -eq 0 ]; then
            echo -e "${GREEN}Repositorios actualizados.${NC}"
            log_message INFO "Repositorios actualizados correctamente."
          else
            echo -e "${RED}Error al actualizar repositorios.${NC}"
            log_message ERROR "Error al actualizar repositorios."
          fi
        fi
        ;;
      2)
        echo -e "${CYAN}Actualizando el sistema (apt update && apt upgrade)...${NC}"
        if [ "$DRY_RUN" = true ]; then
          echo -e "${YELLOW}Simulación: sudo apt update && sudo apt upgrade -y${NC}"
          log_message INFO "Simulación: Actualización completa del sistema."
        else
          sudo apt update && sudo apt upgrade -y
          if [ $? -eq 0 ]; then
            echo -e "${GREEN}Sistema actualizado.${NC}"
            log_message INFO "Sistema actualizado correctamente."
          else
            echo -e "${RED}Error al actualizar el sistema. Revisa el log para más detalles.${NC}"
            log_message ERROR "Error al actualizar el sistema."
          fi
        fi
        ;;
      3)
        # Redirigir al inicio del menú principal para instalación
        return 0 # Exit post_op_menu to return to main_menu loop
        ;;
      4)
        # Redirigir al inicio del menú principal para desinstalación
        return 0 # Exit post_op_menu to return to main_menu loop
        ;;
      5)
        echo -e "\n${CYAN}Mostrando las últimas 50 líneas del registro de operaciones (${LOG_FILE}):${NC}"
        if [ "$DRY_RUN" = true ]; then
          echo -e "${YELLOW}Simulación: Mostrar log de operaciones. (En modo real: tail -n 50 $LOG_FILE)${NC}"
          log_message INFO "Simulación: Solicitado ver log."
        else
          if [ -f "$LOG_FILE" ]; then
            sudo tail -n 50 "$LOG_FILE"
          else
            echo -e "${YELLOW}El archivo de log aún no existe: $LOG_FILE${NC}"
          fi
        fi
        read -p "Presiona Enter para continuar..."
        ;;
      6)
        echo -e "\n${CYAN}Búsqueda de información detallada de un paquete (APT/Snap/Flatpak):${NC}"
        read -p "Ingresa el nombre exacto del paquete: " pkg_name_info
        log_message INFO "Solicitud de información detallada para paquete: $pkg_name_info"
        if [ -z "$pkg_name_info" ]; then
          echo -e "${RED}Nombre de paquete vacío.${NC}"
          continue
        fi

        echo -e "\n${BLUE}Buscando información APT para '$pkg_name_info'...${NC}"
        if [ "$DRY_RUN" = true ]; then
          echo -e "${YELLOW}Simulación: apt-cache show $pkg_name_info${NC}"
        else
          apt-cache show "$pkg_name_info"
        fi

        echo -e "\n${BLUE}Buscando información Snap para '$pkg_name_info'...${NC}"
        if [ "$DRY_RUN" = true ]; then
          echo -e "${YELLOW}Simulación: snap info $pkg_name_info${NC}"
        else
          snap info "$pkg_name_info" 2>/dev/null
        fi

        echo -e "\n${BLUE}Buscando información Flatpak para '$pkg_name_info'...${NC}"
        if [ "$DRY_RUN" = true ]; then
          echo -e "${YELLOW}Simulación: flatpak info $pkg_name_info${NC}"
        else
          flatpak info "$pkg_name_info" 2>/dev/null
        fi
        read -p "Presiona Enter para continuar..."
        ;;
      7)
        echo -e "${YELLOW}Esta función está en desarrollo. ¡Vuelve pronto!${NC}"
        log_message INFO "Accedido a función 'Configurar opciones personalizadas' (en desarrollo)."
        ;;
      8)
        echo -e "\n${MAGENTA}Saliendo del script.${NC}"
        log_message INFO "Saliendo del script principal."
        exit 0 # Exit the entire script
        ;;
      *)
        echo -e "${RED}Opción no válida. Por favor, elige una opción entre 1 y 8.${NC}"
        log_message WARNING "Opción no válida en menú post-operación: $menu_choice"
        ;;
    esac
  done
}


# ----------------------------------------------------------------------------
# Función principal para la gestión de paquetes
# ----------------------------------------------------------------------------
main_menu() {
  local exit_script=0
  log_message INFO "Iniciando el script 'Gestor de Paquetes'."

  # Ensure log file exists and is writable by root
  if [ ! -f "$LOG_FILE" ]; then
    sudo touch "$LOG_FILE"
    sudo chmod 644 "$LOG_FILE"
  fi

  while [ "$exit_script" -eq 0 ]; do
    echo -e "\n${MAGENTA}=== Gestor de Paquetes ===${NC}"
    echo -e "${CYAN}Modo de Simulación (Dry-Run): ${YELLOW}$DRY_RUN${NC}"
    echo -e "${GREEN}1) Instalar un paquete (APT, Snap, Flatpak o Externo)${NC}"
    echo -e "${RED}2) Desinstalar un paquete (APT, Snap o Flatpak)${NC}"
    echo -e "${BLUE}3) Actualizar sistema (apt update && apt upgrade)${NC}"
    echo -e "${YELLOW}4) Buscar un paquete${NC}"
    echo -e "${MAGENTA}5) Alternar modo de simulación (Dry-Run)${NC}"
    echo -e "${BLUE}6) Salir${NC}"
    read -p "Elige una opción (1-6): " opcion
    log_message INFO "Opción seleccionada en menú principal: $opcion"

    case "$opcion" in
      1) # Instalación
        log_message ACTION "Iniciando proceso de instalación."
        echo -e "\n${CYAN}Tipo de instalación:${NC}"
        echo -e "  ${GREEN}1) Paquete APT (desde repositorios de Ubuntu)${NC}"
        echo -e "  ${YELLOW}2) Paquete Snap${NC}"
        echo -e "  ${BLUE}3) Paquete Flatpak${NC}"
        echo -e "  ${MAGENTA}4) Paquete Externo (procesamiento especial, e.g., Chrome, VS Code)${NC}"
        read -p "Elige el tipo de instalación (1-4): " install_type_choice
        local selected_install_type=""
        local paquetes_to_process=()
        local categoria_para_log=""

        case "$install_type_choice" in
          1) # APT
            selected_install_type="apt"
            echo -e "\n${CYAN}Categorías disponibles (APT):${NC}"
            categorias_keys=()
            i=1
            for key in "${!categorias[@]}"; do
                if [ "$key" != "Externos" ]; then # No mostrar externos aquí
                    echo -e "  ${YELLOW}$i) $key${NC}"
                    categorias_keys+=("$key")
                    ((i++))
                fi
            done
            read -p "Elige una categoría (ingresa el número): " cat_choice
            if ! [[ "$cat_choice" =~ ^[0-9]+$ ]] || [ "$cat_choice" -lt 1 ] || [ "$cat_choice" -gt ${#categorias_keys[@]} ]; then
                echo -e "${RED}Selección de categoría no válida.${NC}"
                log_message WARNING "Selección de categoría APT no válida: $cat_choice"
                continue
            fi
            categoria_seleccionada=${categorias_keys[$((cat_choice-1))]}
            categoria_para_log="$categoria_seleccionada (APT)"

            echo -e "\n${CYAN}Subcategorías en ${categoria_seleccionada}:${NC}"
            subcats=$(echo -e "${categorias[$categoria_seleccionada]}")
            IFS=$'\n' read -d '' -r -a subcategorias_array < <(echo -e "$subcats")
            i=1
            for sub in "${subcategorias_array[@]}"; do
              subcat_name=$(echo "$sub" | cut -d'|' -f1)
              echo -e "  ${YELLOW}$i) $subcat_name${NC}"
              ((i++))
            done
            read -p "Elige una subcategoría (número): " sub_choice
            if ! [[ "$sub_choice" =~ ^[0-9]+$ ]] || [ "$sub_choice" -lt 1 ] || [ "$sub_choice" -gt ${#subcategorias_array[@]} ]; then
                echo -e "${RED}Selección de subcategoría no válida.${NC}"
                log_message WARNING "Selección de subcategoría APT no válida: $sub_choice"
                continue
            fi
            selected_line="${subcategorias_array[$((sub_choice-1))]}"
            selected_subcat=$(echo "$selected_line" | cut -d'|' -f1)
            paquetes_str=$(echo "$selected_line" | cut -d'|' -f2)
            read -a paquetes_array <<< "$paquetes_str"

            echo -e "\n${CYAN}Paquetes disponibles en ${categoria_seleccionada} -> ${selected_subcat}:${NC}"
            i=1
            for pkg in "${paquetes_array[@]}"; do
              echo -e "  ${YELLOW}$i) $pkg${NC}"
              ((i++))
            done
            read -p "Elige el paquete a instalar (ingresa el número o escribe el nombre): " entrada
            if [[ "$entrada" =~ ^[0-9]+$ ]]; then
                if [ "$entrada" -lt 1 ] || [ "$entrada" -gt ${#paquetes_array[@]} ]; then
                    echo -e "${RED}Número de paquete no válido.${NC}"
                    log_message WARNING "Número de paquete APT no válido: $entrada"
                    continue 2 # Exit this inner case and outer case
                fi
              paquete_seleccionado=${paquetes_array[$((entrada-1))]}
            else
              paquete_seleccionado=$(verificar_paquete "$entrada" "${paquetes_array[@]}")
            fi
            paquetes_to_process=("$paquete_seleccionado")
            ;;
          2) # Snap
            selected_install_type="snap"
            read -p "Introduce el nombre del paquete Snap a instalar (e.g., 'spotify', 'vlc'): " pkg_snap
            if [ -z "$pkg_snap" ]; then
                echo -e "${RED}Nombre de paquete Snap vacío.${NC}"
                log_message WARNING "Nombre de paquete Snap vacío."
                continue
            fi
            paquetes_to_process=("$pkg_snap")
            categoria_para_log="Snap"
            ;;
          3) # Flatpak
            selected_install_type="flatpak"
            read -p "Introduce el ID de la aplicación Flatpak a instalar (e.g., 'org.kde.krita', 'com.spotify.Client'): " pkg_flatpak
            if [ -z "$pkg_flatpak" ]; then
                echo -e "${RED}ID de aplicación Flatpak vacío.${NC}"
                log_message WARNING "ID de aplicación Flatpak vacío."
                continue
            fi
            paquetes_to_process=("$pkg_flatpak")
            categoria_para_log="Flatpak"
            ;;
          4) # Externos
            selected_install_type="external"
            echo -e "\n${CYAN}Paquetes Externos disponibles:${NC}"
            subcats_ext=$(echo -e "${categorias["Externos"]}")
            # Ensure subcats_ext is not empty before proceeding
            if [ -z "$subcats_ext" ]; then
                echo -e "${RED}No se encontraron paquetes externos definidos.${NC}"
                log_message ERROR "No se encontraron paquetes externos definidos."
                continue
            fi

            selected_line_ext="${subcats_ext}" # Asume que solo hay una línea para externos
            paquetes_str_ext=$(echo "$selected_line_ext" | cut -d'|' -f2)
            read -a paquetes_array_ext <<< "$paquetes_str_ext"

            i=1
            for pkg in "${paquetes_array_ext[@]}"; do
              echo -e "  ${YELLOW}$i) $pkg${NC}"
              ((i++))
            done
            read -p "Elige el paquete externo a instalar (ingresa el número o escribe el nombre): " entrada_ext
            if [[ "$entrada_ext" =~ ^[0-9]+$ ]]; then
                if [ "$entrada_ext" -lt 1 ] || [ "$entrada_ext" -gt ${#paquetes_array_ext[@]} ]; then
                    echo -e "${RED}Número de paquete externo no válido.${NC}"
                    log_message WARNING "Número de paquete externo no válido: $entrada_ext"
                    continue 2 # Exit this inner case and outer case
                fi
              paquete_seleccionado=${paquetes_array_ext[$((entrada_ext-1))]}
            else
              paquete_seleccionado=$(verificar_paquete "$entrada_ext" "${paquetes_array_ext[@]}")
            fi
            paquetes_to_process=("$paquete_seleccionado")
            categoria_para_log="Externos"
            ;;
          *)
            echo -e "${RED}Opción de tipo de instalación no válida.${NC}"
            log_message WARNING "Opción de tipo de instalación no válida: $install_type_choice."
            continue
            ;;
        esac

        if [ ${#paquetes_to_process[@]} -eq 0 ] || [ -z "${paquetes_to_process[0]}" ]; then
          echo -e "${RED}No se seleccionó ningún paquete para instalar.${NC}"
          log_message CANCEL "Instalación cancelada: ningún paquete seleccionado."
          continue
        fi

        for paquete_a_instalar in "${paquetes_to_process[@]}"; do
          # Mostrar descripción y solicitar confirmación
          echo -e "\n${CYAN}Obteniendo descripción del paquete...${NC}"
          local description=""
          if [ "$selected_install_type" == "apt" ]; then
            description=$(apt-cache show "$paquete_a_instalar" 2>/dev/null | awk -F': ' '/Description:/{print $2; exit}')
            if [ -z "$description" ]; then
              description="No hay descripción disponible en el repositorio APT."
            fi
          elif [ "$selected_install_type" == "snap" ]; then
             description=$(snap info "$paquete_a_instalar" 2>/dev/null | grep -A 1 summary: | tail -n 1 | sed 's/^ *//')
             if [ -z "$description" ]; then
               description="No hay descripción disponible para este Snap o no está instalado."
             fi
          elif [ "$selected_install_type" == "flatpak" ]; then
             description=$(flatpak info "$paquete_a_instalar" 2>/dev/null | grep -A 1 Description: | tail -n 1 | sed 's/^ *//')
             if [ -z "$description" ]; then
               description="No hay descripción disponible para este Flatpak o no está instalado."
             fi
          elif [ "$selected_install_type" == "external" ]; then
            description=${descripciones[$paquete_a_instalar]}
            if [ -z "$description" ]; then
              description="No hay descripción disponible para este paquete externo."
            fi
          fi

          echo -e "${CYAN}Descripción:${NC} $description"
          read -p "¿Estás seguro de instalar el paquete '$paquete_a_instalar' (tipo: $selected_install_type)? (S/n): " confirm
          if ! [[ "$confirm" =~ ^[Ss]$ || -z "$confirm" ]]; then
            echo -e "${YELLOW}Instalación de '$paquete_a_instalar' cancelada.${NC}"
            log_message CANCEL "Instalación de '$paquete_a_instalar' cancelada por el usuario."
            continue
          fi

          install_package "$paquete_a_instalar" "$selected_install_type" "$categoria_para_log"
        done
        post_op_menu
        ;;

      2) # Desinstalación
        log_message ACTION "Iniciando proceso de desinstalación."
        echo -e "\n${CYAN}Tipo de desinstalación:${NC}"
        echo -e "  ${GREEN}1) Paquete APT${NC}"
        echo -e "  ${YELLOW}2) Paquete Snap${NC}"
        echo -e "  ${BLUE}3) Paquete Flatpak${NC}"
        read -p "Elige el tipo de desinstalación (1-3): " uninstall_type_choice
        local selected_uninstall_type=""
        local paquete_a_desinstalar=""
        local categoria_para_log_uninstall=""

        case "$uninstall_type_choice" in
          1) # APT
            selected_uninstall_type="apt"
            categoria_para_log_uninstall="APT"
            echo -e "\n${CYAN}Recabando información de paquetes APT instalados según la clasificación...${NC}"
            declare -A inst_categorias
            for cat in "${!categorias[@]}"; do
              if [ "$cat" == "Externos" ]; then
                continue # Los externos se desinstalan con apt si fueron instalados via deb
              fi
              subcats=$(echo -e "${categorias[$cat]}")
              IFS=$'\n' read -d '' -r -a subs_array < <(echo -e "$subcats")
              result=""
              for sub in "${subs_array[@]}"; do
                subcat_name=$(echo "$sub" | cut -d'|' -f1)
                pkg_list=$(echo "$sub" | cut -d'|' -f2)
                pkg_array=($pkg_list)
                installed_packages=()
                for pkg in "${pkg_array[@]}"; do
                  if dpkg -l "$pkg" 2>/dev/null | grep -q "^ii"; then
                    installed_packages+=("$pkg")
                  fi
                done
                if [ ${#installed_packages[@]} -gt 0 ]; then
                  result+="${subcat_name}|$(echo ${installed_packages[@]})"$'\n'
                fi
              done
              if [ -n "$result" ]; then
                inst_categorias["$cat"]="$result"
              fi
            done

            if [ ${#inst_categorias[@]} -eq 0 ]; then
              echo -e "${RED}No se encontraron paquetes APT instalados en las categorías predefinidas.${NC}"
              log_message INFO "No se encontraron paquetes APT predefinidos instalados para desinstalar."
              continue
            fi

            echo -e "\n${CYAN}Categorías disponibles para desinstalación (APT):${NC}"
            available_categories=()
            i=1
            for cat in "${!inst_categorias[@]}"; do
              echo -e "  ${YELLOW}$i) $cat${NC}"
              available_categories+=("$cat")
              ((i++))
            done
            read -p "Elige una categoría (número): " cat_choice_inst
            if ! [[ "$cat_choice_inst" =~ ^[0-9]+$ ]] || [ "$cat_choice_inst" -lt 1 ] || [ "$cat_choice_inst" -gt ${#available_categories[@]} ]; then
                echo -e "${RED}Selección de categoría no válida.${NC}"
                log_message WARNING "Selección de categoría APT para desinstalación no válida: $cat_choice_inst"
                continue
            fi
            chosen_cat="${available_categories[$((cat_choice_inst-1))]}"

            echo -e "\n${CYAN}Subcategorías en ${chosen_cat}:${NC}"
            subcats_inst=$(echo -e "${inst_categorias[$chosen_cat]}")
            IFS=$'\n' read -d '' -r -a subs_inst_array < <(echo -e "$subcats_inst")
            i=1
            for sub in "${subs_inst_array[@]}"; do
              subcat_inst=$(echo "$sub" | cut -d'|' -f1)
              echo -e "  ${YELLOW}$i) $subcat_inst${NC}"
              ((i++))
            done
            read -p "Elige una subcategoría (número): " sub_choice_inst
            if ! [[ "$sub_choice_inst" =~ ^[0-9]+$ ]] || [ "$sub_choice_inst" -lt 1 ] || [ "$sub_choice_inst" -gt ${#subs_inst_array[@]} ]; then
                echo -e "${RED}Selección de subcategoría no válida.${NC}"
                log_message WARNING "Selección de subcategoría APT para desinstalación no válida: $sub_choice_inst"
                continue
            fi
            selected_line_inst="${subs_inst_array[$((sub_choice_inst-1))]}"
            selected_subcat_inst=$(echo "$selected_line_inst" | cut -d'|' -f1)
            pkg_line_inst=$(echo "$selected_line_inst" | cut -d'|' -f2)
            read -a pkg_array_inst <<< "$pkg_line_inst"

            echo -e "\n${CYAN}Paquetes instalados en ${chosen_cat} -> ${selected_subcat_inst}:${NC}"
            i=1
            for pkg in "${pkg_array_inst[@]}"; do
              echo -e "  ${YELLOW}$i) $pkg${NC}"
              ((i++))
            done
            read -p "Elige el paquete a desinstalar (ingresa el número o escribe el nombre): " entrada
            if [[ "$entrada" =~ ^[0-9]+$ ]]; then
                if [ "$entrada" -lt 1 ] || [ "$entrada" -gt ${#pkg_array_inst[@]} ]; then
                    echo -e "${RED}Número de paquete no válido.${NC}"
                    log_message WARNING "Número de paquete APT para desinstalación no válido: $entrada"
                    continue 2 # Exit this inner case and outer case
                fi
              paquete_a_desinstalar=${pkg_array_inst[$((entrada-1))]}
            else
              paquete_a_desinstalar=$(verificar_paquete "$entrada" "${pkg_array_inst[@]}")
            fi
            ;;
          2) # Snap Uninstall
            selected_uninstall_type="snap"
            categoria_para_log_uninstall="Snap"
            echo -e "\n${CYAN}Paquetes Snap instalados:${NC}"
            if [ "$DRY_RUN" = true ]; then
              echo -e "${YELLOW}Simulación: snap list${NC}"
              snap_list_output="spotify\ncode" # Mock data
            else
              snap_list_output=$(snap list | awk 'NR>1 {print $1}')
            fi
            if [ -z "$snap_list_output" ]; then
              echo -e "${YELLOW}No se encontraron paquetes Snap instalados.${NC}"
              log_message INFO "No se encontraron paquetes Snap instalados para desinstalar."
              continue
            fi
            read -a snap_installed_array <<< "$snap_list_output"
            i=1
            for pkg in "${snap_installed_array[@]}"; do
              echo -e "  ${YELLOW}$i) $pkg${NC}"
              ((i++))
            done
            read -p "Elige el paquete Snap a desinstalar (número o nombre): " snap_entry
            if [[ "$snap_entry" =~ ^[0-9]+$ ]]; then
                if [ "$snap_entry" -lt 1 ] || [ "$snap_entry" -gt ${#snap_installed_array[@]} ]; then
                    echo -e "${RED}Número de paquete Snap no válido.${NC}"
                    log_message WARNING "Número de paquete Snap para desinstalación no válido: $snap_entry"
                    continue 2
                fi
              paquete_a_desinstalar=${snap_installed_array[$((snap_entry-1))]}
            else
              paquete_a_desinstalar=$(verificar_paquete "$snap_entry" "${snap_installed_array[@]}")
            fi
            ;;
          3) # Flatpak Uninstall
            selected_uninstall_type="flatpak"
            categoria_para_log_uninstall="Flatpak"
            echo -e "\n${CYAN}Aplicaciones Flatpak instaladas:${NC}"
            if [ "$DRY_RUN" = true ]; then
              echo -e "${YELLOW}Simulación: flatpak list${NC}"
              flatpak_list_output="org.kde.krita\ncom.spotify.Client" # Mock data
            else
              flatpak_list_output=$(flatpak list --app --columns=application | awk 'NR>1 {print $1}')
            fi
            if [ -z "$flatpak_list_output" ]; then
              echo -e "${YELLOW}No se encontraron aplicaciones Flatpak instaladas.${NC}"
              log_message INFO "No se encontraron Flatpak instalados para desinstalar."
              continue
            fi
            read -a flatpak_installed_array <<< "$flatpak_list_output"
            i=1
            for pkg in "${flatpak_installed_array[@]}"; do
              echo -e "  ${YELLOW}$i) $pkg${NC}"
              ((i++))
            done
            read -p "Elige la aplicación Flatpak a desinstalar (número o ID): " flatpak_entry
            if [[ "$flatpak_entry" =~ ^[0-9]+$ ]]; then
                if [ "$flatpak_entry" -lt 1 ] || [ "$flatpak_entry" -gt ${#flatpak_installed_array[@]} ]; then
                    echo -e "${RED}Número de aplicación Flatpak no válido.${NC}"
                    log_message WARNING "Número de aplicación Flatpak para desinstalación no válido: $flatpak_entry"
                    continue 2
                fi
              paquete_a_desinstalar=${flatpak_installed_array[$((flatpak_entry-1))]}
            else
              paquete_a_desinstalar=$(verificar_paquete "$flatpak_entry" "${flatpak_installed_array[@]}")
            fi
            ;;
          *)
            echo -e "${RED}Opción de tipo de desinstalación no válida.${NC}"
            log_message WARNING "Opción de tipo de desinstalación no válida: $uninstall_type_choice."
            continue
            ;;
        esac

        if [ -z "$paquete_a_desinstalar" ]; then
          echo -e "${RED}No se seleccionó ningún paquete para desinstalar.${NC}"
          log_message CANCEL "Desinstalación cancelada: ningún paquete seleccionado."
          continue
        fi

        read -p "¿Estás seguro de desinstalar el paquete '$paquete_a_desinstalar' (tipo: $selected_uninstall_type)? (S/n): " confirm_un
        if ! [[ "$confirm_un" =~ ^[Ss]$ || -z "$confirm_un" ]]; then
          echo -e "${YELLOW}Desinstalación de '$paquete_a_desinstalar' cancelada.${NC}"
          log_message CANCEL "Desinstalación de '$paquete_a_desinstalar' cancelada por el usuario."
          continue
        fi

        uninstall_package "$paquete_a_desinstalar" "$selected_uninstall_type" "$categoria_para_log_uninstall"
        post_op_menu
        ;;

      3) # Actualizar sistema
        log_message ACTION "Iniciando actualización completa del sistema."
        echo -e "${CYAN}Actualizando el sistema (apt update && apt upgrade)...${NC}"
        if [ "$DRY_RUN" = true ]; then
          echo -e "${YELLOW}Simulación: sudo apt update && sudo apt upgrade -y${NC}"
          log_message INFO "Simulación: Actualización completa del sistema."
        else
          sudo apt update && sudo apt upgrade -y
          if [ $? -eq 0 ]; then
            echo -e "${GREEN}Sistema actualizado.${NC}"
            log_message INFO "Sistema actualizado correctamente."
          else
            echo -e "${RED}Error al actualizar el sistema. Revisa el log para más detalles.${NC}"
            log_message ERROR "Error al actualizar el sistema."
          fi
        fi
        post_op_menu
        ;;

      4) # Buscar un paquete
        log_message ACTION "Iniciando búsqueda de paquetes."
        echo -e "\n${CYAN}=== Búsqueda de Paquetes ===${NC}"
        read -p "Introduce el término de búsqueda: " search_term
        if [ -z "$search_term" ]; then
          echo -e "${RED}Término de búsqueda vacío.${NC}"
          log_message WARNING "Búsqueda de paquete: término vacío."
          continue
        fi
        log_message INFO "Buscando paquetes con el término: '$search_term'"

        echo -e "\n${BLUE}Resultados de búsqueda APT para '$search_term':${NC}"
        if [ "$DRY_RUN" = true ]; then
          echo -e "${YELLOW}Simulación: apt-cache search $search_term${NC}"
          echo -e "${YELLOW} (Muestra ejemplos de resultados APT) ${NC}"
          echo "nano - small, friendly text editor"
          echo "htop - interactive process viewer"
        else
          apt-cache search "$search_term" | head -n 20 # Limitar a 20 resultados para no saturar
        fi

        echo -e "\n${BLUE}Resultados de búsqueda Snap para '$search_term':${NC}"
        if [ "$DRY_RUN" = true ]; then
          echo -e "${YELLOW}Simulación: snap find $search_term${NC}"
          echo -e "${YELLOW} (Muestra ejemplos de resultados Snap) ${NC}"
          echo "Name        Version    Summary"
          echo "spotify     1.2.3.4    Music streaming service"
          echo "vlc         3.0.18     VLC media player"
        else
          snap find "$search_term" | head -n 20
        fi

        echo -e "\n${BLUE}Resultados de búsqueda Flatpak para '$search_term':${NC}"
        if [ "$DRY_RUN" = true ]; then
          echo -e "${YELLOW}Simulación: flatpak search $search_term${NC}"
          echo -e "${YELLOW} (Muestra ejemplos de resultados Flatpak) ${NC}"
          echo "Name        Application ID                  Version"
          echo "Krita       org.kde.krita                   5.2.2"
          echo "Discord     com.discordapp.Discord          1.0.9036"
        else
          flatpak search "$search_term" | head -n 20
        fi
        read -p "Presiona Enter para continuar..."
        ;;

      5) # Alternar modo de simulación
        if [ "$DRY_RUN" = true ]; then
          DRY_RUN=false
          echo -e "${GREEN}Modo de Simulación (Dry-Run) DESACTIVADO.${NC}"
          log_message INFO "Modo de Simulación DESACTIVADO."
        else
          DRY_RUN=true
          echo -e "${YELLOW}Modo de Simulación (Dry-Run) ACTIVADO. Ningún comando se ejecutará realmente.${NC}"
          log_message INFO "Modo de Simulación ACTIVADO."
        fi
        ;;

      6) # Salir del script
        exit_script=1
        echo -e "\n${MAGENTA}Saliendo del script.${NC}"
        log_message INFO "Saliendo del script 'Gestor de Paquetes'."
        ;;

      *) # Opción no válida
        echo -e "${RED}Opción no válida. Por favor, elige una opción entre 1 y 6.${NC}"
        log_message WARNING "Opción no válida en menú principal: $opcion"
        ;;
    esac
  done
}

# Iniciar el menú principal
main_menu

Una vegada creat el script tindrem que donar’li permisos:

chmod +x install-and-uninstall.sh

Per ultim executarem una vegada l’escript per que es crei el .desktop:

./install-and-uninstall.sh

Leave a comment

Your email address will not be published. Required fields are marked *