<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SOANNY GERALDINE RIVAS GUTIERREZ</title>
    <description>The latest articles on DEV Community by SOANNY GERALDINE RIVAS GUTIERREZ (@soanny_geraldinerivasgu).</description>
    <link>https://dev.to/soanny_geraldinerivasgu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4160871%2Fa8c6305e-c944-430c-86c3-a81aaa403272.png</url>
      <title>DEV Community: SOANNY GERALDINE RIVAS GUTIERREZ</title>
      <link>https://dev.to/soanny_geraldinerivasgu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/soanny_geraldinerivasgu"/>
    <language>en</language>
    <item>
      <title>Respaldo y recuperación de MySQL con Railway, Aiven y GitHub Actions</title>
      <dc:creator>SOANNY GERALDINE RIVAS GUTIERREZ</dc:creator>
      <pubDate>Sun, 04 Oct 2026 06:38:57 +0000</pubDate>
      <link>https://dev.to/soanny_geraldinerivasgu/respaldo-y-recuperacion-de-mysql-con-railway-aiven-y-github-actions-ngi</link>
      <guid>https://dev.to/soanny_geraldinerivasgu/respaldo-y-recuperacion-de-mysql-con-railway-aiven-y-github-actions-ngi</guid>
      <description>&lt;p&gt;#mysql #database #githubactions #backup&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creado por:&lt;/strong&gt;&lt;br&gt;
Soanny Geraldine Rivas Gutierrez&lt;/p&gt;

&lt;p&gt;En una aplicación de inventario, perder un registro puede alterar el stock disponible y el valor de los productos. Para recuperar esa información hace falta conservar el estado de la base en un momento anterior y comprobar que la copia puede importarse correctamente.&lt;/p&gt;

&lt;p&gt;El repositorio del código permite reconstruir la aplicación; el respaldo permite recuperar sus datos.&lt;/p&gt;

&lt;p&gt;En este proyecto desarrollé un inventario con Node.js, Express y MySQL, junto con scripts de exportación y recuperación. La solución configura copias completas con &lt;code&gt;mysqldump&lt;/code&gt;, las comprime, las cifra y prepara su ejecución diaria con GitHub Actions.&lt;/p&gt;

&lt;p&gt;Para la publicación se plantea alojar la aplicación en Railway y utilizar MySQL administrado en Aiven.&lt;/p&gt;

&lt;p&gt;La validación realizada es local: se utilizó un servidor MySQL real y se recuperaron los registros en una base independiente.&lt;/p&gt;

&lt;p&gt;La configuración del repositorio público, el despliegue en Railway, la conexión a Aiven y las ejecuciones remotas deben completarse desde las cuentas del estudiante antes de presentar sus enlaces como evidencia.&lt;/p&gt;


&lt;h2&gt;
  
  
  Diseño de la solución
&lt;/h2&gt;

&lt;p&gt;La aplicación permite consultar productos, agregar registros y eliminarlos.&lt;/p&gt;

&lt;p&gt;Cada producto tiene:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identificador&lt;/li&gt;
&lt;li&gt;Nombre&lt;/li&gt;
&lt;li&gt;Precio&lt;/li&gt;
&lt;li&gt;Stock&lt;/li&gt;
&lt;li&gt;Fecha de creación&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esta información proporciona un conjunto pequeño y verificable para estudiar el comportamiento de un respaldo.&lt;/p&gt;

&lt;p&gt;La siguiente tabla presenta los componentes y responsabilidades de la solución.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrc9kbgf4u84ok9na6os.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgrc9kbgf4u84ok9na6os.png" alt=" " width="636" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tabla 1. Componentes y responsabilidades de la solución.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;El entorno local sirve para desarrollar y verificar la recuperación.&lt;/p&gt;

&lt;p&gt;En el entorno de nube, la API de Railway se conecta a MySQL en Aiven. GitHub Actions puede conectarse al mismo servicio para generar el respaldo y conservar un artefacto cifrado.&lt;/p&gt;

