Librerías de Funciones y Source

Crea bibliotecas reutilizables para maximizar la eficiencia

Módulo 4 ⏱️ 40-45 min 📚 Librerías 🔄 Reutilización 📚 Avanzado

¿Qué son las Librerías de Funciones?

Las librerías de funciones son archivos que contienen funciones reutilizables que pueden ser importadas y utilizadas en múltiples scripts. Esto permite crear un ecosistema de código modular, mantenible y eficiente.

En Bash, usamos el comando source (o su alias .) para cargar funciones y variables desde otros archivos al script actual.

Estructura Típica de Librerías

📁 mi-proyecto/
📁 lib/
📄 utils.sh # Utilidades generales
📄 database.sh # Funciones de base de datos
📄 logging.sh # Sistema de logging
📄 config.sh # Gestión de configuración
📁 scripts/
📄 deploy.sh # Script de despliegue
📄 backup.sh # Script de respaldo
📄 monitor.sh # Script de monitoreo
📄 main.sh # Script principal

El Comando Source

El comando source carga y ejecuta comandos desde otro archivo en el contexto del shell actual. Esto significa que todas las funciones y variables definidas en el archivo fuente se vuelven disponibles en el script actual.

Comando source
# Sintaxis completa
source /ruta/al/archivo.sh

# Usando ruta relativa
source ./lib/utils.sh

# Con validación
source "$HOME/lib/config.sh"
Comando punto (.)
# Alias del comando source
. /ruta/al/archivo.sh

# Más compacto y común
. ./lib/utils.sh

# Funcionalmente idéntico a source
. "$HOME/lib/config.sh"
Diferencia importante

Usar source o . ejecuta el archivo en el shell actual, mientras que ejecutarlo directamente (./archivo.sh) lo ejecuta en un subshell separado.

Creando Tu Primera Librería

Vamos a crear una librería de utilidades comunes paso a paso:

1. Librería de Utilidades Generales

lib/utils.sh - Librería de utilidades Copiar
#!/bin/bash
# =============================================================================
# LIBRERÍA DE UTILIDADES GENERALES
# Archivo: lib/utils.sh
# Descripción: Funciones comunes para scripts Bash
# =============================================================================

# Prevenir carga múltiple
if [[ "${UTILS_LIB_LOADED:-}" == "true" ]]; then
    return 0
fi
readonly UTILS_LIB_LOADED=true

# =============================================================================
# CONSTANTES
# =============================================================================
readonly UTILS_VERSION="1.0.0"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# =============================================================================
# FUNCIONES DE VALIDACIÓN
# =============================================================================

# Verificar si un comando está disponible
comando_disponible() {
    local comando="$1"
    command -v "$comando" >/dev/null 2>&1
}

# Verificar si es un número
es_numero() {
    local valor="$1"
    [[ "$valor" =~ ^-?[0-9]+([.][0-9]+)?$ ]]
}

# Verificar si es un entero
es_entero() {
    local valor="$1"
    [[ "$valor" =~ ^-?[0-9]+$ ]]
}

# Verificar si es un email válido
es_email() {
    local email="$1"
    [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]
}

# Verificar si es una URL válida
es_url() {
    local url="$1"
    [[ "$url" =~ ^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$ ]]
}

# =============================================================================
# FUNCIONES DE ARCHIVOS Y DIRECTORIOS
# =============================================================================

# Crear directorio con permisos específicos
crear_directorio() {
    local directorio="$1"
    local permisos="${2:-755}"
    
    if [ ! -d "$directorio" ]; then
        mkdir -p "$directorio"
        chmod "$permisos" "$directorio"
        echo "Directorio creado: $directorio"
    else
        echo "Directorio ya existe: $directorio"
    fi
}

# Crear backup de archivo con timestamp
crear_backup() {
    local archivo="$1"
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local backup="${archivo}.backup_${timestamp}"
    
    if [ -f "$archivo" ]; then
        cp "$archivo" "$backup"
        echo "Backup creado: $backup"
        return 0
    else
        echo "Error: Archivo no encontrado: $archivo"
        return 1
    fi
}

# Obtener tamaño de archivo en formato legible
tamaño_archivo() {
    local archivo="$1"
    
    if [ -f "$archivo" ]; then
        if comando_disponible "du"; then
            du -h "$archivo" | cut -f1
        else
            ls -lh "$archivo" | awk '{print $5}'
        fi
    else
        echo "N/A"
    fi
}

