La lección anterior terminó con una decisión pendiente. EFS resolvía el problema de que las cuatro instancias del viernes compartieran /var/www/fotos, pero al precio equivocado: unas trece veces más caro que la alternativa, con todo el tráfico de imágenes pasando por las instancias y sin aprovechar la escala global de AWS. Esa alternativa es Amazon S3 (Simple Storage Service), y esta lección es donde MercadoFresco toma la decisión definitiva sobre sus fotos de producto.

S3 es probablemente el servicio más importante de AWS. Fue el primero que se lanzó, en 2006, y hoy es el sitio donde acaban los datos de casi cualquier arquitectura: ficheros estáticos, copias de seguridad, registros, lagos de datos, artefactos de despliegue. Entenderlo bien —no solo "es donde se suben archivos"— cambia la forma de diseñar sistemas, porque S3 no es un disco: es un almacén de objetos con una API, y esa diferencia tiene consecuencias en todo.

Contenido

  1. Almacenamiento de objetos: bucket, objeto, clave y prefijo
  2. Por qué S3 no es un sistema de archivos
  3. Nombres de bucket, regiones y espacio de nombres global
  4. Durabilidad y disponibilidad: los once nueves
  5. Clases de almacenamiento y cómo elegir
  6. Reglas de ciclo de vida para las fotos antiguas del catálogo
  7. Versionado y protección frente a borrados accidentales
  8. Migrar /var/www/fotos con aws s3 sync
  9. aws s3 frente a aws s3api
  10. Permisos: Block Public Access, políticas de bucket y ACL
  11. URLs prefirmadas: los informes de Sara sin credenciales
  12. Cifrado en reposo y en tránsito
  13. Alojamiento de un sitio estático
  14. Eventos de S3: el disparador de las miniaturas
  15. Multipart upload y Transfer Acceleration
  16. Costes de S3 y cómo no llevarse sorpresas

Almacenamiento de objetos: bucket, objeto, clave y prefijo

El modelo de S3 tiene solo tres piezas, y conviene nombrarlas con precisión porque toda la documentación las usa así.

Concepto Qué es Ejemplo en MercadoFresco
Bucket El contenedor. Vive en una región y tiene nombre único en todo el mundo mercadofresco-catalogo-fotos
Objeto El dato guardado: contenido + metadatos + identificador La foto naranjas-valencia-1kg.jpg
Clave (key) El nombre completo del objeto dentro del bucket productos/frutas/naranjas-valencia-1kg.jpg
Prefijo La parte inicial de la clave, usada para filtrar y organizar productos/frutas/
Metadatos Pares clave/valor: tipo de contenido, cifrado, etiquetas Content-Type: image/jpeg

Un objeto puede pesar desde 0 bytes hasta 5 TiB. Un bucket puede contener un número ilimitado de objetos y crecer sin que nadie provisione nada.

Por qué S3 no es un sistema de archivos

Esta es la idea que hay que interiorizar, porque de ella se derivan casi todas las sorpresas de los principiantes.

En S3 no existen las carpetas. La consola las dibuja, aws s3 ls las lista, pero no están ahí. Lo único que existe es una lista plana de claves, y la barra / es un carácter más dentro del nombre.

Lo que ves en la consola:          Lo que hay realmente:

productos/                          productos/frutas/naranjas.jpg
├── frutas/                         productos/frutas/manzanas.jpg
│   ├── naranjas.jpg                productos/verduras/tomates.jpg
│   └── manzanas.jpg                informes/2026-07-ventas.csv
└── verduras/
    └── tomates.jpg
informes/
└── 2026-07-ventas.csv

Las consecuencias prácticas de esto son concretas:

En un sistema de archivos En S3
Renombrar una carpeta es instantáneo Hay que copiar y borrar cada objeto: con 40.000 fotos, 80.000 operaciones
Se puede modificar un byte en medio de un fichero Los objetos son inmutables: se reemplaza el objeto entero
Existen carpetas vacías Una "carpeta vacía" no existe (salvo un objeto de 0 bytes acabado en /)
mv mueve No hay mv real: es copy + delete
Un open() devuelve un descriptor y lees por partes Cada lectura es una petición HTTPS (aunque admite rangos de bytes)
La latencia es de microsegundos Decenas de milisegundos por petición

Y una consecuencia positiva enorme: al no haber estructura jerárquica real, no hay límite de escala. Un bucket con mil millones de objetos funciona igual de bien que uno con diez, y S3 admite al menos 3.500 escrituras y 5.500 lecturas por segundo y por prefijo, con paralelización prácticamente ilimitada usando varios prefijos.

Por eso el diseño de claves importa. Para MercadoFresco:

productos/frutas/naranjas-valencia-1kg.jpg          # original
productos/frutas/naranjas-valencia-1kg_thumb.jpg    # miniatura (la generará Lambda, 02-05)
informes/2026/07/ventas-julio.csv                   # informes de Sara, particionados por fecha
copias/base-datos/2026-08-02-pedidos.dump           # copias

Nombres de bucket, regiones y espacio de nombres global

El nombre de un bucket es único en todo AWS, en todas las cuentas y todas las regiones del mundo. Si alguien en Australia tiene fotos, tú no puedes tenerlo. Es el único recurso de AWS con espacio de nombres verdaderamente global.

Reglas del nombre:

  • Entre 3 y 63 caracteres.
  • Solo minúsculas, números, guiones y puntos.
  • Debe empezar y acabar con letra o número.
  • No puede parecerse a una dirección IP.
  • Evita los puntos: rompen la validación del certificado TLS con nombres de estilo virtual y causan errores difíciles de diagnosticar.

Convención de MercadoFresco, que aplicaremos siempre:

mercadofresco-<componente>-<proposito>[-<entorno>]

mercadofresco-catalogo-fotos
mercadofresco-informes-analitica
mercadofresco-copias-basedatos
mercadofresco-tienda-web-desarrollo

Aunque el nombre sea global, los datos residen en una región concreta y nunca salen de ella salvo que lo pidas explícitamente. Para MercadoFresco esto no es un detalle: las fotos y los informes se quedan en eu-west-1, dentro de la UE, lo que sostiene el argumento de RGPD que vimos en 01-03.