&lt;p&gt;El código y las copias tienen ciclos de vida diferentes: el código permanece en Git y los artefactos expiran según la retención configurada.&lt;/p&gt;


&lt;h2&gt;
  
  
  Política de respaldo
&lt;/h2&gt;

&lt;p&gt;Se eligió un respaldo lógico completo porque la base es pequeña y su contenido puede revisarse mediante SQL.&lt;/p&gt;

&lt;p&gt;Cada copia incluye la estructura y las filas de la base de demostración.&lt;/p&gt;

&lt;p&gt;No se implementaron respaldos incrementales ni recuperación a un instante arbitrario mediante binlogs.&lt;/p&gt;

&lt;p&gt;La siguiente tabla presenta la política de respaldo y recuperación.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F385fmzu8um2mxqc5wir5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F385fmzu8um2mxqc5wir5.png" alt=" " width="641" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tabla 2. Política de respaldo y recuperación.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La configuración contempla:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Una ejecución diaria.&lt;/li&gt;
&lt;li&gt;Ejecuciones manuales para validación.&lt;/li&gt;
&lt;li&gt;Horario previsto de &lt;code&gt;06:00 UTC&lt;/code&gt;, equivalente a &lt;code&gt;01:00&lt;/code&gt; en Lima.&lt;/li&gt;
&lt;li&gt;Retención de siete días.&lt;/li&gt;
&lt;li&gt;Archivos comprimidos y cifrados con extensión &lt;code&gt;.sql.gz.enc&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Verificación mediante SHA-256.&lt;/li&gt;
&lt;li&gt;Restauración en una base vacía distinta de la original.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Si las ejecuciones diarias terminan correctamente, la pérdida potencial corresponde aproximadamente a los cambios desde la última copia, que puede rondar un día.&lt;/p&gt;

&lt;p&gt;Una ejecución fallida o retrasada amplía ese intervalo.&lt;/p&gt;

&lt;p&gt;El tiempo de recuperación debe medirse con el volumen real de información; no se establece una garantía de RTO para esta demostración.&lt;/p&gt;


&lt;h2&gt;
  
  
  Archivos del proyecto y configuración local
&lt;/h2&gt;

&lt;p&gt;El repositorio &lt;code&gt;mysql-backup-strategy&lt;/code&gt; reúne la aplicación, el esquema SQL y los mecanismos de automatización.&lt;/p&gt;

&lt;p&gt;Los principales archivos del proyecto se presentan en la siguiente tabla.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqder7jejn4fl8zg99afm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqder7jejn4fl8zg99afm.png" alt=" " width="641" height="349"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tabla 3. Archivos principales del proyecto.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Entre los archivos principales se encuentran:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;src&lt;/code&gt; y &lt;code&gt;public&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;database/database.sql&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts/backup.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts/restore.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts/crypto.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.github/workflows&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.env.example&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para utilizar la ruta de Docker en Windows, se copia la plantilla de variables, se reemplazan las claves y se inicia Docker Desktop con contenedores Linux.&lt;/p&gt;

&lt;p&gt;Los comandos se ejecutan desde la carpeta del proyecto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Copy-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;example&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;notepad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;up&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ps&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://localhost:3000/health&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MySQL conserva la información en un volumen.&lt;/p&gt;

&lt;p&gt;Las variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MYSQL_DATABASE
MYSQL_USER
MYSQL_PASSWORD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;inicializan un volumen nuevo.&lt;/p&gt;

&lt;p&gt;Modificar el archivo de variables después no cambia automáticamente las cuentas ya creadas.&lt;/p&gt;

&lt;p&gt;La aplicación utiliza las variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_HOST
DB_PORT
DB_USER
DB_PASSWORD
DB_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;para establecer la conexión.&lt;/p&gt;

&lt;p&gt;En el laboratorio se permite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_SSL=false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;dentro del entorno local.&lt;/p&gt;

&lt;p&gt;Para conectar con Aiven se utiliza:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_SSL=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;junto con el certificado CA del servicio.&lt;/p&gt;

