Servidor disponible solo los jueves de 10:30 a 12:30 y los viernes de 10:00 a 13:00 (hora de Madrid); fuera de ese horario está apagado. Fuera del horario, usa la documentación local del material que te descargaste (el README.md de cada carpeta).
G214 · CUNEF
Inicio / tema5 / 05.08-adicionalbeuribor

Tema 5 · 8 · Ejercicio adicional B: Euribor incremental (BCE) a RDS

Presentación del Tema 5: «Ejercicio B · ETL incremental del Euribor a RDS» (ejercicios adicionales; solo si tu docente te lo pide). Duración: a tu ritmo (el entorno se prepara en 15-20 min). Coste: prácticamente nulo en esta actividad; lo que cuesta es la infraestructura base de la carpeta 05.01 (RDS y EC2 por hora).

Etiquetas de lo que creas. Todo recurso que crees a mano (máquina, volumen, bucket, base de datos, tabla, función...) ponle estas etiquetas, al crearlo (apartado Tags / Etiquetas) o después desde su pestaña de etiquetas: curso = G214, universidad = CUNEF y actividad = 05.08-adicionalbeuribor. Si algún día hay que borrar recursos a mano, se localizan todos de golpe con aws resourcegroupstaggingapi get-resources --region eu-north-1 --tag-filters Key=actividad,Values=05.08-adicionalbeuribor. Más detalle (cómo etiquetar con la CLI, cómo buscar y qué hacer en una emergencia): README principal, sección «Etiquetas».

Qué vas a hacer

Un ETL completo sobre una fuente pública real: una Lambda propia descarga el CSV mensual del Euribor a 1 año del portal del BCE y lo carga en la tabla euribor de una base MariaDB de forma incremental (clave primaria fecha + INSERT ... ON DUPLICATE KEY UPDATE: repetir la ejecución no duplica filas). Esta actividad no trae script ni plantilla: creas tú la función Lambda con la receta de abajo. Este README prepara el entorno, te da el esqueleto corregido del código (adicional-b-euribor.py, el mismo), el SQL de la tabla y cómo comprobarlo; el diseño completo y las reglas son el entregable y los haces tú.

Qué se crea en AWS: una función Lambda propia, un rol de IAM propio y, en la RDS, la base banco y la tabla euribor. Entregable: copia del CSV en raw/euribor/, comprobación de calidad y regla de EventBridge.

Qué contiene esta carpeta

Fichero Para qué sirve En qué paso se usa
adicional-b-euribor.py Esqueleto corregido de la Lambda: descarga el CSV del BCE (con 3 reintentos), lo lee con csv.DictReader y hace el INSERT ... ON DUPLICATE KEY UPDATE en banco.euribor. No es la solución completa: faltan la copia del CSV en raw/euribor/, la comprobación de calidad (menos filas que la carga anterior) y la regla de EventBridge. No lo despliega ningún script. Paso 4 (es el código de tu función)
adicional-b-euribor.sql Sentencias de la tabla banco.euribor con su clave primaria (dos opciones) y consultas de comprobación con valores de referencia. Paso 2 (crear la tabla) y paso 6 (comprobar)
README.md Este documento. Siempre

Antes de empezar

Cómo llevar los ficheros a CloudShell

  1. En tu PC, comprime esta carpeta entera (05.08-adicionalbeuribor): clic derecho → Comprimir en archivo ZIP (Windows 11), Enviar a > Carpeta comprimida en zip (Windows 10) o Comprimir (Mac).
  2. En CloudShell (región eu-north-1): Actions → Upload file y elige 05.08-adicionalbeuribor.zip.
  3. Ejecuta:
cd ~
unzip -o 05.08-adicionalbeuribor.zip && cd 05.08-adicionalbeuribor && chmod -R u+rwX .
ls

Si ya subiste g214-materiales.zip (README principal), no necesitas este zip: entra con cd ~/g214-materiales/tema5/05.08-adicionalbeuribor y, si algún fichero da Permission denied, ejecuta allí chmod -R u+rwX . && chmod +x *.sh.

Qué verás: adicional-b-euribor.py, adicional-b-euribor.sql y README.md. (No hay .sh, no hace falta chmod.)

Paso a paso

