# 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](../../README.md), 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 - **Dónde vive la base `banco`.** La base `banco` y la tabla `euribor` del Tema 3 están en la RDS **del Tema 3**, no en la RDS de este laboratorio. La RDS del Paso 0 solo tiene `titanic` (tras el Ejercicio 1) y `crypto` (tras el tipo 3). Puedes usar la RDS del Tema 3 si sigue creada, o la del Paso 0 creando allí la base y la tabla con `adicional-b-euribor.sql`. Este README asume la **RDS del Paso 0**, que depende de [Tema 5 · 1 · Infraestructura base](../05.01-infraestructurabase/README.md); si usas la del Tema 3, sustituye `$RDS` por su endpoint y la contraseña por la suya. - Región **eu-north-1 (Estocolmo)**. - Usa nombres de función y rol que no choquen con los del laboratorio (no reutilices `crypto-price-tracker*`). Aquí se usan `etl-adicional-b` y `rol-etl-adicional-b`. - La función necesita `pymysql`, que AWS no trae: la empaquetas con el código (paso 4). ## 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: ```bash 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:** ```bash 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: ```bash 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**: - **Opción 1** (la normal en la RDS del Paso 0, que no tiene la base `banco`): `CREATE DATABASE IF NOT EXISTS banco;` y la tabla nueva con su clave primaria: ```sql CREATE DATABASE IF NOT EXISTS banco; CREATE TABLE IF NOT EXISTS banco.euribor ( fecha DATE NOT NULL, periodo VARCHAR(16) NOT NULL, valor DECIMAL(8,4) NOT NULL, PRIMARY KEY (fecha) ); ``` - **Opción 2** (ya tienes la tabla del Tema 3, que **no** tiene clave primaria): vacíala y añade la clave **una sola vez**, fuera de la Lambda: `TRUNCATE TABLE banco.euribor;` y `ALTER TABLE banco.euribor ADD PRIMARY KEY (fecha);`. Si no la vacías y tiene meses repetidos o con otro formato de fecha, el `ALTER` falla o quedan filas duplicadas. Si ya tiene clave primaria, el `ALTER` dará `Multiple primary key defined`: no lo repitas. **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): ```bash 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:::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*): ```bash 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 <" | head -3` (la cabecera debe llevar `TIME_PERIOD` y `OBS_VALUE`). Si la función carga 0 filas, el formato no es el que espera el código: mira la cabecera. - **`Duplicate entry ... for key 'PRIMARY'` al añadir la clave, o meses repetidos.** La tabla del Tema 3 tenía datos cargados a mano con otro formato de fecha: vacíala (`TRUNCATE TABLE banco.euribor;`) antes del `ALTER`. - **La función se queda colgada o falla con `Can't connect ... (timed out)`.** La RDS no es alcanzable (grupo de seguridad del puerto 3306, endpoint equivocado o RDS parada). El esqueleto conecta con `connect_timeout=10`, así que falla a los 10 segundos. - **`OperationalError (1045, "Access denied for user 'admin'...")`.** La contraseña de la función no es la de la RDS (`CunefAdmin!` en la del Paso 0). Revisa `DB_PASS`. - **`OperationalError (1049, "Unknown database 'banco'")`.** No creaste la base: vuelve al paso 2. - **`Unable to import module 'lambda_function'` o `No module named 'pymysql'`.** El paquete no lleva el código o la librería. Repite el empaquetado del paso 4 y vuelve a subir el código. - **Adminer no carga o no deja entrar:** ver "Errores frecuentes" de la carpeta 05.01. ## 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:** ```bash 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](../05.01-infraestructurabase/README.md), sección Limpieza) debe ser el último paso de todo el Tema 5.** ## Cómo sabes que has terminado - [ ] Tres invocaciones seguidas devuelven `{"filas": 393}` (a 2026-10; crece 1 por mes). - [ ] `SELECT COUNT(*) FROM banco.euribor;` no cambia entre ejecuciones y coincide con las líneas de datos del CSV. - [ ] Entregaste lo que pide la diapositiva (código, copia del CSV en `raw/euribor/`, comprobación de calidad y regla de EventBridge). - [ ] Has hecho la limpieza de esta actividad. --- Anterior: [Tema 5 · 7 · Adicional A (REE)](../05.07-adicionalaree/README.md) · Siguiente: [Tema 5 · 9 · Adicional C (clima)](../05.09-adicionalcclima/README.md)