# =============================================================================
# FUNCIONES DE TEXTO Y CADENAS
# =============================================================================

# Convertir a mayúsculas
a_mayusculas() {
    local texto="$1"
    echo "$texto" | tr '[:lower:]' '[:upper:]'
}

# Convertir a minúsculas
a_minusculas() {
    local texto="$1"
    echo "$texto" | tr '[:upper:]' '[:lower:]'
}

# Limpiar espacios en blanco
limpiar_espacios() {
    local texto="$1"
    echo "$texto" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
}

# Generar cadena aleatoria
generar_cadena_aleatoria() {
    local longitud="${1:-8}"
    local caracteres="${2:-A-Za-z0-9}"
    
    if comando_disponible "head" && comando_disponible "tr"; then
        head /dev/urandom | tr -dc "$caracteres" | head -c "$longitud"
        echo
    else
        # Método alternativo usando $RANDOM
        local cadena=""
        local chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
        for ((i=0; i&2; }

# =============================================================================
# FUNCIÓN DE INFORMACIÓN DE LA LIBRERÍA
# =============================================================================

utils_info() {
    echo "=== Librería de Utilidades ==="
    echo "Versión: $UTILS_VERSION"
    echo "Ubicación: $SCRIPT_DIR"
    echo "Funciones disponibles:"
    echo "  Validación: comando_disponible, es_numero, es_entero, es_email, es_url"
    echo "  Archivos: crear_directorio, crear_backup, tamaño_archivo"
    echo "  Texto: a_mayusculas, a_minusculas, limpiar_espacios, generar_cadena_aleatoria"
    echo "  Fecha: timestamp, fecha_iso, fecha_legible, diferencia_tiempo"
    echo "  Sistema: info_sistema, es_root, uso_memoria"
    echo "  Logging: log_info, log_warning, log_error"
}

# Mostrar mensaje de carga
log_info "Librería de utilidades cargada (v$UTILS_VERSION)"

2. Script que Usa la Librería

ejemplo_uso.sh - Script que usa la librería Copiar
#!/bin/bash
# Script de ejemplo que usa la librería de utilidades

# Cargar la librería de utilidades
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/lib/utils.sh"

# Función principal
main() {
    log_info "Iniciando script de ejemplo"
    
    # Mostrar información de la librería
    utils_info
    
    echo ""
    log_info "=== Probando funciones de validación ==="
    
    # Probar validaciones
    local test_email="[email protected]"
    local test_numero="123.45"
    local test_url="https://www.example.com"
    
    if es_email "$test_email"; then
        log_info "✓ Email válido: $test_email"
    else
        log_error "✗ Email inválido: $test_email"
    fi
    
    if es_numero "$test_numero"; then
        log_info "✓ Número válido: $test_numero"
    else
        log_error "✗ Número inválido: $test_numero"
    fi
    
    if es_url "$test_url"; then
        log_info "✓ URL válida: $test_url"
    else
        log_error "✗ URL inválida: $test_url"
    fi
    
    echo ""
    log_info "=== Probando funciones de texto ==="
    
    local texto_original="  Hola Mundo Bash  "
    log_info "Texto original: '$texto_original'"
    log_info "Mayúsculas: '$(a_mayusculas "$texto_original")'"
    log_info "Minúsculas: '$(a_minusculas "$texto_original")'"
    log_info "Sin espacios: '$(limpiar_espacios "$texto_original")'"
    
    echo ""
    log_info "=== Probando generación de cadenas ==="
    log_info "Cadena aleatoria (8 chars): $(generar_cadena_aleatoria)"
    log_info "Cadena aleatoria (16 chars): $(generar_cadena_aleatoria 16)"
    log_info "Solo números (10 chars): $(generar_cadena_aleatoria 10 "0-9")"
    
    echo ""
    log_info "=== Probando funciones de fecha ==="
    log_info "Timestamp: $(timestamp)"
    log_info "Fecha ISO: $(fecha_iso)"
    log_info "Fecha legible: $(fecha_legible)"
    
    echo ""
    log_info "=== Probando funciones de sistema ==="
    info_sistema
    
    if es_root; then
        log_warning "Ejecutándose como root"
    else
        log_info "Ejecutándose como usuario normal"
    fi
    
    log_info "Uso de memoria: $(uso_memoria)"
    
    echo ""
    log_info "=== Probando funciones de archivos ==="
    
    # Crear directorio de prueba
    local test_dir="/tmp/test_library_$$"
    crear_directorio "$test_dir"
    
    # Crear archivo de prueba
    local test_file="$test_dir/test_file.txt"
    echo "Contenido de prueba $(fecha_legible)" > "$test_file"
    log_info "Archivo creado: $test_file"
    log_info "Tamaño del archivo: $(tamaño_archivo "$test_file")"
    
    # Crear backup
    crear_backup "$test_file"
    
    # Listar archivos en el directorio de prueba
    log_info "Archivos en $test_dir:"
    ls -la "$test_dir"
    
    # Limpiar
    rm -rf "$test_dir"
    log_info "Directorio de prueba limpiado"
    
    log_info "Script completado exitosamente"
}

# Ejecutar función principal
main "$@"
$ ./ejemplo_uso.sh
[2024-01-15 10:30:15] [INFO] Librería de utilidades cargada (v1.0.0)
[2024-01-15 10:30:15] [INFO] Iniciando script de ejemplo
=== Librería de Utilidades ===
Versión: 1.0.0
Ubicación: /home/usuario/proyecto/lib
Funciones disponibles:
  Validación: comando_disponible, es_numero, es_entero, es_email, es_url
  Archivos: crear_directorio, crear_backup, tamaño_archivo
  Texto: a_mayusculas, a_minusculas, limpiar_espacios, generar_cadena_aleatoria
  Fecha: timestamp, fecha_iso, fecha_legible, diferencia_tiempo
  Sistema: info_sistema, es_root, uso_memoria
  Logging: log_info, log_warning, log_error

[2024-01-15 10:30:15] [INFO] === Probando funciones de validación ===
[2024-01-15 10:30:15] [INFO] ✓ Email válido: [email protected]
[2024-01-15 10:30:15] [INFO] ✓ Número válido: 123.45
[2024-01-15 10:30:15] [INFO] ✓ URL válida: https://www.example.com

Librerías Especializadas

Ahora creemos librerías más especializadas para diferentes propósitos:

Librería de Base de Datos

lib/database.sh Copiar
#!/bin/bash
# =============================================================================
# LIBRERÍA DE BASE DE DATOS
# Funciones para interactuar con bases de datos
# =============================================================================

if [[ "${DATABASE_LIB_LOADED:-}" == "true" ]]; then
    return 0
fi
readonly DATABASE_LIB_LOADED=true

# Configuración por defecto
DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-5432}"
DB_NAME="${DB_NAME:-postgres}"
DB_USER="${DB_USER:-postgres}"
DB_TIMEOUT="${DB_TIMEOUT:-10}"

# Verificar conexión a PostgreSQL
db_test_connection() {
    local host="${1:-$DB_HOST}"
    local port="${2:-$DB_PORT}"
    local user="${3:-$DB_USER}"
    local database="${4:-$DB_NAME}"
    
    log_info "Probando conexión a $host:$port/$database como $user"
    
    if command -v psql >/dev/null 2>&1; then
        if timeout "$DB_TIMEOUT" psql -h "$host" -p "$port" -U "$user" -d "$database" -c "SELECT 1;" >/dev/null 2>&1; then
            log_info "✓ Conexión exitosa"
            return 0
        else
            log_error "✗ Error de conexión"
            return 1
        fi
    else
        log_error "psql no está disponible"
        return 1
    fi
}

# Ejecutar query y devolver resultado
db_query() {
    local query="$1"
    local host="${2:-$DB_HOST}"
    local port="${3:-$DB_PORT}"
    local user="${4:-$DB_USER}"
    local database="${5:-$DB_NAME}"
    
    if command -v psql >/dev/null 2>&1; then
        psql -h "$host" -p "$port" -U "$user" -d "$database" -t -c "$query" 2>/dev/null
    else
        log_error "psql no está disponible"
        return 1
    fi
}

# Crear backup de base de datos
db_backup() {
    local database="${1:-$DB_NAME}"
    local backup_dir="${2:-/tmp/backups}"
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local backup_file="$backup_dir/${database}_backup_${timestamp}.sql"
    
    crear_directorio "$backup_dir"
    
    log_info "Creando backup de $database en $backup_file"
    
    if command -v pg_dump >/dev/null 2>&1; then
        if pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" "$database" > "$backup_file"; then
            log_info "✓ Backup creado: $backup_file"
            echo "$backup_file"
            return 0
        else
            log_error "✗ Error creando backup"
            return 1
        fi
    else
        log_error "pg_dump no está disponible"
        return 1
    fi
}

log_info "Librería de base de datos cargada"

Librería de Configuración

lib/config.sh Copiar
#!/bin/bash
# =============================================================================
# LIBRERÍA DE CONFIGURACIÓN
# Funciones para manejo de archivos de configuración
# =============================================================================

if [[ "${CONFIG_LIB_LOADED:-}" == "true" ]]; then
    return 0
fi
readonly CONFIG_LIB_LOADED=true

# Array asociativo para almacenar configuración
declare -A CONFIG

# Cargar configuración desde archivo
config_load() {
    local config_file="$1"
    local prefijo="${2:-}"
    
    if [ ! -f "$config_file" ]; then
        log_error "Archivo de configuración no encontrado: $config_file"
        return 1
    fi
    
    log_info "Cargando configuración desde: $config_file"
    
    local contador=0
    while IFS= read -r linea || [ -n "$linea" ]; do
        # Omitir líneas vacías y comentarios
        if [[ "$linea" =~ ^[[:space:]]*$ ]] || [[ "$linea" =~ ^[[:space:]]*# ]]; then
            continue
        fi
        
        # Procesar líneas clave=valor
        if [[ "$linea" =~ ^[[:space:]]*([^=]+)=[[:space:]]*(.*)$ ]]; then
            local clave="${BASH_REMATCH[1]}"
            local valor="${BASH_REMATCH[2]}"
            
            # Limpiar espacios
            clave=$(echo "$clave" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
            valor=$(echo "$valor" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
            
            # Añadir prefijo si se especifica
            if [ -n "$prefijo" ]; then
                clave="${prefijo}_${clave}"
            fi
            
            CONFIG["$clave"]="$valor"
            ((contador++))
        fi
    done < "$config_file"
    
    log_info "✓ $contador configuraciones cargadas"
    return 0
}

# Obtener valor de configuración
config_get() {
    local clave="$1"
    local valor_por_defecto="$2"
    
    if [[ -n "${CONFIG[$clave]:-}" ]]; then
        echo "${CONFIG[$clave]}"
    else
        echo "$valor_por_defecto"
    fi
}

# Establecer valor de configuración
config_set() {
    local clave="$1"
    local valor="$2"
    CONFIG["$clave"]="$valor"
}

# Verificar si una clave existe
config_exists() {
    local clave="$1"
    [[ -n "${CONFIG[$clave]:-}" ]]
}

# Mostrar toda la configuración
config_show() {
    local filtro="${1:-}"
    
    echo "=== Configuración Cargada ==="
    for clave in "${!CONFIG[@]}"; do
        if [ -z "$filtro" ] || [[ "$clave" == *"$filtro"* ]]; then
            echo "$clave = ${CONFIG[$clave]}"
        fi
    done | sort
}

# Guardar configuración a archivo
config_save() {
    local config_file="$1"
    local filtro="${2:-}"
    
    log_info "Guardando configuración en: $config_file"
    
    {
        echo "# Configuración generada el $(fecha_legible)"
        echo ""
        
        for clave in "${!CONFIG[@]}"; do
            if [ -z "$filtro" ] || [[ "$clave" == *"$filtro"* ]]; then
                echo "$clave=${CONFIG[$clave]}"
            fi
        done | sort
    } > "$config_file"
    
    log_info "✓ Configuración guardada"
}

log_info "Librería de configuración cargada"

Mejores Prácticas para Librerías

Prevenir Carga Múltiple

Usa guardas para evitar cargar la misma librería múltiples veces.

if [[ "${MI_LIB_LOADED:-}" == "true" ]]; then
    return 0
fi
readonly MI_LIB_LOADED=true
Usar Prefijos

Añade prefijos a las funciones para evitar conflictos de nombres.

# Bueno
db_connect()
log_info()
config_get()

# Evitar
connect()  # Muy genérico
info()     # Puede causar conflictos
Documentar Funciones

Incluye documentación clara y ejemplos de uso.

# Función para validar email
# Uso: es_email "[email protected]"
# Retorna: 0 si es válido, 1 si no lo es
es_email() {
    local email="$1"
    [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@ ]]
}
Validar Dependencias

Verifica que los comandos necesarios estén disponibles.

if ! command -v jq >/dev/null 2>&1; then
    log_error "jq es requerido para esta función"
    return 1
fi
Manejo de Errores

Implementa manejo robusto de errores y códigos de retorno.

mi_funcion() {
    local archivo="$1"
    
    if [ ! -f "$archivo" ]; then
        log_error "Archivo no encontrado: $archivo"
        return 1
    fi
    
    # Procesamiento...
    return 0
}
Versionado

Mantén versiones de tus librerías para compatibilidad.

readonly UTILS_VERSION="2.1.0"

utils_version() {
    echo "$UTILS_VERSION"
}

utils_require_version() {
    local required="$1"
    # Lógica de comparación de versiones
}

Proyecto Completo: Sistema de Gestión

Vamos a crear un ejemplo completo que integra múltiples librerías:

proyecto_completo.sh - Script principal Copiar
#!/bin/bash
# =============================================================================
# SISTEMA DE GESTIÓN COMPLETO
# Ejemplo de integración de múltiples librerías
# =============================================================================

set -euo pipefail  # Modo estricto

# =============================================================================
# CONFIGURACIÓN Y CARGA DE LIBRERÍAS
# =============================================================================

# Determinar directorio del script
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LIB_DIR="$SCRIPT_DIR/lib"

# Función para cargar librerías de forma segura
cargar_libreria() {
    local libreria="$1"
    local archivo_lib="$LIB_DIR/$libreria"
    
    if [ -f "$archivo_lib" ]; then
        source "$archivo_lib"
        echo "✓ Librería cargada: $libreria"
    else
        echo "✗ Error: No se pudo cargar $libreria"
        exit 1
    fi
}

# Cargar todas las librerías necesarias
echo "=== Cargando librerías ==="
cargar_libreria "utils.sh"
cargar_libreria "config.sh"
cargar_libreria "database.sh"

# =============================================================================
# CONFIGURACIÓN DEL SISTEMA
# =============================================================================

readonly APP_NAME="Sistema de Gestión"
readonly APP_VERSION="1.0.0"
readonly CONFIG_FILE="$SCRIPT_DIR/config/app.conf"

# =============================================================================
# FUNCIONES PRINCIPALES
# =============================================================================

# Inicializar sistema
inicializar_sistema() {
    log_info "Inicializando $APP_NAME v$APP_VERSION"
    
    # Crear directorios necesarios
    crear_directorio "$SCRIPT_DIR/logs" 700
    crear_directorio "$SCRIPT_DIR/config" 755
    crear_directorio "$SCRIPT_DIR/backups" 750
    
    # Crear archivo de configuración por defecto si no existe
    if [ ! -f "$CONFIG_FILE" ]; then
        log_info "Creando archivo de configuración por defecto"
        cat > "$CONFIG_FILE" << 'EOF'
# Configuración del Sistema de Gestión
APP_DEBUG=false
LOG_LEVEL=INFO
DB_HOST=localhost
DB_PORT=5432
DB_NAME=sistema
DB_USER=admin
BACKUP_RETENTION_DAYS=7
MAX_LOG_SIZE_MB=100
EOF
    fi
    
    # Cargar configuración
    if config_load "$CONFIG_FILE"; then
        log_info "Configuración cargada desde $CONFIG_FILE"
    else
        log_error "Error cargando configuración"
        return 1
    fi
}

# Verificar estado del sistema
verificar_sistema() {
    log_info "=== Verificación del Sistema ==="
    
    local errores=0
    
    # Verificar configuración crítica
    if ! config_exists "DB_HOST"; then
        log_error "Configuración DB_HOST no encontrada"
        ((errores++))
    fi
    
    # Verificar conectividad de base de datos
    local db_host=$(config_get "DB_HOST" "localhost")
    local db_port=$(config_get "DB_PORT" "5432")
    local db_user=$(config_get "DB_USER" "admin")
    local db_name=$(config_get "DB_NAME" "sistema")
    
    log_info "Verificando conexión a base de datos..."
    if db_test_connection "$db_host" "$db_port" "$db_user" "$db_name"; then
        log_info "✓ Conexión a base de datos OK"
    else
        log_warning "⚠️  No se pudo conectar a la base de datos"
        ((errores++))
    fi
    
    # Verificar espacio en disco
    local uso_disco=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
    if [ "$uso_disco" -gt 90 ]; then
        log_warning "⚠️  Espacio en disco bajo: ${uso_disco}%"
    else
        log_info "✓ Espacio en disco OK: ${uso_disco}%"
    fi
    
    # Verificar memoria
    log_info "Uso de memoria: $(uso_memoria)"
    
    return $errores
}

# Realizar backup del sistema
realizar_backup() {
    log_info "=== Iniciando Backup del Sistema ==="
    
    local backup_dir="$SCRIPT_DIR/backups/$(fecha_iso)"
    crear_directorio "$backup_dir"
    
    local backup_exitoso=true
    
    # Backup de configuración
    log_info "Respaldando configuración..."
    if cp -r "$SCRIPT_DIR/config" "$backup_dir/"; then
        log_info "✓ Configuración respaldada"
    else
        log_error "✗ Error respaldando configuración"
        backup_exitoso=false
    fi
    
    # Backup de base de datos
    local db_name=$(config_get "DB_NAME" "sistema")
    if [ -n "$db_name" ]; then
        log_info "Respaldando base de datos: $db_name"
        if db_backup "$db_name" "$backup_dir"; then
            log_info "✓ Base de datos respaldada"
        else
            log_warning "⚠️  Error respaldando base de datos"
        fi
    fi
    
    # Comprimir backup
    local backup_tar="$SCRIPT_DIR/backups/backup_$(timestamp).tar.gz"
    log_info "Comprimiendo backup en: $backup_tar"
    if tar -czf "$backup_tar" -C "$SCRIPT_DIR/backups" "$(basename "$backup_dir")"; then
        log_info "✓ Backup comprimido exitosamente"
        rm -rf "$backup_dir"
    else
        log_error "✗ Error comprimiendo backup"
        backup_exitoso=false
    fi
    
    if [ "$backup_exitoso" = true ]; then
        log_info "✓ Backup completado: $backup_tar"
        return 0
    else
        log_error "✗ Backup completado con errores"
        return 1
    fi
}

# Limpiar archivos antiguos
limpiar_sistema() {
    log_info "=== Limpieza del Sistema ==="
    
    local retention_days=$(config_get "BACKUP_RETENTION_DAYS" "7")
    local backups_dir="$SCRIPT_DIR/backups"
    
    # Limpiar backups antiguos
    log_info "Limpiando backups más antiguos de $retention_days días..."
    local archivos_eliminados=0
    
    if [ -d "$backups_dir" ]; then
        while IFS= read -r -d '' archivo; do
            log_info "Eliminando backup antiguo: $(basename "$archivo")"
            rm -f "$archivo"
            ((archivos_eliminados++))
        done < <(find "$backups_dir" -name "backup_*.tar.gz" -mtime +$retention_days -print0)
    fi
    
    log_info "✓ $archivos_eliminados backups antiguos eliminados"
    
    # Limpiar logs grandes
    local max_log_size=$(config_get "MAX_LOG_SIZE_MB" "100")
    log_info "Verificando tamaño de logs (máximo: ${max_log_size}MB)..."
    
    # Aquí podrías añadir lógica para rotar logs si superan el tamaño
    
    log_info "✓ Limpieza completada"
}

# Mostrar estado del sistema
mostrar_estado() {
    echo ""
    echo "=== ESTADO DEL SISTEMA ==="
    echo "Aplicación: $APP_NAME"
    echo "Versión: $APP_VERSION"
    echo "Fecha: $(fecha_legible)"
    echo ""
    
    info_sistema
    
    echo ""
    echo "=== CONFIGURACIÓN ACTIVA ==="
    config_show
    
    echo ""
    echo "=== RECURSOS DEL SISTEMA ==="
    echo "Memoria: $(uso_memoria)"
    echo "Espacio en disco: $(df -h / | tail -1 | awk '{print $4}') disponible"
    
    if es_root; then
        echo "Privilegios: Administrador"
    else
        echo "Privilegios: Usuario normal"
    fi
}

# =============================================================================
# FUNCIÓN PRINCIPAL Y MENÚ
# =============================================================================

mostrar_menu() {
    echo ""
    echo "=== $APP_NAME ==="
    echo "1. Verificar sistema"
    echo "2. Realizar backup"
    echo "3. Limpiar sistema"
    echo "4. Mostrar estado"
    echo "5. Mostrar configuración"
    echo "6. Salir"
    echo ""
}

procesar_opcion() {
    local opcion="$1"
    
    case "$opcion" in
        1)
            verificar_sistema
            ;;
        2)
            realizar_backup
            ;;
        3)
            limpiar_sistema
            ;;
        4)
            mostrar_estado
            ;;
        5)
            config_show
            ;;
        6)
            log_info "Saliendo del sistema..."
            return 1
            ;;
        *)
            log_error "Opción inválida: $opcion"
            ;;
    esac
    
    return 0
}