0. Recupera las variables del Paso 0:

OUT() { aws cloudformation describe-stacks --stack-name cunef-etl --region eu-north-1 \
  --query "Stacks[0].Outputs[?OutputKey=='$1'].OutputValue" --output text; }
BUCKET=$(OUT BucketName); RDS=$(OUT RDSEndpoint); ADMINER=$(OUT AdminerURL)
echo "Bucket : $BUCKET"; echo "RDS    : $RDS"; echo "Adminer: $ADMINER"

Qué verás: tres líneas con valores. Si salen vacías, el stack cunef-etl no existe: vuelve a la carpeta 05.01.

1. Prueba la API del BCE desde CloudShell. Pon la URL entre comillas y en una sola línea. La página del portal (data.ecb.europa.eu) genera el CSV con JavaScript y no sirve para una Lambda; usa esta URL de la API:

curl -s "https://data-api.ecb.europa.eu/service/data/FM/M.U2.EUR.RT.MM.EURIBOR1YD_.HSTA?format=csvdata&detail=dataonly" | head -3
curl -s "https://data-api.ecb.europa.eu/service/data/FM/M.U2.EUR.RT.MM.EURIBOR1YD_.HSTA?format=csvdata&detail=dataonly" | wc -l

Qué verás: una cabecera de 10 columnas (KEY,FREQ,REF_AREA,...,TIME_PERIOD,OBS_VALUE) y una línea por mes: 393 líneas de datos a 2026-09 (de 1994-01 en adelante; con la cabecera, wc -l da una más). La función usa solo TIME_PERIOD (formato AAAA-MM) y OBS_VALUE, y las lee por nombre con csv.DictReader.