&lt;p&gt;El proyecto verifica tanto la confianza del certificado como la identidad del servidor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Modelo de datos y aplicación Node
&lt;/h2&gt;

&lt;p&gt;La tabla de demostración utiliza InnoDB, precios decimales y valores de stock no negativos.&lt;/p&gt;

&lt;p&gt;El script se ejecuta dentro de una base existente, por lo que no fija el nombre de la base mediante &lt;code&gt;USE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Esto facilita recuperar la información en un destino distinto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;productos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;nombre&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;precio&lt;/span&gt; &lt;span class="nb"&gt;DECIMAL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;stock&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;fecha_creacion&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;precio_no_negativo&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;precio&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;CHARSET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4&lt;/span&gt;
  &lt;span class="k"&gt;COLLATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;IGNORE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;productos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nombre&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;precio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Cuaderno'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Lapicero'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Carpeta'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Express expone las operaciones del inventario y un endpoint de salud.&lt;/p&gt;

&lt;p&gt;Las consultas de lectura son públicas.&lt;/p&gt;

&lt;p&gt;Para crear o eliminar productos se requiere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;X-API-Key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La clave se configura mediante:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ADMIN_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El precio debe ser numérico, tener como máximo dos decimales y no ser negativo.&lt;/p&gt;

&lt;p&gt;El stock debe ser un entero no negativo.&lt;/p&gt;

&lt;p&gt;Las rutas disponibles en la aplicación se presentan en la siguiente tabla.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtdzvoh2c0zcczpl6jg8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjtdzvoh2c0zcczpl6jg8.png" alt=" " width="630" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tabla 4. Rutas de la aplicación de inventario.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Las principales rutas son:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /
GET /health
GET /productos
POST /productos
DELETE /productos/:id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Las inserciones y eliminaciones utilizan consultas parametrizadas.&lt;/p&gt;

&lt;p&gt;Las credenciales de MySQL se mantienen en el servidor y nunca forman parte de la respuesta al navegador.&lt;/p&gt;

&lt;p&gt;La siguiente figura muestra la interfaz propia del inventario conectada al servidor MySQL local.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fduaqdporsu8nhmp5c6pl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fduaqdporsu8nhmp5c6pl.png" alt=" " width="658" height="312"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figura 1. Interfaz propia del inventario conectada al servidor MySQL local.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Exportación del respaldo con mysqldump
&lt;/h2&gt;

&lt;p&gt;El script &lt;code&gt;backup.sh&lt;/code&gt; verifica las variables, prepara un directorio temporal privado y ejecuta la exportación.&lt;/p&gt;

&lt;p&gt;La contraseña se escribe en un archivo temporal de opciones del cliente y no se coloca como argumento visible del comando.&lt;/p&gt;

&lt;p&gt;El archivo temporal se elimina al terminar.&lt;/p&gt;

&lt;p&gt;El núcleo de la exportación utiliza las siguientes opciones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysqldump &lt;span class="nt"&gt;--defaults-extra-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;&lt;span class="s2"&gt;/client.cnf"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--single-transaction&lt;/span&gt; &lt;span class="nt"&gt;--quick&lt;/span&gt; &lt;span class="nt"&gt;--no-tablespaces&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set-gtid-purged&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;OFF &lt;span class="nt"&gt;--column-statistics&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--hex-blob&lt;/span&gt; &lt;span class="nt"&gt;--default-character-set&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf8mb4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK_DIR&lt;/span&gt;&lt;span class="s2"&gt;/dump.sql.gz"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La opción:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--single-transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;proporciona una instantánea consistente para tablas transaccionales como InnoDB.&lt;/p&gt;

&lt;p&gt;Durante el dump deben evitarse modificaciones de estructura como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER
DROP
TRUNCATE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La demostración exporta la base utilizada por la aplicación.&lt;/p&gt;

&lt;p&gt;No conserva las cuentas y permisos del servidor ni incorpora procedimientos o eventos.&lt;/p&gt;

&lt;p&gt;Después de verificar el archivo comprimido, &lt;code&gt;crypto.js&lt;/code&gt; deriva una clave con &lt;code&gt;scrypt&lt;/code&gt; y una sal aleatoria.&lt;/p&gt;