# Función principal
main() {
    # Inicializar sistema
    if ! inicializar_sistema; then
        log_error "Error en la inicialización del sistema"
        exit 1
    fi
    
    # Si se pasan argumentos, ejecutar en modo no interactivo
    if [ $# -gt 0 ]; then
        case "$1" in
            "check"|"verificar")
                verificar_sistema
                ;;
            "backup")
                realizar_backup
                ;;
            "clean"|"limpiar")
                limpiar_sistema
                ;;
            "status"|"estado")
                mostrar_estado
                ;;
            *)
                echo "Uso: $0 [check|backup|clean|status]"
                exit 1
                ;;
        esac
        exit $?
    fi
    
    # Modo interactivo
    while true; do
        mostrar_menu
        read -p "Selecciona una opción: " opcion
        
        if ! procesar_opcion "$opcion"; then
            break
        fi
        
        echo ""
        read -p "Presiona Enter para continuar..."
        clear
    done
    
    log_info "Sistema terminado"
}

# Manejo de señales
trap 'log_warning "Interrupción recibida, terminando..."; exit 130' INT TERM

# Ejecutar función principal
main "$@"
Características del Sistema Completo
  • Modular: Usa múltiples librerías especializadas
  • Configurable: Sistema de configuración flexible
  • Robusto: Manejo de errores y validaciones
  • Interactivo: Menú y modo línea de comandos
  • Completo: Backup, limpieza, monitoreo

