#!/usr/bin/env bash
# Interfaz común para operaciones habituales con gestores de paquetes.
# Los nombres de paquetes se pasan como argumentos separados, nunca como
# fragmentos que se evalúan en un shell.
set -u

PROGRAMA=${0##*/}
DIR_PROGRAMA=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
SUMMAPACK_ROOT=$(cd -- "$DIR_PROGRAMA/.." 2>/dev/null && pwd || true)
if [[ -d $DIR_PROGRAMA/packlets ]]; then
    DIR_PACKLETS=$DIR_PROGRAMA/packlets
else
    DIR_PACKLETS=$SUMMAPACK_ROOT/share/summapack/sp/packlets
fi

uso() {
    cat <<'EOF'
Uso:
  sp [--distro DISTRO] [--ensayo] [--sin-upgrade] ACCION OBJETO...

Acciones de Babel:
  install PAQUETE...             Instalar paquetes externos o proyectos Simusol conocidos
  install PERL MODULO...         Preferir paquete de la distribución; si falta, usar cpanm
  install CPAN MODULO...         Instalar módulos Perl ausentes mediante cpanm
  install WORLD|SYSTEM|CACHE     Actualizar paquetes, distribución o caché
  remove PAQUETE...              Quitar paquetes
  clean WORLD|CACHE              Limpiar dependencias o caché
  fix WORLD|SYSTEM               Reparar dependencias o sincronizar el sistema
  search [NAMES] TEXTO           Buscar paquetes
  info PAQUETE...                Mostrar información de paquetes
  buildtools                     Preparar herramientas de desarrollo de esta distribución
  podman ENTORNO                 Descargar y etiquetar una imagen base limpia

Tipos de objeto:
  PAQ, DEB, RPM, TGZ, SUMMA, SOURCE, PERL, CPAN; NAMES para búsquedas

Órdenes de packlet:
  update | distupdate | refresh | depclean | buildtools
  addrepo REPO | delrepo REPO    (APT, DNF y Zypper)

Conjuntos: WORLD, SYSTEM, ALL, CACHE. ALL todavía no está instrumentado.
Los packlets de gestores disponibles son APT, DNF, Zypper, Pacman, Portage y URPMI; TGZ usa
instalar-tgz, y SUMMA/SOURCE usan bajar-paquetes.

Opciones:
  --distro DISTRO   Seleccionar una distribución concreta o familia; por defecto se detecta
                    con /etc/os-release
  --ensayo          Mostrar los comandos nativos sin ejecutarlos
  --sin-upgrade     Omitir la actualización automática de paquetes
  -h, --help        Mostrar esta ayuda

Familias reconocidas: debian/ubuntu, fedora/dnf, opensuse/zypper, arch/pacman,
gentoo/ututo/portage y mandriva/urpmi. OpenMandriva actual usa DNF.
Los nombres de paquete de referencia Debian se traducen con diccionariodepaquetes de SummaPack.
ENTORNO admite ubuntu/debian/fedora/suse/arch con sufijo -viejo o -actual.
EOF
}

error() {
    printf '%s: %s\n' "$PROGRAMA" "$*" >&2
}

ensayo=0
sin_upgrade=0
distro_dada=
while (($#)); do
    case $1 in
        --ensayo) ensayo=1; shift ;;
        --sin-upgrade) sin_upgrade=1; shift ;;
        --distro)
            (($# >= 2)) || { error 'falta DISTRO después de --distro'; exit 2; }
            distro_dada=$2
            shift 2
            ;;
        -h|--help|--ayuda) uso; exit 0 ;;
        --) shift; break ;;
        -*) error "opción desconocida: $1"; uso >&2; exit 2 ;;
        *) break ;;
    esac
done

(($#)) || { error 'falta la operación'; uso >&2; exit 2; }
operacion=$1
shift

if [[ $operacion == podman ]]; then
    (($# == 1)) || { error 'podman requiere exactamente un ENTORNO'; exit 2; }
    SP_ENSAYO=$ensayo "$DIR_PACKLETS/podman.sh" "$1"
    exit $?
fi

familia_de() {
    local id=${1,,}
    id=${id%%:*}
    case $id in
        apt|debian|ubuntu|linuxmint|pop|raspbian|kali|elementary|zorin) printf apt ;;
        dnf|fedora|rhel|redhat|centos|rocky|almalinux|ol|nobara) printf dnf ;;
        zypper|suse|opensuse|opensuse-leap|opensuse-tumbleweed|sles|sled) printf zypper ;;
        pacman|arch|archlinux|manjaro|endeavouros) printf pacman ;;
        portage|gentoo|ututo) printf portage ;;
        urpmi|mandriva|mandrake) printf urpmi ;;
        openmandriva|openmandriva-lx) printf dnf ;;
        *) return 1 ;;
    esac
}

