Creamos un generador de propósitos de Año Nuevo con ChatGPT

ene. 1, 2025

Casi todo el mundo se hace propósitos de año nuevo al empezar o terminar uno, o, cuánto menos, nos replanteamos metas y objetivos. Para este artículo, teniendo en cuenta las fechas en las que estamos, vamos a hacer algo “diferente”, algo sencillo pero práctico. Vamos a crear una pequeña aplicación que nos permita generar nuestros propósitos para el año que viene, utilizando Inteligenica Artificial.

Haremos uso de ChatGPT de OpenAI para, enviando una petición a su API, recibir como respuesta un número determinado de propósitos para este nuevo año, lo importante de este artículo, más allá de su utilidad y funcionalidad, es el uso de ChatGPT a través de su API y cómo utilizar los prompts del sistema para obtener respuestas personalizadas y estructuradas para luego consumir en la parte del cliente.

La idea es simple, una página web que conste de una caja de texto y un botón, la caja de texto permitirá ingresar al usuario una o varias palabras sobre el tema del que le gustaría obtener propósitos, por ejemplo, “Desarrollo de Software”, “Deporte”, “Salud”, “Ocio y Entretenimiento”, etc. Al presionar el botón, se enviará una petición a la API de ChatGPT con el texto ingresado y se mostrarán los propósitos generados.

Para ello, necesitaremos una cuenta en OpenAI, la cual nos permitirá obtener una clave para poder hacer uso de la a API de ChatGPT. Una vez tengamos la API Key, ya podremos enviar solicitudes a la API de ChatGPT para generar nuestros propósitos. Utilizaremos HTML, CSS, Javascript y Docker para crear y arrancar la aplicación. En el repositorio de Github encontrarás el código fuente completo de la aplicación. Aquí te muestro el archivo HTML con la estructura de nuestra página:

    
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Generador de Propósitos 2025</title>
        <link rel="stylesheet" href="styles.css">
    </head>
    <body>
    <header>
        <h1>🎉 Propósitos para 2025 🎉</h1>
        <p>Genera metas motivadoras para el Año Nuevo</p>
    </header>
    <main>
        <div class="form-container">
            <div class="input-container">
                <input type="text" id="topic" placeholder="Escribe un tema (ej. Tecnología, Salud)"/>
                <button onclick="generateGoals()">Generar Propósitos</button>
            </div>
            <div id="output" class="results"></div>
        </div>
    </main>
    <footer>
        <p>💡 Inspiración para el Año Nuevo 💡</p>
        <p><a href="https://betazeta.dev" target="_blank" class="beta-link">Por BetaZetaDev</a></p>
    </footer>
    <script src="script.js"></script>
    </body>
    </html>
    

El método generateGoals() se encuentra en el fichero Javascript del cliente, dentro de la carpeta frontend y se encarga de realizar la petición al servidor (backend) entre otras cosas:


    const response = await fetch("http://localhost:3000/generate", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ topic }),
    });
    

Gracias al CSS que lo acompaña aplicamos un diseño sencillo utilizando tarjetas que simulan ser notas escritas a mano para darle una apariencia más acorde al contexto de nuestra aplicación. Además, almacenamos la clave de la API de OpenAI en un fichero .env y utilizamos dos ficheros JavaScript, uno para manejar la lógica de la aplicación y otro para la parte del servidor:


    require("dotenv").config();
    const express = require("express");
    const cors = require("cors");
    const bodyParser = require("body-parser");
    const axios = require("axios");
    
    const app = express();
    const port = 3000;
    
    app.use(cors());
    app.use(bodyParser.json());
    
    const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
    
    app.post("/generate", async (req, res) => {
        const {topic} = req.body;
    
        if (!topic) {
            return res.status(400).send({error: "El tema es obligatorio"});
        }
    
        try {
            const response = await axios.post(
                "https://api.openai.com/v1/chat/completions",
                {
                    model: "gpt-4o",
                    messages: [
                        {
                            role: "system",
                            content: `You are an assistant that must always return valid JSON data. The responses must be written in Spanish. Generate exactly 5 resolutions for the given topic. Follow this strict JSON format:
                            {
                              "goals": [
                                "Resolution 1",
                                "Resolution 2",
                                "Resolution 3",
                                "Resolution 4",
                                "Resolution 5"
                              ]
                            }
                            Do not include any additional text, comments, or explanations. Only return valid JSON.`
                        },
                        {
                            role: "user",
                            content: `Genera resoluciones para el tema: ${topic}`
                        }
                    ],
                    temperature: 0.7,
                },
                {
                    headers: {
                        "Content-Type": "application/json",
                        Authorization: `Bearer ${OPENAI_API_KEY}`,
                    },
                }
            );
    
            // Obtener el contenido de la respuesta
            let content = response.data.choices[0].message.content.trim();
    
            // Eliminar delimitadores de bloques de código (```json y ```)
            if (content.startsWith("```json")) {
                content = content.slice(7).trim(); // Quitar "```json"
            }
            if (content.endsWith("```")) {
                content = content.slice(0, -3).trim(); // Quitar "```"
            }
    
            // Intentar parsear el contenido como JSON
            try {
                const parsed = JSON.parse(content);
    
                if (!parsed.goals || !Array.isArray(parsed.goals) || parsed.goals.length !== 5) {
                    throw new Error("El JSON no contiene el formato esperado.");
                }
    
                // Formatear y enviar la respuesta
                res.send({
                    title: `Propósitos de Año Nuevo para ${topic}`,
                    goals: parsed.goals,
                });
            } catch (parseError) {
                console.error("Error al parsear JSON:", parseError.message, content);
                res.status(500).send({error: "La respuesta no es un JSON válido."});
            }
        } catch (error) {
            console.error("Error al llamar a la API de OpenAI:", error.response?.data || error.message);
            res.status(500).send({error: "Hubo un problema al generar los propósitos."});
        }
    });
    
    app.listen(port, () => {
        console.log(`Servidor corriendo en http://localhost:${port}`);
    });
    