Ejercicio Práctico

Ejercicio: Crear Tu Propia Librería

Crea una librería de red con las siguientes funciones:

lib/network.sh - Tu librería de red Copiar
#!/bin/bash
# =============================================================================
# LIBRERÍA DE RED - EJERCICIO
# Completa las siguientes funciones
# =============================================================================

# Prevenir carga múltiple
if [[ "${NETWORK_LIB_LOADED:-}" == "true" ]]; then
    return 0
fi
readonly NETWORK_LIB_LOADED=true

# Función para hacer ping a un host
# Uso: ping_host "google.com" [intentos]
ping_host() {
    local host="$1"
    local intentos="${2:-3}"
    
    # TODO: Implementar ping con número de intentos especificado
    # Devolver 0 si hay respuesta, 1 si no la hay
}

# Función para verificar si un puerto está abierto
# Uso: puerto_abierto "localhost" "80"
puerto_abierto() {
    local host="$1"
    local puerto="$2"
    
    # TODO: Usar nc (netcat) o timeout con bash para verificar puerto
    # Devolver 0 si está abierto, 1 si está cerrado
}

# Función para obtener IP pública
# Uso: ip_publica
ip_publica() {
    # TODO: Usar curl para obtener IP desde un servicio como ipinfo.io
    # Manejar errores si no hay internet
}

# Función para descargar archivo con reintentos
# Uso: descargar_archivo "url" "destino" [reintentos]
descargar_archivo() {
    local url="$1"
    local destino="$2"
    local reintentos="${3:-3}"
    
    # TODO: Implementar descarga con curl/wget y reintentos
    # Mostrar progreso y manejar errores
}

# Función para verificar conectividad a internet
# Uso: hay_internet
hay_internet() {
    # TODO: Verificar conectividad haciendo ping a DNS públicos
    # o intentando resolver un dominio conocido
}

log_info "Librería de red cargada - ¡Completa las funciones!"
Tareas a Completar
  1. Implementa cada función siguiendo las especificaciones
  2. Añade manejo de errores apropiado
  3. Usa las funciones de logging de la librería utils
  4. Crea un script de prueba que use todas las funciones
  5. Documenta cada función con comentarios