detectar_distro() {
    local detector resultado
    detector=$SUMMAPACK_ROOT/bin/distro.sh
    [[ -r $detector ]] || { error "falta el detector de distribuciones: $detector"; return 1; }
    resultado=$(sh "$detector") || return 1
    familia_de "$resultado" >/dev/null || return 1
    printf '%s' "$resultado"
}

if [[ -n $distro_dada ]]; then
    distro_detectada=${distro_dada,,}
    familia=$(familia_de "$distro_detectada") || {
        error "distribución no reconocida: $distro_dada"
        exit 2
    }
else
    distro_detectada=$(detectar_distro) || {
        error 'distro.sh no identificó una distribución con packlet; use --distro'
        exit 2
    }
    familia=$(familia_de "$distro_detectada")
fi
distro_detectada=${distro_detectada,,}
distro_detectada=${distro_detectada%%:*}

gestor_disponible() {
    local gestor
    case $familia in
        apt) gestor=apt-get ;;
        dnf) gestor=dnf ;;
        zypper) gestor=zypper ;;
        pacman) gestor=pacman ;;
        portage) gestor=emerge ;;
        urpmi) gestor=urpmi ;;
    esac
    command -v "$gestor" >/dev/null 2>&1 || {
        error "la familia $familia requiere $gestor, pero no está disponible"
        return 1
    }
}

# Los sellos pertenecen al usuario que invoca sp; el gestor puede usar sudo.
# Se registran por separado: --sin-upgrade nunca acredita una actualización.
directorio_refresco=${SP_REFRESH_STATE_DIR:-${XDG_CACHE_HOME:-${HOME:-/tmp}/.cache}/summapack/sp}
sello_refresco=$directorio_refresco/refresh-$distro_detectada
sello_upgrade=$directorio_refresco/upgrade-$distro_detectada
vigencia_refresco=43200

marca_vigente() {
    local sello=$1 ahora fecha
    [[ -f $sello ]] || return 1
    ahora=$(date +%s) || return 1
    fecha=$(stat -c %Y -- "$sello") || return 1
    ((fecha <= ahora && ahora - fecha < vigencia_refresco))
}

registrar_marca() {
    local sello=$1
    ((ensayo)) && return 0
    mkdir -p -- "$directorio_refresco" && touch -- "$sello"
}