aws s3api create-bucket \
  --bucket mercadofresco-catalogo-fotos \
  --region eu-west-1 \
  --create-bucket-configuration LocationConstraint=eu-west-1 \
  --profile mercadofresco-dev

# Etiquetado obligatorio del proyecto
aws s3api put-bucket-tagging \
  --bucket mercadofresco-catalogo-fotos \
  --tagging 'TagSet=[
    {Key=Proyecto,Value=mercadofresco},
    {Key=Entorno,Value=produccion},
    {Key=Componente,Value=catalogo},
    {Key=Propietario,Value=luis},
    {Key=CentroCoste,Value=marketing}]' \
  --profile mercadofresco-dev --region eu-west-1

La trampa de LocationConstraint. En us-east-1 (y solo ahí) no debe pasarse ese parámetro: el comando falla si lo incluyes. En cualquier otra región es obligatorio. Es una herencia histórica de que S3 nació en Virginia.

Durabilidad y disponibilidad: los once nueves

S3 Standard promete 99,999999999 % de durabilidad anual: los famosos once nueves. La cifra suena a marketing, pero tiene un significado operativo preciso.

Si guardas 10 millones de objetos, la expectativa estadística es perder uno cada 10.000 años. Dicho de otro modo: es muchísimo más probable que pierdas los datos por un borrado accidental o una política mal escrita que por un fallo de S3.

Cómo se consigue: cada objeto se replica automáticamente en al menos tres zonas de disponibilidad de la región, con verificación continua de integridad y reparación automática. Tú no configuras nada.

No hay que confundir dos métricas distintas:

Métrica Qué mide S3 Standard Consecuencia si falla
Durabilidad Que el dato no se pierda 99,999999999 % Pérdida permanente
Disponibilidad Que puedas acceder ahora 99,99 % (≈53 min/año) Errores temporales; se reintenta

Un matiz que importa mucho para MercadoFresco: la durabilidad de S3 no te protege de ti mismo. Si Luis ejecuta un aws s3 rm --recursive sobre el bucket equivocado, S3 lo borrará con once nueves de fiabilidad. La protección contra eso es el versionado, que veremos más abajo.

Clases de almacenamiento y cómo elegir

No todos los datos se acceden igual. Las fotos de los tomates de temporada se piden mil veces al día en junio y ninguna en enero. S3 ofrece clases con distinto equilibrio entre coste de almacenamiento y coste/latencia de acceso.

Clase Coste GB-mes (eu-west-1, aprox.) Latencia de recuperación Coste de recuperación AZ Mínimo de permanencia Caso de uso
Standard 0,023 USD Milisegundos No ≥3 Ninguno Datos activos: fotos del catálogo actual
Intelligent-Tiering 0,023 → 0,0025 USD Milisegundos No (pequeña cuota de monitorización) ≥3 Ninguno Patrón de acceso desconocido o cambiante
Standard-IA 0,0125 USD Milisegundos Sí, por GB ≥3 30 días Acceso mensual, se necesita rápido
One Zone-IA 0,01 USD Milisegundos Sí, por GB 1 30 días Copias reproducibles, miniaturas regenerables
Glacier Instant Retrieval 0,004 USD Milisegundos Sí, mayor ≥3 90 días Archivo consultado un par de veces al año
Glacier Flexible Retrieval 0,0036 USD Minutos a 12 horas ≥3 90 días Copias de seguridad de cumplimiento
Glacier Deep Archive 0,00099 USD 12 a 48 horas Sí, el mayor ≥3 180 días Retención legal de 7 o 10 años

Entre Standard y Deep Archive hay un factor de 23 veces en el coste de almacenamiento. Ese es el premio por clasificar bien los datos.

Tres criterios de decisión:

  1. Si no sabes el patrón de acceso, usa Intelligent-Tiering. Mueve los objetos entre niveles automáticamente, sin coste de recuperación, cobrando una pequeña cuota de monitorización por objeto. Es la opción segura por defecto para datos heterogéneos.
  2. Cuidado con el mínimo de permanencia. Si subes un objeto a Standard-IA y lo borras a los 10 días, se te cobran los 30. Para datos de vida corta, esta clase sale más cara que Standard.
  3. One Zone-IA solo para datos reproducibles. Vive en una sola AZ: si esa AZ se pierde, el dato se pierde. Es perfecto para las miniaturas de MercadoFresco, porque siempre se pueden regenerar a partir del original.

Reglas de ciclo de vida para las fotos antiguas del catálogo

Una regla de ciclo de vida aplica transiciones y caducidades automáticamente según la edad de los objetos. Es la forma de que el ahorro anterior ocurra sin que nadie intervenga.

El análisis de MercadoFresco: las fotos de producto se consultan intensamente los primeros meses tras publicarse, luego el interés cae, y las de productos descatalogados solo se necesitan por motivos contables. Traducido a política, en ciclo-vida-fotos.json:

{
  "Rules": [
    {
      "ID": "fotos-productos-enfriamiento",
      "Filter": {"Prefix": "productos/"},
      "Status": "Enabled",
      "Transitions": [
        {"Days": 90,  "StorageClass": "STANDARD_IA"},
        {"Days": 365, "StorageClass": "GLACIER_IR"},
        {"Days": 1095,"StorageClass": "DEEP_ARCHIVE"}
      ]
    },
    {
      "ID": "miniaturas-una-sola-az",
      "Filter": {"Prefix": "miniaturas/"},
      "Status": "Enabled",
      "Transitions": [
        {"Days": 30, "StorageClass": "ONEZONE_IA"}
      ]
    },
    {
      "ID": "informes-analitica-caducan-a-2-anos",
      "Filter": {"Prefix": "informes/"},
      "Status": "Enabled",
      "Transitions": [
        {"Days": 60, "StorageClass": "STANDARD_IA"}
      ],
      "Expiration": {"Days": 730}
    },
    {
      "ID": "limpiar-cargas-multiparte-incompletas",
      "Filter": {},
      "Status": "Enabled",
      "AbortIncompleteMultipartUpload": {"DaysAfterInitiation": 7}
    },
    {
      "ID": "versiones-antiguas-caducan-a-90-dias",
      "Filter": {},
      "Status": "Enabled",
      "NoncurrentVersionExpiration": {"NoncurrentDays": 90}
    }
  ]
}
aws s3api put-bucket-lifecycle-configuration \
  --bucket mercadofresco-catalogo-fotos \
  --lifecycle-configuration file://ciclo-vida-fotos.json \
  --profile mercadofresco-dev --region eu-west-1

