Con awk y sed el toolkit ya sabe leer, resumir y transformar cualquier texto que le pongas delante. Pero todo ese texto sigue viniendo de ficheros que alguien dejó en el disco. Un script de operaciones necesita algo más: preguntarle al propio sistema en qué estado está —cuánto disco queda, qué carga soporta, quién está conectado, qué versión de sistema operativo corre, si veloz-api está viva— para poder decidir en función de la respuesta. Esta lección recorre las órdenes que interrogan al sistema, cuáles son fiables para un script y cómo convertir sus respuestas en umbrales y decisiones, que es lo que separa un script que informa de uno que actúa.

Contenido

  1. Identidad y contexto: quién soy y dónde estoy
  2. Detectar la distribución correctamente
  3. Tiempo y configuración regional
  4. Disco: df y du
  5. Memoria, CPU y carga
  6. /proc: la fuente fiable
  7. Almacenamiento y ficheros abiertos, en pinceladas
  8. Usuarios y sesiones
  9. Dependencias: comprobar antes de fallar
  10. Preguntar por los servicios
  11. El entorno del proceso
  12. Devolver información: stderr, logger y correo
  13. Umbrales y decisiones
  14. Aplicación: nace estado-servicio.sh

  1. Identidad y contexto: quién soy y dónde estoy

Lo primero que un script debería saber es con qué identidad se está ejecutando, porque de ello depende que pueda leer /var/log/veloz/ o reiniciar un servicio:

Comando Devuelve Uso típico en script
whoami Nombre del usuario efectivo Mensajes y registro
id -u UID numérico (0 = root) [[ $(id -u) -eq 0 ]] para exigir o prohibir root
id -nG Grupos a los que pertenece Comprobar acceso a un recurso
hostname Nombre corto de la máquina Identificar el origen de un informe
hostname -f Nombre completo (FQDN) Correos y avisos
uname -s / -r / -m Núcleo / versión / arquitectura Decisiones por plataforma
uname -a Todo lo anterior junto Diagnóstico manual, no para parsear

En un script, la comprobación se escribe [[ $EUID -eq 0 ]] && veloz_morir 1 "no ejecutes esto como root". $EUID es una variable de Bash y no lanza ningún proceso, así que es preferible a $(id -u) dentro de un bucle. La regla de oro es la contraria a la intuición: un script de informes debe negarse a correr como root, porque no lo necesita y cualquier error suyo se amplifica (08-03).

  1. Detectar la distribución correctamente

uname te dice el núcleo (Linux), no la distribución. Para eso existe /etc/os-release, un fichero estándar en todas las distribuciones modernas con formato CLAVE=valor pensado para ser cargado desde un script:

if [[ -r /etc/os-release ]]; then
    . /etc/os-release          # define ID, VERSION_ID, PRETTY_NAME, ID_LIKE...
    printf 'Sistema: %s (id=%s, version=%s)\n' "$PRETTY_NAME" "$ID" "$VERSION_ID"
fi                     # -> Sistema: Ubuntu 24.04.1 LTS (id=ubuntu, version=24.04)

Con $ID (ubuntu, debian, fedora…) y $ID_LIKE se elige la rama correcta en un case (04-05) sin adivinar. Las alternativas que se ven por ahí son peores: lsb_release -a lanza un proceso y muchas veces no está instalado, y mirar si existe /etc/debian_version es frágil. Un matiz de seguridad: cargar el fichero con . ejecuta su contenido, algo aceptable en /etc/os-release (propiedad de root) pero que no debes hacer con ficheros que pueda escribir cualquiera.

  1. Tiempo y configuración regional

uptime da de un vistazo cuánto lleva encendida la máquina y su carga; uptime -p lo dice en prosa y uptime -s da la fecha de arranque, que es lo útil para detectar un reinicio inesperado. Para fechas, date ya se usó en 04-06: recuerda date +%F (2026-08-03), date +%s (época) y date -d 'yesterday' +%F. Las zonas horarias se controlan por entorno: TZ=UTC date +'%F %T %Z' imprime 2026-08-03 17:42:10 UTC sin tocar la configuración de la máquina, y timedatectl muestra la zona del sistema y si está sincronizado por NTP. Registrar en UTC ahorra disgustos: los cambios de hora hacen que una hora se repita y otra no exista, lo que rompe informes y comparaciones. Y aquí entra una de las lecciones más rentables del módulo: LC_ALL=C en scripts. La configuración regional cambia el comportamiento de las herramientas, no solo los mensajes.