&lt;p&gt;El cifrado utiliza:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AES 256 GCM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;junto con un nonce aleatorio y una etiqueta de autenticación.&lt;/p&gt;

&lt;p&gt;El resultado se guarda con fecha y un identificador aleatorio, acompañado de un checksum SHA-256.&lt;/p&gt;

&lt;p&gt;La frase secreta debe conservarse por separado durante todo el periodo de retención.&lt;/p&gt;

&lt;p&gt;Desde PowerShell, la ejecución utiliza el contenedor de herramientas del proyecto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--profile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scripts/backup.sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;backups&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los archivos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.sql.gz.enc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;no deben abrirse como si fueran archivos SQL sin cifrar.&lt;/p&gt;

&lt;p&gt;El archivo cifrado y su checksum deben conservarse juntos.&lt;/p&gt;

&lt;p&gt;La política de siete días se refiere a antigüedad, no a una cantidad exacta de archivos.&lt;/p&gt;

&lt;p&gt;Varias ejecuciones manuales pueden producir varias copias durante el mismo día.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recuperación en una base independiente
&lt;/h2&gt;

&lt;p&gt;La recuperación se diseñó para revisar la copia sin sobrescribir el inventario activo.&lt;/p&gt;

&lt;p&gt;El script &lt;code&gt;restore.sh&lt;/code&gt; exige un nombre de destino diferente de &lt;code&gt;DB_NAME&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Además:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verifica el checksum.&lt;/li&gt;
&lt;li&gt;Autentica el descifrado.&lt;/li&gt;
&lt;li&gt;Comprueba la compresión.&lt;/li&gt;
&lt;li&gt;Confirma que el destino exista.&lt;/li&gt;
&lt;li&gt;Confirma que el destino no contenga tablas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En el laboratorio, una cuenta con permisos de administración crea la base de recuperación y concede acceso al usuario de la aplicación.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;backup_demo_restore&lt;/span&gt;
  &lt;span class="nb"&gt;CHARACTER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt;
  &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;backup_demo_restore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'demo'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Después se configura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RESTORE_DB_NAME=backup_demo_restore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A continuación se selecciona una copia de seguridad.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$backup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;backups/&lt;/span&gt;&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="nf"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Sort-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LastWriteTime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Descending&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finalmente se ejecuta la restauración.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;compose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--profile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--rm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scripts/restore.sh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"backups/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$backup&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La prueba realizada con MySQL 8.0.46 creó un cuarto producto antes de respaldar.&lt;/p&gt;

&lt;p&gt;La recuperación importó las cuatro filas y comprobó la coincidencia de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identificador&lt;/li&gt;
&lt;li&gt;Nombre&lt;/li&gt;
&lt;li&gt;Precio&lt;/li&gt;
&lt;li&gt;Stock&lt;/li&gt;
&lt;li&gt;Fecha&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Posteriormente se eliminó el producto de prueba desde la API y se verificó que siguiera presente en la copia restaurada.&lt;/p&gt;

&lt;p&gt;Los productos recuperados se muestran en la siguiente tabla.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwlyiuznz6zyg8hzwtfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjwlyiuznz6zyg8hzwtfm.png" alt=" " width="643" height="232"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tabla 5. Productos recuperados en la prueba local.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Los registros recuperados fueron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ID 1 - Cuaderno - 12.50 - Stock 30
ID 2 - Lapicero - 2.00 - Stock 50
ID 3 - Carpeta - 8.00 - Stock 10
ID 4 - Prueba recuperación - 25.50 - Stock 15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La prueba también confirmó el rechazo de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Un archivo modificado.&lt;/li&gt;
&lt;li&gt;Una base de destino con tablas.&lt;/li&gt;
&lt;li&gt;Un destino con el mismo nombre de la base original.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La importación de una base completa no es atómica.&lt;/p&gt;

&lt;p&gt;Si falla después de crear objetos, debe revisarse el destino antes de repetir la operación.&lt;/p&gt;