Comentario regla por regla:

  • fotos-productos-enfriamiento: escalera de tres saltos. A los 3 meses baja a IA, al año a Glacier Instant (que sigue devolviendo en milisegundos), a los 3 años a Deep Archive.
  • miniaturas-una-sola-az: aprovecha que son regenerables. Ahorro sin riesgo real.
  • informes-analitica-caducan-a-2-anos: además de mover, borra. Los informes de Sara no tienen valor pasados dos años y borrar es el ahorro definitivo.
  • limpiar-cargas-multiparte-incompletas: la más olvidada y la que más dinero silencioso ahorra. Una subida grande interrumpida deja fragmentos que se facturan y no aparecen al listar el bucket. Esta regla debería estar en absolutamente todos tus buckets.
  • versiones-antiguas-caducan-a-90-dias: sin esto, el versionado hace crecer el bucket para siempre.

Estimación del ahorro para 40.000 fotos de 500 KB (unos 20 GB) con una distribución de edades típica:

Escenario Coste mensual
Todo en Standard 20 GB × 0,023 = 0,46 USD
Con ciclo de vida (5 GB Standard, 8 GB IA, 7 GB Glacier IR) 0,115 + 0,10 + 0,028 = 0,24 USD

Con 20 GB la diferencia es calderilla. Con los 20 TB que MercadoFresco tendrá tras abrir en tres ciudades más (problema 3), son más de 250 USD al mes de diferencia. Las reglas se ponen ahora, cuando cuestan un minuto, no cuando duelen.

Versionado y protección frente a borrados accidentales

Con el versionado activado, S3 nunca sobrescribe ni borra de verdad:

  • Al subir un objeto con una clave existente, se crea una versión nueva y la anterior se conserva.
  • Al borrar, se coloca un marcador de borrado (delete marker): el objeto desaparece de los listados pero todas sus versiones siguen ahí.
  • Para recuperarlo basta con borrar el marcador de borrado.
aws s3api put-bucket-versioning \
  --bucket mercadofresco-catalogo-fotos \
  --versioning-configuration Status=Enabled \
  --profile mercadofresco-dev --region eu-west-1

Recuperación de un borrado accidental, el escenario real:

# 1. Luis borra una foto por error
aws s3 rm s3://mercadofresco-catalogo-fotos/productos/frutas/naranjas.jpg \
  --profile mercadofresco-dev

# 2. Ya no aparece al listar... pero sigue existiendo
aws s3api list-object-versions \
  --bucket mercadofresco-catalogo-fotos \
  --prefix productos/frutas/naranjas.jpg \
  --query '{Versiones:Versions[].[VersionId,LastModified],Marcadores:DeleteMarkers[].[VersionId]}' \
  --profile mercadofresco-dev --region eu-west-1

# 3. Se borra el MARCADOR DE BORRADO (no la versión) y la foto reaparece
aws s3api delete-object \
  --bucket mercadofresco-catalogo-fotos \
  --key productos/frutas/naranjas.jpg \
  --version-id "3HL4kqCxf3vjVBH40Nrjfkd" \
  --profile mercadofresco-dev --region eu-west-1

Una vez activado, el versionado no se puede desactivar, solo suspender (las versiones ya creadas se conservan). Y ojo al coste: cada versión se factura por separado, de ahí la regla de ciclo de vida NoncurrentVersionExpiration del apartado anterior.

Dos protecciones adicionales, mencionadas brevemente porque son de casos concretos:

  • MFA Delete: exige un código de autenticación multifactor del usuario root para borrar versiones o cambiar el estado del versionado. Muy fuerte, pero solo se activa con las credenciales de root y no se puede gestionar desde la consola, lo que lo hace incómodo para el día a día.
  • Object Lock (WORM: write once, read many): impide borrar o modificar un objeto durante un periodo fijado, incluso al administrador de la cuenta. Tiene dos modos, Governance (un rol con permiso especial puede saltárselo) y Compliance (nadie puede, ni el root). Es para retención legal. Solo se puede activar al crear el bucket. Para las copias de la base de datos de MercadoFresco tendría sentido; para las fotos, no.

Migrar /var/www/fotos con aws s3 sync

Llega el momento. Las 40.000 fotos del servidor de la oficina se van a S3.

# Ensayo en seco: muestra qué haría SIN hacer nada. Hazlo siempre primero.
aws s3 sync /var/www/fotos s3://mercadofresco-catalogo-fotos/productos/ \
  --dryrun \
  --profile mercadofresco-dev

# Migración real
aws s3 sync /var/www/fotos s3://mercadofresco-catalogo-fotos/productos/ \
  --storage-class STANDARD \
  --exclude "*" \
  --include "*.jpg" --include "*.jpeg" --include "*.png" --include "*.webp" \
  --metadata-directive REPLACE \
  --cache-control "public, max-age=86400" \
  --profile mercadofresco-dev

Qué hace cada opción:

  • sync es incremental: compara tamaño y fecha de modificación, y solo sube lo que ha cambiado. Se puede interrumpir y relanzar sin duplicar trabajo, que es justo lo que se necesita para migrar 40.000 ficheros por una conexión de oficina.
  • --dryrun: imprescindible antes de cualquier sync con destino nuevo.
  • --exclude "*" seguido de varios --include: el orden importa. Primero se excluye todo y después se readmiten las extensiones deseadas. Así no se cuelan .DS_Store, Thumbs.db ni copias .bak.
  • --cache-control: se guarda como metadato y lo respetarán los navegadores y, más adelante, CloudFront (03-04). Un día de caché para fotos que casi nunca cambian.

Verificación tras la migración:

# Número de ficheros en origen
find /var/www/fotos -type f \( -name "*.jpg" -o -name "*.png" -o -name "*.webp" \) | wc -l

# Número de objetos y tamaño total en destino
aws s3 ls s3://mercadofresco-catalogo-fotos/productos/ --recursive --summarize \
  --human-readable --profile mercadofresco-dev | tail -3

