# Tema 5 · 5 · Ejercicio 2, tipo 3: CoinGecko a S3 y RDS Presentación del Tema 5: «Ejercicio 2: CoinGecko (Tipo 3)» (Power BI sobre la tabla) y «Ejercicio 2 · programar la ejecución con EventBridge». Duración: 25-30 min (sin contar Power BI ni EventBridge). Coste: prácticamente nulo en esta actividad; lo que cuesta es la infraestructura base de la carpeta 05.01 (RDS y EC2 por hora). > **Solo haces UNA variante del Ejercicio 2.** La carpeta 05.03 (S3), la 05.04 (DynamoDB) y esta (S3 + RDS) son las tres variantes. Haz la que te asigne tu docente. No hagas las tres. Aun así no se pisan: cada una tiene su propia regla de EventBridge y su propia carpeta de datos (esta usa `crypto-rds/` en el bucket y la regla `crypto-rds-cada-minuto`). > **¿Dudas con un script?** Todos los scripts de esta carpeta traen su propia ayuda: `./gestionar-crypto-s3-rds.sh --ayuda` (también vale `--help` o `-h`) explica los pasos y las opciones. Este `README.md` es el paso a paso de la actividad: tenlo a mano y consúltalo antes de preguntar. > **Etiquetas de lo que creas.** `./gestionar-crypto-s3-rds.sh crear` etiqueta el stack (Lambda, rol) y la regla de EventBridge con `curso` = `G214`, `universidad` = `CUNEF`, `actividad` = `05.05-cryptoas3yrds` y `script` = `gestionar-crypto-s3-rds.sh`. Las verás en la consola (pestaña *Tags* del recurso) y, si hiciera falta borrar a mano, se localizan con `aws resourcegroupstaggingapi get-resources --region eu-north-1 --tag-filters Key=actividad,Values=05.05-cryptoas3yrds`. 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 Vas a crear una función Lambda que pide a **CoinGecko** (API pública, sin clave) el precio en USD y EUR de cinco monedas (`bitcoin`, `ethereum`, `cardano`, `solana`, `polkadot`) con su capitalización, volumen y variación en 24 horas, guarda los datos originales en S3 y **después** inserta una fila por moneda y divisa en una tabla de la RDS MariaDB. **Qué se crea en AWS:** el stack `cunef-etl-crypto-rds` (rol + función `crypto-price-tracker-rds`, 60 s, 256 MB). La primera ejecución crea en la RDS la base `crypto` y la tabla `coins`. Lo crea, lo comprueba y lo borra **un único script**: `gestionar-crypto-s3-rds.sh`. **Qué hace la función:** guarda los datos originales en S3 (`crypto-rds/raw/history/crypto_prices_AAAAMMDD_HHMMSS.json` y `crypto-rds/raw/crypto_prices_latest.json`) e inserta en la RDS una fila por moneda y divisa (5 x 2 = 10 filas por ejecución) en la tabla `crypto.coins`, que crea si no existe. Las credenciales y el bucket los toma de variables de entorno que fija el script (`S3_BUCKET`, `DB_HOST`, `DB_USER`, `DB_PASS`, `DB_NAME`, `DB_TABLE`). **Importante:** si falla la parte de RDS, la función **no devuelve 500**: devuelve `statusCode` 200 y dentro de `body` el campo `rds` con `"success": false` y el motivo en `rds.message`. Mira siempre `rds.success`. ## Lo que debes saber antes de empezar (vale para las tres variantes) 1. **Un solo script hace todo.** `./gestionar-crypto-s3-rds.sh crear` detecta la RDS y el bucket del Paso 0, empaqueta el código con la librería `pymysql`, crea el stack de CloudFormation (con la plantilla que lleva dentro), sube el código de la función y, si quieres, la prueba. **Ya no hay que crear el stack a mano.** Es seguro repetirlo: si todo existe, solo actualiza el código; si quedó algo a medias de un intento anterior, lo repara. 2. **Menú o comandos.** Sin argumentos y en una terminal, el script muestra un menú numérico. Con argumentos ejecuta directamente un comando: `crear`, `estado`, `probar`, `logs`, `programar`, `extender`, `desprogramar`, `eliminar`, `extraer` y `ayuda` (lista completa en *Los comandos del script*). 3. **El script pregunta** `¿Deseas probar la función ahora? (s/n):` al terminar `crear`. Escribe `s` y pulsa Intro. Con `n` solo despliega. Para no tener que contestar: `crear --si` (prueba) o `crear --no` (no prueba). Sin terminal interactiva nunca se queda esperando: no prueba. 4. **El script ya no crea un rol de IAM propio.** La función usa el rol del stack. Si en una versión anterior se te creó un rol sobrante (`LambdaCryptoTrackerRDSRole`), `eliminar` lo borra. 5. **El script marca el resultado con el `statusCode` de la función.** Tras la prueba verás `✓ Función ejecutada correctamente (statusCode 200)` si fue bien, o `✗ La función devolvió statusCode 500` seguido del motivo (campo `error`) si falló por dentro. Con el CLI a mano ocurre lo contrario: `"StatusCode": 200` solo dice que la llamada de invocación terminó, aunque dentro vaya `statusCode` 500. Comprueba siempre el dato **en el destino**. 6. **Cada ejecución añade datos nuevos** (no se sobrescribe lo anterior), salvo `crypto_prices_latest.json`. Repetir la invocación es seguro, pero acumula ficheros y filas. 7. **Los "Comandos útiles" que imprime el script** ya llevan `--cli-binary-format raw-in-base64-out` (AWS CLI v2). 8. **CoinGecko limita las peticiones y el límite es bajo.** Tras unas 5 peticiones seguidas desde la misma IP responde error 429 (`Status 429` o `HTTP Error 429` en el campo `error`; en una prueba, 5 respuestas 200 y 7 con 429 en una ráfaga de 12). No encadenes el `curl` de comprobación, el script con `s`, el `invoke` a mano y la regla de cada minuto: deja 1-2 minutos entre pruebas. Si toda la clase sale por la misma IP, el 429 aparecerá antes. No es culpa de tu código: espera uno o dos minutos y vuelve a invocar. La función no reintenta sola. Qué deberías ver tras la **primera** ejecución correcta: **2 ficheros JSON bajo `crypto-rds/raw/`** y **10 filas en `crypto.coins`** (5 monedas x 2 divisas); en cada ejecución siguiente, 2 ficheros nuevos y 10 filas más. ## Qué contiene esta carpeta | Fichero | Para qué sirve | En qué paso se usa | |---|---|---| | `gestionar-crypto-s3-rds.sh` | **Todo el ejercicio en un solo archivo:** detecta la RDS y el bucket del Paso 0, empaqueta el código con `pymysql`, crea el stack y sube el código, comprueba el estado, prueba la función, programa EventBridge y lo elimina todo (incluida la tabla de la RDS). Lleva dentro el código de la Lambda y la plantilla de CloudFormation. | Pasos 3, 4, 5 y 7, y la Limpieza | | `README.md` | Este documento. | Siempre | **El código de la función y la plantilla viajan dentro del script.** Para verlos o editarlos, extráelos a la carpeta actual: ```bash ./gestionar-crypto-s3-rds.sh extraer ``` Esto escribe `lambda-crypto-s3-rds.py` (el código de la función) y `plantilla-crypto-s3-rds.yaml` (la plantilla de CloudFormation). **No son necesarios para desplegar.** En este ejercicio no tienes que editar nada (el bucket llega a la función por variable de entorno), pero si editas el `.py` y ejecutas `crear`, el script despliega **tu** versión (también detecta un `lambda_function.py` en la carpeta, o el fichero que le indiques con `--codigo mi-codigo.py`; si no compila, no despliega nada y te lo dice). La plantilla extraída solo se usa con `crear --plantilla plantilla-crypto-s3-rds.yaml`. Si el fichero ya existe, `extraer` no lo sobrescribe (`extraer --forzar` lo hace y guarda una copia `.bak`). ## Antes de empezar - **Depende de [Tema 5 · 1 · Infraestructura base](../05.01-infraestructurabase/README.md)**: necesita el stack `cunef-etl` creado (bucket y RDS). Si no existe, el script dice `✗ No se encontró el stack cunef-etl` y se detiene **antes de crear nada**; no usa buckets de demostración ni crea uno propio. - Región **eu-north-1 (Estocolmo)** seleccionada en la consola (el script ya trabaja en esa región). - CloudShell (trae `aws`, `zip`, `python3` y `pip3`). No hace falta `jq`. `pip3` hace falta porque el script instala `pymysql` dentro del paquete de la función. ## Cómo llevar los ficheros a CloudShell 1. En tu PC, comprime **esta carpeta entera** (`05.05-cryptoas3yrds`): 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.05-cryptoas3yrds.zip`. 3. Ejecuta: ```bash cd ~ unzip -o 05.05-cryptoas3yrds.zip && cd 05.05-cryptoas3yrds chmod -R u+rwX . && chmod +x *.sh ls ``` > Si ya subiste `g214-materiales.zip` (README principal), no necesitas este zip: entra con `cd ~/g214-materiales/tema5/05.05-cryptoas3yrds` y, si algún fichero da `Permission denied`, ejecuta allí `chmod -R u+rwX . && chmod +x *.sh`. **Qué verás:** `gestionar-crypto-s3-rds.sh` y `README.md`. Ejecuta el script siempre **dentro de esta carpeta**. Las variables de shell se pierden al cerrar la pestaña. ## Los comandos del script `./gestionar-crypto-s3-rds.sh COMANDO [opciones]` (`ayuda` los lista; sin comando y con terminal, abre el menú). | Comando (alias) | Qué hace | |---|---| | `crear` (`desplegar`, `deploy`) | Detecta RDS y bucket, empaqueta `pymysql`, crea o actualiza el stack, sube el código y, si quieres, prueba la función. Repetible. | | `estado` (`status`, `verificar`) | Muestra qué existe (infraestructura base, stack, función y si tiene la RDS configurada, rol, datos en S3, reglas, logs), la última ejecución, las últimas líneas del log y **qué hacer a continuación**. | | `probar` (`test`, `invocar`) | Ejecuta la función una vez y comprueba `statusCode`, los ficheros en S3 y `rds.success`. | | `logs` | Últimas líneas de los logs (`logs --seguir`: en directo). | | `programar [N]` (`eventbridge`) | Ejecuta la función cada N minutos (por defecto 1) con EventBridge **durante 4 h**: después la regla se desactiva sola (ver *Apagado automático*). Si la regla ya existe, la reactiva y renueva el plazo. Ver [05.06](../05.06-ProgramarEventBridge.md). | | `extender` (`ampliar`) | Solo cambia el plazo del apagado automático (otras 4 h; `--horas N` o `--permanente`). | | `desprogramar` (`parar`) | Quita (borra) la programación. | | `eliminar` (`borrar`, `limpiar`) | **Borra todo lo de este ejercicio** (incluidos los datos de S3 y la tabla de la RDS) y comprueba al final qué queda. Pide confirmación (con `--si` o `-y` no). | | `extraer` | Escribe en la carpeta el `.py` y el `.yaml` embebidos para verlos o editarlos. | Opciones: `--si`/`-y` (responde sí: `crear` prueba la función, `eliminar` no pregunta), `--no` (`crear` no prueba; `eliminar` no borra), `--codigo F.py`, `--plantilla F.yaml`, `--forzar` (con `extraer`), `--seguir` (con `logs`), `--cada "rate(1 hour)"`, `--regla NOMBRE`, `--horas N` (1-24) y `--permanente` (con `programar` y `extender`). ## Paso a paso **0. Recupera las variables del Paso 0** (también si cerraste CloudShell): ```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); FN=$(OUT LambdaName); ADMINER=$(OUT AdminerURL) echo "Bucket : $BUCKET"; echo "RDS : $RDS"; echo "Lambda : $FN"; echo "Adminer: $ADMINER" ``` **Qué verás:** cuatro líneas con valores (aquí necesitas `BUCKET`, y `RDS` y `ADMINER` para Adminer). Si salen vacías, el stack `cunef-etl` no existe: vuelve a la carpeta 05.01. **1. Comprueba que CoinGecko responde desde CloudShell:** ```bash curl -s -o /dev/null -w "%{http_code}\n" "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,ethereum&vs_currencies=eur" ``` **Qué verás:** `200`. Si ves `429` u otro número, la API te está limitando o no responde: espera un poco y repite antes de seguir. **2. (Opcional) Mira el código de la función:** `./gestionar-crypto-s3-rds.sh extraer` y abre `lambda-crypto-s3-rds.py`. No hay que cambiar nada: el bucket y la RDS le llegan por variables de entorno. **3. Crea todo y despliega el código:** ```bash ./gestionar-crypto-s3-rds.sh crear ``` **Qué verás, en orden** *(los textos pueden variar ligeramente)*: "Verificando prerequisitos"; "Detectando Configuración de RDS" con "RDS detectado automáticamente" (host, usuario, contraseña, base `crypto` y tabla `coins`); "Configurando Bucket S3" con "Usando bucket del stack ETL: ..." (sin el Paso 0 se para aquí **sin crear nada**); "Preparando el código y la plantilla"; "Creando Paquete Lambda" (instala `pymysql`, "Tamaño del paquete"; se hace **antes** de crear nada, así que si falla no queda nada a medias); "Stack de CloudFormation" ("Creando el stack cunef-etl-crypto-rds ..." con puntos de progreso y "Stack ... creado"; tarda 1-2 minutos); "Desplegando Función Lambda" con **"Actualizando función existente..."**, "Código actualizado" y "Configuración actualizada"; y la pregunta `¿Deseas probar la función ahora? (s/n):`. Responde `s`. Tras la prueba verás un JSON con `"statusCode": 200`; en `body.s3` las rutas guardadas y en `body.rds` **`"success": true`** con `"records_inserted": 10`, `"database": "crypto"` y `"table": "coins"`. El script añade "Datos guardados en RDS exitosamente" y "Registros insertados: 10". Si esas dos líneas no aparecen, `rds.success` no fue `true`: el script avisa con `⚠ La parte de RDS no se completó` y el motivo; lee también `rds.message`. El script espera a que la configuración de la función esté activa antes de probar; si aun así el motivo dice `Host: NO CONFIGURADO`, espera 30 segundos y repite la prueba con `./gestionar-crypto-s3-rds.sh probar`. **4. Comprueba el estado cuando quieras:** ```bash ./gestionar-crypto-s3-rds.sh estado ``` **Qué verás**: una línea por elemento con ✓ o ⚠ (RDS y bucket de la infraestructura base, stack, función con su estado, tamaño de código y si tiene la RDS configurada, rol, datos en S3, reglas de EventBridge, grupo de logs), la fecha de la última ejecución, las últimas líneas del log y una última línea **"Siguiente paso: ..."**. Las filas de la tabla se miran en Adminer. **5. Prueba la función a mano:** ```bash aws lambda invoke --function-name crypto-price-tracker-rds \ --cli-binary-format raw-in-base64-out --payload '{}' \ --region eu-north-1 out.json cat out.json ``` **Qué verás:** `"StatusCode": 200` del CLI y, en `out.json`, `statusCode` 200 con `rds.success` en `true`. Esta vez no le pasas el bucket en el evento: lo toma de su variable de entorno `S3_BUCKET`. (`./gestionar-crypto-s3-rds.sh probar` hace lo mismo pasando el bucket y resumiendo el resultado.) **6. Comprueba los dos destinos:** ```bash aws s3 ls s3://$BUCKET/crypto-rds/ --recursive --human-readable ``` En Adminer (abre `$ADMINER` y entra como en la carpeta 05.01: Sistema `MySQL`, Servidor `$RDS`, usuario `admin`, contraseña `CunefAdmin!`; base `crypto`, enlace **SQL command**): ```sql SELECT COUNT(*) FROM crypto.coins; SELECT * FROM crypto.coins ORDER BY timestamp DESC LIMIT 10; ``` **Qué verás:** en S3, `crypto-rds/raw/history/crypto_prices_....json` y `crypto-rds/raw/crypto_prices_latest.json`. En Adminer, `10` tras la primera ejecución (10 más por cada ejecución siguiente) y las filas con las columnas `id, timestamp, coin_id, currency, price, market_cap, volume_24h, change_24h, last_updated_at, created_at`. Para Power BI (si la presentación te lo pide) usa el mismo servidor, usuario y contraseña que en el Ejercicio 1 (carpeta 05.02, paso 6) y la base `crypto`. **7. (Opcional) Programar la ejecución cada minuto**: `./gestionar-crypto-s3-rds.sh programar` (y `desprogramar` al terminar), o los comandos a mano de la carpeta [Tema 5 · 6 · Programar con EventBridge](../05.06-ProgramarEventBridge.md) con `FN=crypto-price-tracker-rds`. Se apaga sola a las 4 h (ver *Apagado automático*); **bórrala al terminar.** En esta variante, cada minuto añade dos ficheros y diez filas. ## Apagado automático La programación de EventBridge **se apaga sola a las 4 horas**: una regla de cada minuto que se olvida sigue ejecutando la función (y llamando a CoinGecko) para siempre. **La regla se desactiva, no se borra**; solo `desprogramar` o `eliminar` la borran. | Quiero... | Comando | |---|---| | Programar la función (4 h por defecto) | `./gestionar-crypto-s3-rds.sh programar` (cada minuto) o, por ejemplo, `./gestionar-crypto-s3-rds.sh programar 5 --horas 2` | | Ver cuánto queda | `./gestionar-crypto-s3-rds.sh estado` (línea `Apagado automático: se apagará a las HH:MM UTC (dentro de N min)`) | | Ampliar el plazo otras 4 h | `./gestionar-crypto-s3-rds.sh extender` (otro plazo: `extender --horas 8`) | | Que no se apague nunca | `./gestionar-crypto-s3-rds.sh programar --permanente` o `extender --permanente` (¡la función seguirá ejecutándose hasta que la quites!) | | Reactivar tras el apagado | `./gestionar-crypto-s3-rds.sh programar` (reactiva la regla y renueva el plazo) | | Quitar la regla / borrarlo todo | `./gestionar-crypto-s3-rds.sh desprogramar` / `./gestionar-crypto-s3-rds.sh eliminar` (funcionan también con la regla desactivada) | Cómo funciona: `programar` guarda el plazo (hora UTC de vencimiento, o `permanente`) en la variable de entorno `APAGAR_TRAS` de la función. En cada ejecución programada, la función la mira y, si ya venció, **desactiva su propia regla** (su rol de IAM solo puede desactivar esa regla) y no ejecuta el ETL. Se nota en la primera ejecución tras el vencimiento (con `rate(1 hour)` puede tardar hasta 1 h). `probar` y las invocaciones a mano no se ven afectadas. Si editas el código con `extraer`, conserva la función `apagado_automatico` y su llamada al principio de `lambda_handler`. ## Errores frecuentes - **`Permission denied` al ejecutar `./gestionar-crypto-s3-rds.sh`.** Solución: `chmod +x *.sh`. Si dice `bad interpreter` o `^M`, el fichero se guardó con saltos de línea de Windows: `tr -d '\r' < gestionar-crypto-s3-rds.sh > x.sh && mv x.sh gestionar-crypto-s3-rds.sh && chmod +x gestionar-crypto-s3-rds.sh`. - **El script se queda mostrando el menú / no hace nada.** Sin argumentos y con terminal muestra el menú: elige una opción o usa un comando (`crear`, `estado`...). Sin terminal imprime la ayuda y sale. - **El script dice `No se encontró el stack cunef-etl`.** El Paso 0 no existe en esta cuenta: este tipo lo necesita (escribe en su bucket y en su RDS). Créalo en la carpeta 05.01 (`./gestionar-etl-base.sh`) y repite el script. No se ha creado nada. - **`statusCode` 500 con `AccessDenied ... is not authorized to perform: s3:PutObject`.** El rol de la función no deja escribir en tu bucket (stack antiguo creado con una plantilla antigua, `cunef-etl-demo-*`). Solución, sin borrar nada: vuelve a ejecutar `./gestionar-crypto-s3-rds.sh crear`; el script actualiza el stack con la plantilla correcta. Si no se arregla, lee el campo `error` o usa `eliminar` y `crear`. - **`Status 429` o `HTTP Error 429` en `error`.** Límite de peticiones de CoinGecko, que es bajo. Espera uno o dos minutos, no encadenes pruebas y vuelve a invocar (`probar`). Con otro código distinto (`403`, `5xx`) la API ha cambiado sus condiciones o está caída: avisa a tu docente. - **"No se pudo instalar pymysql" / "No encuentro pip".** `pip` no pudo descargar la librería (sin red) o no está instalado. Prueba `pip3 install pymysql -t /tmp/prueba --quiet && echo OK` (en CloudShell funciona) y repite el script. No se ha creado nada en AWS: el paquete se prepara antes que el stack. - **`rds.success` es `false`.** Lee `rds.message`: `Host: NO CONFIGURADO` (si ocurre justo tras el despliegue) significa que la prueba corrió antes de que la configuración estuviera activa: espera 30 segundos y repite `probar`; `Can't connect ... (timed out)` indica que la RDS no es alcanzable; `Access denied for user` indica contraseña distinta; `Unable to import module ... pymysql` (error de función, sin `statusCode`) indica que el paquete no llevaba la librería: repite `crear`. - **`ResourceConflictException` al desplegar o al probar.** La función seguía actualizándose. El script ya espera y reintenta; si aun así sale, espera 10 segundos y repite el comando. - **La prueba dice `✗ La función devolvió statusCode 500, no se ha guardado nada`.** Es la forma que tiene el script de avisarte; el motivo va justo debajo (`Motivo: ...`). Los más habituales: `AccessDenied` (arriba) y `429` de CoinGecko. El script continúa y muestra el resumen. - **`crear` dice que el stack está en `ROLLBACK_COMPLETE`, `CREATE_FAILED` u otro estado fallido.** No tienes que hacer nada: el script lo borra y lo vuelve a crear. Si lo prefieres, `eliminar --si` y luego `crear`. - **El script avisa de "una función fuera de CloudFormation".** Es un resto de la versión antigua del script (que creaba la función por su cuenta): lo borra y deja que el stack la cree. - **`eliminar` dice que no pudo borrar la tabla de la RDS.** El script la borra ejecutando una pequeña limpieza dentro de la propia función. Si no es posible (la función ya no existe, la RDS está parada o no hay `pip`), te enseña qué ejecutar a mano en Adminer: `DROP TABLE crypto.coins; DROP DATABASE crypto;`. El resto de la limpieza continúa. - **Adminer no carga o no deja entrar:** ver "Errores frecuentes" de la carpeta 05.01. ## Limpieza Hazla cuando termines: ```bash ./gestionar-crypto-s3-rds.sh eliminar ``` Te enseña qué va a borrar y pide confirmación (con `--si` no pregunta). Borra, **forzando** si hace falta y de forma repetible (puedes lanzarlo otra vez sin riesgo): las reglas de EventBridge de la función y su permiso, los ficheros `crypto-rds/raw/` que la función guardó en el bucket, la tabla `crypto.coins` de la RDS (y la base `crypto` si queda vacía; la hace borrar la propia función justo antes de eliminarse), el stack `cunef-etl-crypto-rds` (función y rol; si CloudFormation se atasca, reintenta y retiene solo lo imprescindible), el rol sobrante `LambdaCryptoTrackerRDSRole` de versiones antiguas (si existe) y el grupo de logs. **No toca** el stack `cunef-etl` (bucket, RDS y Adminer) ni otros ejercicios. **Qué verás**: cinco bloques ("1/5 Programación de EventBridge", "2/5 Datos del ejercicio", "3/5 Stack...", "4/5 Recursos sueltos", "5/5 Comprobación final") y, al final, `✓ Todo eliminado. No queda nada de este ejercicio en AWS.` Si algo no se pudo borrar, lo dice con ⚠ y termina con error: repite el comando. Para comprobar la RDS, en Adminer: `SHOW DATABASES;` (no debe aparecer `crypto`). Opcional: ficheros de CloudShell (`cd ~ && rm -rf 05.05-cryptoas3yrds 05.05-cryptoas3yrds.zip`). **No borres la infraestructura base todavía** si vas a hacer más actividades (05.07 y 05.08 también la usan). **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 - [ ] `./gestionar-crypto-s3-rds.sh estado` muestra el stack `cunef-etl-crypto-rds` en `CREATE_COMPLETE`, la función con el código real y la RDS configurada. - [ ] El `statusCode` de la función es 200 y `rds.success` es `true` (`records_inserted` 10). - [ ] Has comprobado el dato **en los dos destinos**: ficheros en `crypto-rds/raw/` del bucket y 10 filas en `crypto.coins` (Adminer) tras la primera ejecución. - [ ] Has hecho la limpieza de esta actividad (`eliminar` terminó con "Todo eliminado"). --- Anterior: [Tema 5 · 4 · CoinGecko a DynamoDB](../05.04-cryptoadynamodb/README.md) · Siguiente: [Tema 5 · 6 · Programar con EventBridge](../05.06-ProgramarEventBridge.md)