&lt;h2&gt;
  
  
  MySQL en Aiven y aplicación en Railway
&lt;/h2&gt;

&lt;p&gt;Aiven proporciona el servicio de MySQL.&lt;/p&gt;

&lt;p&gt;Desde su consola se obtienen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host&lt;/li&gt;
&lt;li&gt;Puerto&lt;/li&gt;
&lt;li&gt;Usuario&lt;/li&gt;
&lt;li&gt;Contraseña&lt;/li&gt;
&lt;li&gt;Nombre de base&lt;/li&gt;
&lt;li&gt;Certificado CA&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Puede utilizarse una base creada para el proyecto o la base disponible en el servicio, como &lt;code&gt;defaultdb&lt;/code&gt; si ese es su nombre real.&lt;/p&gt;

&lt;p&gt;No se debe asumir que el puerto remoto sea &lt;code&gt;3306&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Railway aloja la aplicación Node.js.&lt;/p&gt;

&lt;p&gt;El servicio utiliza el &lt;code&gt;Dockerfile&lt;/code&gt; y las variables de conexión para llegar a Aiven.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;railway.json&lt;/code&gt; prepara la inicialización de la tabla antes del despliegue y define:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;como comprobación de funcionamiento.&lt;/p&gt;

&lt;p&gt;Si la tabla ya existe, un nuevo despliegue conserva sus datos.&lt;/p&gt;

&lt;p&gt;Las variables necesarias para Railway se presentan en la siguiente tabla.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fztreghy5jysepw16n68l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fztreghy5jysepw16n68l.png" alt=" " width="647" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tabla 6. Variables de conexión del servicio Railway.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Las variables utilizadas son:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_HOST
DB_PORT
DB_USER
DB_PASSWORD
DB_NAME
DB_SSL
DB_SSL_CA
ADMIN_API_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para la conexión externa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_SSL=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Después de crear el servicio se genera un dominio público y se comprueban:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/
 /health
 /productos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La aplicación escucha en:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.0.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;y utiliza el puerto proporcionado por el entorno.&lt;/p&gt;

&lt;p&gt;La conexión desde GitHub Actions también necesita alcanzar Aiven.&lt;/p&gt;

&lt;p&gt;Si el servicio limita las direcciones de acceso, debe autorizarse el origen del runner o utilizarse un runner con salida permitida.&lt;/p&gt;

&lt;p&gt;Las claves se guardan como Secrets y el certificado se configura sin desactivar la verificación TLS.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pruebas y despliegue automatizado
&lt;/h2&gt;

&lt;p&gt;El proyecto separa el ciclo de publicación del proceso que respalda los datos.&lt;/p&gt;

&lt;p&gt;El archivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ci.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;está configurado para:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ejecutar pruebas de API.&lt;/li&gt;
&lt;li&gt;Ejecutar pruebas de cifrado.&lt;/li&gt;
&lt;li&gt;Iniciar MySQL 8.4.&lt;/li&gt;
&lt;li&gt;Comprobar una recuperación de laboratorio.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El archivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deploy.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;escucha la finalización de CI para la rama principal y despliega el commit que terminó correctamente.&lt;/p&gt;

&lt;p&gt;El evento se define de esta manera:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;workflows&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;MySQL&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;recuperación"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;completed&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El job comprueba:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Que CI haya concluido correctamente.&lt;/li&gt;
&lt;li&gt;Que proceda de un push del mismo repositorio.&lt;/li&gt;
&lt;li&gt;Que &lt;code&gt;DEPLOY_ENABLED&lt;/code&gt; sea &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Posteriormente utiliza un Project Token de Railway y el ID del servicio.&lt;/p&gt;

&lt;p&gt;El despliegue se ejecuta mediante:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;railway up &lt;span class="nt"&gt;--service&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RAILWAY_SERVICE_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El comando espera el resultado del despliegue.&lt;/p&gt;