Para volúmenes muy grandes hay opciones específicas que conviene conocer aunque no las usemos: AWS DataSync para transferencias masivas y recurrentes por red, y la familia AWS Snowball para cuando mover los datos por internet tardaría más que enviarlos físicamente en un dispositivo. Para los 20 GB de MercadoFresco, sync sobra.

aws s3 frente a aws s3api

La CLI ofrece dos interfaces distintas para S3, y confundirlas provoca mucha frustración.

aws s3 aws s3api
Qué es Comandos de alto nivel, estilo Unix Correspondencia 1:1 con las operaciones de la API
Comandos ls, cp, mv, rm, sync, mb, rb, presign put-object, get-object, list-object-versions, put-bucket-policy
Multipart automático , para ficheros grandes No, hay que orquestarlo a mano
Recursividad --recursive Hay que paginar
Control fino Limitado Total: cualquier parámetro de la API
Cuándo usarlo Día a día: copiar, sincronizar, listar Configurar el bucket, versiones, políticas, ACL

Regla práctica: mueve datos con aws s3, configura con aws s3api.

# Alto nivel: cómodo y suficiente el 90 % del tiempo
aws s3 cp informe.csv s3://mercadofresco-informes-analitica/informes/2026/07/ \
  --profile mercadofresco-dev
aws s3 ls s3://mercadofresco-catalogo-fotos/productos/frutas/ --profile mercadofresco-dev

# Bajo nivel: cuando hace falta un parámetro que el alto nivel no expone
aws s3api put-object \
  --bucket mercadofresco-informes-analitica \
  --key informes/2026/07/ventas.csv \
  --body informe.csv \
  --server-side-encryption aws:kms \
  --content-type text/csv \
  --tagging "Proyecto=mercadofresco&Componente=analitica&Propietario=sara" \
  --profile mercadofresco-dev --region eu-west-1

Permisos: Block Public Access, políticas de bucket y ACL

Las filtraciones de datos por buckets S3 mal configurados han sido titular de prensa muchas veces. AWS respondió cambiando los valores por defecto: hoy todo bucket nuevo nace completamente privado y con Block Public Access activado a nivel de cuenta y de bucket.

# Verificar el estado (debe dar true en las cuatro opciones)
aws s3api get-public-access-block \
  --bucket mercadofresco-catalogo-fotos \
  --profile mercadofresco-dev --region eu-west-1
{
  "PublicAccessBlockConfiguration": {
    "BlockPublicAcls": true,
    "IgnorePublicAcls": true,
    "BlockPublicPolicy": true,
    "RestrictPublicBuckets": true
  }
}

Los cuatro interruptores, que mucha gente activa o desactiva sin saber qué hace cada uno:

Opción Qué impide
BlockPublicAcls Que se creen ACL públicas nuevas
IgnorePublicAcls Que las ACL públicas existentes surtan efecto
BlockPublicPolicy Que se añada una política de bucket que conceda acceso público
RestrictPublicBuckets Que una política pública ya existente funcione para usuarios anónimos

La postura de MercadoFresco es dejar Block Public Access activado en todos los buckets, sin excepción. Las fotos se servirán públicamente a través de CloudFront con un control de acceso de origen, no abriendo el bucket. Eso se monta en la lección 03-04.

Políticas de bucket

Una política de bucket es un documento JSON de IAM adjunto al bucket que dice quién puede hacer qué. Ejemplo real de MercadoFresco: permitir que solo el rol de las instancias de la tienda lea las fotos, y obligar a que todo el tráfico use HTTPS.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PermitirLecturaAlRolDeLaTienda",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111122223333:role/rol-mercadofresco-tienda"
      },
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::mercadofresco-catalogo-fotos",
        "arn:aws:s3:::mercadofresco-catalogo-fotos/*"
      ]
    },
    {
      "Sid": "DenegarTodoLoQueNoSeaHTTPS",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::mercadofresco-catalogo-fotos",
        "arn:aws:s3:::mercadofresco-catalogo-fotos/*"
      ],
      "Condition": {
        "Bool": {"aws:SecureTransport": "false"}
      }
    }
  ]
}
aws s3api put-bucket-policy \
  --bucket mercadofresco-catalogo-fotos \
  --policy file://politica-bucket-fotos.json \
  --profile mercadofresco-dev --region eu-west-1