Sin LC_ALL=C (p. ej. es_ES.UTF-8) Con LC_ALL=C
sort ignora guiones y mayúsculas según reglas del idioma Orden por byte, predecible
[a-z] en regex puede incluir letras acentuadas [a-z] son 26 letras
Los mensajes de error salen traducidos Salen en inglés, como esperan los grep del script
date imprime mié 03 ago Imprime Wed 03 Aug
Procesado de ficheros grandes más lento Notablemente más rápido

La consecuencia práctica: si tu script compara, ordena o busca patrones en la salida de un comando, ponle LC_ALL=C delante. Si su salida la va a leer una persona, déjala en su idioma.

  1. Disco: df y du

df informa del espacio por sistema de ficheros montado; du mide lo que ocupa un directorio concreto. No son intercambiables: df pregunta al sistema de ficheros y es instantáneo, du recorre el árbol y puede tardar minutos.

df -h /srv/veloz                      # legible por humanos: 1.9G, 87%
df -P /srv/veloz | awk 'NR==2 { gsub(/%/,"",$5); print $5 }'   # solo el porcentaje
du -sh /var/log/veloz                 # cuanto ocupa el directorio de logs
du -sh /var/log/veloz/* | sort -h | tail -5   # los cinco mayores

-P es la opción clave para scripts, y merece explicación. Sin ella, df parte la línea en dos cuando el nombre del dispositivo es largo, y tu awk '{print $5}' lee la columna equivocada de una línea partida. -P (formato POSIX) garantiza una línea por sistema de ficheros. Fíjate también en que -h y el procesado automático son incompatibles: 1.9G no se puede comparar con > (04-06). En scripts se usa df -P (bloques) o df -PBM para megabytes, y -h solo para lo que lee una persona. sort -h sí entiende sufijos como 1.9G, y por eso funciona con du -sh.

  1. Memoria, CPU y carga

free -m muestra la memoria en megabytes, nproc el número de núcleos disponibles y uptime las cargas al final de la línea. De free -m el número que importa es available, no free. Linux usa la memoria libre como caché de disco, así que free casi siempre parece bajo y no significa nada; available estima cuánta hay realmente disponible para arrancar algo nuevo. Un script que avise con free bajo dará falsas alarmas todos los días.

La carga media (load average) son tres números: promedio de procesos ejecutables o esperando disco en 1, 5 y 15 minutos. Su interpretación depende del número de núcleos: una carga de 4 es cómoda en una máquina de 8 núcleos y grave en una de 2. Por eso el umbral correcto nunca es un número fijo, sino una comparación con nproc, y la que vale para decidir es la de 5 o 15 minutos —la de 1 minuto es demasiado nerviosa y dispara alertas por cualquier pico—.

  1. /proc: la fuente fiable

Casi todos los comandos anteriores no son más que lectores bonitos de /proc, un sistema de ficheros virtual donde el núcleo publica su estado como texto plano. Leerlo directamente es más rápido (no lanza procesos) y mucho más estable, porque el formato de /proc no cambia con la versión del comando ni con el idioma:

Fuente Contiene Ejemplo de lectura
/proc/loadavg Las tres cargas, procesos y último PID read -r c1 c5 c15 _ < /proc/loadavg
/proc/meminfo Memoria en kB, clave/valor awk '/^MemAvailable:/ { print $2 }' /proc/meminfo
/proc/cpuinfo Un bloque por núcleo lógico grep -c ^processor /proc/cpuinfo
/proc/uptime Segundos encendido y ocioso read -r seg _ < /proc/uptime
/proc/<pid>/status Estado, UID y memoria de un proceso grep VmRSS /proc/1234/status
/proc/<pid>/cmdline Orden completa que lo lanzó (con \0) tr '\0' ' ' < /proc/1234/cmdline

Ese read -r carga1 carga5 _ < /proc/loadavg de 03-05 no lanza ni un solo proceso y sustituye a uptime | awk ...: dos procesos menos y ningún formato que pueda cambiar. Es la diferencia entre parsear la salida de un comando interactivo —pensada para humanos, susceptible de cambiar de formato, traducida— y leer una interfaz del núcleo, que es un contrato estable. Siempre que exista la segunda opción, tómala.

  1. Almacenamiento y ficheros abiertos, en pinceladas

lsblk muestra los discos y particiones en forma de árbol, con sus puntos de montaje y tamaños; lsblk -f añade el sistema de ficheros y el UUID. mount (o mejor, findmnt, que da salida tabulada y filtrable) lista lo montado y con qué opciones —comprobar que un /mnt/backup está montado antes de escribir el respaldo evita llenar el disco raíz, un clásico que veremos en 07-03—. Y lsof lista ficheros abiertos: lsof /var/log/veloz/acceso.log dice qué proceso lo tiene abierto, y lsof -p 1234 qué tiene abierto un proceso. Es la herramienta para averiguar por qué no se puede desmontar algo o quién retiene un fichero ya borrado que sigue ocupando disco.

  1. Usuarios y sesiones

who lista las sesiones abiertas, w añade qué está ejecutando cada una y la carga, y last muestra el histórico de conexiones y reinicios leyendo /var/log/wtmplast -x reboot es la forma rápida de ver cuándo se reinició la máquina—.

Para consultar cuentas, la tentación es grep alopez /etc/passwd, y es un error: solo funciona si los usuarios son locales, y en cuanto hay LDAP o un directorio corporativo devuelve vacío. La forma correcta es getent, que consulta las mismas bases que el sistema (fichero, LDAP, DNS…) según /etc/nsswitch.conf:

Así, getent passwd alopez || veloz_morir 1 "el usuario alopez no existe" es una guarda válida en cualquier máquina, y getent group veloz | cut -d: -f4 lista los miembros del grupo. Es la misma orden que en 06-04 servirá para resolver nombres de máquina con getent hosts, y por eso conviene cogerle cariño: una interfaz para todas las bases de datos del sistema.

  1. Dependencias: comprobar antes de fallar

Un script que usa jq y se ejecuta en una máquina sin jq falla a mitad, dejando ficheros temporales y trabajo a medias. Comprobar los requisitos al arrancar es la aplicación directa de las cláusulas de guarda de 03-04:

veloz_requiere() {                       # en lib/comun.sh
    local faltan=() cmd
    for cmd in "$@"; do
        command -v "$cmd" >/dev/null 2>&1 || faltan+=("$cmd")
    done
    (( ${#faltan[@]} == 0 )) || { veloz_log_error "faltan ordenes: ${faltan[*]}"; return 127; }
}
veloz_requiere awk sed curl jq flock || exit 127

command -v es la forma correcta: es un builtin (no lanza procesos), es POSIX, y encuentra también funciones y alias, a diferencia de which, que es un ejecutable externo, no está en todas partes y devuelve códigos inconsistentes. El array acumula todas las que faltan en vez de morir en la primera, que es mucho más amable con quien instala. El código 127 es el que Bash usa para "orden no encontrada" (05-03).

Para saber qué versión de un paquete hay instalada, dpkg -l <paquete> en Debian/Ubuntu y rpm -q <paquete> en Red Hat/Fedora. Úsalos para diagnóstico, no como comprobación: lo que importa a tu script es que la orden esté disponible y funcione, no cómo se instaló.

  1. Preguntar por los servicios

Para saber si veloz-api está viva hay dos vías complementarias. pgrep (05-02) pregunta si el proceso existe; systemctl is-active pregunta si el gestor de servicios lo considera activo, que es más fiable porque tiene en cuenta reinicios y fallos:

if systemctl is-active --quiet veloz-api; then
    veloz_log_info "veloz-api activo"
else
    veloz_log_error "veloz-api inactivo (estado: $(systemctl is-active veloz-api))"
fi

--quiet suprime la salida y deja solo el código de retorno, que es lo que interesa en un if. Sin --quiet, is-active imprime active, inactive, failed o activating, útil para el mensaje de error. Para ver por qué falló, journalctl -u veloz-api -n 20 --no-pager muestra las últimas 20 líneas de su registro; --no-pager es imprescindible en un script, porque si no journalctl intenta abrir less y se queda colgado esperando. El detalle de systemd —unidades, temporizadores, dependencias— es la lección 07-05; aquí solo se consulta.

  1. El entorno del proceso

env (o printenv) lista las variables exportadas, y en srv-veloz-01 conviene recordar que el entorno de tu sesión interactiva no es el que tendrá el script cuando lo lance cron (07-01): allí el PATH es mínimo y HOME puede ser otro. Esa diferencia es la causa número uno de "funciona en mi terminal y falla en cron".

env -i va al extremo contrario: ejecuta una orden con un entorno completamente vacío, y es la manera de comprobar de qué depende realmente tu script:

env -i PATH=/usr/bin:/bin HOME="$HOME" ~/veloz-ops/bin/informe-diario.sh

Si el script funciona con esa línea, funcionará en cron. Es una prueba de dos segundos que ahorra una tarde de depuración.

  1. Devolver información: stderr, logger y correo

Un script que interroga al sistema tiene que contar lo que ha visto, y hay tres destinos según quién escucha. El primero, ya conocido de 02-04, es stderr para los diagnósticos y stdout solo para el resultado, de modo que informe-diario.sh > informe.txt no mezcle avisos con datos. El segundo es el registro del sistema, con logger:

logger -t veloz-ops -p daemon.warning "disco al 91% en /srv/veloz"

-t pone la etiqueta con la que aparecerá en el registro y -p la facilidad y prioridad. La ventaja frente a escribir en un fichero propio es que el mensaje entra en el mismo sistema que todo lo demás —consultable con journalctl -t veloz-ops, rotado y reenviable a un servidor central—. El tercero es el correo: mail -s "asunto" [email protected] o sendmail leen el cuerpo de la entrada estándar, y son la forma tradicional de que cron avise. Los tres se combinan en la estrategia de monitorización de 07-04; aquí basta con saber que existen y que un script serio no se limita a imprimir por pantalla.

  1. Umbrales y decisiones

Todo lo anterior son datos. Lo que convierte un script en una herramienta de operaciones es aplicarles un umbral y actuar. Tres reglas para que los umbrales no den la lata:

  • Configurables, no incrustados. El 85 % de disco va en veloz-ops.conf (05-06), no dentro del if.
  • Relativos cuando el absoluto no dice nada. La carga se compara con nproc; los megabytes libres, con el tamaño del disco.
  • Con margen. Un umbral que dispara con 84,9 % y calla con 85,1 % genera alertas intermitentes. Es preferible avisar una vez y no repetir hasta que se cruce de vuelta con holgura.
uso_disco=$(df -P /srv/veloz | awk 'NR==2 { gsub(/%/,"",$5); print $5 }')
read -r carga1 carga5 _ < /proc/loadavg
mem_libre=$(awk '/^MemAvailable:/ { printf "%d", $2/1024 }' /proc/meminfo)
nucleos=$(nproc)

(( uso_disco > ${UMBRAL_DISCO:-85} )) && veloz_log_error "disco al ${uso_disco}%"
awk -v c="$carga5" -v n="$nucleos" 'BEGIN { exit !(c > n) }' &&
    veloz_log_error "carga $carga5 por encima de $nucleos nucleos"
(( mem_libre < ${UMBRAL_MEM_MB:-512} )) && veloz_log_error "solo ${mem_libre} MB disponibles"

Fíjate en el truco de la carga: como es un número decimal, (( )) no sirve (04-06), así que la comparación se delega en awk, cuyo BEGIN { exit !(c > n) } convierte el resultado en un código de salida —0 si se cumple— utilizable directamente en un &&. Es el puente natural entre el awk de 06-01 y la lógica de Bash. Esta batería de comprobaciones es exactamente el esqueleto del proyecto 09-01.

  1. Aplicación: nace estado-servicio.sh

El toolkit gana su segundo ejecutable. informe-diario.sh responde "¿qué pasó ayer?"; estado-servicio.sh responde "¿cómo está esto ahora?":

#!/usr/bin/env bash
# estado-servicio.sh — Estado de srv-veloz-01 y de veloz-api.
set -Eeuo pipefail
BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$BASE_DIR/lib/comun.sh"
export LC_ALL=C

main() {
    veloz_requiere awk df systemctl || exit 127
    . /etc/os-release
    printf '== %s (%s) ==\n' "$(hostname -f)" "$PRETTY_NAME"
    printf 'Arranque: %s | Usuario: %s\n' "$(uptime -s)" "$(whoami)"

    local uso carga5 nucleos estado
    uso=$(df -P /srv/veloz | awk 'NR==2 { gsub(/%/,"",$5); print $5 }')
    read -r _ carga5 _ < /proc/loadavg
    nucleos=$(nproc)
    printf 'Disco /srv/veloz: %s%% | Carga 5m: %s de %s nucleos\n' "$uso" "$carga5" "$nucleos"

    if systemctl is-active --quiet veloz-api && pgrep -f veloz-api >/dev/null; then
        estado=activo
    else
        estado=CAIDO
        logger -t veloz-ops -p daemon.err "veloz-api no responde en $(hostname)"
    fi
    printf 'veloz-api: %s\n' "$estado"
    (( uso > ${UMBRAL_DISCO:-85} )) && { veloz_log_error "disco al ${uso}%"; return 1; }
    [[ $estado == activo ]] || return 1
    return 0
}
main "$@"

El export LC_ALL=C al principio fija el comportamiento de todo lo que el script invoque, no solo de una orden. La doble comprobación de systemctl y pgrep no es redundancia gratuita: systemd puede considerar activa una unidad cuyo proceso se ha quedado zombi. Y el código de salida final es lo que hace útil al script: estado-servicio.sh || avisar funciona desde otro script, desde cron o desde un temporizador. Lo que aún no sabe hacer es comprobar si el puerto 8080 responde, que es distinto de que el proceso exista: ese hueco lo cierra la próxima lección.

Errores Comunes y Consejos

  • Parsear df -h en un script. 1.9G no se compara con números y la línea puede partirse. Usa df -P y, si acaso, -BM.
  • Mirar free en vez de available. Linux usa la RAM libre como caché; free bajo es lo normal, no una alerta.
  • Usar which. No es POSIX, es un proceso externo y sus códigos varían. command -v siempre.
  • Comparar la carga con (( )). Es decimal: (( 1.5 > 1 )) da error de sintaxis. Delega en awk.
  • grep a /etc/passwd. Solo ve usuarios locales. getent passwd ve todos.
  • Umbral de carga fijo. Compáralo siempre con nproc; 4 no significa lo mismo en 2 núcleos que en 16.
  • journalctl sin --no-pager. El script se queda colgado esperando a less.
  • Consejo: prefiere /proc/loadavg y /proc/meminfo a parsear uptime o free: menos procesos y un formato que no cambia.
  • Consejo: prueba tu script con env -i PATH=/usr/bin:/bin ... antes de meterlo en cron; verás las dependencias ocultas del entorno.

Ejercicios

Ejercicio 1. Escribe una función veloz_uso_disco para lib/comun.sh que reciba un punto de montaje y devuelva su porcentaje de uso como número entero, sin %, funcionando aunque el nombre del dispositivo sea largo y devolviendo código 1 si el punto de montaje no existe.

Ejercicio 2. Escribe un fragmento que compruebe si la carga media de 15 minutos supera el número de núcleos y, en tal caso, registre un aviso en el registro del sistema con etiqueta veloz-ops y salga con código 1. La carga es decimal.

Ejercicio 3. Añade a estado-servicio.sh un resumen del sistema con: distribución y versión, tiempo encendido, número de sesiones abiertas, memoria disponible en MB y las tres particiones más llenas. Usa las fuentes más fiables de cada dato.

Soluciones

Solución 1.

# veloz_uso_disco — Porcentaje de uso de un punto de montaje. Uso: veloz_uso_disco /srv/veloz
veloz_uso_disco() {
    local punto="${1:?falta el punto de montaje}"
    [[ -d "$punto" ]] || { veloz_log_error "no existe: $punto"; return 1; }
    LC_ALL=C df -P "$punto" | awk 'NR==2 { gsub(/%/,"",$5); print $5+0 }'
}

-P garantiza una sola línea por sistema de ficheros, que es justo el caso que rompe la versión ingenua. gsub(/%/,"",$5) quita el símbolo y $5+0 fuerza el resultado a número, de modo que el llamante puede usarlo con (( )) sin más limpieza. LC_ALL=C delante de df —y no exportado— aplica solo a esa orden. Ojo: si el llamante usa set -e, conviene invocarla como uso=$(veloz_uso_disco /srv/veloz) || return 1.

Solución 2.

read -r _ _ carga15 _ < /proc/loadavg
nucleos=$(nproc)
if awk -v c="$carga15" -v n="$nucleos" 'BEGIN { exit !(c > n) }'; then
    logger -t veloz-ops -p daemon.warning "carga 15m=$carga15 supera $nucleos nucleos"
    exit 1
fi

/proc/loadavg tiene cinco campos (1min 5min 15min procesos ultimo_pid), así que el tercero se toma con dos _ delante. La comparación decimal va en awk: exit !(c > n) devuelve 0 —éxito para el if— cuando la condición es cierta, porque en awk exit 0 significa éxito y la negación ! convierte el verdadero (1) en 0. Es el idioma estándar para usar awk como evaluador de condiciones numéricas desde Bash.

Solución 3.

resumen_sistema() {
    . /etc/os-release
    printf 'Sistema   : %s\n' "$PRETTY_NAME"
    printf 'Encendido : %s (desde %s)\n' "$(uptime -p)" "$(uptime -s)"
    printf 'Sesiones  : %s\n' "$(who | wc -l)"
    printf 'Mem. disp.: %s MB\n' "$(awk '/^MemAvailable:/ { printf "%d", $2/1024 }' /proc/meminfo)"
    printf 'Particiones mas llenas:\n'
    LC_ALL=C df -P -x tmpfs -x devtmpfs |
        awk 'NR>1 { gsub(/%/,"",$5); printf "  %-24s %3d%%\n", $6, $5 }' | sort -k2 -rn | head -3
}

Cada dato usa su fuente correcta: /etc/os-release en vez de lsb_release, /proc/meminfo en vez de parsear free, df -P en vez de df -h. -x tmpfs -x devtmpfs excluye los sistemas de ficheros en memoria, que siempre aparecen al 0 % o al 100 % y solo ensucian el listado. El sort -k2 -rn va después del awk porque ordenar el porcentaje ya limpio es trivial, mientras que ordenar la salida cruda de df con el % pegado no lo es.

Conclusión

Un script decide bien cuando pregunta bien. La identidad se obtiene con $EUID, id y whoami; el contexto, con hostname -f, uname y sobre todo /etc/os-release, que es la forma correcta de saber en qué distribución estás. El tiempo se maneja con date, uptime -s y TZ, registrando en UTC, y LC_ALL=C fija el comportamiento de sort, las regex y los mensajes para que el script no dependa del idioma de la máquina. Los recursos salen de df -P (nunca -h para procesar), du -sh, free -m mirando available y nproc; pero la fuente realmente fiable es /procloadavg, meminfo, cpuinfo, <pid>/status—, un contrato del núcleo que no cambia de formato ni se traduce, y que además se lee sin lanzar procesos. getent consulta usuarios y grupos vengan de donde vengan, command -v comprueba dependencias al arrancar en lugar de fallar a medias, systemctl is-active --quiet y journalctl --no-pager interrogan a los servicios, y env -i revela de qué entorno depende tu script antes de que lo descubra cron. Todo ello culmina en umbrales configurables, relativos y con margen que convierten datos en decisiones, con awk como evaluador cuando el número es decimal.

estado-servicio.sh ya sabe decir si la máquina está sana y si el proceso veloz-api existe. Pero "el proceso existe" y "el servicio responde" no son lo mismo: un proceso puede estar vivo y con el puerto 8080 cerrado, bloqueado o inaccesible desde otra máquina. Para eso hay que salir del sistema local y mirar la red: comprobar conectividad y resolución de nombres, ver qué puertos están escuchando, probar si un puerto responde —con nc o con el /dev/tcp que quedó apuntado en 05-05— y esperar con reintentos a que un servicio levante. Es la próxima lección (06-04).

Curso de Programación en Bash

Módulo 1: Introducción a Bash

Módulo 2: Comandos Básicos de Bash

Módulo 3: Fundamentos de Scripting

Módulo 4: Scripting Intermedio

Módulo 5: Técnicas Avanzadas de Scripting

Módulo 6: Trabajando con Herramientas Externas

Módulo 7: Automatización y Programación

Módulo 8: Mejores Prácticas y Optimización

Módulo 9: Proyectos del Mundo Real

© Copyright 2026. Todos los derechos reservados