resolver_paquete() {
    local nombre=$1 distro_diccionario distro_respaldo= valor_respaldo= archivo linea clave distro valor
    local exigir_equivalencia=0 equivalencia=0
    case $familia in
        apt)
            case $distro_detectada in
                ubuntu|linuxmint|pop|elementary|zorin) distro_diccionario=UBUNTU; distro_respaldo=DEBIAN ;;
                *) distro_diccionario=DEBIAN ;;
            esac
            ;;
        dnf)
            if [[ $distro_detectada == openmandriva || $distro_detectada == openmandriva-lx ]]; then
                distro_diccionario=OPENMANDRIVA; exigir_equivalencia=1
            else
                distro_diccionario=FEDORA
            fi
            ;;
        zypper) distro_diccionario=SUSE ;;
        pacman) distro_diccionario=ARCH ;;
        portage)
            if [[ $distro_detectada == ututo ]]; then
                distro_diccionario=UTUTO; distro_respaldo=GENTOO
            else
                distro_diccionario=GENTOO
            fi
            exigir_equivalencia=1
            ;;
        urpmi) distro_diccionario=MANDRIVA; exigir_equivalencia=1 ;;
    esac
    archivo=${SUMMAPACK_DICCIONARIO:-}
    if [[ -z $archivo ]]; then
        for archivo in \
            "$SUMMAPACK_ROOT/share/summapack/data/diccionariodepaquetes" \
            "$DIR_PROGRAMA/../share/summapack/data/diccionariodepaquetes" \
            /usr/share/summapack/data/diccionariodepaquetes \
            /usr/local/share/summapack/data/diccionariodepaquetes; do
            [[ -r $archivo ]] && break
        done
    fi
    [[ -r $archivo ]] || { error 'no encuentro diccionariodepaquetes de SummaPack'; return 1; }
    while IFS= read -r linea || [[ -n $linea ]]; do
        linea=${linea%%#*}
        [[ $linea =~ ^[[:space:]]*$ ]] && continue
        IFS=: read -r clave distro valor _ <<< "$linea"
        clave=${clave//[[:space:]]/}
        distro=${distro//[[:space:]]/}
        valor=${valor#"${valor%%[![:space:]]*}"}
        valor=${valor%"${valor##*[![:space:]]}"}
        if [[ ${clave,,} == "${1,,}" && -n $distro_respaldo && $distro == "$distro_respaldo" ]]; then
            valor_respaldo=$valor
            continue
        fi
        if [[ ${clave,,} == "${1,,}" && $distro == "$distro_diccionario" ]]; then
            equivalencia=1
            if [[ $valor == - ]]; then
                return 3
            fi
            [[ -z $valor ]] || nombre=$valor
            break
        fi
    done < "$archivo"
    if [[ $nombre == "$1" && -n $valor_respaldo && $valor_respaldo != - ]]; then
        nombre=$valor_respaldo
        equivalencia=1
    elif [[ $nombre == "$1" && $valor_respaldo == - ]]; then
        return 3
    fi
    if ((exigir_equivalencia && !equivalencia)); then
        error "no hay equivalencia de $1 para $distro_diccionario en diccionariodepaquetes"
        return 2
    fi
    printf '%s' "$nombre"
}

resolver_simusol() {
    local archivo nombre pedido
    archivo=${SP_PROYECTOS:-}
    if [[ -z $archivo ]]; then
        for archivo in \
            "$SUMMAPACK_ROOT/share/summapack/data/proyectos-simusol" \
            "$DIR_PROGRAMA/../share/summapack/data/proyectos-simusol" \
            /usr/share/summapack/data/proyectos-simusol \
            /usr/local/share/summapack/data/proyectos-simusol; do
            [[ -r $archivo ]] && break
        done
    fi
    [[ -r $archivo ]] || { error 'no encuentro proyectos-simusol de SummaPack'; return 2; }
    while IFS=: read -r nombre pedido; do
        [[ -n $nombre && $nombre != \#* ]] || continue
        if [[ $nombre == "$1" ]]; then printf '%s' "$pedido"; return 0; fi
    done < "$archivo"
    return 1
}

validar_paquetes() {
    local nombre resuelto estado
    local -a equivalentes
    (($#)) || { error 'indique al menos un paquete'; return 2; }
    PAQUETES_RESUELTOS=()
    PAQUETES_OMITIDOS=()
    for nombre in "$@"; do
        [[ -n $nombre && $nombre != -* ]] || {
            error "nombre de paquete inválido: $nombre"
            return 2
        }
        if resuelto=$(resolver_paquete "$nombre"); then
            read -r -a equivalentes <<< "$resuelto"
            ((${#equivalentes[@]})) || { error "equivalencia vacía para $nombre"; return 2; }
            PAQUETES_RESUELTOS+=("${equivalentes[@]}")
        else
            estado=$?
            if ((estado == 3)) && [[ $operacion == install || $operacion == buildtools ]]; then
                PAQUETES_OMITIDOS+=("$nombre")
            elif ((estado == 3)); then
                error "$nombre no tiene paquete en $distro_detectada según diccionariodepaquetes; se omite solo al instalar dependencias"
                return 2
            else
                return 2
            fi
        fi
    done
}

mostrar_comando() {
    local prefijo=$1
    shift
    if ((ensayo)); then
        printf 'Ensayo:' >&2
    else
        printf 'Ejecutando:' >&2
    fi
    [[ -n $prefijo ]] && printf ' %s' "$prefijo" >&2
    printf ' %q' "$@" >&2
    printf '\n' >&2
}

ejecutar() {
    local requiere_privilegios=$1
    shift
    local prefijo=
    if ((requiere_privilegios)) && ((EUID != 0)); then
        if command -v sudo >/dev/null 2>&1; then
            prefijo=sudo
        elif ((!ensayo)); then
            error 'esta operación necesita privilegios; ejecute como root o instale/configure sudo'
            return 1
        else
            prefijo=sudo
        fi
    fi
    mostrar_comando "$prefijo" "$@"
    ((ensayo)) && return 0
    if [[ $prefijo == sudo ]]; then
        sudo -- "$@"
    else
        "$@"
    fi
}

validar_busqueda() {
    (($#)) || { error 'indique el texto de búsqueda'; return 2; }
    BUSQUEDA=$*
    [[ $BUSQUEDA != -* ]] || { error 'el texto de búsqueda no puede comenzar con guion'; return 2; }
}

ejecutar_packlet_sin_preparar() {
    local orden=$1 privilegios=$2; shift 2
    local packlet=$DIR_PACKLETS/$familia.sh
    [[ -x $packlet ]] || { error "no hay packlet para $familia"; return 2; }
    if ((!ensayo)); then gestor_disponible || return 3; fi
    if ((ensayo)); then
        SP_ENSAYO=1 SP_SUMMAPACK_ROOT="$SUMMAPACK_ROOT" "$packlet" "$orden" "$@"
    elif ((privilegios)) && ((EUID != 0)); then
        command -v sudo >/dev/null 2>&1 || { error 'esta operación necesita root o sudo'; return 1; }
        sudo -- env SP_SUMMAPACK_ROOT="$SUMMAPACK_ROOT" "$packlet" "$orden" "$@"
    else
        SP_SUMMAPACK_ROOT="$SUMMAPACK_ROOT" "$packlet" "$orden" "$@"
    fi
}

preparar_gestor() {
    local actualizado=0
    if ! marca_vigente "$sello_refresco"; then
        if ((sin_upgrade)) && [[ $familia == pacman ]]; then
            error 'Pacman no permite refrescar índices sin actualizar el sistema; quite --sin-upgrade'
            return 2
        fi
        ejecutar_packlet_sin_preparar refresh 1 || return $?
        registrar_marca "$sello_refresco" || return $?
        # El refresh de Pacman ya ejecuta -Syu: no repetir la actualización.
        if [[ $familia == pacman ]]; then
            registrar_marca "$sello_upgrade" || return $?
            actualizado=1
        fi
    fi
    if ((!sin_upgrade && !actualizado)) && ! marca_vigente "$sello_upgrade"; then
        ejecutar_packlet_sin_preparar update 1 || return $?
        registrar_marca "$sello_upgrade" || return $?
    fi
}

ejecutar_packlet() {
    local orden=$1 privilegios=$2; shift 2
    case $orden in
        refresh)
            if ((sin_upgrade)) && [[ $familia == pacman ]]; then
                error 'Pacman no permite refrescar índices sin actualizar el sistema; quite --sin-upgrade'
                return 2
            fi
            ejecutar_packlet_sin_preparar refresh "$privilegios" "$@" || return $?
            registrar_marca "$sello_refresco" || return $?
            if [[ $familia == pacman ]]; then registrar_marca "$sello_upgrade"; return $?; fi
            if ((!sin_upgrade)); then
                ejecutar_packlet_sin_preparar update 1 || return $?
                registrar_marca "$sello_upgrade" || return $?
            fi
            ;;
        update|distupdate)
            preparar_gestor || return $?
            ejecutar_packlet_sin_preparar "$orden" "$privilegios" "$@" || return $?
            registrar_marca "$sello_upgrade"
            ;;
        *)
            preparar_gestor || return $?
            ejecutar_packlet_sin_preparar "$orden" "$privilegios" "$@" || return $?
            if ((!ensayo)) && [[ $orden == addrepo || $orden == delrepo ]]; then
                # El próximo uso debe incluir los repositorios recién modificados.
                rm -f -- "$sello_refresco"
            fi
            ;;
    esac
}

despachar_conjunto() {
    local conjunto=${1^^}; shift
    (($# == 0)) || { error "$conjunto no admite argumentos adicionales"; return 2; }
    case $conjunto in
        WORLD) ejecutar_packlet update 1 ;;
        SYSTEM) ejecutar_packlet distupdate 1 ;;
        CACHE) ejecutar_packlet refresh 1 ;;
        ALL) error 'install ALL todavía no está instrumentado en el modelo de SummaPack'; return 2 ;;
        *) return 1 ;;
    esac
}

despachar_archivo() {
    local tipo=${1^^}; shift
    (($#)) || { error "falta archivo para el objeto $tipo"; return 2; }
    case $tipo in
        TGZ)
            local packlet=$DIR_PACKLETS/tgz.sh
            if ((!ensayo)) && ((EUID != 0)); then
                command -v sudo >/dev/null 2>&1 || { error 'instalar TGZ necesita root o sudo'; return 1; }
                sudo -- env SP_SUMMAPACK_ROOT="$SUMMAPACK_ROOT" "$packlet" install "$@"
            else
                SP_ENSAYO=$ensayo SP_SUMMAPACK_ROOT="$SUMMAPACK_ROOT" "$packlet" install "$@"
            fi
            ;;
        DEB)
            [[ $familia == apt ]] || { error 'el objeto DEB requiere una distribución APT'; return 2; }
            local -a archivos=()
            local archivo
            for archivo in "$@"; do
                [[ $archivo == *.deb && -f $archivo ]] || { error "no es un archivo DEB existente: $archivo"; return 2; }
                if [[ $archivo == /* ]]; then archivos+=("$archivo"); else archivos+=("./$archivo"); fi
            done
            ejecutar_packlet install-local-deb 1 "${archivos[@]}"
            ;;
        RPM)
            [[ $familia == dnf || $familia == zypper ]] || { error 'el objeto RPM requiere Fedora o SUSE'; return 2; }
            local -a archivos=()
            local archivo
            for archivo in "$@"; do
                [[ $archivo == *.rpm && -f $archivo ]] || { error "no es un archivo RPM existente: $archivo"; return 2; }
                if [[ $archivo == /* ]]; then archivos+=("$archivo"); else archivos+=("./$archivo"); fi
            done
            ejecutar_packlet install-local-rpm 1 "${archivos[@]}"
            ;;
        *) error "tipo de objeto no instrumentado: $tipo"; return 2 ;;
    esac
}

despachar_summa() {
    local tipo=${1^^}; shift
    (($#)) || { error "falta proyecto o archivo para $tipo"; return 2; }
    local packlet=$DIR_PACKLETS/summapack.sh orden
    case $tipo in SUMMA) orden=install-summa ;; SOURCE) orden=install-source ;; esac
    if ((!ensayo)) && ((EUID != 0)); then
        command -v sudo >/dev/null 2>&1 || { error "instalar $tipo necesita root o sudo"; return 1; }
        sudo -- env SP_SUMMAPACK_ROOT="$SUMMAPACK_ROOT" SP_FAMILIA="$familia" "$packlet" "$orden" "$@"
    else
        SP_ENSAYO=$ensayo SP_SUMMAPACK_ROOT="$SUMMAPACK_ROOT" SP_FAMILIA="$familia" "$packlet" "$orden" "$@"
    fi
}

despachar_cpan() {
    local modulo
    (($#)) || { error 'CPAN requiere al menos un módulo Perl'; return 2; }
    for modulo in "$@"; do
        [[ $modulo =~ ^[A-Za-z_][A-Za-z_0-9]*(::[A-Za-z_][A-Za-z_0-9]*)*$ ]] || {
            error "nombre de módulo Perl inválido: $modulo"
            return 2
        }
    done
    for modulo in "$@"; do
        if perl "-M$modulo" -e 1 >/dev/null 2>&1; then
            printf '%s: módulo Perl ya disponible: %s\n' "$PROGRAMA" "$modulo" >&2
            continue
        fi
        if ((!ensayo)); then
            command -v cpanm >/dev/null 2>&1 || { error 'falta cpanm para instalar módulos Perl'; return 3; }
        fi
        ejecutar 1 cpanm "$modulo" || return $?
    done
}

despachar_perl() {
    local modulo archivo clave distro nombre _ paquete resuelto
    local -a equivalentes
    (($#)) || { error 'PERL requiere al menos un módulo'; return 2; }
    archivo=${SUMMAPACK_DICCIONARIO:-}
    if [[ -z $archivo ]]; then
        for archivo in \
            "$SUMMAPACK_ROOT/share/summapack/data/diccionariodepaquetes" \
            /usr/share/summapack/data/diccionariodepaquetes \
            /usr/local/share/summapack/data/diccionariodepaquetes; do
            [[ -r $archivo ]] && break
        done
    fi
    [[ -r $archivo ]] || { error 'no encuentro diccionariodepaquetes'; return 2; }
    for modulo in "$@"; do
        [[ $modulo =~ ^[A-Za-z_][A-Za-z_0-9]*(::[A-Za-z_][A-Za-z_0-9]*)*$ ]] || {
            error "nombre de módulo Perl inválido: $modulo"; return 2;
        }
        if perl "-M$modulo" -e 1 >/dev/null 2>&1; then
            printf '%s: módulo Perl ya disponible: %s\n' "$PROGRAMA" "$modulo" >&2
            continue
        fi
        paquete=
        while IFS=: read -r clave distro nombre _; do
            [[ $distro == PERL && -n $clave ]] || continue
            if [[ ${nombre//--/::} == "$modulo" ]]; then paquete=$clave; break; fi
        done < "$archivo"
        if [[ -n $paquete ]]; then
            resuelto=$(resolver_paquete "$paquete") || return $?
            if [[ $familia == apt || $resuelto != "$paquete" ]]; then
                read -r -a equivalentes <<< "$resuelto"
                ((${#equivalentes[@]})) || { error "equivalencia vacía para $paquete"; return 2; }
                ejecutar_packlet install 1 "${equivalentes[@]}" || return $?
                continue
            fi
        fi
        despachar_cpan "$modulo" || return $?
    done
}

case $operacion in
    install)
        (($#)) || { error 'install requiere paquetes u objeto'; exit 2; }
        case ${1^^} in
            WORLD|SYSTEM|ALL|CACHE) despachar_conjunto "$@"; exit $? ;;
            REPO) shift; ejecutar_packlet addrepo 1 "$@"; exit $? ;;
            CPAN) shift; despachar_cpan "$@"; exit $? ;;
            PERL) shift; despachar_perl "$@"; exit $? ;;
            DEB|RPM|TGZ) tipo=$1; shift; despachar_archivo "$tipo" "$@"; exit $? ;;
            SUMMA|SOURCE) tipo=$1; shift; despachar_summa "$tipo" "$@"; exit $? ;;
            EBUILD|PKG|TXZ|TBZ|SVN|CPAN|SH|LSH|COMMAND)
                error "objeto $1 todavía no tiene packlet instrumentado"; exit 2 ;;
            PAQ|PACKAGES) shift ;;
        esac
        paquetes_externos=() paquetes_simusol=()
        for nombre in "$@"; do
            if pedido=$(resolver_simusol "$nombre"); then
                paquetes_simusol+=("$pedido")
            else
                estado=$?
                ((estado == 1)) || exit "$estado"
                paquetes_externos+=("$nombre")
            fi
        done
        if ((${#paquetes_externos[@]})); then
            validar_paquetes "${paquetes_externos[@]}" || exit $?
        else
            PAQUETES_RESUELTOS=() PAQUETES_OMITIDOS=()
        fi
        ((${#PAQUETES_OMITIDOS[@]} == 0)) || printf '%s: no hace falta en %s: %s\n' "$PROGRAMA" "$distro_detectada" "${PAQUETES_OMITIDOS[*]}" >&2
        if ((${#PAQUETES_RESUELTOS[@]})); then ejecutar_packlet install 1 "${PAQUETES_RESUELTOS[@]}" || exit $?; fi
        for pedido in "${paquetes_simusol[@]}"; do
            despachar_summa SUMMA "$pedido" || exit $?
        done
        exit 0
        ;;
    remove)
        (($#)) || { error 'remove requiere paquetes u objeto'; exit 2; }
        case ${1^^} in
            REPO) shift; ejecutar_packlet delrepo 1 "$@"; exit $? ;;
            TGZ)
                shift
                (($#)) || { error 'remove TGZ requiere el nombre del paquete'; exit 2; }
                packlet=$DIR_PACKLETS/tgz.sh
                if ((!ensayo)) && ((EUID != 0)); then sudo -- env SP_SUMMAPACK_ROOT="$SUMMAPACK_ROOT" "$packlet" remove "$@"; else SP_ENSAYO=$ensayo SP_SUMMAPACK_ROOT="$SUMMAPACK_ROOT" "$packlet" remove "$@"; fi
                exit $? ;;
            PAQ|PACKAGES) shift ;;
            DEB|RPM|EBUILD|SUMMA|PKG|TXZ|TBZ|SOURCE|SVN|CPAN|SH|LSH|COMMAND)
                error "remove no admite el objeto de archivo $1; indique el nombre del paquete instalado"; exit 2 ;;
        esac
        validar_paquetes "$@" || exit $?
        ejecutar_packlet remove 1 "${PAQUETES_RESUELTOS[@]}"
        exit $?
        ;;
    clean)
        (($# == 1)) || { error 'clean requiere WORLD o CACHE'; exit 2; }
        case ${1^^} in
            WORLD) ejecutar_packlet depclean 1 ;;
            CACHE) ejecutar_packlet clean 1 ;;
            *) error 'clean admite WORLD o CACHE en esta etapa'; exit 2 ;;
        esac
        exit $?
        ;;
    fix)
        (($# <= 1)) || { error 'fix admite como máximo WORLD o SYSTEM'; exit 2; }
        if (($#)) && [[ ${1^^} != WORLD && ${1^^} != SYSTEM ]]; then error 'fix admite WORLD o SYSTEM'; exit 2; fi
        ejecutar_packlet fix 1
        exit $?
        ;;
    search)
        [[ ${1:-} == NAMES || ${1:-} == NAME ]] && shift
        orden=search
        if [[ ${1:-} == all || ${1:-} == ALL ]]; then orden=search-all; shift; fi
        validar_busqueda "$@" || exit $?
        ejecutar_packlet "$orden" 0 "$BUSQUEDA"
        exit $?
        ;;
    info)
        (($#)) || { error 'info requiere paquetes u objeto'; exit 2; }
        if (($# == 1)) && [[ ${1^^} == WORLD || ${1^^} == SYSTEM || ${1^^} == ALL || ${1^^} == CACHE ]]; then
            ejecutar_packlet stats 0
            exit $?
        fi
        case ${1^^} in
            TGZ) packlet=$DIR_PACKLETS/tgz.sh; SP_ENSAYO=$ensayo SP_SUMMAPACK_ROOT="$SUMMAPACK_ROOT" "$packlet" info; exit $? ;;
            PAQ|PACKAGES) shift ;;
            DEB|RPM|EBUILD|SUMMA|PKG|TXZ|TBZ|SOURCE|SVN|CPAN|SH|LSH|COMMAND)
                error "info no tiene packlet para el objeto $1"; exit 2 ;;
        esac
        validar_paquetes "$@" || exit $?
        ejecutar_packlet info 0 "${PAQUETES_RESUELTOS[@]}"
        exit $?
        ;;
    update|distupdate|refresh|depclean|buildtools)
        (($# == 0)) || { error "$operacion no recibe argumentos"; exit 2; }
        case $operacion in
            update) ejecutar_packlet update 1 ;;
            distupdate) ejecutar_packlet distupdate 1 ;;
            refresh) ejecutar_packlet refresh 1 ;;
            depclean) ejecutar_packlet depclean 1 ;;
            buildtools)
                herramientas=(build-essential gfortran cmake autoconf automake libtool pkg-config
                    bison flex git curl tar gzip xz-utils python3 perl dpkg-dev debhelper fakeroot
                    rpm rsync findutils which file diffutils ca-certificates patchelf m4
                    bzip2 libgsl-dev libopenblas-dev libsuitesparse-dev libpcre2-dev
                    libreadline-dev libncurses-dev)
                validar_paquetes "${herramientas[@]}" || exit $?
                ejecutar_packlet buildtools 1 "${PAQUETES_RESUELTOS[@]}"
                ;;
        esac
        exit $?
        ;;
    addrepo|delrepo)
        (($#)) || { error "$operacion requiere un repositorio"; exit 2; }
        case $operacion in addrepo) ejecutar_packlet addrepo 1 "$@" ;; delrepo) ejecutar_packlet delrepo 1 "$@" ;; esac
        exit $?
        ;;
    *)
        error "acción desconocida: $operacion"
        uso >&2
        exit 2
        ;;
esac