&lt;p&gt;Después se consulta la URL pública de:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para demostrar la automatización, se debe:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cambiar un texto de la interfaz.&lt;/li&gt;
&lt;li&gt;Hacer commit.&lt;/li&gt;
&lt;li&gt;Hacer push a &lt;code&gt;main&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Observar la ejecución de CI.&lt;/li&gt;
&lt;li&gt;Observar el despliegue posterior.&lt;/li&gt;
&lt;li&gt;Comprobar el cambio en la aplicación pública.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Conviene mantener un solo mecanismo de despliegue y desactivar el autodeploy nativo de Railway para evitar publicaciones duplicadas.&lt;/p&gt;




&lt;h2&gt;
  
  
  Backup remoto programado con GitHub Actions
&lt;/h2&gt;

&lt;p&gt;El archivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backup.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;prepara un contenedor con cliente MySQL 8.4 y ejecuta el mismo procedimiento de exportación, compresión y cifrado utilizado en el laboratorio.&lt;/p&gt;

&lt;p&gt;Los parámetros de Aiven y la frase de cifrado se obtienen mediante Secrets.&lt;/p&gt;

&lt;p&gt;No se imprime la contraseña ni se sube un dump sin cifrar.&lt;/p&gt;

&lt;p&gt;La programación utiliza:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;6&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*"&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto configura una ejecución diaria a las 06:00 UTC y también permite ejecutarla manualmente.&lt;/p&gt;