2. Crea la base y la tabla en la RDS. Abre Adminer ($ADMINER, http://, raíz): Sistema MySQL (o MySQL / MariaDB), Servidor $RDS, usuario admin, contraseña CunefAdmin!, base de datos vacía; pulsa SQL command. Abre adicional-b-euribor.sql, copia el contenido y ejecuta solo la opción que te toque:

Qué verás: Adminer confirma las sentencias; en la columna izquierda aparece la base banco.

3. Crea un rol propio (una sola vez). La RDS no necesita permiso de IAM (se conecta con usuario y contraseña); el rol solo necesita escribir logs. Si además guardas el CSV en raw/euribor/ (entregable), añade s3:PutObject sobre tu bucket como en la carpeta 05.07 (paso 2):

ROLE=rol-etl-adicional-b
cat > confianza.json <<'EOF'
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"lambda.amazonaws.com"},"Action":"sts:AssumeRole"}]}
EOF
ROLE_ARN=$(aws iam create-role --role-name $ROLE \
  --assume-role-policy-document file://confianza.json --query Role.Arn --output text)
aws iam attach-role-policy --role-name $ROLE \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
echo "$ROLE_ARN"
sleep 10

Qué verás: el ARN del rol (arn:aws:iam::<cuenta>:role/rol-etl-adicional-b).

4. Empaqueta con pymysql y crea la función. Sube el timeout a 60 s. Las variables de entorno van en un fichero JSON porque la URL del CSV lleva ?, & y = (formato --environment file:// probado en AWS real; si prefieres, define las variables en la consola, Lambda → tu función → Configuration → Environment variables):

rm -rf paquete && mkdir paquete
pip3 install pymysql==1.1.0 -t paquete --quiet
cp adicional-b-euribor.py paquete/lambda_function.py
(cd paquete && zip -q -r ../funcion.zip .)

cat > entorno.json <<EOF
{"Variables":{"DB_HOST":"$RDS","DB_USER":"admin","DB_PASS":"CunefAdmin!","CSV_URL":"https://data-api.ecb.europa.eu/service/data/FM/M.U2.EUR.RT.MM.EURIBOR1YD_.HSTA?format=csvdata&detail=dataonly"}}
EOF
aws lambda create-function --function-name etl-adicional-b --runtime python3.12 \
  --handler lambda_function.lambda_handler --role "$ROLE_ARN" \
  --zip-file fileb://funcion.zip --timeout 60 --memory-size 256 \
  --environment file://entorno.json --region eu-north-1

Qué verás: un JSON con la configuración de la función ("FunctionName": "etl-adicional-b", "Timeout": 60). Si create-function dice que el rol no se puede asumir, espera unos segundos y repite. Las variables DB_HOST, DB_USER, DB_PASS son las del Paso 0; en el esqueleto la base banco va fija en el código. Para volver a subir el código después de cambiarlo:

aws lambda update-function-code --function-name etl-adicional-b \
  --zip-file fileb://funcion.zip --region eu-north-1
aws lambda wait function-updated --function-name etl-adicional-b --region eu-north-1

5. Invócala tres veces seguidas y mira los logs:

for i in 1 2 3; do
  aws lambda invoke --function-name etl-adicional-b --cli-binary-format raw-in-base64-out \
    --payload '{}' --region eu-north-1 salida.json && cat salida.json
done
aws logs tail /aws/lambda/etl-adicional-b --since 10m --region eu-north-1

Qué verás: {"filas": 393} las tres veces (a 2026-10; crece 1 por mes). Si tu función lanza una excepción, invoke muestra "FunctionError": "Unhandled" y el detalle está en salida.json y en los logs.

6. Comprueba la tabla en Adminer (SQL command), con las consultas de adicional-b-euribor.sql:

SELECT COUNT(*), MIN(fecha), MAX(fecha) FROM banco.euribor;
SELECT MIN(valor), MAX(valor) FROM banco.euribor;
SELECT YEAR(fecha) AS anio, ROUND(AVG(valor), 4) AS media FROM banco.euribor GROUP BY YEAR(fecha) ORDER BY anio;

Qué verás: valores de referencia con la serie completa (1994-01 en adelante; el recuento crece en 1 cada mes nuevo): a 2026-10-03 había 393 filas, de 1994-01-01 a 2026-09-01, mínimo -0.5047 y máximo 8.0200. El recuento no debe cambiar tras las tres ejecuciones (esa es la prueba de que la carga es incremental). Si el mínimo es 0.0000 o el recuento no coincide con las líneas del CSV, hay filas vacías o cabecera cargada.

7. Entregable (lo haces tú): la copia del CSV en raw/euribor/, la comprobación de calidad (menos filas que la carga anterior) y la regla de EventBridge (ver la carpeta 05.06 para los comandos de una regla; aquí con tu función etl-adicional-b y una frecuencia mensual).

Errores frecuentes

Limpieza

Hazla cuando termines, en este orden, y solo borra lo que hayas creado tú:

  1. Regla de EventBridge (si creaste una): aws events remove-targets --rule NOMBRE --ids 1 --region eu-north-1 y aws events delete-rule --name NOMBRE --region eu-north-1 (o EventBridge → Rules → Delete).

  2. La función y su rol:

    aws lambda delete-function --function-name etl-adicional-b --region eu-north-1
    aws iam detach-role-policy --role-name rol-etl-adicional-b \
      --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
    aws iam list-role-policies --role-name rol-etl-adicional-b
    aws iam delete-role --role-name rol-etl-adicional-b
    

    Si list-role-policies muestra una política en línea (por ejemplo la de s3:PutObject, si la añadiste), bórrala antes del rol con aws iam delete-role-policy --role-name rol-etl-adicional-b --policy-name NOMBRE. Un error NoSuchEntity significa que ese elemento ya no existía.

  3. La base banco (opcional): si la creaste en la RDS del Paso 0, desaparece con ella; para borrarla antes, en Adminer DROP DATABASE banco;. Si usaste la RDS del Tema 3, no la borres: es de ese tema.

  4. Opcional: el grupo de logs (aws logs delete-log-group --log-group-name /aws/lambda/etl-adicional-b --region eu-north-1), la copia del CSV en raw/euribor/ si la guardaste (aws s3 rm s3://$BUCKET/raw/euribor/ --recursive) y los ficheros de CloudShell (cd ~ && rm -rf 05.08-adicionalbeuribor 05.08-adicionalbeuribor.zip).

El borrado de la infraestructura base (carpeta 05.01, sección Limpieza) debe ser el último paso de todo el Tema 5.

Cómo sabes que has terminado


Anterior: Tema 5 · 7 · Adicional A (REE) · Siguiente: Tema 5 · 9 · Adicional C (clima)