La parte crucial de nuestra aplicación, se ejecuta en el servidor y se encarga de gestionar las peticiones contra ChatGPT, petición que, como decíamos antes para la hace uso del prompt de sistema. Éste le indica a ChatGPT los requisitos que deben cumplir su respuesta y lo aprovechamos también para indicarle la estructura del JSON que esperamos recibir para poder parsear correctamente la respuesta una vez recibida y mostrar los propósitos en la página. Hay que tener en cuenta que en una de las actualizaciones de la API de ChatGPT se añadieron lo que llaman Structured Outputs. El problema de hacerlo de la forma de nuestro ejemplo es que puede pasar que ChatGPT no sea totalmente estricto con los requisitos indicados en el prompt del sistema y devuelva una respuesta con un formato diferente al indicado, por lo que es importante tener en cuenta este detalle. Para cerciorarnos de que la respuesta es correcta y tiene el formato esperado sin sorpresas tendríamos que utilizar los Structured Outputs.

Otro detalle a tener en cuenta es que, para poder gestionar de forma segura las claves de las API en general y no exponerlas en el código fuente, lo ideal es utilizar un servidor backend que se encargue de hacer las peticiones y devolver los resultados. Esto es bastante común, ya que permite separar la lógica de negocio del frontend y mantener las claves de las API seguras para que no puedan ser accedidas y utilizadas por terceros.

La estructura del proyecto quedaría de la siguiente manera:


    .
    ├── `backend`
    │   ├── .env
    │   ├── package.json
    │   └── server.js
    ├── docker-compose.yml
    ├── Dockerfile.`backend`
    ├── Dockerfile.`frontend`
    └── `frontend`
        ├── index.html
        ├── script.js
        └── styles.css

Para poder ejecutar la aplicación haremos uso de Docker, preparado ya para levantar un contenedor para el frontend y otro para el backend. Utilizaremos un archivo docker-compose.yml, además de dos ficheros Dockerfile. En el repositorio puedes ver todos los ficheros y su contenido.

Una vez tengamos todos los archivos necesarios, podremos construir y ejecutar la aplicación con el siguiente comando:


    docker compose up --build -d
  

Esto lanzará ambos contenedores Docker en segundo plano, frontend y backend, los cuales se comunicarán entre sí, las peticiones del frontend se envían al backend que se encarga de realizar la solicitud y gestionar la respuesta devolviéndola al cliente. Una vez los contenedores estén en ejecución, podremos acceder a la aplicación desde nuestro navegador web en la dirección http://localhost:8080 y empezar a generar nuestros propósitos para el año nuevo.

Ha sido un ejemplo sencillo pero práctico, en el que observamos cómo utilizar la inteligencia artificial en aplicaciones web de forma sencilla y el uso del prompt del sistema para obtener respuestas personalizadas al utilizar ChatGPT. También podemos comprobar cómo separar la lógica de negocio del frontend utilizando un servidor backend de forma sencilla, sin necesidad de frameworks complejos ni librerías adicionales, más allá de las estrictamente necesarias. Cabe mencionar que, en realidad, la parte del frontend en Docker no es necesaria ya que la aplicación puede ejecutarse directamente desde el navegador abriendo el archivo index.html, pero utilizamos Docker para practicar el uso de contenedores.

Por nuestra parte simplemente desearte un muy feliz año nuevo, que los propósitos que te marques se cumplan y mucho éxito en todos tus proyectos y metas, tanto personales como profesionales.

¡Feliz año nuevo y Happy Coding!

Artículos relacionados

Quizá te puedan interesar