{"id":27235609,"url":"https://github.com/galiprandi/api-stock","last_synced_at":"2025-04-10T16:47:51.806Z","repository":{"id":276415255,"uuid":"929223949","full_name":"galiprandi/api-stock","owner":"galiprandi","description":"Aprende a construir una API REST profesional con Node.js, TypeScript, Express, Prisma y PostgreSQL.","archived":false,"fork":false,"pushed_at":"2025-03-02T21:13:39.000Z","size":135,"stargazers_count":3,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-02T21:30:17.823Z","etag":null,"topics":["backend","expressjs","nodejs","posgresql","prisma-orm","product","tsx","typescript"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/galiprandi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-02-08T03:40:34.000Z","updated_at":"2025-03-02T21:13:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"397af394-715b-4adf-bf1b-782872896c9a","html_url":"https://github.com/galiprandi/api-stock","commit_stats":null,"previous_names":["galiprandi/api-stock"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/galiprandi%2Fapi-stock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/galiprandi%2Fapi-stock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/galiprandi%2Fapi-stock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/galiprandi%2Fapi-stock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/galiprandi","download_url":"https://codeload.github.com/galiprandi/api-stock/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248254700,"owners_count":21073133,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backend","expressjs","nodejs","posgresql","prisma-orm","product","tsx","typescript"],"created_at":"2025-04-10T16:47:46.194Z","updated_at":"2025-04-10T16:47:51.788Z","avatar_url":"https://github.com/galiprandi.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# 📦 API Stock – Curso de Backend en TypeScript\n\nEste es un curso práctico y progresivo diseñado para personas con conocimientos básicos de JavaScript y TypeScript que quieren aprender a construir un backend moderno con tecnologías actuales. A diferencia de otros cursos, aquí trabajarás en un proyecto real desde el primer día, evolucionando tu propio repositorio en GitHub a medida que avanzas.\n\n## 🎯 Objetivo del Curso\n\nEl objetivo es que, al finalizar, tengas una API funcional y escalable en tu GitHub, construida con buenas prácticas y herramientas profesionales. Aprenderás desde los fundamentos hasta técnicas avanzadas, ganando experiencia en un entorno similar al de un equipo de desarrollo real.\n\nEste curso no solo te enseña a programar, sino que también te prepara para enfrentar desafíos comunes en el desarrollo backend, con un enfoque en depuración, pruebas automatizadas y uso de herramientas modernas.\n\n## 👤 ¿A quién va dirigido?\n\n- Personas que ya hicieron cursos introductorios de JavaScript/TypeScript y quieren dar el siguiente paso en backend.\n- Desarrolladores que buscan experiencia práctica con un proyecto real.\n- Estudiantes o autodidactas que quieran fortalecer su portafolio en GitHub.\n- Quienes quieran aprender herramientas modernas como Prisma, GitHub Copilot y Zod aplicadas a un backend real.\n\nNo es un curso para **absolutos principiantes** en programación. Se asume que entendés los fundamentos de JavaScript y TypeScript, pero no necesitás experiencia profesional en backend.\n\n## 🛠️ Tecnologías y herramientas\n\nDurante el curso, trabajarás con un stack moderno que simula lo que se usa en la industria:\\\n\n✅ **TypeScript**: Código tipado para mayor seguridad y escalabilidad.\\\n✅ **Express v5**: Framework rápido y flexible para APIs REST.\\\n✅ **Prisma ORM**: Interfaz moderna para bases de datos SQL.\\\n✅ **PostgreSQL**: Base de datos robusta y estándar en la industria.\\\n✅ **Vitest + Supertest**: Pruebas automatizadas con TDD.\\\n✅ **Zod** – Validaciones de datos seguras y declarativas.\\\n✅ **GitHub Copilot + ChatGPT**: Aprendizaje asistido con IA.\\\n✅ **GitHub Actions**: _(opcional)_ Automatización de pruebas y despliegue.\\\n✅ **Docker + Railway**: _(opcional)_ Entornos de desarrollo y producción reales.\n\n## 🚀 ¿Qué aprenderás?\n\n✔️ Configurar un backend profesional desde cero.\\\n✔️ Crear endpoints REST con buenas prácticas.\\\n✔️ Manejar errores y validaciones avanzadas.\\\n✔️ Conectar y gestionar bases de datos SQL con Prisma.\\\n✔️ Aplicar TDD con pruebas automatizadas.\\\n✔️ Implementar autenticación y autorización con JWT.\\\n✔️ Estructurar código de forma escalable con una arquitectura en capas.\\\n✔️ Optimizar la API con paginación, filtros y consultas eficientes.\\\n✔️ Desplegar la API en producción _(opcional, con pasos extra)_.\n\n## 📌 Metodología y progresión\n\nEste curso tiene un enfoque práctico y progresivo:\n\n1. **Inicio guiado** – Al principio, cada paso está detallado línea por línea.\n2. **Menos guía, más autonomía** – Luego, se dan requisitos y tests, dejando que el estudiante implemente.\n3. **Uso de herramientas como GitHub Copilot** – Para fomentar la resolución de problemas de forma autónoma.\n4. **Validación con pruebas** – Todo el código debe pasar tests para considerarse correcto.\n5. **Pasos opcionales** – Funcionalidades extra para quienes quieran profundizar más.\n\n## 🌍 Comunidad y soporte\n\nEl curso fomenta el aprendizaje colaborativo dentro de GitHub, usando:\n\n📌 **GitHub Discussions** – Espacio para dudas y debates.\\\n📌 **Issues** – Para reportar errores o sugerir mejoras.\\\n📌 **Pull Requests** – Para desafíos opcionales y contribuciones.\n\n## 💡 ¿Por qué este curso?\n\nA diferencia de otros cursos en español que se quedan en teoría o ejemplos básicos, este curso:\n\n✅ Te da un proyecto real que podés mostrar en GitHub.\\\n✅ Usa herramientas actuales y prácticas reales de la industria.\\\n✅ Te entrena en depuración y pruebas automatizadas.\\\n✅ No te deja todo servido: progresivamente te hace pensar y resolver problemas.\n\nSi querés aprender backend de verdad, no solo copiar código, este curso es para vos. 🚀\n\n## 🤝 Contribuciones\n\nSi querés mejorar este curso, podés contribuir de varias formas:\n\n- Reportando errores o mejoras en la sección de Issues.\n- Proponiendo cambios mediante Pull Requests.\n- Compartiendo el curso con otros desarrolladores.\n\nTodas las sugerencias y mejoras son bienvenidas. 🚀\n\n## 👤 Autor\n\nEste curso fue creado por [Germán Aliprandi](https://www.linkedin.com/in/galiprandi). Si tenés preguntas o sugerencias, no dudes en contactarme o abrir una discusión en GitHub.\n\n## 📜 Licencia\n\nEste proyecto está bajo la Licencia MIT, lo que significa que podés usar, modificar y distribuir el código con total libertad, siempre que incluyas la licencia original.\n\n## Paso 1: Fork del repositorio [api-stock](https://github.com/galiprandi/api-stock)\n\n\u003e 📚 ¿Que es un fork? Un fork es una copia de un repositorio que se crea en tu cuenta de GitHub. Permite que puedas hacer cambios en el proyecto original sin afectar el repositorio principal. Los forks son útiles cuando deseas contribuir a un proyecto de código abierto, ya que puedes trabajar en tus propias modificaciones y luego proponer que se integren en el proyecto original mediante un pull request.\n\n### Verificar que tengas una cuenta en GitHub\n\nSi no tienes una cuenta en GitHub, [regístrate en GitHub](https://github.com/join).\nSi ya tienes una cuenta, asegúrate de haber iniciado sesión.\n\n### Instalar y configurar Git y GitHub CLI\n\nSigue las instrucciones en la [página oficial de Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).\nInstalar GitHub CLI (gh):\n\nSigue las instrucciones en la [página oficial de GitHub CLI](https://cli.github.com/).\nConfigurar Git:\n\nConfigura tu nombre de usuario y correo electrónico:\n\n```bash\ngit config --global user.name \"Tu Nombre\"\ngit config --global user.email \"tu-email@example.com\"\n```\n\n### Hacer el fork, clonar el repo y abrirlo en VSCode\n\nEn tu navegador, ve al repositorio original: https://github.com/galiprandi/api-stock.\nHaz clic en el botón \"Fork\" en la esquina superior derecha y selecciona tu cuenta.\nClonar el repositorio:\n\nEn tu terminal, clona el repositorio forkeado:\nCambia tu-usuario por tu nombre de usuario de GitHub.\nAbrir el proyecto en VSCode:\n\nNavega al directorio del proyecto clonado:\nAbre el proyecto en VSCode:\n\nCon estos pasos, habrás completado la configuración inicial y estarás listo para comenzar a trabajar en el proyecto.\n\n## Paso 2: Configuremos nuestro proyecto\n\nPrimero, necesitamos inicializar TypeScript en nuestro proyecto. Abre tu terminal y ejecuta el siguiente comando:\n\n```bash\nnpm install -D typescript # Instalar TypeScript como dependencia de desarrollo\nnpx tsc --init # Inicializar un archivo de configuración de TypeScript\n```\n\nEsto creará un archivo `tsconfig.json` en tu proyecto. A continuación, te muestro un ejemplo de un archivo `tsconfig.json` optimizado para una aplicación Node.js, reemplaza el contenido de tu archivo `tsconfig.json` con el siguiente código, te recomendamos que habras el proyecto en VSCode para hacerlo más fácil:\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"module\": \"commonjs\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    \"resolveJsonModule\": true,\n    \"sourceMap\": true\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n```\n\nEste archivo de configuración establece varias opciones importantes para un proyecto Node.js, como el objetivo de compilación (target), el sistema de módulos (module), y los directorios de salida y raíz (outDir y rootDir).\n\n\u003e 📚 Para más detalles sobre las opciones de configuración de TypeScript, te recomiendo leer la [cheat sheet de tsconfig](https://www.totaltypescript.com/tsconfig-cheat-sheet).\n\n### Scripts del package.json\n\nAhora, vamos a agregar algunos scripts útiles en nuestro archivo `package.json`. Abre el archivo `package.json` y agrega los siguientes scripts en la sección \"scripts\":\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"tsx watch --env-file=.env src/index.ts\",\n    \"build\": \"tsc\",\n    \"start\": \"node --env-file=.env dist/index.js\"\n  }\n}\n```\n\n- `npm run dev`: Este script utiliza tsx para ejecutar tu aplicación en modo de desarrollo, permitiendo recargas automáticas cuando cambias el código.\n\n- `npm run build`: Este script compila tu código TypeScript en JavaScript, colocando los archivos compilados en el directorio dist.\n\n- `npm run start`: Este script ejecuta la versión compilada de tu aplicación desde el directorio dist.\n\nCon estos scripts, estarás listo para desarrollar, compilar y ejecutar tu aplicación Node.js con TypeScript.\n\n### Instalemos dependencias de desarrollo\n\nPara poder ejecutar nuestra aplicación en modo de desarrollo, necesitamos instalar tsx y @types/node. Ejecuta el siguiente comando en tu terminal:\n\n\u003e 📚 ¿Qué es tsx? tsx es una herramienta que permite ejecutar TypeScript en Node.js con soporte para hot-reloading, lo que significa que la aplicación se reinicia automáticamente cuando se detectan cambios en el código fuente.\n\n\u003e 📚 ¿Cuales son las diferencias entre dependencias de desarrollo y de producción? Las dependencias de desarrollo son aquellas que solo se necesitan durante el proceso de desarrollo, como herramientas de prueba y compiladores. Las dependencias de producción son aquellas que se necesitan para que la aplicación funcione en un entorno de producción, como bibliotecas y frameworks necesarios para la ejecución del código.\n\n```bash\nnpm install -D tsx @types/node\n```\n\n### Gestión de variables de entorno\n\n\u003e 📚 ¿Qué son las variables de entorno? Las variables de entorno son valores dinámicos que pueden afectar el comportamiento de un programa. Se utilizan para configurar la aplicación en diferentes entornos, como desarrollo, pruebas y producción.\n\nAntes de crear el archivo `.env` que contrendrá información sensible, debemos crear un archivo `.gitignore` en la raíz de tu proyecto y agregues las siguientes líneas:\n\n```env\n.env\nnode_modules\ndist\n```\n\n⚠️ IMPORTANTE: Esto evitará que el archivo .env, con las variables de entorno sensibles, se suba al repositorio.\n\nPara gestionar las variables de entorno en nuestro proyecto, vamos a crear un archivo `.env` en la raíz de nuestro proyecto. Este archivo contendrá las variables de entorno necesarias para configurar nuestra aplicación.\n\nCrea un archivo `.env` en la raíz de tu proyecto y agrega las siguientes variables de entorno:\n\n```env\nPORT=3000\n```\n\nPara centrar la gestión de las variables de entorno en un solo lugar, vamos a crear un archivo de configuración `src/config.ts` que cargará las variables de entorno y las exportará para su uso en la aplicación.\n\nCrea un archivo `src/config.ts` y agrega el siguiente código:\n\n```typescript\nexport const config = {\n  ENV: process.env.NODE_ENV || \"development\",\n  PORT: process.env.PORT || 3000,\n};\n```\n\n### Hello World!\n\nPara verificar que todo está configurado correctamente, vamos a crear un simple \"Hello World!\" en la consola. Crea un archivo `src/index.ts` con el siguiente contenido:\n\n```typescript\nconsole.log(\"Hello, World!\");\n```\n\nAhora, ejecuta el siguiente comando en tu terminal:\n\n```bash\nnpm run dev\n```\n\nDeberías ver el mensaje \"Hello, World!\" impreso en la consola. Si ves este mensaje, ¡tu configuración de TypeScript está lista y funcionando! Ahora, puedes avanzar al siguiente paso.\n\n```bash\n# Salida esperada\n\u003e npx tsx watch --env-file=.env src/index.ts\n\nHello, World!\n```\n\n### Probando el hot-reloading\n\nPara probar el hot-reloading, modifica el mensaje en `src/index.ts` por \"Hello, TypeScript!\" y guarda el archivo. Deberías ver que el servidor se reinicia automáticamente y muestra el nuevo mensaje en la consola.\n\n\u003e 📚 ¿Qué es hot-reloading? Hot-reloading es una técnica que permite recargar automáticamente la aplicación cuando se detectan cambios en el código fuente. Es una característica muy útil para el desarrollo de aplicaciones, ya que permite ver los cambios en tiempo real sin tener que reiniciar manualmente el servidor.\n\n```bash\n# Salida esperada\nHello, TypeScript!\n```\n\n¡Excelente! Has configurado correctamente tu proyecto con TypeScript y tsx. Ahora, puedes avanzar al siguiente paso para configurar un servidor Express.\n\n## Paso 3: Configuración del Servidor Express y primer endpoint\n\nEn este paso, vamos a instalar Express y CORS, y crearemos un endpoint /api/health-check que devolverá `status: \"ready\"`.\n\n\u003e 📚 ¿Qué función tiene este endpoint? Este tipo de endpoints son comunes en las aplicaciones web y sirven para verificar si el servidor está en funcionamiento y listo para recibir solicitudes. Proporciona una forma sencilla de comprobar el estado del servidor y la conexión a la base de datos.\n\n### Instalar Dependencias\n\nEjecuta el siguiente comando en tu terminal para instalar Express y CORS:\n\n```bash\n# Instalar Express y CORS\nnpm install \"express@\u003e=5.0.0\" cors \n# Instalar tipos de TypeScript para Express y CORS\nnpm install -D @types/express @types/cors\n```\n\n### Configurar el Servidor\n\nCrea el archivo `src/libs/server.ts` y agrega el siguiente código:\n\n```typescript\nimport cors from \"cors\";\nimport express from \"express\";\n\nconst app = express();\n\n// Middleware\napp.use(cors());\napp.use(express.json());\n\n// Rutas\napp.get(\"/api/health-check\", (_req, res) =\u003e {\n  res.json({ status: \"ready\", uptime: process.uptime() });\n});\n\n// Exportar el servidor para usarlo en index.ts\nexport { app };\n```\n\n\u003e 📚 ¿Qué es un middleware? En Express, un middleware es una función que tiene acceso al objeto de solicitud (req), al objeto de respuesta (res) y a la siguiente función de middleware en el ciclo de solicitud-respuesta. Los middlewares se utilizan para realizar tareas como el registro de solicitudes, la validación de datos, la autenticación de usuarios, etc.\n\n### Inicializar el Servidor\n\nEdita `src/index.ts` para importar e iniciar el servidor:\n\n```typescript\nimport { config } from \"./config\";\nimport { app } from \"./libs/server\";\n\nconst { PORT } = config;\n\napp.listen(PORT, () =\u003e {\n  console.log(\n    `🚀 Server is up and running! Access it at: http://localhost:${PORT}/api/health-check`,\n  );\n});\n```\n\n### Probar el Servidor\n\nEjecuta el siguiente comando:\n\n```bash\nnpm run dev\n```\n\nLuego, abre tu navegador o usa curl para probar el endpoint:\n\n```bash\ncurl http://localhost:3000/api/health-check\n```\n\nDeberías recibir esta respuesta:\n\n```json\n{ \"status\": \"ready\", \"uptime\": 0.123 }\n```\n\n\u003e 📚 ¿Qué es curl? curl es una herramienta de línea de comandos que permite transferir datos con URL sintácticas. Es una herramienta muy practica para realizar solicitudes HTTP desde la terminal.\n\n### Criterios de Aceptación del Paso 3\n\n- [ ] Deberás instalar Express y CORS en tu proyecto.\n- [ ] Deberás crear un servidor Express en el archivo `src/libs/server.ts`.\n- [ ] El servidor deberá tener un endpoint GET `/api/health-check` que devuelva `{ status: 'ready' }`.\n- [ ] Deberás inicializar el servidor en el archivo `src/index.ts` y escuchar en el puerto 3000.\n- [ ] Deberás probar el servidor y verificar que el endpoint `/api/health-check` responda correctamente.\n\n### 🎉 ¡Felicitaciones!\n\nHas creado tu primer endpoint en Express. Ahora, puedes avanzar al siguiente paso para implementar más funcionalidades en tu API.\n\n## Paso 4: Agregar Pruebas Unitarias\n\n\u003e 📚 ¿Qué son las pruebas unitarias? Las pruebas unitarias son pruebas automatizadas que verifican que una unidad de código (como una función o un módulo) funcione correctamente. Estas pruebas se centran en probar partes específicas del código para garantizar que se comporten como se espera.\n\nEn este paso, agregaremos pruebas automatizadas para verificar que el endpoint /api/health-check responde correctamente.\n\n### Instalar Dependencias necesarias\n\nPrimero, necesitamos instalar Vitest y Supertest para realizar pruebas automatizadas. Ejecuta el siguiente comando en tu terminal:\n\n```bash\nnpm install -D vitest supertest @types/supertest\n```\n\n### Crear el Archivo de Pruebas\n\n\u003e 💡 Todos los archivos de pruebas unitarias estarán alojados en la carpeta `src/tests/` lo que facilita su organización y mantenimiento.\n\nCrea el archivo `src/tests/health-check.get.test.ts` y agrega el siguiente código:\n\n```typescript\nimport request from \"supertest\";\nimport { describe, expect, it } from \"vitest\";\nimport { app } from \"../libs/server\";\n\ndescribe(\"GET /api/health-check\", () =\u003e {\n  it(\"should return { status: 'ready' }\", async () =\u003e {\n    const response = await request(app).get(\"/api/health-check\");\n\n    expect(response.status).toBe(200);\n    expect(response.body).toHaveProperty(\"status\", \"ready\");\n  });\n});\n```\n\n### Agregar el Script para lanzar las Pruebas\n\nAgrega el siguiente script en la sección \"scripts\" de tu archivo `package.json`:\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"vitest\"\n  }\n}\n```\n\n### Ejecutar las Pruebas\n\nEjecuta el siguiente comando en tu terminal para ejecutar las pruebas:\n\n```bash\nnpm test\n```\n\nDeberías ver una salida similar a esta:\n\n```bash\n ✓ src/tests/health-check.get.test.ts (1 test) 28ms\n   ✓ GET /api/health-check \u003e should return { status: 'ready' }\n\n Test Files  1 passed (1)\n      Tests  1 passed (1)\n   Start at  02:44:18\n   Duration  208ms\n\n PASS  Waiting for file changes...\n       press h to show help, press q to quit\n```\n\n¡Listo! Ahora tienes pruebas automatizadas para validar que el endpoint /api/health-check funciona correctamente. 🚀\n\n### 🎉 ¡Felicitaciones!\n\nEn este punto, has configurado tu proyecto con TypeScript y Express, además haz configurado tu primer endpoint y pruebas automatizadas. ¡Estás en camino de construir una API REST moderna para control de inventario!\n\nSi aún tienes ganas de explorar más en profundidad, puedes visitar los siguientes recursos:\n\n- [Express.js](https://expressjs.com/en/guide/routing.html)\n- [Pruebas con Vites](https://vitest.dev/guide)\n- [Intercambio de recursos de origen cruzado (CORS)](https://developer.mozilla.org/es/docs/Web/HTTP/CORS)\n- [Supertest](https://github.com/ladjs/supertest)\n- [API REST](https://es.wikipedia.org/wiki/Transferencia_de_Estado_Representacional)\n\n## Paso 5: Ruta /api/products\n\nEn este paso, vamos a implementar una ruta que devuelva una lista mockeada de productos. Esta ruta será accesible a través de /api/products con el método GET y devolverá una lista de productos en formato JSON.\n\n### Crear un Mock de Productos\n\nCrea un archivo `src/data/products.ts` y agrega el siguiente código:\n\n```typescript\nexport const products = [\n  {\n    id: 1,\n    title: \"Laptop\",\n    brand: \"Apple\",\n    category: \"Electronics\",\n    price: 1299.99,\n    stock: 10,\n  },\n  {\n    id: 2,\n    title: \"Smartphone\",\n    brand: \"Samsung\",\n    category: \"Electronics\",\n    price: 899.99,\n    stock: 20,\n  },\n  {\n    id: 3,\n    title: \"Tablet\",\n    brand: \"Amazon\",\n    category: \"Electronics\",\n    price: 299.99,\n    stock: 5,\n  },\n  {\n    id: 4,\n    title: \"Smartwatch\",\n    brand: \"Fitbit\",\n    category: \"Electronics\",\n    price: 199.99,\n    stock: 15,\n  },\n  {\n    id: 5,\n    title: \"Headphones\",\n    brand: \"Sony\",\n    category: \"Electronics\",\n    price: 99.99,\n    stock: 30,\n  },\n  {\n    id: 6,\n    title: \"Backpack\",\n    brand: \"North Face\",\n    category: \"Fashion\",\n    price: 79.99,\n    stock: 25,\n  },\n  {\n    id: 7,\n    title: \"Sneakers\",\n    brand: \"Nike\",\n    category: \"Fashion\",\n    price: 129.99,\n    stock: 40,\n  },\n  {\n    id: 8,\n    title: \"T-shirt\",\n    brand: \"Adidas\",\n    category: \"Fashion\",\n    price: 29.99,\n    stock: 50,\n  },\n  {\n    id: 9,\n    title: \"Jeans\",\n    brand: \"Levi's\",\n    category: \"Fashion\",\n    price: 59.99,\n    stock: 20,\n  },\n  {\n    id: 10,\n    title: \"Sunglasses\",\n    brand: \"Ray-Ban\",\n    category: \"Fashion\",\n    price: 149.99,\n    stock: 10,\n  },\n];\n```\n\n\u003e 📚 ¿Qué es un mock de datos? Un mock de datos es un conjunto de datos falsos o simulados que se utilizan para pruebas o desarrollo. En este caso, hemos creado un mock de productos que se utilizará para simular una base de datos de productos hasta que implementemos la integración con Prisma y PostgreSQL.\n\n### Crear la Ruta /api/products\n\nEn este punto es importante que separemos las rutas en archivos diferentes para mantener nuestro código organizado y fácil de mantener. Crea un archivo `src/routes/products.ts` y agrega el siguiente código:\n\n```typescript\nimport { Router } from \"express\";\nimport { products } from \"../data/products\";\n\nconst router = Router();\n\nrouter.get(\"/\", (_req, res) =\u003e {\n  res.json(products);\n});\n\nexport { router as productsRouter };\n```\n\n### Agregar la Ruta en el Servidor\n\nImporta y usa la ruta `/api/products` en tu servidor. Edita el archivo `src/libs/server.ts` para agregar la ruta de productos:\n\n```typescript\nimport cors from \"cors\";\nimport express from \"express\";\nimport { productsRouter } from \"../routes/products\";\n\nconst app = express();\n\n// Middleware\napp.use(cors());\napp.use(express.json());\n\n// Rutas\napp.get(\"/api/health-check\", (_req, res) =\u003e {\n  res.json({ status: \"ready\", uptime: process.uptime() });\n});\n\napp.use(\"/api/products\", productsRouter);\n\n// Exportar el servidor para usarlo en index.ts\nexport { app };\n```\n\n### Probar la Ruta /api/products\n\nEjecuta tu servidor con `npm run dev` y luego abre tu navegador o usa curl para probar la ruta `/api/products`:\n\n```bash\ncurl http://localhost:3000/api/products\n```\n\nDeberías recibir una respuesta con la lista de productos en formato JSON.\n\n### Pruebas Automatizadas para /api/products\n\nAgrega pruebas automatizadas para la ruta `/api/products`. Crea un archivo `src/tests/products.get.test.ts` y agrega el siguiente código:\n\n```typescript\nimport request from \"supertest\";\nimport { describe, expect, it } from \"vitest\";\nimport { products } from \"../data/products\";\nimport { app } from \"../libs/server\";\n\n// ⚠️ Introducimos un error intencional en la prueba\ndescribe(\"GET /api/products\", () =\u003e {\n  it(\"should return a list of products\", async () =\u003e {\n    const response = await request(app).get(\"/api/products\");\n\n    expect(response.status).toBe(200);\n    expect(response.body).not.toEqual(products);\n  });\n});\n```\n\n\u003e 📚 ¿Qué es TDD? El Desarrollo Guiado por Pruebas (TDD) es una metodología de desarrollo de software en la que las pruebas se escriben antes del código de producción. El ciclo de TDD generalmente sigue estos pasos:\n\u003e\n\u003e 1. **Escribir una prueba que falle:** Crear una prueba automatizada para una nueva funcionalidad que aún no está implementada.\n\u003e 2. **Escribir el código mínimo para pasar la prueba:** Implementar el código necesario para que la prueba pase.\n\u003e 3. **Refactorizar el código:** Mejorar el código asegurándose de que todas las pruebas sigan pasando.\n\u003e\n\u003e En este caso, hemos introducido un error intencional en la prueba para que falle. El siguiente paso es corregir el código para que la prueba pase.\n\n### ⚠️ Recuerda corregir la Prueba\n\nCorrige la prueba en `src/tests/products.get.test.ts` para que pase correctamente. Lee atentamente el código de la prueba, ejecuta las pruebas y asegúrate de que pasen correctamente.\n\n### 🎉 ¡Felicitaciones!\n\nHaz avanzado mucho y ya tiene la estructura básica de tu API REST y los conocimientos necesarios para agregar nuevas rutas y funcionalidades. A partir de ahora las intrucciones serán menos precisas y tendrás que investigar y probar por tu cuenta. Las proximas tareas serán más parecidas a requeriemientos de un cliente y tendrás que implementarlos por tu cuenta, pero siempre especificaremos los criterios de aceptación que deberás cumplir.\n\n\u003e 💡 Recuerda apoyarte en las sugerencias de GitHub Copilot a partir de ahora, será tu compañero y te facilitará el aprendizaje y las tareas repetitivas. Para más información, consulta la [documentación oficial de GitHub Copilot](https://docs.github.com/en/copilot/quickstart?tool=visualstudio).\n\n## Paso 6: Implementar un Endpoint para Crear Productos\n\nEn este paso, vamos a implementar un endpoint POST /api/products que permita crear un nuevo producto. El endpoint recibirá los datos del producto en el cuerpo de la solicitud y devolverá el producto creado con un ID único.\n\nLos pasos a seguir son los siguientes:\n\n1. Crear la ruta POST /api/products en el archivo `src/routes/products.ts`.\n2. Recuperar del body de la solicitud los datos del producto a crear.\n3. Generar un ID único para el nuevo producto. Por ahora puedes usar la longitud del array + 1 como ID.\n4. Agregar el nuevo producto al array de productos usando el método push.\n5. Devolver el producto creado con el código de estado 201 (Created).\n6. Verifica que el GET /api/products devuelva las lista de productos con el nuevo producto creado.\n7. Agregar pruebas automatizadas para el endpoint POST /api/products.\n\nComencemos creando primero las pruebas unitarias, crea el archivo `src/tests/products.post.test.ts` y agrega el siguiente código:\n\n```typescript\nimport request from \"supertest\";\nimport { describe, expect, it } from \"vitest\";\nimport { products } from \"../data/products\";\nimport { app } from \"../libs/server\";\n\ndescribe(\"POST /api/products\", () =\u003e {\n  it(\"should create a new product\", async () =\u003e {\n    const totlasProducts = products.length;\n    const newProduct = {\n      title: \"Smart Speaker\",\n      brand: \"Google\",\n      category: \"Electronics\",\n      price: 99.99,\n      stock: 15,\n    };\n\n    const response = await request(app)\n      .post(\"/api/products\")\n      .send(newProduct);\n\n    expect(response.status).toBe(201);\n    expect(response.body.id).toBe(totlasProducts + 1);\n    expect(response.body).toMatchObject(newProduct);\n  });\n});\n```\n\nUna vez que hayan implementado el endpoint POST /api/products y las pruebas unitarias unitarias pasen correctamente, prueba manualmente crea un nuevo producto usando el siguiente curl:\n\n```bash\n# Curl para crear un nuevo producto\ncurl -X POST http://localhost:3000/api/products -H \"Content-Type: application/json\" -d '{\"title\": \"Smart Speaker\", \"brand\": \"Google\", \"category\": \"Electronics\", \"price\": 99.99, \"stock\": 15}'\n```\n\n### Criterios de Aceptación del Paso 6\n\n- [ ] Deberás implementar el endpoint POST /api/products en el archivo src/routes/products.ts.\n- [ ] El endpoint deberá recibir los datos del producto a crear en el cuerpo de la solicitud.\n- [ ] El endpoint deberá devolver el producto creado con un ID único y el código de estado 201 (Created).\n- [ ] El producto creado deberá ser agregado al array de productos.\n- [ ] El endpoint GET /api/products deberá devolver la lista de productos con el nuevo producto creado.\n- [ ] Deberás agregar pruebas automatizadas para el endpoint POST /api/products.\n\n## Paso 7: Implementar un Endpoint para Actualizar Productos\n\n\u003e 📚 ¿Qué significa CRUD? CRUD es un acrónimo que significa Crear, Leer, Actualizar y Eliminar. Se utiliza para describir las cuatro operaciones básicas que se pueden realizar en una base de datos o en una API REST.\n\nEn este paso, vamos a implementar un endpoint PUT /api/products/:id que permita actualizar un producto existente. El endpoint recibirá el ID del producto a actualizar en la URL y los nuevos datos del producto en el cuerpo de la solicitud.\n\nLos pasos a seguir son los siguientes:\n\n1. Crear la ruta PUT /api/products/:id en el archivo `src/routes/products.ts`.\n2. Recuperar el ID del producto a actualizar de los parámetros de la URL.\n3. Recuperar los nuevos datos del producto del cuerpo de la solicitud.\n4. Buscar el producto con el ID proporcionado en el array de productos.\n5. Actualizar los datos del producto con los nuevos datos proporcionados.\n6. Devolver el producto actualizado con el código de estado 200 (OK).\n7. Verificar que el GET /api/products devuelva las lista de productos con el producto actualizado.\n8. Agregar pruebas automatizadas para el endpoint PUT /api/products/:id.\n\nComencemos creando primero las pruebas unitarias, crea el archivo `src/tests/products.put.test.ts` y agrega el siguiente código:\n\n```typescript\nimport request from \"supertest\";\nimport { describe, expect, it } from \"vitest\";\nimport { app } from \"../libs/server\";\n\ndescribe(\"PUT /api/products/:id\", () =\u003e {\n  it(\"should update an existing product\", async () =\u003e {\n    const productId = 1;\n    const updatedProduct = {\n      title: \"Updated Laptop\",\n      brand: \"Apple\",\n      category: \"Electronics\",\n      price: 1499.99,\n      stock: 5,\n    };\n\n    const response = await request(app)\n      .put(`/api/products/${productId}`)\n      .send(updatedProduct);\n\n    expect(response.status).toBe(200);\n    expect(response.body).toMatchObject(updatedProduct);\n  });\n\n  it(\"should return 404 if product not found\", async () =\u003e {\n    const productId = \"invalid-id\";\n    const updatedProduct = {\n      title: \"Updated Laptop\",\n      brand: \"Apple\",\n      category: \"Electronics\",\n      price: 1499.99,\n      stock: 5,\n    };\n\n    const response = await request(app)\n      .put(`/api/products/${productId}`)\n      .send(updatedProduct);\n\n    expect(response.status).toBe(404);\n    expect(response.body).toMatchObject({ message: \"Product not found\" });\n  });\n});\n```\n\nUna vez que hayan implementado el endpoint PUT /api/products/:id y las pruebas unitarias unitarias pasen correctamente, prueba manualmente actualizando un producto usando el siguiente curl:\n\n```bash\n# Curl para actualizar un producto\ncurl -X PUT http://localhost:3000/api/products/1 -H \"Content-Type: application/json\" -d '{\"title\": \"Updated Laptop\", \"brand\": \"Apple\", \"category\": \"Electronics\", \"price\": 1499.99, \"stock\": 5}'\n```\n\n### Criterios de Aceptación del Paso 7\n\n- [ ] Deberás implementar el endpoint PUT /api/products/:id en el archivo src/routes/products.ts.\n- [ ] El endpoint deberá recibir el ID del producto a actualizar en los parámetros de la URL.\n- [ ] El endpoint deberá recibir los nuevos datos del producto en el cuerpo de la solicitud.\n- [ ] El producto actualizado deberá ser devuelto con el código de estado 200 (OK).\n- [ ] Deberás agregar pruebas automatizadas para el endpoint PUT /api/products/:id.\n\n## Paso 8: Implementar un Endpoint para Eliminar Productos\n\nEn este paso, vamos a implementar un endpoint DELETE /api/products/:id que permita eliminar un producto existente. El endpoint recibirá el ID del producto a eliminar en la URL.\n\nLos pasos a seguir son los siguientes:\n\n1. Crear la ruta DELETE /api/products/:id en el archivo `src/routes/products.ts`.\n2. Recuperar el ID del producto a eliminar de los parámetros de la URL.\n3. Buscar el producto con el ID proporcionado en el array de productos.\n4. Eliminar el producto del array de productos.\n5. Devolver el producto eliminado con el código de estado 200 (OK).\n6. Verificar que el GET /api/products devuelva las lista de productos sin el producto eliminado.\n7. Agregar pruebas automatizadas para el endpoint DELETE /api/products/:id.\n\nComencemos creando primero las pruebas unitarias, crea el archivo `src/tests/products.delete.test.ts` y agrega el siguiente código:\n\n```typescript\nimport request from \"supertest\";\nimport { describe, expect, it } from \"vitest\";\nimport { products } from \"../data/products\";\nimport { app } from \"../libs/server\";\n\ndescribe(\"DELETE /api/products/:id\", () =\u003e {\n  it(\"should delete an existing product\", async () =\u003e {\n    const productId = 1;\n    const response = await request(app).delete(`/api/products/${productId}`);\n\n    expect(response.status).toBe(200);\n    expect(response.body).toMatchObject({ message: \"Product deleted\" });\n    expect(products.some(product =\u003e product.id === productId)).toBe(false);\n  });\n\n  it(\"should return 404 if product not found\", async () =\u003e {\n    const productId = \"invalid-id\";\n    const response = await request(app).delete(`/api/products/${productId}`);\n\n    expect(response.status).toBe(404);\n    expect(response.body).toMatchObject({ message: \"Product not found\" });\n  });\n});\n```\n\nUna vez que hayan implementado el endpoint DELETE /api/products/:id y las pruebas unitarias unitarias pasen correctamente, prueba manualmente eliminando un producto usando el siguiente curl:\n\n```bash\n# Curl para eliminar un producto\ncurl -X DELETE http://localhost:3000/api/products/1\n```\n\n### Criterios de Aceptación del Paso 8\n\n- [ ] Deberás implementar el endpoint DELETE /api/products/:id en el archivo src/routes/products.ts.\n- [ ] El endpoint deberá recibir el ID del producto a eliminar en los parámetros de la URL.\n- [ ] El producto eliminado deberá ser devuelto con el código de estado 200 (OK).\n- [ ] El producto eliminado deberá ser removido del array de productos.\n- [ ] Deberás agregar pruebas automatizadas para el endpoint DELETE /api/products/:id.\n\n## Paso 9: Introducción a la Observabilidad y Configuración de Herramientas\n\n\u003e 📚 ¿Qué es la observabilidad? La observabilidad es la capacidad de comprender y depurar un sistema complejo a través de la recopilación y análisis de datos. En el contexto de las aplicaciones web, la observabilidad se refiere a la capacidad de monitorear y analizar el comportamiento de la aplicación en tiempo real.\n\nEn este paso, vamos a introducir los conceptos básicos de observabilidad y configurar herramientas para el monitoreo y logging de nuestra API. La observabilidad es crucial para entender el comportamiento de nuestra aplicación en producción y detectar problemas antes de que afecten a los usuarios.\n\n### Instalación de Pino Logger\n\n[Pino](https://getpino.io) es una biblioteca de logging rápida y eficiente para Node.js. Vamos a instalar y configurar Pino para registrar eventos y errores en nuestra API.\n\nEjecuta el siguiente comando en tu terminal para instalar Pino:\n\n```bash\nnpm install pino pino-pretty pino-http\n```\n\n### Configuración de Pino Logger\n\nCrea un archivo `src/libs/logger.ts` para configurar Pino y exportar un logger personalizado:\n\n```typescript\nimport pino from \"pino\";\nimport { config } from \"../config\";\n\nconst { ENV } = config;\n\nexport const logger = pino({\n  transport: {\n    target: \"pino-pretty\",\n    options: {\n      colorize: true,\n    },\n  },\n  level: ENV === \"production\" ? \"info\" : \"debug\",\n});\n```\n\nLuego, integra Pino en tu servidor Express. Edita el archivo `src/libs/server.ts` para importar y usar Pino como un nuevo middleware de Express, recuerda importar el `logger` que acabas de crear:\n\n```typescript\nimport pinoHttp from \"pino-http\";\nimport { logger } from \"./logger\";\n\n// Middleware\napp.use(pinoHttp({ logger }));\n```\n\nTambien podemos usar Pino para registar el evento que indica que el servidor está corriendo, para ello edita el archivo `src/index.ts` y modifica el mensaje de inicio del servidor por el siguiente:\n\n```typescript\nlogger.info(\n  `🚀 Server is up and running! Access it at: http://localhost:${PORT}/api/health-check`,\n);\n```\n\n\u003e 📚 ¿Cuál es la diferencia entre pino y pino-http?\n\u003e\n\u003e - `pino` es un logger rápido para registrar eventos generales en la aplicación.\n\u003e - `pino-http` es un middleware de Express que captura automáticamente las solicitudes HTTP y las registra en el log.\n\u003e\n\u003e Usamos pino-http({ logger }) en server.ts para que todas las peticiones queden registradas automáticamente.\n\nLuego de instalar y configurar Pino, ejecuta tu servidor con `npm run dev` y verifica que los eventos y errores se registren correctamente en la consola.\n\n```bash\n# Salida esperada\n\u003e tsx watch --env-file=.env src/index.ts\n\n[01:41:32.832] INFO (160460): 🚀 Server is up and running! Access it at: http://localhost:3000/api/health-check\n```\n\n### Criterios de Aceptación del Paso 9\n\n- [ ] Deberás instalar la librería `pino` para el logging y configurarla en tu proyecto.\n- [ ] Deberás crear un archivo `src/libs/logger.ts` para configurar Pino y exportar un logger personalizado.\n- [ ] Deberás integrar Pino en tu servidor Express como un middleware.\n- [ ] Deberás crear un archivo `src/libs/datadog.ts` para configurar Datadog y exportar el agente.\n- [ ] Deberás verificar que el logging y la integración con Datadog funcionen correctamente en tu aplicación.\n\n### 🎉 ¡Felicitaciones!\n\nHas hecho avances muy impresionantes en tu proyecto, y mejorado la infraestructura de tu API con herramientas de observabilidad y monitoreo que son claves para escalar y mantener aplicaciones en producción. ¡Sigue así!\n\n## Paso 10: Le pongamos estilo a nuestro código con Biome\n\nEn este paso, vamos a asegurarnos que el código de nuestra API siga las mejores prácticas y estándares de codificación. Para ello, vamos a utilizar Biome, su extensión para vscode y definiremos un estilo de código minimalista.\n\n### Instalar la extensión de Biome para Visual Studio Code\n\nLa extensión de Biome para Visual Studio Code proporciona una integración perfecta con la herramienta de análisis de código estático. Vamos a instalar la extensión para mejorar la experiencia de desarrollo.\n\nAbre Visual Studio Code y busca la extensión \"Biome\" en el Marketplace. Haz clic en \"Install\" para instalar la extensión.\n\n### Instalación de Biome\n\nBiome es una herramienta de análisis de código estático que ayuda a mantener un código limpio y consistente. Vamos a instalar Biome y su extensión para Visual Studio Code.\n\nEjecuta el siguiente comando en tu terminal para instalar Biome:\n\n```bash\nnpm install -D @biomejs/biome\nnpx @biomejs/biome init\n```\n\n### Configuración de Biome\n\nReemplaza el contenido de archivo `biome.json` recientemente creado en la raíz de tu proyecto con el siguiente contenido:\n\n```json\n{\n  \"$schema\": \"https://biomejs.dev/schemas/1.9.4/schema.json\",\n  \"vcs\": {\n    \"enabled\": false,\n    \"clientKind\": \"git\",\n    \"useIgnoreFile\": false\n  },\n  \"files\": {\n    \"ignoreUnknown\": false,\n    \"ignore\": []\n  },\n  \"formatter\": {\n    \"enabled\": true,\n    \"indentStyle\": \"tab\"\n  },\n  \"organizeImports\": {\n    \"enabled\": true\n  },\n  \"linter\": {\n    \"enabled\": true,\n    \"rules\": {\n      \"correctness\": {\n        \"noUnusedImports\": \"warn\",\n        \"noUnusedVariables\": \"warn\",\n        \"noUnusedFunctionParameters\": \"warn\"\n      },\n      \"recommended\": true\n    }\n  },\n  \"javascript\": {\n    \"formatter\": {\n      \"quoteStyle\": \"single\",\n      \"semicolons\": \"asNeeded\"\n    }\n  }\n}\n```\n\n### Script que corrige automáticamente los errores de estilo\n\nAgrega el siguiente script en la sección \"scripts\" de tu archivo `package.json`:\n\n```json\n{\n  \"scripts\": {\n    \"check\": \"biome check --write .\"\n  }\n}\n```\n\n### Configuración de Visual Studio Code\n\nAbre las configuraciones de Visual Studio Code presionando `Shift + Ctrl + P` y selecciona \"Preferences: Open User Settings (JSON)\". Agrega la siguiente configuración para que Biome chequeé automáticamente tu código al guardar:\n\n```json\n{\n  // Mantén el resto de configuraciones\n  \"editor.formatOnSave\": true,\n  \"[javascript]\": {\n    \"editor.defaultFormatter\": \"biomejs.biome\"\n  },\n  \"[typescript]\": {\n    \"editor.defaultFormatter\": \"biomejs.biome\"\n  },\n  \"[json]\": {\n    \"editor.defaultFormatter\": \"biomejs.biome\"\n  },\n  \"[jsonc]\": {\n    \"editor.defaultFormatter\": \"vscode.json-language-features\"\n  }\n}\n```\n\n### Da formato a tu código con Biome\n\nEjecuta el siguiente comando en tu terminal para dar formato a tu código con Biome:\n\n```bash\nnpm run check\n```\n\nEsto formateará automáticamente TODO tu código según las reglas definidas en el archivo `biome.json` por lo que verás muchos cambios en tus archivos. Si ingresas a ver esos cambios verás que Biome ha corregido automáticamente los errores de estilo en tu código uniformando el estilo de todo tu proyecto.\n\n💡 Este es un buen momento para hacer un commit con todo tu código formateado. Antes de hacerlo, asegúrate de que tu servidor funcione correctamente y que todas las pruebas unitarias se ejecuten sin errores.\n\n### Criterios de Aceptación del Paso 10\n\n- [ ] Deberás instalar la extensión de Biome para Visual Studio Code.\n- [ ] Deberás instalar Biome y configurarlo en tu proyecto.\n- [ ] Deberás agregar un script en el archivo `package.json` para corregir automáticamente los errores de estilo.\n- [ ] Deberás configurar Visual Studio Code para que Biome chequeé automáticamente tu código al guardar.\n- [ ] Deberás verificar que Biome funcione correctamente y corrija los errores de estilo en tu código.\n- [ ] Deberás chequear tu código con Biome y corregir los errores de estilo.\n- [ ] Deberas verificar que las pruebas unitarias sigan pasando después de dar formato a tu código.\n\n### 🎉 ¡Felicitaciones!\n\nHas mejorado la calidad y consistencia de tu código con Biome, una herramienta de análisis de código estático que te ayudará a mantener un código limpio y consistente. ¡Sigue así!\n\n## Paso 11: Refactorización del CRUD con Servicios y Controladores\n\nEn este paso, vamos a refactorizar el código de nuestra API para seguir una arquitectura más escalable y mantenible. Vamos a separar la lógica de negocio en servicios y controladores para mejorar la organización y reutilización del código. Además vamos a implementar una arquitectura en capas (Layered Architecture) que es más escalable y mantenible.\n\n### Agreguemos los typos necesarios\n\nAntes de continuar con la refactorización, necesitamos agregar los tipos necesarios para TypeScript. Crea un archivo `src/api/products/products.interfaces.ts` y agrega el siguiente código:\n\n```typescript\n// Interface para el objeto Producto\nexport type ProductDTO = {\n  id: number;\n  title: string;\n  brand: string;\n  category: string;\n  price: number;\n  stock: number;\n}\n\n// Interface para crear un nuevo producto\nexport type CreateProductDTO = Omit\u003cProductDTO, \"id\"\u003e;\n\n// Interface para actualizar un producto existente\nexport type UpdateProductDTO = Partial\u003cProductDTO\u003e;\n```\n\nEstas interfaces nos ayudarán a definir la forma de los objetos de producto, así como los datos necesarios para crear y actualizar un producto.\n\n### Creemos la estructura de carpetas\n\nOrganizaremos los servicios y controladores en módulos dentro del directorio `src/api`. Cada módulo representará un recurso de la aplicación, como products y users.\n\n```bash\nsrc/\n│── api/\n│   ├── health-check/\n│   │   ├── controllers/\n│   │   ├── services/\n│   │   ├── health-check.route.ts\n│   ├── products/\n│   │   ├── controllers/\n│   │   ├── services/\n│   │   ├── products.routes.ts\n│   ├── users/ # Ejemplo de un módulo de usuarios\n│   │   ├── controllers/\n│   │   ├── services/\n│   │   ├── users.route.ts\n│   ├── orders/ # Ejemplo de un módulo de órdenes\n│   │   ├── controllers/\n│   │   ├── services/\n│   │   ├── orders.route.ts\n│   ├── ... # Otros módulos\n```\n\n\u003e 📚 Separación en módulos: La separación en módulos es una técnica de diseño de software que consiste en dividir una aplicación en partes más pequeñas y manejables. Cada módulo se enfoca en una tarea específica y se comunica con otros módulos a través de interfaces bien definidas.\n\n### Creemos nuestro primer Servicio\n\nCrea un archivo `src/api/products/services/products.get.all.service.ts` y agrega el siguiente código:\n\n```typescript\nimport { products } from \"../../../data/products\";\n\nexport const getAllProductsService = () =\u003e {\n  return products;\n};\n```\n\n### Creemos el primer Controlador\n\nCrea un archivo `src/api/products/controllers/products.get.all.controller.ts` y agrega el siguiente código:\n\n```typescript\nimport type { Request, Response } from \"express\";\nimport { getAllProductsService } from \"../services/products.get.all.service\";\n\nexport const getAllProductsController = (_req: Request, res: Response) =\u003e {\n  const allProducts = getAllProductsService();\n  res.json(allProducts);\n};\n```\n\n### Creemos la Ruta para los Productos\n\nCrea un archivo `src/api/products/products.routes.ts` y agrega el siguiente código:\n\n```typescript\nimport { Router } from \"express\";\nimport { getAllProductsController } from \"./controllers/products.get.all.controller\";\n\nexport const productsRoutes = Router();\n\nproductsRoutes.get(\"/\", getAllProductsController);\n```\n\n### Integremos la Ruta en el Servidor\n\nEdita el archivo `src/libs/server.ts` para importar y usar la ruta de productos y eliminar las rutas antiguas:\n\n```typescript\nimport cors from \"cors\";\nimport express from \"express\";\nimport pinoHttp from \"pino-http\";\nimport { healthCheckRoutes } from \"../api/health-check/health-check.routes\";\nimport { productsRoutes } from \"../api/products/products.routes\";\nimport { logger } from \"./logger\";\n\nconst app = express();\n\n// Middleware\napp.use(cors());\napp.use(express.json());\napp.use(pinoHttp({ logger }));\n\n// Rutas\napp.use(\"/api/health-check\", healthCheckRoutes);\napp.use(\"/api/products\", productsRoutes);\n\n// Exportar el servidor para usarlo en index.ts\nexport { app };\n```\n\n### Refactorizamos la ruta de creación de productos\n\nCrea un archivo `src/api/products/services/products.create.service.ts` y agrega el siguiente código:\n\n```typescript\nimport { products } from \"../../../data/products\";\nimport { CreateProductDTO } from \"../products.interfaces\";\n\nexport const createProductService = (newProduct: CreateProductDTO) =\u003e {\n  const id = products.length + 1;\n  const product = { id, ...newProduct };\n  products.push(product);\n  return product;\n};\n```\n\nCrea un archivo `src/api/products/controllers/products.create.controller.ts` y agrega el siguiente código:\n\n```typescript\nimport type { Request, Response } from \"express\";\nimport { createProductService } from \"../services/products.create.service\";\n\nexport const createProductController = (req: Request, res: Response) =\u003e {\n  const newProduct = req.body;\n  const product = createProductService(newProduct);\n  res.status(201).json(product);\n};\n```\n\nModifica el archivo `src/api/products/products.routes.ts` y agrega el siguiente código:\n\n```typescript\nimport { Router } from \"express\";\nimport { createProductController } from \"./controllers/products.create.controller\";\nimport { getAllProductsController } from \"./controllers/products.get.all.controller\";\n\nexport const productsRoutes = Router();\n\nproductsRoutes.get(\"/\", getAllProductsController);\nproductsRoutes.post(\"/\", createProductController);\n```\n\n### Tu guía de desarrollo deben ser la pruebas unitarias\n\nSi ejecutas las pruebas unitarias ahora, es posible que algunas fallen debido a la refactorización. Asegúrate de refactorizar las rutas PUT y DELETE siguiendo el mismo proceso hasta que las pruebas pasen correctamente.\n\n```bash\n  RERUN  rerun all tests \n\n ✓ src/tests/products.get.test.ts (1 test) 39ms\n   ✓ GET /api/products \u003e should return a list of products\n ❯ src/tests/products.delete.test.ts (2 tests | 2 failed) 66ms\n   × DELETE /api/products/:id \u003e should delete an existing product 49ms\n     → expected 404 to be 200 // Object.is equality\n   × DELETE /api/products/:id \u003e should return 404 if product not found 16ms\n     → expected {} to match object { message: 'Product not found' }\n ✓ src/tests/health-check.get.test.ts (1 test) 39ms\n   ✓ GET /api/health-check \u003e should return { status: 'ready' }\n ❯ src/tests/products.put.test.ts (2 tests | 2 failed) 78ms\n   × PUT /api/products/:id \u003e should update an existing product 64ms\n     → expected 404 to be 200 // Object.is equality\n   × PUT /api/products/:id \u003e should return 404 if product not found 13ms\n     → expected {} to match object { message: 'Product not found' }\n ✓ src/tests/products.post.test.ts (1 test) 42ms\n   ✓ POST /api/products \u003e should create a new product\n```\n\n### Refactoriza el resto de las rutas\n\nMueve la lógica de negocio del archivo `src/routes/products.ts` a los servicios y controladores correspondientes en el directorio `src/api/products`. Repite el proceso para las rutas de creación, actualización y eliminación de productos.\n\n💡 Usa GitHub Copilot o ChatGPT para obtener sugerencias y asistencia mientras desarrollas tu API. Estas herramientas pueden ayudarte a escribir código más rápido y a resolver problemas comunes de programación.\n\n⚠️ Recuerda que por cada ruta deberás crear un servicio y un controlador correspondiente, además de crear la ruta en el archivo `src/api/products/products.routes.ts` y verificar que el test unitario correspondiente pase correctamente.\n\nLa estructura de carpetas y archivos debería verse así:\n\n```bash\nsrc/\n│── api/\n│   ├──   /\n│   │   ├── controllers/\n│   │   │   ├── health-check.get.controller.ts\n│   │   ├── services/\n│   │   │   ├── health-check.get.service.ts\n│   │   ├── health-check.routes.ts\n│   ├── products/\n│   │   ├── controllers/\n│   │   │   ├── products.get.all.controller.ts\n│   │   │   ├── products.create.controller.ts\n│   │   │   ├── products.update.controller.ts\n│   │   │   ├── products.delete.controller.ts\n│   │   ├── services/\n│   │   │   ├── products.get.all.service.ts\n│   │   │   ├── products.create.service.ts\n│   │   │   ├── products.update.service.ts\n│   │   │   ├── products.delete.service.ts\n│   │   ├── products.routes.ts\n```\n\n⚠️ Ya puedes eliminar el archivo `src/routes/products.ts`.\nLuego ejecuta los tests para verificar que todo sigue funcionando correctamente, y has los ajustes necesarios en caso de que algo falle.\n\n### Criterios de Aceptación del Paso 11\n\n- [ ] Deberás crear una estructura de carpetas y archivos para los servicios y controladores de la API.\n- [ ] Deberás crear servicios y controladores para las operaciones CRUD de los productos.\n- [ ] Deberás crear interfaces para los objetos de producto y los datos necesarios para crear y actualizar un producto.\n- [ ] Deberás refactorizar las rutas de productos para seguir una arquitectura en capas.\n- [ ] Deberás mover la lógica de negocio de las rutas a los servicios y controladores correspondientes.\n- [ ] Deberás integrar las rutas de productos en el servidor Express y eliminar las rutas antiguas.\n- [ ] Deberás verificar que las rutas de productos funcionen correctamente después de la refactorización.\n\n### 🎉 ¡Felicitaciones!\n\nHas refactorizado tu API para seguir una arquitectura más escalable y mantenible, utilizando servicios y controladores para separar la lógica de negocio de las rutas. ¡Sigue así!\n\n## Paso 12: Implementar una base de datos PostgreSQL con Prisma\n\nEn este paso, vamos a implementar una base de datos PostgreSQL con Prisma, un ORM moderno y seguro para Node.js y TypeScript. Prisma nos permitirá interactuar con la base de datos de forma segura y eficiente, y nos facilitará la implementación de consultas y migraciones de esquema.\n\nAntes de comenzar con el código, necesitas hacer lo siguiente:\n\n1. Crea una cuenta en Prisma ORM.\n2. Crea un nuevo proyecto en la Prisma Console. Te sugerimos el nombre api-stock.\n3. Crea una base de datos Prisma PostgreSQL. Este proceso tomará unos minutos.\n4. Copia tu `DATABASE_URL` y agregalo en el archivo `.env` de tu proyecto.\n5. Copia tu DATABASE_URL y agrégalo en el archivo .env de tu proyecto.\n\nListo, ya podemos comenzar con la implementación de Prisma ORM\n\n### Instalación de Prisma\n\nEjecuta el siguiente comando en tu terminal para instalar Prisma CLI y Prisma Client:\n\n```bash\nnpm install -D prisma\nnpx prisma init\n```\n\n### Configuración de Prisma\n\nLuego de inicializar Prisma, se creará un archivo `prisma/schema.prisma` con la configuración de la base de datos. Este archivo es clave para definir el esquema de la base de datos y las tablas que vamos a utilizar en nuestra aplicación y la relación entre ellas.\n\nEdita el archivo `prisma/schema.prisma` y reemplázalo con el siguiente código:\n\n```prisma\ngenerator client {\n  provider = \"prisma-client-js\"\n}\n\ndatasource db {\n  provider = \"postgresql\"\n  url      = env(\"DATABASE_URL\")\n}\n\nmodel Configuration {\n  id            String   @id @default(cuid())\n  currency      String\n  cents         Int\n  negativeStock Boolean\n  createdAt     DateTime @default(now())\n  updatedAt     DateTime @updatedAt\n}\n\nmodel User {\n  id          String   @id @default(cuid())\n  email       String   @unique\n  displayName String?\n  password    String\n  createdAt   DateTime @default(now())\n  updatedAt   DateTime @updatedAt\n}\n\nmodel Product {\n  id         String          @id @default(cuid())\n  title      String\n  price      Int\n  stock      Int\n  brand      ProductBrand    @relation(fields: [brandId], references: [id], onDelete: Cascade)\n  brandId    String\n  category   ProductCategory @relation(fields: [categoryId], references: [id], onDelete: Cascade)\n  categoryId String\n  createdAt  DateTime        @default(now())\n  updatedAt  DateTime        @updatedAt\n}\n\nmodel ProductBrand {\n  id        String    @id @default(cuid())\n  name      String\n  createdAt DateTime  @default(now())\n  updatedAt DateTime  @updatedAt\n  Product   Product[]\n\n  @@map(\"product_brand\")\n}\n\nmodel ProductCategory {\n  id        String    @id @default(cuid())\n  name      String\n  createdAt DateTime  @default(now())\n  updatedAt DateTime  @updatedAt\n  Product   Product[]\n\n  @@map(\"product_category\")\n}\n```\n\nHemos creado 5 modelos en el archivo `prisma/schema.prisma`:\n\n- `Configuration`: Configuración de la aplicación, como la moneda y la cantidad de decimales. (Lo necesitarás más adelante)\n- `User`: Usuarios de la aplicación, con un email, nombre de usuario y contraseña. (Lo necesitarás más adelante)\n- `Product`: Productos de la aplicación, con un título, precio, stock, marca y categoría.\n- `ProductBrand`: Marcas de los productos, con un nombre.\n- `ProductCategory`: Categorías de los productos, con un nombre.\n\nCada modelo tiene campos y relaciones que definen la estructura de la base de datos. Si quieres aprender más sobre el modelado de datos en Prisma, consulta la [documentación oficial de Prisma](https://www.prisma.io/docs/orm/prisma-schema/data-model/models).\n\n### Generación de Migraciones y Prisma Client\n\n\u003e 📚 ¿Que son las migraciones en Prisma ORM? Las migraciones en Prisma ORM son cambios en el esquema de la base de datos que se aplican de forma incremental y controlada. Las migraciones permiten modificar la estructura de la base de datos sin perder datos existentes.\n\nEjecuta los siguientes comandos en tu terminal para generar tu primera migración y generar el Prisma Client:\n\n```bash\nnpx prisma migrate dev --name init\nnpx prisma generate\n```\n\n### Instalación de Prisma Client\n\nPrisma Client es una biblioteca de base de datos generada automáticamente que se utiliza para interactuar con la base de datos PostgreSQL. Vamos a instalar Prisma Client en nuestra aplicación para realizar consultas y operaciones en la base de datos.\n\nEn tu terminal:\n\n```bash\nnpm install @prisma/client\n```\n\nAdemás vamos a modificar el archivo `package.json` para agregar un script que genere el Prisma Client automáticamente después de cada migración:\n\n```json\n{\n  \"scripts\": {\n    \"prisma:generate\": \"npx prisma generate\"\n  }\n}\n```\n\n### Configuración de Prisma Client\n\nCrea un archivo `src/libs/prisma.ts` para configurar y exportar el Prisma Client:\n\n```typescript\nimport { PrismaClient } from \"@prisma/client\";\nimport { logger } from \"./logger\";\n\nexport const DB = new PrismaClient();\n\nDB.$connect()\n  .then(() =\u003e {\n    logger.info(\"💾 Database connected successfully\");\n  })\n  .catch((error) =\u003e {\n    logger.fatal(error, \"💾 Database connection error\");\n    throw error;\n  });\n\nprocess.on(\"beforeExit\", async () =\u003e {\n  await DB.$disconnect();\n  logger.info(\"💾 Database connection closed\");\n});\n```\n\n### Integración de Prisma Client en el Servidor\n\nAgrega al archivo `src/index.ts` la importación del cliente de Prisma para que se conecte a la base de datos al iniciar el servidor:\n\n```typescript\nimport \"./libs/prisma\";\n```\n\n### Criterios de Aceptación del Paso 12\n\n- [ ] Deberás crear una cuenta en Prisma y configurar una base de datos PostgreSQL.\n- [ ] Deberás obtener el string de conexión DATABASE_URL y agregarlo en el archivo .env.\n- [ ] Deberás instalar Prisma CLI y Prisma Client en tu proyecto.\n- [ ] Deberás configurar el archivo prisma/schema.prisma con los modelos y relaciones de la base de datos.\n- [ ] Deberás generar la primera migración y el Prisma Client.\n- [ ] Deberás configurar y exportar el Prisma Client en un archivo src/libs/prisma.ts.\n\n### 🎉 ¡Felicitaciones!\n\nHas implementado una base de datos PostgreSQL con Prisma, un ORM moderno y seguro para Node.js y TypeScript. Prisma te permitirá interactuar con la base de datos de forma segura y eficiente, y facilitará la implementación de consultas y migraciones de esquema. ¡Sigue así!\n\n## Paso 13: Manejo de Errores y Validaciones Avanzadas\n\nEn este paso, vamos a implementar un middleware de manejo de errores y validaciones avanzadas utilizando Zod. Esto nos permitirá estandarizar las respuestas de error y asegurarnos de que los datos recibidos en las solicitudes sean válidos.\n\n\u003e 📚 ¿Qué es Zod? Zod es una biblioteca de validación de esquemas para TypeScript y JavaScript. Permite definir esquemas de datos y validar objetos de manera declarativa y segura.\n\n### Instalación de Zod\n\nPrimero, necesitamos instalar Zod, una biblioteca de validación de esquemas para TypeScript.\n\nEjecuta el siguiente comando en tu terminal:\n\n```bash\nnpm install zod\n```\n\n### Creación del Middleware de Manejo de Errores\n\nVamos a crear un middleware para manejar los errores de forma centralizada. Crea un archivo `src/middleware/errorHandler.ts` y agrega el siguiente código:\n\n```typescript\nimport type { NextFunction, Request, Response } from \"express\";\nimport { ZodError } from \"zod\";\nimport { logger } from \"../libs/logger\";\n\nexport const errorHandler = (\n  err: any,\n  _req: Request,\n  res: Response,\n  _next: NextFunction,\n) =\u003e {\n  if (err instanceof ZodError) {\n    return res.status(400).json({\n      message: \"Validation error\",\n      errors: err.errors,\n    });\n  }\n\n  logger.error(err);\n  res.status(500).json({\n    message: \"Internal server error\",\n  });\n};\n```\n\n### Integración del Middleware en el Servidor\n\nEdita el archivo `src/libs/server.ts` para usar el middleware de manejo de errores:\n\n```typescript\nimport cors from \"cors\";\nimport express from \"express\";\nimport pinoHttp from \"pino-http\";\nimport { healthCheckRoutes } from \"../api/health-check/health-check.routes\";\nimport { productsRoutes } from \"../api/products/products.routes\";\nimport { errorHandler } from \"../middleware/errorHandler\";\nimport { logger } from \"./logger\";\n\nconst app = express();\n\n// Middleware\napp.use(cors());\napp.use(express.json());\napp.use(pinoHttp({ logger }));\n\n// Routes\napp.use(\"/api/health-check\", healthCheckRoutes);\napp.use(\"/api/products\", productsRoutes);\n\n// Error handling middleware\napp.use(errorHandler);\n\n// Exportar el servidor para usarlo en index.ts\nexport { app };\n```\n\n### Validaciones con Zod\n\nVamos a crear un esquema de validación para los productos utilizando Zod. Crea un archivo `src/api/products/schemas/product.schema.ts` y agrega el siguiente código:\n\n```typescript\nimport { z } from \"zod\";\n\nexport const productSchema = z.object({\n  title: z.string().min(1, \"Title is required\"),\n  brand: z.string().min(1, \"Brand is required\"),\n  category: z.string().min(1, \"Category is required\"),\n  price: z.number().positive(\"Price must be a positive number\"),\n  stock: z.number().int().nonnegative(\"Stock must be a non-negative integer\"),\n});\n```\n\n### Validación en el Controlador de Creación de Productos\n\nEdita el archivo `src/api/products/controllers/products.create.controller.ts` para validar los datos del producto antes de crear uno nuevo:\n\n```typescript\nimport type { Request, Response } from \"express\";\nimport { productSchema } from \"../schemas/product.schema\";\nimport { createProductService } from \"../services/products.create.service\";\n\nexport const createProductController = (req: Request, res: Response) =\u003e {\n  const validationResult = productSchema.safeParse(req.body);\n\n  if (!validationResult.success) {\n    throw validationResult.error;\n  }\n\n  const newProduct = createProductService(validationResult.data);\n  res.status(201).json(newProduct);\n};\n```\n\n\u003e 💡 Es importante usar `safeParse` de Zod para filtrar cualquier dato sensible en la respuesta y asegurarse de que solo los datos válidos sean procesados.\n\n### Criterios de Aceptación del Paso 13\n\n- [ ] Deberás instalar la librería `zod` para validaciones.\n- [ ] Deberás crear un middleware de manejo de errores en `src/middleware/errorHandler.ts`.\n- [ ] Deberás integrar el middleware de manejo de errores en el servidor Express.\n- [ ] Deberás crear un esquema de validación para los productos en `src/api/products/schemas/product.schema.ts`.\n- [ ] Deberás validar los datos del producto en el controlador de creación de productos.\n\n### 🎉 ¡Felicitaciones!\n\nHas implementado un middleware de manejo de errores y validaciones avanzadas utilizando Zod. Esto te permitirá estandarizar las respuestas de error y asegurarte de que los datos recibidos en las solicitudes sean válidos. ¡Sigue así!\n\n## Paso 14: Adaptar Servicios para Usar Base de Datos Real\n\nEn este paso, vamos a actualizar los servicios de tu API de productos para que, en lugar de usar datos mockeados (como arrays en memoria), realicen operaciones reales contra la base de datos PostgreSQL a través de Prisma. Esto significa que modificarás los servicios de obtención, creación, actualización y eliminación de productos para que interactúen directamente con las tablas definidas en tu modelo Prisma (por ejemplo, la tabla `Product` y sus relaciones con `ProductBrand` y `ProductCategory`). Además, ajustaremos los controladores correspondientes para manejar las operaciones asíncronas y los errores de forma correcta.\n\nLa integración con la base de datos se realizará utilizando la instancia de Prisma Client configurada en `src/libs/prisma.ts`. Con estos cambios, tu API trabajará con datos persistentes y podrás aplicar consultas reales, transacciones y operaciones complejas en la base de datos.\n\n### 1. Servicio para Obtener Todos los Productos\n\nActualiza el servicio para obtener todos los productos consultando la base de datos.\n\nModifica el archivo `src/api/products/services/products.get.all.service.ts`:\n\n```typescript\nimport { DB } from \"../../../libs/prisma\";\n\nexport const getAllProductsService = async () =\u003e {\n  const products = await DB.product.findMany({\n    include: {\n      brand: true,\n      category: true,\n    },\n  });\n  return products;\n};\n```\n\n### 2. Servicio para Crear un Producto\n\nActualiza el servicio de creación para insertar un nuevo producto en la base de datos. Se consultarán las entidades relacionadas (marca y categoría) antes de crear el producto.\n\nModifica el archivo `src/api/products/services/products.create.service.ts`:\n\n```typescript\nimport { DB } from \"../../../libs/prisma\";\n\nexport interface CreateProductData {\n  title: string;\n  brand: string;\n  category: string;\n  price: number;\n  stock: number;\n}\n\nexport const createProductService = async (data: CreateProductData) =\u003e {\n  // Buscar la marca y categoría por nombre\n  const brandRecord = await DB.productBrand.findFirst({\n    where: { name: data.brand },\n  });\n  const categoryRecord = await DB.productCategory.findFirst({\n    where: { name: data.category },\n  });\n\n  if (!brandRecord || !categoryRecord) {\n    throw new Error(\"La marca o categoría no existen en la base de datos\");\n  }\n\n  const newProduct = await DB.product.create({\n    data: {\n      title: data.title,\n      price: Math.floor(data.price), // Asegurarse de que el precio sea un entero\n      stock: data.stock,\n      brandId: brandRecord.id,\n      categoryId: categoryRecord.id,\n    },\n    include: {\n      brand: true,\n      category: true,\n    },\n  });\n\n  return newProduct;\n};\n```\n\n### 3. Servicio para Actualizar un Producto\n\nActualiza el servicio para modificar un producto existente. Se leerán los posibles cambios, incluyendo la actualización de la marca y/o categoría, validando y obteniendo sus IDs correspondientes.\n\nModifica el archivo `src/api/products/services/products.update.service.ts`:\n\n```typescript\nimport { DB } from \"../../../libs/prisma\";\n\nexport interface UpdateProductData {\n  title?: string;\n  brand?: string;\n  category?: string;\n  price?: number;\n  stock?: number;\n}\n\nexport const updateProductService = async (\n  id: string,\n  data: UpdateProductData,\n) =\u003e {\n  let brandId: string | undefined;\n  let categoryId: string | undefined;\n\n  if (data.brand) {\n    const brandRecord = await DB.productBrand.findFirst({\n      where: { name: data.brand },\n    });\n    if (!brandRecord) {\n      throw new Error(\"Marca no encontrada\");\n    }\n    brandId = brandRecord.id;\n  }\n  if (data.category) {\n    const categoryRecord = await DB.productCategory.findFirst({\n      where: { name: data.category },\n    });\n    if (!categoryRecord) {\n      throw new Error(\"Categoría no encontrada\");\n    }\n    categoryId = categoryRecord.id;\n  }\n\n  const updatedProduct = await DB.product.update({\n    where: { id },\n    data: {\n      title: data.title,\n      price: data.price !== undefined ? Math.floor(data.price) : undefined,\n      stock: data.stock,\n      ...(brandId \u0026\u0026 { brandId }),\n      ...(categoryId \u0026\u0026 { categoryId }),\n    },\n    include: {\n      brand: true,\n      category: true,\n    },\n  });\n\n  return updatedProduct;\n};\n```\n\n### 4. Servicio para Eliminar un Producto\n\nActualiza el servicio para eliminar un producto, validando que existe en la base de datos antes de borrar el registro.\n\nModifica el archivo `src/api/products/services/products.delete.service.ts`:\n\n```typescript\nimport { DB } from \"../../../libs/prisma\";\n\nexport const deleteProductService = async (id: string) =\u003e {\n  const productToDelete = await DB.product.findUnique({ where: { id } });\n  if (!productToDelete) {\n    throw new Error(\"Product not found\");\n  }\n\n  await DB.product.delete({\n    where: { id },\n  });\n\n  return { message: \"Product deleted\", id };\n};\n```\n\n### 5. Actualización de los Controladores\n\nModifica los controladores para utilizar las funciones de los servicios adaptadas a Prisma y para manejar correctamente las operaciones asíncronas y los errores.\n\nPara modificar el controlador para Crear un Producto, modifica el archivo `src/api/products/controllers/products.create.controller.ts`:\n\n```typescript\nimport type { Request, Response } from \"express\";\nimport { productSchema } from \"../schemas/product.schema\";\nimport { createProductService } from \"../services/products.create.service\";\n\nexport const createProductController = async (req: Request, res: Response) =\u003e {\n  const validationResult = productSchema.safeParse(req.body);\n\n  if (!validationResult.success) {\n    return res.status(400).json({\n      message: \"Validation error\",\n      errors: validationResult.error.errors,\n    });\n  }\n\n  try {\n    const newProduct = await createProductService(validationResult.data);\n    res.status(201).json(newProduct);\n  } catch (error: any) {\n    res.status(500).json({ message: error.message });\n  }\n};\n```\n\nPara modificar el controlador para Actualizar un Producto, modifica el archivo `src/api/products/controllers/products.update.controller.ts`:\n\n```typescript\nimport type { Request, Response } from \"express\";\nimport { productSchema } from \"../schemas/product.schema\"; // (Opcional: para validar datos)\nimport { updateProductService } from \"../services/products.update.service\";\n\nexport const updateProductController = async (req: Request, res: Response) =\u003e {\n  const { id } = req.params;\n  const validationResult = productSchema.safeParse(req.body);\n\n  if (!validationResult.success) {\n    return res.status(400).json({\n      message: \"Validation error\",\n      errors: validationResult.error.errors,\n    });\n  }\n\n  try {\n    const updatedProduct = await updateProductService(\n      id,\n      validationResult.data,\n    );\n    res.status(200).json(updatedProduct);\n  } catch (error: any) {\n    if (\n      error.message === \"Marca no encontrada\"\n      || error.message === \"Categoría no encontrada\"\n    ) {\n      return res.status(404).json({ message: error.message });\n    }\n    res.status(500).json({ message: error.message });\n  }\n};\n```\n\nPara modificar el controlador para Eliminar un Producto, modifica el archivo `src/api/products/controllers/products.delete.controller.ts`:\n\n```typescript\nimport type { Request, Response } from \"express\";\nimport { deleteProductService } from \"../services/products.delete.service\";\n\nexport const deleteProductController = async (req: Request, res: Response) =\u003e {\n  const { id } = req.params;\n\n  try {\n    const result = await deleteProductService(id);\n    res.status(200).json(result);\n  } catch (error: any) {\n    if (error.message === \"Product not found\") {\n      return res.status(404).json({ message: error.message });\n    }\n    res.status(500).json({ message: error.message });\n  }\n};\n```\n\n💡 Es una buena práctica utilizar Zod dentro de cada servicio para validar tanto los datos de entrada como los datos de salida. Esto asegura que los datos que se procesan y se devuelven cumplen con los esquemas definidos, lo que ayuda a prevenir errores y a mantener la integridad de los datos en toda la aplicación.\n\nPor ejemplo, puedes definir un esquema de validación para los datos de entrada en el servicio de creación de productos y otro esquema para los datos de salida:\n\nCrea un archivo `src/api/products/schemas/product.schema.ts` y agrega los esquemas de validación:\n\n```typescript\nimport { z } from \"zod\";\n\nconst createProductInputSchema = z.object({\n  title: z.string().min(1, \"Title is required\"),\n  brand: z.string().min(1, \"Brand is required\"),\n  category: z.string().min(1, \"Category is required\"),\n  price: z.number().positive(\"Price must be a positive number\"),\n  stock: z.number().int().nonnegative(\"Stock must be a non-negative integer\"),\n});\n\nconst createProductOutputSchema = z.object({\n  id: z.string(),\n  title: z.string(),\n  brand: z.string(),\n  category: z.string(),\n  price: z.number(),\n  stock: z.number(),\n  createdAt: z.string(),\n  updatedAt: z.string(),\n});\n\nexport const createProductService = async (data: CreateProductData) =\u003e {\n  const validationResult = createProductInputSchema.safeParse(data);\n\n  if (!validationResult.success) {\n    throw new Error(\"Validation error\");\n  }\n\n  // Lógica para crear el producto en la base de datos\n  const newProduct = await DB.product.create({\n    data: validationResult.data,\n    include: {\n      brand: true,\n      category: true,\n    },\n  });\n\n  const outputValidationResult = createProductOutputSchema.safeParse(\n    newProduct,\n  );\n\n  if (!outputValidationResult.success) {\n    throw new Error(\"Output validation error\");\n  }\n\n  return outputValidationResult.data;\n};\n```\n\nDe esta manera, aseguras que tanto los datos de entrada como los datos de salida cumplen con los esquemas definidos, lo que mejora la robustez y la confiabilidad de tu aplicación. Te invitamos a crear schemas para los demás servicios y controladores siguiendo esta misma lógica.\n\n### Criterios de Aceptación del Paso 14\n\n- [ ] Actualizar el servicio de obtención de productos en `src/api/products/services/products.get.all.service.ts` para consultar la base de datos usando Prisma.\n- [ ] Actualizar el servicio de creación en `src/api/products/services/products.create.service.ts` para insertar un nuevo producto, validando y consultando las relaciones de marca y categoría.\n- [ ] Actualizar el servicio de actualización en `src/api/products/services/products.update.service.ts` para modificar un producto existente.\n- [ ] Actualizar el servicio de eliminación en `src/api/products/services/products.delete.service.ts` para remover el producto de la base de datos.\n- [ ] Modificar los controladores en `src/api/products/controllers/` para utilizar las funciones asíncronas de los servicios y manejar errores de forma adecuada.\n- [ ] Ejecutar las pruebas para verificar que las operaciones CRUD funcionan correctamente contra la base de datos real.\n\n### 🎉 ¡Felicitaciones!\n\nHas adaptado exitosamente tus servicios y controladores para interactuar con una base de datos PostgreSQL real mediante Prisma. Con estos cambios, tu API ahora trabajará con datos persistentes y estarás un paso más cerca de construir una solución escalable y profesional. ¡Sigue así y continúa avanzando en el curso!\n\n## Paso 15: Despliegue y Configuración en Producción\n\nEn este paso, desplegaremos tu API en Railway, una plataforma en la nube que facilita el hosting de aplicaciones. Aprovecharemos la base de datos PostgreSQL y la configuración de Prisma ya establecidas, y configuraremos las variables de entorno y logging para el ambiente de producción.\n\n### Creación de una Cuenta y Servicio en Railway\n\nSi aún no tienes una cuenta en Railway, puedes crear una gratuita visitando [Railway](https://railway.app/). Sigue estos pasos básicos:\n\n1. Regístrate con tu email o con tu cuenta de GitHub.\n2. Una vez dentro, haz clic en **\"New Project\"**.\n3. Selecciona la opción **\"Deploy from GitHub Repo\"** y conecta tu repositorio.\n4. Configura el servicio, asegurándote de establecer la variable `DATABASE_URL` (con el string de conexión de tu base de datos PostgreSQL) y otras variables necesarias para la ejecución, como `NODE_ENV` con el valor `\"production\"`.\n5. Railway generará una URL pública para acceder a tu API en producción.\n\n### Configuración de Variables de Entorno y Logs\n\n1. **Variables de Entorno:**\\\n   Asegúrate de definir en Railway las siguientes variables:\n   - `DATABASE_URL`: Conecta tu base de datos PostgreSQL.\n   - `NODE_ENV`: Establecido a `\"production\"`.\n   - Cualquier otra variable que tu aplicación requiera (por ejemplo, `PORT`).\n\n2. **Logging en Producción:**\\\n   Revisa que en el archivo `src/libs/logger.ts` el nivel de log se configure según el entorno:\n   ```typescript\n   import pino from \"pino\";\n   import { config } from \"../config\";\n\n   const { ENV } = config;\n\n   export const logger = pino({\n     transport: {\n       target: \"pino-pretty\",\n       options: {\n         colorize: true,\n       },\n     },\n     level: ENV === \"production\" ? \"info\" : \"debug\",\n   });\n   ```\n   Esto garantizará que en producción se muestren logs a nivel `info` para evitar el exceso de información.\n\n### Actualización del Script de Inicio\n\nAsegúrate de que el script de inicio en tu `package.json` utilice el código compilado y respete las variables de entorno:\n\n```json\n{\n  \"scripts\": {\n    \"start\": \"node --env-file=.env dist/index.js\"\n  }\n}\n```\n\n\u003e Nota: Railway manejará sus propias variables de entorno en el dashboard, por lo que es crucial que la variable `DATABASE_URL` y `NODE_ENV` estén correctamente definidas allí.\n\n### Pruebas y Validaciones Previas al Despliegue\n\nAntes de desplegar, verifica los siguientes puntos:\n\n- Ejecuta `npm test` para asegurarte de que todos los tests pasen.\n- Comprueba el funcionamiento de la API en ambiente local en modo producción:\n  1. Compila el proyecto con `npm run build`.\n  2. Inicia la aplicación con `npm start`.\n- Revisa los logs para confirmar que no se presenten errores y que la conexión a la base de datos sea exitosa.\n\nUna vez validados estos pasos, realiza el despliegue en Railway siguiendo el proceso de importación del repositorio.\n\n### Solución de Problemas Comunes\n\n- Verificá que las variables de entorno estén correctamente definidas.\n- Revisá los logs (usando Pino) para identificar problemas de conexión a la base de datos.\n- Probá localmente en modo producción antes del despliegue:\n\n```bash\nnpm run build\nnpm start\n```\n\n\u003e 💡 Consejo: Si se presentan errores en la conexión a la base de datos, consultá la documentación de Prisma y Railway.\n\n### Criterios de Aceptación del Paso 15\n\n- [ ] Deberás tener una cuenta en Railway (puedes crear una gratuita [aquí](https://railway.app/)).\n- [ ] Deberás configurar en Railway las variables de entorno, incluyendo `DATABASE_URL` y `NODE_ENV=production`.\n- [ ] La API deberá conectarse correctamente a la base de datos PostgreSQL en el entorno de Railway.\n- [ ] Los logs deben indicar que la aplicación se inicia sin errores y en modo producción.\n- [ ] Deberás verificar el funcionamiento de la API en producción accediendo a la URL proporcionada por Railway.\n\n### 🎉 ¡Felicitaciones!\n\nHas completado el despliegue de tu API en Railway, configurando adecuadamente las variables de entorno y los logs para el ambiente de producción. ¡Tu API ahora está lista para recibir tráfico real y crecer de forma escalable! ¡Excelente trabajo y sigue adelante!\n\n## Paso 16: Implementación de CI/CD\n\nEn este paso, configuraremos GitHub Actions para automatizar las pruebas y el despliegue continuo de tu API. Con esta integración, cada vez que realices cambios en la rama principal se ejecutarán los tests y, de estar todo correcto, se compilará y desplegará la aplicación. Esto te ayudará a detectar errores rápidamente y a mantener un proceso de despliegue confiable y eficiente.\n\n### Creación del Workflow en GitHub Actions\n\n1. Dentro de tu repositorio, crea el directorio `.github/workflows`.\n2. Crea un archivo llamado `ci-cd.yml` en dicho directorio.\n\nEn este archivo definirás el workflow de CI/CD. Un ejemplo de configuración es el siguiente:\n\n```yaml\nname: CI/CD Pipeline\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Setup Node.js environment\n        uses: actions/setup-node@v3\n        with:\n          node-version: 16\n\n      - name: Install dependencies\n        run: npm install\n\n      - name: Run tests\n        run: npm test\n\n      - name: Build project\n        run: npm run build\n\n      - name: Deploy to Railway\n        if: github.ref == 'refs/heads/main'\n        run: |\n          npx railway deploy\n        env:\n          RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }}\n```\n\n\u003e 📚 Nota:\n\u003e\n\u003e - Asegúrate de tener configurado el secret `RAILWAY_TOKEN` en la sección de secrets de tu repositorio en GitHub. Railway ofrece un CLI que permite desplegar tu servicio; si aún no lo tienes instalado, puedes agregarlo a través de `npx railway` en tus scripts.\n\u003e - Revisa que los scripts definidos en tu `package.json` (como `npm test` y `npm run build`) funcionen correctamente.\n\n### Pruebas y Validaciones Previas al Despliegue\n\nAntes de integrar el workflow de CI/CD, sigue estos pasos en local:\n\n- Ejecuta `npm test` para confirmar que las pruebas pasen sin errores.\n- Realiza la compilación con `npm run build` y verifica que no se produzcan errores.\n- Si usas Railway, prueba manualmente el despliegue con `npx railway deploy` para asegurarte de que la conexión y las variables de entorno están correctamente configuradas.\n\n### Criterios de Aceptación del Paso 16\n\n- [ ] Deberás crear el directorio `.github/workflows` en tu repositorio si aún no existe.\n- [ ] Deberás crear un archivo `ci-cd.yml` que defina el workflow para ejecutar tests, compilar la aplicación y desplegarla.\n- [ ] El workflow deberá ejecutarse en cada push o pull request a la rama `main`.\n- [ ] Deberás tener configurado el secret `RAILWAY_TOKEN` en GitHub para el despliegue.\n- [ ] Los tests deben correr y pasar, y el despliegue se debe ejecutar sin errores.\n\n### 🎉 ¡Felicitaciones!\n\nHas implementado con éxito un pipeline de CI/CD utilizando GitHub Actions. Ahora, cada cambio en la rama principal activará automáticamente pruebas y despliegues, lo que garantiza la calidad y la estabilidad de tu API en producción. ¡Excelente trabajo y sigue avanzando en tu aprendizaje!\n\n\u003e 💡 Consejo: Revisa periódicamente los logs de GitHub Actions para detectar posibles fallos o áreas de mejora en tu pipeline de CI/CD.\n\n## ⚠️ Recuerda detener y eliminar cualquier instancia de tu API en Railway si ya no la necesitas para evitar costos innecesarios.\n\nSi bien Railway ofrece un plan gratuito, es importante mantener un control de los recursos utilizados para evitar cargos adicionales. Si no estás utilizando tu API en producción, asegúrate de detener y eliminar cualquier instancia para evitar costos innecesarios.\n\n# 🏁 ¡Felicidades! Has completado el Curso de API REST con Node.js y TypeScript\n\n¡Enhorabuena! Has completado el Curso de API REST con Node.js y TypeScript. Has aprendido a construir una API RESTful escalable y mantenible utilizando tecnologías modernas como Express, Prisma, Zod y Railway. Has implementado funcionalidades esenciales como CRUD de productos, servicios, controladores, validaciones, manejo de errores, base de datos PostgreSQL, despliegue en producción y CI/CD. ¡Excelente trabajo!\n\n💡 Es fundamental que entiendas que este curso es solo el comienzo para convertirte en un gran desarrollador backend, pero que debes seguir aprendiendo y practicando con frecuencia para lograr tus objetivos. La práctica constante y la curiosidad por aprender nuevas tecnologías y mejores prácticas te ayudarán a mejorar tus habilidades y a mantenerte actualizado en un campo que evoluciona rápidamente. ¡Sigue adelante y nunca dejes de aprender!\n\n# 🚀 Recursos Adicionale\n\n- [Express.js](https://expressjs.com/): Documentación oficial de Express.js.\n- [Prisma](https://www.prisma.io/): Documentación oficial de Prisma ORM.\n- [Zod](https://zod.dev/): Documentación oficial de Zod.\n- [Railway](https://railway.app/): Documentación oficial de Railway.\n- [GitHub Actions](https://docs.github.com/en/actions): Documentación oficial de GitHub Actions.\n- [Documentación de Pino](https://getpino.io/): Documentación oficial de Pino.\n- [Biome para vscode](https://marketplace.visualstudio.com/items?itemName=biomejs.biome): Extensión de Biome para Visual Studio Code.\n\n# 🔜 Próximos Pasos\n\n\u003e ### ⚠️ Importante: Esta guía se encuentra en desarrollo y puede sufrir cambios en el futuro. Si tienes alguna sugerencia o corrección, no dudes en abrir un issue o una pull request. ¡Gracias por tu colaboración!\n\n- Paso 16: Implementación de CI/CD\n  - Configurar GitHub Actions para pruebas automatizadas y despliegue continuo.\n\n# Pasos opcionales\n\nEsta serie de pasos busca que el usuario continue en su aprendizaje incluyendo desafíos más complejos y avanzados. Si deseas continuar con el curso, puedes seguir con los pasos opcionales.\n\n- Opcional: Implementar Paginación y Filtros en Endpoints\n  - Agregar paginación y filtros dinámicos en los endpoints.\n  - Optimizar consultas para mejorar el rendimiento en grandes volúmenes de datos.\n\n- Opcional: Documentación Automática con OpenAPI (Swagger)\n  - Generar documentación interactiva para la API.\n  - Agregar ejemplos de uso y esquemas de respuesta.\n  - Permitir pruebas de endpoints directamente desde la documentación.\n\n- Opcional: CRUD de Usuarios, Roles y Autenticación\n  - Implementar endpoints para crear, leer, actualizar y eliminar usuarios y roles.\n  - Agregar endpoints para obtener y refrescar tokens de acceso.\n  - Implementar endpoints para asignar y revocar roles y permisos.\n  - Implementar autenticación con JWT y Passport.\n  - Implementar autorización basada en roles y permisos.\n  - Proteger rutas sensibles y recursos críticos.\n  - Agregar pruebas automatizadas para los endpoints de usuarios y roles.\n\n- Opcional: Reportes y Estadísticas\n  - Implementar endpoints para generar reportes y estadísticas.\n  - Agregar filtros y parámetros para personalizar los reportes.\n\n- Paso 18: Movimientos de stocks y Control de Inventario\n  - Implementar endpoints para registrar movimientos de stocks.\n  - Agregar lógica de negocio para controlar el inventario.\n  - Implementar endpoints para consultar el stock disponible y los movimientos de inventario.\n  - Agregar pruebas automatizadas para los endpoints de inventario.\n\n- Paso 20: Módulo de importar/exportar datos\n  - Implementar endpoints para importar y exportar datos en formato CSV o JSON.\n  - Agregar validaciones y transformaciones de datos para garantizar la integridad.\n\n- Paso 21: Seguridad y Buenas Prácticas en Producción\n  - Configurar Helmet y Rate Limiting para proteger la API.\n  - Evitar inyecciones SQL y ataques XSS.\n  - Agregar CORS con restricciones adecuadas.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgaliprandi%2Fapi-stock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgaliprandi%2Fapi-stock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgaliprandi%2Fapi-stock/lists"}