# Preinicialización de bases de datos con esquema y datos al inicio para entornos de desarrollo


La preinicialización (pre-seeding) de bases de datos con datos y esquemas esenciales durante el desarrollo local es una práctica común para mejorar el flujo de trabajo de desarrollo y pruebas. Al simular escenarios del mundo real, esta práctica ayuda a detectar problemas del frontend a tiempo, garantiza la alineación entre los administradores de bases de datos y los ingenieros de software, y facilita una colaboración más fluida. La preinicialización ofrece beneficios como despliegues seguros, coherencia entre entornos y detección temprana de problemas, mejorando en última instancia el proceso de desarrollo general.

En esta guía, aprenderás a:

- Usar Docker para iniciar un contenedor de Postgres
- Preinicializar Postgres usando un script SQL
- Preinicializar Postgres copiando archivos SQL en la imagen de Docker
- Preinicializar Postgres usando código JavaScript

## Uso de Postgres con Docker

La [imagen oficial de Docker para Postgres](https://hub.docker.com/_/postgres) proporciona una forma conveniente de ejecutar la base de datos Postgres en tu máquina de desarrollo. Una imagen Docker de Postgres es un entorno preconfigurado que encapsula el sistema de bases de datos PostgreSQL. Es una unidad autónoma lista para ejecutarse en un contenedor Docker. Al usar esta imagen, puedes configurar una instancia de Postgres de manera rápida y directa, sin necesidad de realizar configuraciones manuales.

## Requisitos previos

Se requieren los siguientes requisitos previos para seguir esta guía práctica:

- [Docker Desktop](https://www.docker.com/products/docker-desktop/)

## Iniciar Postgres

Inicia una demostración rápida de Postgres siguiendo estos pasos:

1. Abre la terminal y ejecuta el siguiente comando para iniciar un contenedor de Postgres.

   Este ejemplo iniciará un contenedor de Postgres, expondrá el puerto `5432` en el host para permitir que una aplicación que se ejecuta de forma nativa se conecte a él con la contraseña `mysecretpassword`.

   ```console
   $ docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=mysecretpassword postgres
   ```

2. Verifica que Postgres esté en funcionamiento seleccionando el contenedor y revisando los registros en el panel de Docker Desktop.

   ```plaintext
   PostgreSQL Database directory appears to contain a database; Skipping initialization

   2024-09-08 09:09:47.136 UTC [1] LOG:  starting PostgreSQL 16.4 (Debian 16.4-1.pgdg120+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
   2024-09-08 09:09:47.137 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
   2024-09-08 09:09:47.137 UTC [1] LOG:  listening on IPv6 address "::", port 5432
   2024-09-08 09:09:47.139 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
   2024-09-08 09:09:47.142 UTC [29] LOG:  database system was shut down at 2024-09-08 09:07:09 UTC
   2024-09-08 09:09:47.148 UTC [1] LOG:  database system is ready to accept connections
   ```

3. Conéctate a Postgres desde el sistema local.

   `psql` es la consola interactiva de PostgreSQL que se utiliza para conectarse a una base de datos Postgres y te permite comenzar a ejecutar comandos SQL. Asumiendo que ya tienes instalada la utilidad `psql` en tu sistema local, es hora de conectarse a la base de datos. Ejecuta el siguiente comando en tu terminal local:

   ```console
   $ docker exec -it postgres psql -h localhost -U postgres
   ```

   Ahora puedes ejecutar cualquier consulta o comando SQL que necesites dentro del prompt de `psql`.

   Usa `\q` o `\quit` para salir de la consola interactiva de Postgres.

## Preinicializar la base de datos Postgres usando un script SQL

Una vez que te hayas familiarizado con Postgres, es hora de ver cómo preinicializarlo con datos de ejemplo. En esta demostración, primero crearás un script con comandos SQL. El script define la base de datos y la estructura de la tabla, e inserta datos de ejemplo. Luego te conectarás a la base de datos para verificar los datos.

Asumiendo que tienes una instancia de base de datos Postgres en funcionamiento, sigue estos pasos para inicializar la base de datos.

1. Crea un archivo vacío llamado `seed.sql` y agrega el siguiente contenido.

   ```sql
   CREATE DATABASE sampledb;

   \c sampledb

   CREATE TABLE users (
     id SERIAL PRIMARY KEY,
     name VARCHAR(50),
     email VARCHAR(100) UNIQUE
   );

   INSERT INTO users (name, email) VALUES
     ('Alpha', 'alpha@example.com'),
     ('Beta', 'beta@example.com'),
     ('Gamma', 'gamma@example.com');  
   ```

   El script SQL crea una nueva base de datos llamada `sampledb`, se conecta a ella y crea una tabla `users`. La tabla incluye un `id` autoincremental como clave primaria, un campo `name` con una longitud máxima de 50 caracteres y un campo `email` único de hasta 100 caracteres.

   Después de crear la tabla, el comando `INSERT` introduce tres usuarios en la tabla `users` con sus respectivos nombres y correos electrónicos. Esta configuración establece una estructura de base de datos básica para almacenar información de usuarios con direcciones de correo electrónico únicas.

2. Inicializar la base de datos.

   Es hora de enviar el contenido de `seed.sql` directamente a la base de datos. El comando se utiliza para ejecutar el script SQL llamado `seed.sql` en la base de datos Postgres llamada `sampledb`. 

   ```console
   $ cat seed.sql | docker exec -i postgres psql -h localhost -U postgres -f-
   ```

   Una vez que la consulta se haya ejecutado, verás los siguientes resultados:

   ```plaintext
   CREATE DATABASE
   You are now connected to database "sampledb" as user "postgres".
   CREATE TABLE
   INSERT 0 3
   ```

3. Ejecuta el siguiente comando `psql` para verificar si la tabla llamada users se ha poblado en la base de datos `sampledb` o no. 

   ```console
   $ docker exec -it postgres psql -h localhost -U postgres sampledb
   ```

   Puedes ejecutar `\l` en la consola de `psql` para listar todas las bases de datos en el servidor de Postgres.

   ```console
   sampledb=# \l
                                               List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    | ICU Locale | Locale Provider |   Access privileges
   -----------+----------+----------+------------+------------+------------+-----------------+-----------------------
   postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            |
   sampledb  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            |
   template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | =c/postgres          +
             |          |          |            |            |            |                 | postgres=CTc/postgres
   template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | =c/postgres          +
             |          |          |            |            |            |                 | postgres=CTc/postgres
   (4 rows)
   ```

   Para recuperar todos los datos de la tabla users, ingresa la siguiente consulta:

   ```console
   sampledb=# SELECT * FROM users;
   id | name  |       email
   ----+-------+-------------------
    1 | Alpha | alpha@example.com
    2 | Beta  | beta@example.com
    3 | Gamma | gamma@example.com
   (3 rows)
   ```
  
   Usa `\q` o `\quit` para salir de la consola interactiva de Postgres.

## Preinicializar la base de datos montando un script SQL

En Docker, el montaje (mounting) se refiere a hacer que los archivos o directorios del sistema host sean accesibles dentro de un contenedor. Esto te ayuda a compartir datos o archivos de configuración entre el host y el contenedor, permitiendo una mayor flexibilidad y persistencia.

Una vez que has aprendido a iniciar Postgres y a preinicializar la base de datos usando un script SQL, es hora de aprender a montar un archivo SQL directamente en el directorio de inicialización de los contenedores de Postgres (`/docker-entrypoint-initdb.d`). El directorio `/docker-entrypoint-initdb.d` es una carpeta especial en los contenedores Docker de PostgreSQL que se utiliza para inicializar la base de datos cuando el contenedor se inicia por primera vez.

Asegúrate de detener cualquier contenedor de Postgres que esté en ejecución (junto con sus volúmenes) para evitar conflictos de puertos antes de seguir los pasos:

```console
$ docker container stop postgres
```

1. Modifica `seed.sql` con las siguientes entradas:

   ```sql
   CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    email VARCHAR(100) UNIQUE
   );

   INSERT INTO users (name, email) VALUES
    ('Alpha', 'alpha@example.com'),
    ('Beta', 'beta@example.com'),
    ('Gamma', 'gamma@example.com')
   ON CONFLICT (email) DO NOTHING;
   ```
   
2. Crea un archivo de texto llamado `Dockerfile` y copia el siguiente contenido.

   ```plaintext
   # syntax=docker/dockerfile:1
   FROM postgres:18
   COPY seed.sql /docker-entrypoint-initdb.d/
   ```

   Este Dockerfile copia el script `seed.sql` directamente en el directorio de inicialización del contenedor de PostgreSQL.
   

3. Usar Docker Compose.
   
   El uso de Docker Compose facilita aún más la administración y el despliegue del contenedor de PostgreSQL con la base de datos inicializada. Este archivo compose.yml define un servicio de Postgres llamado `db` que utiliza la última imagen de Postgres, el cual configura una base de datos con el nombre `sampledb`, junto con un usuario `postgres` y la contraseña `mysecretpassword`. 

   ```yaml
   services:
     db:
       build:
         context: .
         dockerfile: Dockerfile
       container_name: my_postgres_db
       environment:
         POSTGRES_USER: postgres
         POSTGRES_PASSWORD: mysecretpassword
         POSTGRES_DB: sampledb
       ports:
         - "5432:5432"
       volumes:
         - data_sql:/var/lib/postgresql   # Almacenamiento persistente de datos

   volumes:
     data_sql:
    ```
  
     Mapea el puerto `5432` del host al `5432` del contenedor, lo que te permite acceder a la base de datos Postgres desde fuera del contenedor. También define `data_sql` para conservar los datos de la base de datos de manera persistente, asegurando que la información no se pierda al detener el contenedor.

     Cabe destacar que el mapeo de puertos al host solo es necesario si deseas conectarte a la base de datos desde programas no contenedorizados. Si contenedorizas el servicio que se conecta a la base de datos, deberías conectarte a ella a través de una red puente (bridge network) personalizada.

4. Iniciar el servicio de Compose.

     Asumiendo que has colocado el archivo `seed.sql` en el mismo directorio que el Dockerfile, ejecuta el siguiente comando:

     ```console
     $ docker compose up -d --build
     ```

5. Es hora de verificar si la tabla `users` se ha poblado con los datos. 

     ```console
     $ docker exec -it my_postgres_db psql -h localhost -U postgres sampledb
     ```

     ```sql 
     sampledb=# SELECT * FROM users;
       id | name  |       email
     ----+-------+-------------------
        1 | Alpha | alpha@example.com
        2 | Beta  | beta@example.com
        3 | Gamma | gamma@example.com
      (3 rows)

     sampledb=#
     ```


## Preinicializar la base de datos usando código JavaScript

Una vez que has aprendido a inicializar la base de datos utilizando varios métodos como scripts SQL, montaje de volúmenes, etc., es hora de intentar lograrlo utilizando código JavaScript. 

1. Crea un archivo .env con lo siguiente:

   ```plaintext
   POSTGRES_USER=postgres
   POSTGRES_DB_HOST=localhost
   POSTGRES_DB=sampledb
   POSTGRES_PASSWORD=mysecretpassword
   POSTGRES_PORT=5432
   ```

2. Crea un nuevo archivo JavaScript llamado seed.js con el siguiente contenido:

   El siguiente código JavaScript importa el paquete `dotenv`, el cual se utiliza para cargar variables de entorno desde un archivo `.env`. El método `.config()` lee el archivo `.env` y establece las variables de entorno como propiedades del objeto `process.env`. Esto te permite almacenar de forma segura información confidencial, como las credenciales de la base de datos, fuera de tu código.

   Luego, crea una nueva instancia de Pool de la biblioteca pg, la cual proporciona un grupo de conexiones (connection pool) para interacciones eficientes con la base de datos. La función `seedData` está definida para realizar las operaciones de inicialización de la base de datos. Se llama al final del script para iniciar el proceso de inicialización. El bloque try...catch...finally se utiliza para el manejo de errores. 

   ```js
   require('dotenv').config();  // Carga variables de entorno desde el archivo .env
   const { Pool } = require('pg');

   // Crea una nueva pool usando variables de entorno
   const pool = new Pool({
     user: process.env.POSTGRES_USER,
     host: process.env.POSTGRES_DB_HOST,
     database: process.env.POSTGRES_DB,
     port: process.env.POSTGRES_PORT,
     password: process.env.POSTGRES_PASSWORD,
   });

   const seedData = async () => {
     try {
        // Elimina la tabla si ya existe (opcional)
        await pool.query(`DROP TABLE IF EXISTS todos;`);

        // Crea la tabla con la estructura correcta
        await pool.query(`
          CREATE TABLE todos (
            id SERIAL PRIMARY KEY,
            task VARCHAR(255) NOT NULL,
            completed BOOLEAN DEFAULT false
              );
        `   );

        // Inserta datos de inicialización
        await pool.query(`
          INSERT INTO todos (task, completed) VALUES
          ('Watch netflix', false),
          ('Finish podcast', false),
          ('Pick up kid', false);
          `);
          console.log('Database seeded successfully!');
        } catch (err) {
          console.error('Error seeding the database', err);
        } finally {
          pool.end();
       }
     };

     // Llama a la función seedData para ejecutar el script
     seedData();
     ```

3. Inicia el proceso de inicialización

     ```console
     $ node seed.js
     ```

     Deberías ver el siguiente mensaje:

     ```plaintext
     Database seeded successfully!
     ```

4. Verifica si la base de datos se ha inicializado correctamente:

     ```console
     $ docker exec -it postgres psql -h localhost -U postgres sampledb
     ```

     ```console
     sampledb=# SELECT * FROM todos;
     id |      task      | completed
     ----+----------------+-----------
     1 | Watch netflix  | f
     2 | Finish podcast | f
     3 | Pick up kid    | f
     (3 rows)  
     ```

## Resumen

La preinicialización de una base de datos con esquema y datos al inicio es esencial para crear un entorno de pruebas coherente y realista, lo que ayuda a identificar problemas al principio del desarrollo y a alinear el trabajo del frontend y el backend. Esta guía te ha proporcionado los conocimientos y los pasos prácticos para lograr la preinicialización mediante varios métodos, incluyendo scripts SQL, integración con Docker y código JavaScript.

