Blog · Desarrollo web · Mayo 2025

Cómo construí Maestro Cocinero: un portal de recetas con Next.js y PostgreSQL

Next.jsPostgreSQLDockerTypeScript
Mesa con platos de comida — Maestro Cocinero portal de recetas

Maestro Cocinero es un portal de recetas personalizadas que nació de un problema doméstico: tienes ingredientes en la nevera pero no sabes qué cocinar con ellos. La mayoría de aplicaciones de recetas te obligan a buscar por nombre del plato. Este proyecto invierte el proceso.

En este artículo explico las decisiones de diseño y arquitectura que tomé durante el desarrollo: por qué elegí Next.js 14, cómo modela la búsqueda por ingredientes en PostgreSQL y cómo simplifiqué el despliegue con Docker Compose.

El problema a resolver

El eterno dilema de "¿qué cocino hoy?" no es un problema de inspiración, sino de información. Tienes ingredientes disponibles, pero los portales de recetas existentes no están diseñados para que empieces por ahí. Quería construir algo que funcionase como un motor de búsqueda inverso: parte de lo que tienes, no de lo que quieres hacer.

Los requisitos del proyecto eran claros desde el principio:

Por qué Next.js 14 con App Router

La decisión más importante al principio fue elegir entre una SPA pura y un framework con SSR. Para un portal de recetas, el SEO es crítico: cada receta es una URL indexable con su propio título, descripción e imagen. Eso descartó las soluciones puramente client-side.

Elegí Next.js 14 con el App Router por varias razones:

El buscador por ingredientes en PostgreSQL

El núcleo del proyecto es el buscador. Una receta tiene múltiples ingredientes, y el usuario selecciona los que tiene disponibles. El portal debe devolver las recetas ordenadas por el porcentaje de ingredientes que el usuario ya posee.

Modelé la base de datos con tres tablas principales: recipes, ingredients y una tabla de unión recipe_ingredients. La query de búsqueda calcula el porcentaje de coincidencia como:

SELECT r.id, r.title, r.slug, COUNT(ri.ingredient_id) FILTER ( WHERE ri.ingredient_id = ANY(:user_ingredients) ) AS matched, COUNT(ri.ingredient_id) AS total, ROUND( COUNT(ri.ingredient_id) FILTER ( WHERE ri.ingredient_id = ANY(:user_ingredients) ) * 100.0 / COUNT(ri.ingredient_id) ) AS match_pct FROM recipes r JOIN recipe_ingredients ri ON ri.recipe_id = r.id GROUP BY r.id HAVING match_pct > 0 ORDER BY match_pct DESC;

Esta aproximación es eficiente para el tamaño del catálogo actual y permite añadir fácilmente filtros adicionales (tiempo de preparación, dificultad, tipo de dieta) como condiciones WHERE antes del GROUP BY.

Decisión de diseño: Usé Prisma como ORM para las operaciones CRUD estándar, pero las queries de búsqueda con ranking las ejecuto con prisma.$queryRaw para mantener el control total sobre el SQL sin perder la seguridad de los parámetros tipados.

Infraestructura con Docker Compose

Desde el primer día quise que el despliegue fuese reproducible: el mismo docker-compose.yml que uso en local debe funcionar en producción sin modificaciones. El stack tiene tres servicios:

El mayor reto fue gestionar las migraciones de base de datos sin downtime. La solución fue añadir un paso de migración en el entrypoint de la imagen de la app que ejecuta prisma migrate deploy antes de arrancar el servidor de Next.js. Así las migraciones siempre se aplican antes de que la aplicación acepte tráfico.

Diseño mobile-first con Tailwind CSS

El diseño parte de la pantalla más pequeña. La mayoría de usuarios consultarán el portal desde el móvil, delante de la nevera o en el supermercado, así que la interfaz tiene que ser rápida de usar con una sola mano. Los breakpoints de Tailwind fueron suficientes para implementar la progresión de diseño sin necesidad de media queries personalizadas.

Para el rendimiento, aproveché la optimización de imágenes de Next.js con el componente Image y las fotos de recetas se sirven en formato WebP con lazy loading automático. El resultado es un Largest Contentful Paint por debajo de 2 segundos en conexiones 4G.

Lecciones aprendidas

Ver Maestro Cocinero en producción

Entra al portal, busca recetas por ingredientes y descubre qué puedes cocinar hoy.

Ver landing del proyecto →