Dos detalles fundamentales:

  • s3:ListBucket se concede sobre el bucket (arn:aws:s3:::bucket), mientras que s3:GetObject se concede sobre los objetos (arn:aws:s3:::bucket/*). Son ARN distintos y confundirlos es el error de política más frecuente.
  • Un Deny explícito gana siempre sobre cualquier Allow, venga de donde venga. Por eso la segunda instrucción es una defensa sólida: nadie, ni el administrador, puede acceder por HTTP sin cifrar. La lógica completa de evaluación de permisos es la lección 04-01.

ACL: legado

Las ACL (Access Control Lists) son el mecanismo original de 2006, anterior a IAM. Conceden permisos objeto a objeto a cuentas o grupos predefinidos.

AWS recomienda no usarlas. Desde 2023, los buckets nuevos nacen con Object Ownership en "Bucket owner enforced", que las desactiva por completo. Solo debes conocerlas para dos cosas: reconocerlas al heredar una cuenta antigua, y saber que si un tutorial te dice --acl public-read, ese tutorial está desactualizado. Usa políticas de bucket.

URLs prefirmadas: los informes de Sara sin credenciales

Sara, la analista de negocio, necesita descargar el informe mensual de ventas. No tiene usuario de AWS, no debe tenerlo, y el bucket es privado. La solución es una URL prefirmada: un enlace temporal que incorpora una firma criptográfica y caduca.

"""
Genera una URL temporal de descarga para un informe de MercadoFresco.
Ejecutar con el perfil mercadofresco-dev configurado (lección 01-05).
"""
import boto3
from botocore.exceptions import ClientError

BUCKET = "mercadofresco-informes-analitica"
REGION = "eu-west-1"


def url_de_descarga(clave: str, segundos: int = 3600) -> str | None:
    """Devuelve una URL prefirmada de descarga, o None si algo falla.

    :param clave: clave del objeto, p. ej. 'informes/2026/07/ventas-julio.csv'
    :param segundos: validez del enlace. Máximo 7 días con credenciales de usuario.
    """
    # La firma v4 exige que el cliente conozca la región del bucket.
    s3 = boto3.session.Session(profile_name="mercadofresco-dev").client(
        "s3", region_name=REGION
    )
    try:
        return s3.generate_presigned_url(
            ClientMethod="get_object",
            Params={"Bucket": BUCKET, "Key": clave},
            ExpiresIn=segundos,
        )
    except ClientError as e:
        print(f"No se pudo generar la URL: {e}")
        return None


def url_de_subida(clave: str, segundos: int = 900) -> dict | None:
    """URL prefirmada de SUBIDA, con condiciones que limitan lo que se puede subir.

    Se usa para que un proveedor externo suba fotos de producto sin tener
    credenciales de AWS ni acceso al resto del bucket.
    """
    s3 = boto3.session.Session(profile_name="mercadofresco-dev").client(
        "s3", region_name=REGION
    )
    try:
        return s3.generate_presigned_post(
            Bucket="mercadofresco-catalogo-fotos",
            Key=clave,
            Fields={"Content-Type": "image/jpeg"},
            Conditions=[
                {"Content-Type": "image/jpeg"},          # solo JPEG
                ["content-length-range", 1024, 5242880],  # entre 1 KB y 5 MB
            ],
            ExpiresIn=segundos,
        )
    except ClientError as e:
        print(f"No se pudo generar la URL de subida: {e}")
        return None


if __name__ == "__main__":
    enlace = url_de_descarga("informes/2026/07/ventas-julio.csv", segundos=3600)
    print(f"Enlace para Sara (válido 1 hora):\n{enlace}")

Lo esencial de este mecanismo:

  • Quien tenga el enlace puede usarlo. No hay autenticación adicional: el enlace es la credencial. Por eso los plazos deben ser cortos.
  • La URL hereda los permisos de quien la firma. Si el perfil que firma no puede leer el objeto, la URL tampoco funcionará.
  • Caducidad máxima: 7 días con credenciales de usuario IAM; 1 hora si se firma desde un rol con credenciales temporales, como sería el caso dentro de una Lambda.
  • generate_presigned_post con Conditions es la forma correcta de aceptar subidas de terceros: limita tipo y tamaño antes de que el fichero llegue.

Cifrado en reposo y en tránsito

Todo objeto nuevo en S3 se cifra en reposo automáticamente desde enero de 2023. No hay que hacer nada para tener cifrado; la decisión es solo quién gestiona la clave.

Modo Quién gestiona la clave Coste extra Auditoría del uso de la clave Cuándo
SSE-S3 (AES256) AWS, de forma transparente Ninguno No Valor por defecto; suficiente casi siempre
SSE-KMS (aws:kms) Tú, en AWS KMS Sí, por clave y por petición Sí, en CloudTrail Datos regulados, separación de funciones
SSE-C Tú, enviando la clave en cada petición Ninguno en AWS No Requisitos muy específicos
DSSE-KMS Doble cifrado con KMS Mayor Requisitos gubernamentales

Decisión de MercadoFresco: SSE-S3 para las fotos (son datos públicos en la práctica, no compensa el coste por petición de KMS) y SSE-KMS para los informes de ventas y las copias de la base de datos, que contienen datos de negocio y donde interesa el registro de quién descifra.

# Forzar SSE-KMS por defecto en el bucket de informes
aws s3api put-bucket-encryption \
  --bucket mercadofresco-informes-analitica \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "alias/mercadofresco-datos"
      },
      "BucketKeyEnabled": true
    }]
  }' \
  --profile mercadofresco-dev --region eu-west-1

BucketKeyEnabled: true reduce hasta un 99 % las llamadas a KMS y su coste asociado; actívalo siempre que uses SSE-KMS. La gestión de claves se ve a fondo en la lección 04-02.

En tránsito, todo el tráfico con S3 va por HTTPS. Para garantizar que nadie use HTTP en claro está la instrucción DenegarTodoLoQueNoSeaHTTPS de la política de bucket anterior.

Alojamiento de un sitio estático

S3 puede servir un sitio web estático directamente: HTML, CSS, JavaScript e imágenes, sin ningún servidor.

aws s3 website s3://mercadofresco-tienda-web-desarrollo/ \
  --index-document index.html \
  --error-document error.html \
  --profile mercadofresco-dev

El resultado es una URL del tipo http://mercadofresco-tienda-web-desarrollo.s3-website-eu-west-1.amazonaws.com.

Limitaciones que hay que conocer antes de emocionarse:

  • Solo HTTP, sin HTTPS. El punto de enlace de sitio web estático no admite TLS.
  • Sin dominio propio con certificado.
  • Exige el bucket público, lo que choca con la política de Block Public Access.

Por eso, en producción no se usa así. La arquitectura correcta es bucket privado + CloudFront delante con Origin Access Control: da HTTPS, dominio propio, caché en más de 400 ubicaciones de borde y protección. Eso es exactamente la lección 03-04. El modo sitio web de S3 queda para pruebas rápidas de Luis.

Eventos de S3: el disparador de las miniaturas

S3 puede notificar cuando pasa algo en un bucket. Es la puerta de entrada a las arquitecturas orientadas a eventos, y para MercadoFresco resuelve un problema concreto: las fotos originales pesan demasiado para el listado de productos y hacen falta miniaturas.

Eventos disponibles y destinos posibles:

Tipo de evento Cuándo se dispara
s3:ObjectCreated:* Cualquier creación (Put, Post, Copy, fin de multipart)
s3:ObjectRemoved:* Borrado o marcador de borrado
s3:ObjectRestore:* Restauración desde Glacier completada
s3:LifecycleTransition Un objeto ha cambiado de clase por una regla de ciclo de vida
s3:ReducedRedundancyLostObject Pérdida de un objeto (clase obsoleta)
Destino Cuándo elegirlo
AWS Lambda Procesar el objeto con código propio: nuestro caso
Amazon SQS Encolar para procesamiento en lotes o con reintentos (módulo 7)
Amazon SNS Notificar a varios suscriptores a la vez (módulo 7)
EventBridge Enrutado avanzado con reglas y filtros (módulo 7)

El flujo que montaremos:

flowchart LR
    A["Luis sube<br/>naranjas.jpg"] --> B["Bucket<br/>mercadofresco-catalogo-fotos<br/>prefijo productos/"]
    B -->|"s3:ObjectCreated:*"| C["Notificacion de evento"]
    C --> D["Funcion Lambda<br/>mercadofresco-generar-miniaturas"]
    D --> E["Escribe en<br/>prefijo miniaturas/<br/>200x200 px"]
    E -.->|"regla de ciclo de vida"| F["ONEZONE_IA a los 30 dias"]

Configuración de la notificación (la función se escribe en 02-05):

aws s3api put-bucket-notification-configuration \
  --bucket mercadofresco-catalogo-fotos \
  --notification-configuration '{
    "LambdaFunctionConfigurations": [{
      "Id": "generar-miniaturas-al-subir",
      "LambdaFunctionArn": "arn:aws:lambda:eu-west-1:111122223333:function:mercadofresco-generar-miniaturas",
      "Events": ["s3:ObjectCreated:*"],
      "Filter": {"Key": {"FilterRules": [
        {"Name": "prefix", "Value": "productos/"},
        {"Name": "suffix", "Value": ".jpg"}
      ]}}
    }]
  }' \
  --profile mercadofresco-dev --region eu-west-1

El filtro por prefijo y sufijo es imprescindible. Sin él, la Lambda se dispararía también al escribir las miniaturas en el mismo bucket, que a su vez generarían otro evento: un bucle recursivo infinito que es una de las formas más rápidas y más caras de equivocarse en AWS.

Dos advertencias sobre la semántica de estos eventos: la entrega es al menos una vez (la función puede recibir el mismo evento dos veces, así que debe ser idempotente) y no está garantizado el orden. Aquí solo dejamos montado el disparador; el código lo escribimos en la lección 02-05.

Multipart upload y Transfer Acceleration

Multipart upload trocea un objeto grande y sube las partes en paralelo. Es obligatorio por encima de 5 GB y recomendable a partir de 100 MB.

Ventajas: mayor rendimiento por paralelismo, reintento de una sola parte si falla la red, y posibilidad de pausar y reanudar. aws s3 cp y aws s3 sync lo hacen automáticamente; solo necesitas s3api si programas el proceso a mano.

# Ajustar los umbrales de la CLI para una conexión rápida
aws configure set default.s3.multipart_threshold 64MB
aws configure set default.s3.multipart_chunksize 16MB
aws configure set default.s3.max_concurrent_requests 20

El riesgo: si una subida multiparte se interrumpe, las partes ya subidas se quedan y se facturan, y no aparecen en un aws s3 ls normal. Por eso incluimos la regla AbortIncompleteMultipartUpload en el ciclo de vida. Para verlas:

aws s3api list-multipart-uploads --bucket mercadofresco-catalogo-fotos \
  --profile mercadofresco-dev --region eu-west-1

Transfer Acceleration enruta las subidas a través de la ubicación de borde de CloudFront más cercana al cliente, y de ahí por la red troncal de AWS hasta el bucket. Acelera de forma significativa cuando quien sube está lejos de la región. Tiene coste adicional (≈0,04 USD/GB) y para MercadoFresco, que sube desde España a Irlanda, no compensa. Sería útil si un proveedor de Sudamérica subiera catálogos de fotos.

# Comprobar si merecería la pena, con la herramienta de comparación de AWS:
# https://s3-accelerate-speedtest.s3-accelerate.amazonaws.com/en/accelerate-speed-comparsion.html
aws s3api put-bucket-accelerate-configuration \
  --bucket mercadofresco-catalogo-fotos --accelerate-configuration Status=Enabled \
  --profile mercadofresco-dev --region eu-west-1

Costes de S3 y cómo no llevarse sorpresas

La factura de S3 tiene cuatro componentes, y casi todo el mundo solo mira el primero.

Componente Precio orientativo (eu-west-1) Comentario
Almacenamiento 0,023 USD/GB-mes (Standard) El más visible y a menudo el menor
Peticiones PUT: 0,0054 USD por 1.000
GET: 0,00043 USD por 1.000
Domina el coste con muchos objetos pequeños
Transferencia de salida 0,09 USD/GB hacia internet Suele ser el mayor. Entrada gratis; salida a otra región, también de pago
Recuperación y gestión Variable por clase Recuperar de Glacier, monitorización de Intelligent-Tiering, inventario

Cálculo realista para MercadoFresco: 20 GB de fotos, 500.000 visitas al mes con 10 fotos vistas por visita (5.000.000 de peticiones GET), foto media de 200 KB:

Almacenamiento:  20 GB × 0,023                     =   0,46 USD
Peticiones GET:  5.000.000 / 1.000 × 0,00043       =   2,15 USD
Transferencia:   5.000.000 × 200 KB = 1.000 GB
                 1.000 GB × 0,09                   =  90,00 USD
------------------------------------------------------------------
Total                                              ≈ 92,61 USD/mes

El 97 % del coste es transferencia de salida. Y ese dato justifica por sí solo la lección 03-04: poniendo CloudFront delante, la mayoría de las peticiones se sirven desde la caché de borde, la transferencia de S3 a CloudFront es gratuita, y el precio por GB hacia internet es menor. La factura puede bajar a menos de una tercera parte, y además la web irá más rápida.

Herramientas de control:

# Tamaño y número de objetos por bucket
aws s3 ls s3://mercadofresco-catalogo-fotos --recursive --summarize --human-readable \
  --profile mercadofresco-dev | tail -3
  • S3 Storage Lens ofrece un panel gratuito con métricas de uso y recomendaciones de ahorro.
  • S3 Inventory genera un informe periódico en CSV o Parquet con todos los objetos, su clase y su tamaño: la forma correcta de auditar buckets con millones de objetos sin listarlos uno a uno.
  • El presupuesto presupuesto-mensual-mercadofresco de la lección 01-02 sigue siendo la red de seguridad.

Limpieza de la lección. Si has creado buckets de prueba, elimínalos. Un bucket con versionado requiere borrar también las versiones y los marcadores:

aws s3 rm s3://mi-bucket-de-pruebas --recursive --profile mercadofresco-dev
aws s3api delete-objects --bucket mi-bucket-de-pruebas \\
  --delete "$(aws s3api list-object-versions --bucket mi-bucket-de-pruebas \\
    --query '{Objects: Versions[].{Key:Key,VersionId:VersionId}}' --output json)" \\
  --profile mercadofresco-dev
aws s3 rb s3://mi-bucket-de-pruebas --profile mercadofresco-dev

Errores Comunes y Consejos

  • Tratar S3 como un disco. Renombrar un prefijo con 40.000 objetos son 80.000 operaciones y minutos de espera. Diseña las claves bien desde el principio.
  • Hacer un bucket público "para que funcione rápido". Es el origen de la mayoría de las filtraciones publicadas. Deja Block Public Access activado y usa CloudFront con OAC.
  • Olvidar LocationConstraint fuera de us-east-1, o ponerlo dentro de us-east-1. Ambos fallan, con mensajes poco claros.
  • No configurar AbortIncompleteMultipartUpload. Coste invisible que crece para siempre.
  • Activar el versionado y no caducar las versiones antiguas. El bucket engorda sin límite y nadie entiende por qué la factura sube si "no se sube nada nuevo".
  • Confundir el ARN del bucket con el de los objetos en las políticas: ListBucket va sobre el bucket, GetObject sobre bucket/*.
  • Poner una Lambda a escuchar eventos del mismo bucket donde escribe. Bucle infinito. Filtra siempre por prefijo, o usa buckets distintos.
  • Usar --acl public-read porque lo dice un tutorial antiguo. Las ACL están desactivadas por defecto en buckets nuevos y AWS desaconseja su uso.
  • Subir a Standard-IA datos de vida corta. El mínimo de 30 días de facturación hace que salga más caro que Standard.
  • Mirar solo el coste de almacenamiento. La transferencia de salida suele ser el 90 % de la factura de S3.
  • Consejo: ejecuta sync con --dryrun siempre la primera vez. Un destino mal escrito puede copiar 40.000 ficheros al prefijo equivocado, y deshacerlo es más trabajo que hacerlo.

Ejercicios

Ejercicio 1: diseñar el ciclo de vida de las copias de la base de datos

MercadoFresco guardará en mercadofresco-copias-basedatos los volcados diarios de la base de datos de pedidos, bajo el prefijo copias/base-datos/. Cada volcado ocupa 2 GB. Los requisitos son:

  • Los últimos 30 días deben poder restaurarse en minutos.
  • Entre 30 días y 1 año, la restauración puede tardar horas.
  • Entre 1 y 7 años (retención legal fiscal) se conservan al coste mínimo posible.
  • Pasados 7 años se borran automáticamente.

Escribe la regla de ciclo de vida en JSON y calcula el coste mensual en régimen estacionario, comparándolo con dejarlo todo en Standard. Usa: Standard 0,023; Glacier Flexible 0,0036; Deep Archive 0,00099 USD/GB-mes.

Ejercicio 2: la política de bucket de los informes

Sara debe poder leer los informes bajo informes/, pero no borrarlos ni tocar otros prefijos. El rol rol-mercadofresco-analitica debe poder escribir en informes/. Nadie debe acceder sin HTTPS ni subir objetos sin cifrar con KMS.

Escribe la política de bucket completa y explica por qué la última condición necesita un Deny y no un Allow.

Ejercicio 3: decidir dónde va cada dato

Para cada dato de MercadoFresco, indica bucket, prefijo, clase de almacenamiento inicial, si activas versionado y qué regla de ciclo de vida aplicarías:

  • A) Las 40.000 fotos originales del catálogo, en alta resolución.
  • B) Las miniaturas 200×200 generadas automáticamente a partir de las anteriores.
  • C) Los registros de acceso del servidor web, unos 3 GB al día, consultados solo si hay una incidencia y obligatorios durante 90 días.
  • D) El informe mensual de ventas que Sara descarga por URL prefirmada.

Soluciones

Solución 1.

{
  "Rules": [
    {
      "ID": "copias-base-datos-retencion-fiscal",
      "Filter": {"Prefix": "copias/base-datos/"},
      "Status": "Enabled",
      "Transitions": [
        {"Days": 30,  "StorageClass": "GLACIER"},
        {"Days": 365, "StorageClass": "DEEP_ARCHIVE"}
      ],
      "Expiration": {"Days": 2555}
    },
    {
      "ID": "limpiar-multipart",
      "Filter": {},
      "Status": "Enabled",
      "AbortIncompleteMultipartUpload": {"DaysAfterInitiation": 7}
    }
  ]
}

Volumen en régimen estacionario (2 GB/día):

Standard (0-30 días):        30 × 2 GB =    60 GB
Glacier Flexible (30-365):  335 × 2 GB =   670 GB
Deep Archive (365-2555):   2190 × 2 GB = 4.380 GB
Total                                   = 5.110 GB
Configuración Cálculo Coste mensual
Con ciclo de vida 60×0,023 + 670×0,0036 + 4.380×0,00099 8,13 USD
Todo en Standard 5.110 × 0,023 117,53 USD
Ahorro 109,40 USD/mes (93 %)

Notas de diseño: 2.555 días son 7 años. Deep Archive tiene 180 días de permanencia mínima, sin problema aquí porque los objetos llegan con 365 días de vida y se quedan más de seis años. Y el requisito "restaurar en minutos los últimos 30 días" es lo que impide bajar a Glacier antes: Glacier Flexible tarda de minutos a 12 horas según el modo de recuperación.

Solución 2.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SaraLeeInformes",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::111122223333:user/sara"},
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::mercadofresco-informes-analitica/informes/*"
    },
    {
      "Sid": "SaraListaSoloElPrefijoInformes",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::111122223333:user/sara"},
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::mercadofresco-informes-analitica",
      "Condition": {"StringLike": {"s3:prefix": "informes/*"}}
    },
    {
      "Sid": "RolAnaliticaEscribeInformes",
      "Effect": "Allow",
      "Principal": {"AWS": "arn:aws:iam::111122223333:role/rol-mercadofresco-analitica"},
      "Action": ["s3:PutObject", "s3:GetObject"],
      "Resource": "arn:aws:s3:::mercadofresco-informes-analitica/informes/*"
    },
    {
      "Sid": "DenegarSinHTTPS",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::mercadofresco-informes-analitica",
        "arn:aws:s3:::mercadofresco-informes-analitica/*"
      ],
      "Condition": {"Bool": {"aws:SecureTransport": "false"}}
    },
    {
      "Sid": "DenegarSubidasSinCifradoKMS",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::mercadofresco-informes-analitica/*",
      "Condition": {
        "StringNotEquals": {"s3:x-amz-server-side-encryption": "aws:kms"}
      }
    }
  ]
}

Por qué las dos últimas necesitan Deny y no Allow. Un Allow solo concede: si simplemente "permitieras HTTPS", cualquier otra política —una política de identidad de IAM adjunta al usuario, por ejemplo— podría seguir concediendo el acceso por HTTP, porque los permisos se suman. Un Deny explícito, en cambio, tiene precedencia absoluta: ninguna política de ninguna capa puede anularlo. Para requisitos de seguridad que deben cumplirse siempre y sin excepción, la formulación correcta es prohibir la condición indeseada, no permitir la deseada.

Detalle adicional: sin la condición s3:prefix en ListBucket, Sara podría listar todo el bucket y ver los nombres de objetos de otros prefijos, aunque no pudiera descargarlos. Nombres que a veces revelan más de lo que parece.

Solución 3.

Dato Bucket y prefijo Clase inicial Versionado Ciclo de vida
A) Fotos originales mercadofresco-catalogo-fotos / productos/ Standard : son el original y no se pueden regenerar; protege del rm accidental IA a 90 d, Glacier IR a 365 d, Deep Archive a 3 años; versiones antiguas caducan a 90 d
B) Miniaturas mercadofresco-catalogo-fotos / miniaturas/ Standard, y One Zone-IA a los 30 d No: son regenerables con la Lambda; el versionado solo añadiría coste A One Zone-IA a los 30 d. Prefijo distinto del origen para no crear bucle de eventos
C) Registros del servidor web mercadofresco-registros-web / nginx/AAAA/MM/DD/ Standard No IA a 30 d, caducidad a 90 d exactamente (el requisito legal). Particionar por fecha en la clave facilita consultarlos y borrarlos
D) Informe mensual de ventas mercadofresco-informes-analitica / informes/AAAA/MM/ Standard con SSE-KMS : es un dato de negocio que no debe perderse por sobrescritura IA a 60 d, caducidad a 2 años. Acceso solo por URL prefirmada, bucket privado

Observación transversal: el prefijo de las miniaturas debe estar fuera del prefijo que dispara el evento (productos/), o el filtro de la notificación debe excluirlas. Es la protección contra el bucle recursivo mencionado antes.

Conclusión

MercadoFresco ya tiene sus fotos donde deben estar. Has entendido que S3 es un almacén de objetos y no un sistema de archivos: no hay carpetas, solo claves planas donde la barra es un carácter más; los objetos son inmutables; renombrar un prefijo significa copiar y borrar. A cambio de esas restricciones se obtiene escala prácticamente ilimitada, once nueves de durabilidad por replicación automática en tres o más zonas de disponibilidad, y un coste por GB unas trece veces menor que EFS.

Sabes construir nombres de bucket válidos en un espacio de nombres global, con la trampa de LocationConstraint incluida, y tienes una convención propia (mercadofresco-<componente>- <propósito>). Dominas las siete clases de almacenamiento y sus compensaciones —latencia, coste de recuperación, permanencia mínima, número de AZ— y has escrito reglas de ciclo de vida que enfrían las fotos del catálogo automáticamente, mueven las miniaturas a One Zone-IA por ser regenerables, caducan los informes a los dos años, borran las versiones antiguas y —la regla que todo el mundo olvida— abortan las cargas multiparte incompletas que se facturan en silencio.

Has activado el versionado y practicado la recuperación real de un borrado accidental eliminando el marcador de borrado, y conoces MFA Delete y Object Lock para los casos que exigen retención inviolable. Has migrado /var/www/fotos con aws s3 sync, con su --dryrun obligatorio y sus filtros de inclusión, y tienes clara la división de trabajo entre aws s3 para mover datos y aws s3api para configurar.

En seguridad, has visto por qué hoy los buckets nacen cerrados: Block Public Access con sus cuatro interruptores, políticas de bucket en JSON con la distinción crítica entre el ARN del bucket y el de los objetos, la precedencia absoluta del Deny explícito, y las ACL como legado que no debes usar. Has generado URLs prefirmadas con boto3 para que Sara descargue sus informes sin tener credenciales de AWS, y también para aceptar subidas de terceros con límites de tipo y tamaño. Sabes que todo se cifra en reposo por defecto y cuándo compensa pasar de SSE-S3 a SSE-KMS con BucketKeyEnabled.

Has visto el alojamiento estático y por qué no se usa así en producción, has dejado configurada la notificación de eventos que disparará la generación de miniaturas —con el filtro por prefijo que evita el bucle infinito—, conoces multipart upload y Transfer Acceleration, y has hecho el cálculo que ordena todo lo demás: en la factura de S3 de MercadoFresco, el 97 % es transferencia de salida, no almacenamiento.

Quedan las fotos resueltas y falta el corazón del negocio. Los pedidos de MercadoFresco siguen en la base de datos del servidor de la oficina, con su copia de seguridad poco fiable y su imposibilidad de crecer. En la lección 02-04, «Amazon RDS», migraremos esa base de datos a un servicio gestionado: veremos qué aporta frente a instalar PostgreSQL en una EC2, montaremos Multi-AZ con réplica síncrona en espera, crearemos réplicas de lectura para que los informes de Sara no castiguen a producción, y cerraremos definitivamente el problema 2 con copias automáticas y restauración a un punto en el tiempo.

Curso de AWS

Módulo 1: Introducción a AWS

Módulo 2: Servicios principales de AWS

Módulo 3: Redes y entrega de contenido

Módulo 4: Seguridad e identidad

Módulo 5: Monitorización y gestión

Módulo 6: Bases de datos

Módulo 7: Integración de aplicaciones

Módulo 8: Herramientas para desarrolladores

Módulo 9: Infraestructura como código y gobierno de cuentas

Módulo 10: Contenedores en AWS

Módulo 11: Mejores prácticas y gestión de costos

© Copyright 2026. Todos los derechos reservados