&lt;p&gt;Los artefactos se guardan mediante:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql-backup-${{ github.run_id }}&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;backups/*.sql.gz.enc&lt;/span&gt;
      &lt;span class="s"&gt;backups/*.sql.gz.enc.sha256&lt;/span&gt;
    &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
    &lt;span class="na"&gt;if-no-files-found&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La condición del job requiere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BACKUP_ENABLED=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Los Secrets incluyen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_HOST
DB_PORT
DB_USER
DB_PASSWORD
DB_NAME
DB_SSL_CA
BACKUP_PASSPHRASE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para Aiven también se utiliza:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BACKUP_DB_SSL=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El usuario del dump necesita los permisos de lectura y de los objetos que exporta el procedimiento.&lt;/p&gt;

&lt;p&gt;Para validar la configuración se ejecuta primero:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Run workflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Después se descarga el artefacto cifrado y se restaura en una base vacía compatible utilizando la misma frase del respaldo.&lt;/p&gt;

&lt;p&gt;El historial también deberá revisarse para confirmar una ejecución:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scheduled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una prueba manual exitosa no demuestra por sí sola que se haya ejecutado el cron.&lt;/p&gt;

&lt;p&gt;GitHub puede retrasar las ejecuciones programadas.&lt;/p&gt;




&lt;h2&gt;
  
  
  Evidencia y estado de los resultados
&lt;/h2&gt;

&lt;p&gt;La interfaz también se probó en navegador mediante el registro de un producto.&lt;/p&gt;

&lt;p&gt;La siguiente figura muestra ese producto dentro del inventario local.&lt;/p&gt;

&lt;p&gt;No representa una aplicación ya publicada en Railway.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwixsfx2y1x126xm3y58f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwixsfx2y1x126xm3y58f.png" alt=" " width="641" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Figura 2. Registro de un producto desde la interfaz del proyecto local.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Las validaciones realizadas y las actividades pendientes se presentan en la siguiente tabla.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F172o7ywrx6nn2gys74uk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F172o7ywrx6nn2gys74uk.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tabla 7. Validaciones realizadas y actividades pendientes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Los principales resultados fueron:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API, validación y cifrado: cuatro pruebas automáticas aprobadas.&lt;/li&gt;
&lt;li&gt;Respaldo y recuperación MySQL local: cuatro filas recuperadas con todos los campos iguales.&lt;/li&gt;
&lt;li&gt;Protecciones de restauración: archivo alterado, destino ocupado y destino original rechazados.&lt;/li&gt;
&lt;li&gt;Navegador: registro de producto y vista móvil comprobados.&lt;/li&gt;
&lt;li&gt;Docker y CI MySQL 8.4: configurados, pendientes de ejecución en la cuenta.&lt;/li&gt;
&lt;li&gt;GitHub público, Aiven y Railway: publicación y configuración externa pendientes.&lt;/li&gt;
&lt;li&gt;Backup remoto manual y Scheduled: pendientes de ejecutar y documentar.&lt;/li&gt;
&lt;li&gt;Video: archivo local de 4 minutos 58 segundos, publicación pendiente.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Los resultados de MySQL corresponden a una prueba real con la versión:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MySQL 8.0.46
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Las imágenes de Docker y los workflows están configurados para MySQL 8.4, pero no se atribuyen ejecuciones remotas a esa configuración.&lt;/p&gt;

&lt;p&gt;Al terminar la publicación deberán añadirse capturas propias de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repositorio.&lt;/li&gt;
&lt;li&gt;CI.&lt;/li&gt;
&lt;li&gt;Railway.&lt;/li&gt;
&lt;li&gt;URL pública.&lt;/li&gt;
&lt;li&gt;Historial de backups de Aiven.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusiones
&lt;/h2&gt;

&lt;p&gt;La recuperación comprobó que el archivo producido conserva información utilizable y permite revisar un estado anterior del inventario. El destino separado hizo posible validar las filas recuperadas sin importar el dump sobre la base activa.&lt;br&gt;
La solución reúne tres tareas diferentes: probar la aplicación, publicar sus cambios y respaldar los datos. GitHub Actions permite describir cada una por separado. Railway ejecuta la API y Aiven proporciona el MySQL remoto al que deben conectarse tanto la aplicación como el proceso de respaldo.&lt;br&gt;
Esta estrategia resulta adecuada para una base pequeña de demostración. Para producción se necesitarían alertas de fallos, simulacros periódicos, permisos mínimos y otra copia en almacenamiento independiente.&lt;/p&gt;


&lt;h2&gt;
  
  
  Enlaces del proyecto
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repositorio público del proyecto:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/Soanny/mysql-backup-strategy_Stevie-Marca_Soanny-Rivas.git" rel="noopener noreferrer"&gt;https://github.com/Soanny/mysql-backup-strategy_Stevie-Marca_Soanny-Rivas.git&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aplicación pública en Railway:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://mysql-backup-strategystevie-marcasoanny-rivas-production.up.railway.app/" rel="noopener noreferrer"&gt;https://mysql-backup-strategystevie-marcasoanny-rivas-production.up.railway.app/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Video público:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://youtu.be/ViPQl8gZ20U" rel="noopener noreferrer"&gt;https://youtu.be/ViPQl8gZ20U&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;p&gt;Aiven. (s. f.-a). &lt;em&gt;Connect to Aiven for MySQL® from the command line&lt;/em&gt;. Recuperado el 3 de octubre de 2026.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://aiven.io/docs/products/mysql/howto/connect-from-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aiven. (s. f.-b). &lt;em&gt;Connect to Aiven for MySQL® with MySQL Workbench&lt;/em&gt;. Recuperado el 3 de octubre de 2026.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://aiven.io/docs/products/mysql/howto/connect-from-mysql-workbench
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub. (s. f.). &lt;em&gt;Downloading workflow artifacts&lt;/em&gt;. Recuperado el 3 de octubre de 2026.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://docs.github.com/en/actions/how-tos/manage-workflow-runs/download-workflow-artifacts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oracle. (s. f.). &lt;em&gt;6.5.4 mysqldump — A database backup program&lt;/em&gt;. Recuperado el 3 de octubre de 2026.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://dev.mysql.com/doc/refman/8.4/en/mysqldump.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Railway. (s. f.-a). &lt;em&gt;Deploying with the CLI&lt;/em&gt;. Recuperado el 3 de octubre de 2026.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://docs.railway.com/cli/deploying
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Railway. (s. f.-b). &lt;em&gt;railway up&lt;/em&gt;. Recuperado el 3 de octubre de 2026.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://docs.railway.com/cli/up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>automation</category>
      <category>database</category>
      <category>devops</category>
      <category>githubactions</category>
    </item>
  </channel>
</rss>
