<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Juan Manzanero</title><description>Bienvenido a mi dominio, extraño.</description><link>https://juanmanzanero.com/es</link><language>es-mx</language><image><url>https://juanmanzanero.com//logo.png</url><title>Juan Manzanero</title><link>https://juanmanzanero.com/</link></image><atom:link href="https://juanmanzanero.com//es/feed.xml" rel="self" type="application/rss+xml"/><item><title>Cómo Funcionan las Computadoras</title><link>https://juanmanzanero.com/blog/es/como-funcionan-las-computadoras</link><guid isPermaLink="true">https://juanmanzanero.com/blog/es/como-funcionan-las-computadoras</guid><description>Hoy en día usamos, de alguna manera, una computadora en casi toda actividad de nuestras vidas, volviéndose indispensables.</description><pubDate>Mon, 29 May 2023 06:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hoy en día usamos, de alguna manera, una computadora en casi toda actividad de nuestras vidas, podría ser para trabajo o solo por diversión, pero si lo pensamos con cuidado, las computadoras son un invento del siglo pasado, y han cambiado nuestras vidas.&lt;/p&gt;
&lt;p&gt;Nuevos empleos han aparecido, nuevas carreras por estudiar, nuevos problemas por resolver.&lt;/p&gt;
&lt;p&gt;Pero, ¿realmente sabemos cómo las computadoras penetraron en nuestras vidas?, ¿realmente sabemos cómo funciona una computadora?, ¿cómo funciona Internet?&lt;/p&gt;
&lt;p&gt;Hay muchas personas que usan sus smartphones para comunicarse con su familia y amigos, para compartir sus vidas, pero no saben cómo es esto posible.&lt;/p&gt;
&lt;p&gt;No estoy diciendo que todos deberían tener una Ingeniería en Software o ser expertos en IT, pero entender nuestra actual línea temporal puede ser un conocimiento destacable a tener.&lt;/p&gt;
&lt;h2&gt;El poder de las computadoras&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Las computadoras están hechas para expandir nuestros cerebros, desde cosas como enviar mensajes a personas del otro lado del planeta, hasta crear una app que agiliza un proceso de entrega.&lt;/p&gt;
&lt;p&gt;Todo esto se hace tan solo “girando” unos y ceros (1´s y 0´s), pero, ¿cómo es esto posible?&lt;/p&gt;
&lt;p&gt;Si ya has visto Él Código Enigma, ya sabes parte de esa historia.&lt;/p&gt;
&lt;h3&gt;Fundamentos de Computación&lt;/h3&gt;
&lt;hr /&gt;
&lt;p&gt;Alan Turing fue el inventor de la Máquina de Turing, una simple pero poderosa máquina que puede recibir instrucciones para moverse en una larga cinta, cambiando el estado de cada ranura. Estas 3 cosas, una cabeza, una cinta larga y un conjunto de instrucciones son las bases para las computadoras modernas.&lt;/p&gt;
&lt;p&gt;La cabeza es la Unidad de Procesamiento Central (CPU), una pieza de hardware que puede ser usada con propósitos generales, recibiendo instrucciones (Algoritmo) que son convertidos a pulsos eléctricos, entendiendo si la electricidad pasa o no, si es verdadero o falso, 1 ó 0. Todas estas instrucciones son guardadas en la Memoria Aleatoria de Acceso, Random Access Memory (RAM) para un acceso rápido del trabajo que se ha hecho. La Memoria de Solo Lectura, Read-Only Memory (ROM) es usada para guardar datos persistentes que se necesitan recuperar aunque la computadora se apague.&lt;/p&gt;
&lt;p&gt;Un algoritmo puede ser visto como una receta de cocina, declaras los ingredientes (variables) y los pasos a seguir (funciones) para obtener un resultado.&lt;/p&gt;
&lt;p&gt;Una variable es un identificador que apunta a un slot en la RAM, guardando un valor que puede ser un número, un texto (se le conoce como “string”), un booleano (true o false), un object (un conjunto de múltiples variables y funciones que puede ser instanciado), etc.&lt;/p&gt;
&lt;p&gt;Las funciones son bloques de instrucciones que cumplen una tarea, como obtener la ubicación actual o enviar un mensaje.&lt;/p&gt;
&lt;p&gt;Y te estarás preguntando, ¿cómo puedo decirle a una computadora lo que quiero hacer?&lt;/p&gt;
&lt;h3&gt;Lenguajes de programación&lt;/h3&gt;
&lt;hr /&gt;
&lt;p&gt;Si intentas hablar con alguien que no habla el mismo idioma que tu, intentas usar un traductor o usas gestos, algo que sabes que ambos puedan de alguna manera entender, lo mismo ocurre con las computadoras.&lt;/p&gt;
&lt;p&gt;Las computadoras son poderosas, pero necesitan a alguien que les diga qué hacer, este es el trabajo de los humanos, y para conseguirlo usamos lenguajes de programación. Con un lenguaje de programación usas una sintaxis específica para decirle a la computadora lo que quieres hacer, una vez hecho esto compilas el archivo que contiene las instrucciones que escribiste, entonces es transformado a un lenguaje que las computadoras pueden entender con mayor facilitad (1´s y 0´s) y entonces la computadora realiza la tarea.&lt;/p&gt;
&lt;p&gt;Hay diferentes lenguajes de programación, y todos ellos están diseñados para solucionar necesidades específicas. Lenguajes como C y C++ son lenguajes de bajo nivel, esto quiere decir que son muy cercanos a cómo una computadora “habla” y son usados para controlar y administrar la memoria en apps de alto rendimiento, o para iluminar las pantallas de tu computadora.&lt;/p&gt;
&lt;p&gt;Existe Java, un lenguaje que crea un entorno cuando es compilado, lo que le permite ser utilizado en casi cualquier computadora.&lt;/p&gt;
&lt;p&gt;JavaScript (NO tiene que ver con Java o algo así) es un lenguaje que los navegadores entienden, gracias a JavaScript podemos acceder a una página web y ver asombrosas interacciones cuando le damos click a un botón, inicar sesión con un username y password, y más.&lt;/p&gt;
&lt;p&gt;JavaScript es un lenguaje de alto nivel, es más fácil de aprender que Java o C, pero no por eso es mejor o peor, simplemente resuelve una necesidad diferente.&lt;/p&gt;
&lt;h3&gt;El Navegador&lt;/h3&gt;
&lt;hr /&gt;
&lt;p&gt;Es un poderoso software que puede acceder a otras computadoras usando el Hyper Text Transfer Protocol (HTTP), significa que gracias a este protocolo distintas computadoras pueden enviar y recibir información para comunicarse, incluso si están muy lejos. Los navegadores reciben datos en forma de archivos, principalmente tres:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hyper Text Markup Language (HTML)&lt;/li&gt;
&lt;li&gt;Cascading Styles Sheets (CSS)&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;HTML&lt;/h3&gt;
&lt;p&gt;Ayuda al navegador a estructurar los datos como textos o imágenes, usando un lenguaje de marcado (mediante tags). El navegador puede reconocer dónde hay un input para escribir nuestro email, o un botón para subscribirnos a nuestro artista favorito.&lt;/p&gt;
&lt;h3&gt;CSS&lt;/h3&gt;
&lt;p&gt;Ayuda a colorear y formar los tags de HTML usando selectores. Puede ser usado para cambiar el color de fondo de la página, darle a un botón bordes redondeados, cambiar el color de texto, todo lo que tu creatividad pueda imaginar.&lt;/p&gt;
&lt;h3&gt;JavaScript&lt;/h3&gt;
&lt;p&gt;Combinando HTML y CSS con Javascript se crea un sitio web interactivo, o aplicación web (como este). Puedes por ejemplo, añadir un botón que cambie el tema oscuro al claro, o guardar items en un carrito de compras y mostrar el número de items que tienes guardados.&lt;/p&gt;
&lt;p&gt;Para todo esto necesitas guardar tus archivos en algún sitio, permitiéndole a las personas acceder a la computadora para descargar todos estos archivos usando sus navegadores, y para eso existen los servidores.&lt;/p&gt;
&lt;p&gt;Los servidores son computadoras conectadas a Internet, guardan archivos que pueden ser descargados o subidos usando protocolos y reglas de seguridad. Algunas compañías como Google o Microsoft tienen múltiples centros con muchos servidores en diferentes regiones en el planeta, llamados Data Centers, y pueden ser usados por una tarifa para guardar tu web app, estos múltiples Data Centers se les conoce como Cloud.&lt;/p&gt;
&lt;h3&gt;La Nube&lt;/h3&gt;
&lt;p&gt;Administrar una poderosa computadora puede ser difícil, pero si sabes cómo usarla, puedes ahorrarte mucho dinero en lugar de mantener servidores locales que necesiten estar encendidos 24/7. Gracias a la Cloud podemos entregar apps más rápidas, y podemos tener un servicio 24/7 para nuestros clientes con un costo marginal.&lt;/p&gt;
&lt;h2&gt;Las Computadoras Cambiaron la Humanidad&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Las computadoras simplifican nuestras tareas diarias, el software puede ser fácilmente replicado y distribuido sin la necesidad de logística como si lo necesita un producto tangible. Solo necesitas una conexión a Internet para acceder al proyecto de alguien.&lt;/p&gt;
&lt;p&gt;No necesitas una fábrica o un recurso natural como madera para producir papel, necesitas un grupo de ingenieros, diseñadores UX/UI, digital marketers, y otras personas enfocadas en IT para alcanzar a miles de clientes.&lt;/p&gt;
&lt;p&gt;La razón por la que las computadoras son tan poderosas es debido a que su costo marginal es mínimo, no necesitas extraer una materia prima de la tierra para construir una app, necesitas un grupo de personas talentosas que usen sus cerebros para crear soluciones.&lt;/p&gt;
</content:encoded><author>Juan Manzanero</author></item><item><title>Construye una App Fullstack</title><link>https://juanmanzanero.com/blog/es/construye-una-app-fullstack</link><guid isPermaLink="true">https://juanmanzanero.com/blog/es/construye-una-app-fullstack</guid><description>Construye una app fullstack usando Next.js como meta-framework y PostgreSQL como base de datos.</description><pubDate>Thu, 18 Jan 2024 06:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/juanmanzanero-com/fullstack-app&quot;&gt;GitHub repo&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Contenido&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#1-introducci%C3%B3n&quot;&gt;Introducción&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#2-configuraci%C3%B3n-inicial&quot;&gt;Configuración inicial&lt;/a&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#21-instalar-shadcnui&quot;&gt;Instalar shadcn/ui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#22-crear-una-base-de-datos-postgresql-usando-docker&quot;&gt;Crear una base de datos PostgreSQL usando Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#23-instalar-prisma&quot;&gt;Instalar Prisma&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#24-configurar-authjs&quot;&gt;Configurar Auth.js&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#3-mejora-tu-ui&quot;&gt;Mejora tu UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#4-a%C3%B1ade-funcionalidad-crud&quot;&gt;Añade funcionalidad CRUD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#5-conclusi%C3%B3n&quot;&gt;Conclusión&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. Introducción&lt;/h2&gt;
&lt;p&gt;En este tutorial, desarrollaremos una app fullstack con el siguiente tech stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://nextjs.org/&quot;&gt;Next.js&lt;/a&gt; como meta-framework&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tailwindcss.com/&quot;&gt;TailwindCSS&lt;/a&gt; para estilos&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ui.shadcn.com/&quot;&gt;shadcn/ui&lt;/a&gt; para componentes UI&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.prisma.io/&quot;&gt;Prisma&lt;/a&gt; como ORM&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt; como base de datos&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://authjs.dev/&quot;&gt;Auth.js&lt;/a&gt; para autenticación&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt; para crear una instancia de una base de datos PostgreSQL localmente&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Aprenderemos algunos de los fundamentos de este tech stack, como usar &lt;strong&gt;server components&lt;/strong&gt; en Next.js, o crear &lt;strong&gt;endpoints API&lt;/strong&gt; usando el &lt;strong&gt;app router&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;2. Configuración inicial&lt;/h2&gt;
&lt;p&gt;Comencemos creando un nuevo proyecto Next.js, en tu &lt;code&gt;terminal&lt;/code&gt; ejecuta:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx create-next-app@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Asegúrate de marcar &lt;code&gt;Yes&lt;/code&gt; en las siguientes opciones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Would you like to use &lt;strong&gt;TypeScript&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Would you like to use &lt;strong&gt;ESLint&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Would you like to use &lt;strong&gt;Tailwind CSS&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Would you like to use &lt;strong&gt;&apos;src/&apos; directory&lt;/strong&gt;?&lt;/li&gt;
&lt;li&gt;Would you like to use &lt;strong&gt;App Router&lt;/strong&gt;? (recommended)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; What is your project named? fullstack-app
&amp;gt; Would you like to use TypeScript? No / Yes
&amp;gt; Would you like to use ESLint? No / Yes
&amp;gt; Would you like to use Tailwind CSS? No / Yes
&amp;gt; Would you like to use `src/` directory? No / Yes
&amp;gt; Would you like to use App Router? (recommended) No / Yes
&amp;gt; Would you like to customize the default import alias (@/*)? No / Yes
&amp;gt; What import alias would you like configured? @/*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Espera a que la instalación de las dependencias termine, luego accede a la carpeta del proyecto:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd fullstack-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Abre tu editor de código de preferencia.&lt;/p&gt;
&lt;h3&gt;2.1 Instalar shadcn/ui&lt;/h3&gt;
&lt;p&gt;Estos componentes nos ayudarán mucho a construir la &lt;strong&gt;UI&lt;/strong&gt; junto con TailwindCSS.&lt;/p&gt;
&lt;p&gt;Primero, inicializa shadcn/ui:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx shadcn-ui@latest init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Asegúrate de configurar shadcn/ui de acuerdo a tu proyecto.&lt;/p&gt;
&lt;p&gt;Puedes revisar la &lt;a href=&quot;https://ui.shadcn.com/docs&quot;&gt;documentación de shadcn/ui&lt;/a&gt; para cada componente que puedas necesitar, cada componente se instala individualmente.&lt;/p&gt;
&lt;h3&gt;2.2 Crear una base de datos PostgreSQL usando Docker&lt;/h3&gt;
&lt;p&gt;Asegúrate de tener &lt;a href=&quot;https://www.docker.com/&quot;&gt;Docker&lt;/a&gt; instalado en tu máquina.&lt;/p&gt;
&lt;p&gt;Primero, necesitas descargar una imagen de PostgreSQL de Docker Hub:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker pull postgres
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Luego, crea un contenedor con la imagen:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run --name my-postgres -e POSTGRES_PASSWORD=password -p 5432:5432 -d postgres
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.3 Instalar Prisma&lt;/h3&gt;
&lt;p&gt;Instala Prisma usando tu gestor de dependencias, en este caso &lt;strong&gt;npm&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install prisma -D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ahora inicializa Prisma:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx prisma init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Se creará un directorio &lt;code&gt;./prisma&lt;/code&gt; en la raíz de tu proyecto, con un archivo &lt;code&gt;schema.prisma&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Aquí crearás tus schemas.&lt;/p&gt;
&lt;p&gt;Agrega este modelo como ejemplo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;model User {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  email     String   @unique
  name      String?
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Actualiza tu archivo .env con la siguiente URL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DATABASE_URL=&quot;postgresql://postgres:password@localhost:5432/postgres?schema=public&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En esta URL está tu nombre de usuario (por defecto es postgres), tu contraseña (en este caso password), el host (por defecto es localhost), el puerto (por defecto es 5432), el nombre de la base de datos (por defecto es postgres) y el schema (por defecto es public).&lt;/p&gt;
&lt;p&gt;Crea tu primera migración para probar si Prisma puede conectarse a tu base de datos local:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx prisma migrate dev --name init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si todo está bien, verás un nuevo directorio &lt;code&gt;/migrations&lt;/code&gt; con un nuevo archivo dentro.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Si tienes un error, asegúrate de poder conectarte a tu base de datos local. Elimina y crea el contenedor de nuevo si es necesario.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.4 Configurar Auth.js&lt;/h3&gt;
&lt;p&gt;Agrega este modelo a tu &lt;code&gt;schema.prisma&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String? @db.Text
  session_state     String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Estos modelos son para &lt;strong&gt;Auth.js&lt;/strong&gt;, ahora podemos instalarlo con el adaptador de Prisma:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @prisma/client @auth/prisma-adapter
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instalaremos nodemailer también, ya que usaremos magic links para la autenticación:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install nodemailer -D
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ahora crea &lt;code&gt;src/utils/db.ts&lt;/code&gt; e inicializa &lt;strong&gt;prisma&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { PrismaClient } from &apos;@prisma/client&apos;;

const prismaClientSingleton = () =&amp;gt; {
  return new PrismaClient();
};

declare global {
  var prisma: undefined | ReturnType&amp;lt;typeof prismaClientSingleton&amp;gt;;
}

const prisma = globalThis.prisma ?? prismaClientSingleton();

export default prisma;

if (process.env.NODE_ENV !== &apos;production&apos;) globalThis.prisma = prisma;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Luego, crea &lt;code&gt;src/libs/auth.ts&lt;/code&gt; para configurar Auth.js:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import type { NextAuthOptions } from &apos;next-auth&apos;;
import { PrismaAdapter } from &apos;@auth/prisma-adapter&apos;;
import EmailProvider from &apos;next-auth/providers/email&apos;;
import prisma from &apos;@/libs/db&apos;;
import { Adapter } from &apos;next-auth/adapters&apos;;

export const authOptions = {
  adapter: PrismaAdapter(prisma) as Adapter,
  providers: [
    EmailProvider({
      server: {
        host: process.env.EMAIL_SERVER_HOST,
        port: process.env.EMAIL_SERVER_PORT,
        auth: {
          user: process.env.EMAIL_SERVER_USER,
          pass: process.env.EMAIL_SERVER_PASSWORD,
        },
      },
      from: process.env.EMAIL_FROM,
    }),
  ],
  callbacks: {
    session: async ({ session, user }) =&amp;gt; {
      return {
        ...session,
        user: user,
      };
    },
  },
} satisfies NextAuthOptions;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esta configuración es para usar un proveedor de email, para este proyecto usaremos &lt;a href=&quot;https://resend.com/&quot;&gt;Resend&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Crea una cuenta y luego obtén las siguientes credenciales en tu archivo .env:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EMAIL_SERVER_HOST: smtp.resend.com&lt;/li&gt;
&lt;li&gt;EMAIL_SERVER_PORT: 465&lt;/li&gt;
&lt;li&gt;EMAIL_SERVER_USER: resend&lt;/li&gt;
&lt;li&gt;EMAIL_FROM: onboarding@resend(dot)dev&lt;/li&gt;
&lt;li&gt;EMAIL_SERVER_PASSWORD: yor api key&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ahora, crea un archivo &lt;code&gt;src/pages/api/auth/[...nextauth].ts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { authOptions } from &apos;@/libs/auth&apos;;
import NextAuth from &apos;next-auth/next&apos;;

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Este archivo es para manejar la autenticación en nuestra app.&lt;/p&gt;
&lt;p&gt;Ahora puedes autenticar usuarios con un magic link enviado por email.&lt;/p&gt;
&lt;p&gt;Crea un archivo &lt;code&gt;src/app/auth/signin-form.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;use client&apos;;

import { useState } from &apos;react&apos;;
import { signIn } from &apos;next-auth/react&apos;;

export default function SigninForm() {
  const [email, setEmail] = useState&amp;lt;null | string&amp;gt;(null);

  async function handleSubmit() {
    await signIn(&apos;email&apos;, {
      email,
      callbackUrl: `${window.location.origin}`,
    });
  }

  return (
    &amp;lt;form className=&apos;mt-5 space-y-4&apos; action={handleSubmit}&amp;gt;
      &amp;lt;section className=&apos;flex flex-col gap-2&apos;&amp;gt;
        &amp;lt;label htmlFor=&apos;email&apos;&amp;gt;Email&amp;lt;/label&amp;gt;
        &amp;lt;input
          id=&apos;email&apos;
          type=&apos;email&apos;
          name=&apos;email&apos;
          onChange={(e) =&amp;gt; setEmail(e.target.value)}
          className=&apos;w-max p-1 border border-slate-400&apos;
        /&amp;gt;
      &amp;lt;/section&amp;gt;
      &amp;lt;button type=&apos;submit&apos;&amp;gt;Sign in&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Impórtalo en &lt;code&gt;src/app/auth/page.tsx&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;Import it to your &lt;code&gt;src/app/auth/page.tsx&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { authOptions } from &apos;@/libs/auth&apos;;
import { getServerSession } from &apos;next-auth&apos;;
import { redirect } from &apos;next/navigation&apos;;
import SigninForm from &apos;./form&apos;;

export default async function Signin() {
  const session = await getServerSession(authOptions);

  if (session) {
    return redirect(&apos;/&apos;);
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;Sign in&amp;lt;/h1&amp;gt;

      &amp;lt;SigninForm /&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Como puedes ver, puedes redirigir a los usuarios si no están autenticados obteniendo la sesión con &lt;strong&gt;getServerSession&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;3. Mejora tu UI&lt;/h2&gt;
&lt;p&gt;Creemos una app para posts cortos.&lt;/p&gt;
&lt;p&gt;Primero, agrega algunos componentes de shadcn/ui y actualiza tus componentes, también crearemos nuevos componentes:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx shadcn-ui@latest add button
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;npx shadcn-ui@latest add dialog
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;npx shadcn-ui@latest add input
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;npx shadcn-ui@latest add textarea
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;npx shadcn-ui@latest add form
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;npx shadcn-ui@latest add label
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;npx shadcn-ui@latest add sonner
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Agregaremos las URL de los endpoints para estos componentes, pero más adelante.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/app/auth/signin-form.tsx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Aquí actualizaremos la UI y agregaremos validación de formulario.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;use client&apos;;

import { signIn } from &apos;next-auth/react&apos;;
import { Button } from &apos;@/components/ui/button&apos;;
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from &apos;@/components/ui/form&apos;;
import { Input } from &apos;@/components/ui/input&apos;;
import { Textarea } from &apos;@/components/ui/textarea&apos;;
import { useForm } from &apos;react-hook-form&apos;;
import * as z from &apos;zod&apos;;
import { zodResolver } from &apos;@hookform/resolvers/zod&apos;;

const formSchema = z.object({
  email: z.string().email(),
});

export default function SigninForm() {
  const form = useForm&amp;lt;z.infer&amp;lt;typeof formSchema&amp;gt;&amp;gt;({
    resolver: zodResolver(formSchema),
    defaultValues: {
      email: &apos;&apos;,
    },
  });

  async function onSubmit({ email }: z.infer&amp;lt;typeof formSchema&amp;gt;) {
    await signIn(&apos;email&apos;, {
      email,
      callbackUrl: `${window.location.origin}`,
    });
  }

  return (
    &amp;lt;Form {...form}&amp;gt;
      &amp;lt;form className=&apos;space-y-4&apos; onSubmit={form.handleSubmit(onSubmit)}&amp;gt;
        &amp;lt;FormField
          control={form.control}
          name=&apos;email&apos;
          render={({ field }) =&amp;gt; (
            &amp;lt;FormItem&amp;gt;
              &amp;lt;FormLabel&amp;gt;Email&amp;lt;/FormLabel&amp;gt;
              &amp;lt;FormControl&amp;gt;
                &amp;lt;Input placeholder=&apos;address@example.com&apos; {...field} /&amp;gt;
              &amp;lt;/FormControl&amp;gt;
              &amp;lt;FormMessage /&amp;gt;
            &amp;lt;/FormItem&amp;gt;
          )}
        /&amp;gt;
        &amp;lt;Button className=&apos;w-full&apos; type=&apos;submit&apos;&amp;gt;
          Send magic link
        &amp;lt;/Button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/Form&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/components/post/create.tsx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Actualiza los componentes de UI y agrega validación de formulario.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;use client&apos;;

import { useState } from &apos;react&apos;;
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from &apos;@/components/ui/dialog&apos;;
import { Button } from &apos;@/components/ui/button&apos;;
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from &apos;@/components/ui/form&apos;;
import { Input } from &apos;@/components/ui/input&apos;;
import { Textarea } from &apos;@/components/ui/textarea&apos;;
import { useForm } from &apos;react-hook-form&apos;;
import * as z from &apos;zod&apos;;
import { zodResolver } from &apos;@hookform/resolvers/zod&apos;;
import { useRouter } from &apos;next/navigation&apos;;
import { toast } from &apos;sonner&apos;;
import { SessionProps } from &apos;./types&apos;;

const formSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string().min(1),
});

export default function CreatePost(props: SessionProps) {
  const [open, setOpen] = useState(false);
  const router = useRouter();
  const form = useForm&amp;lt;z.infer&amp;lt;typeof formSchema&amp;gt;&amp;gt;({
    resolver: zodResolver(formSchema),
    defaultValues: {
      title: &apos;&apos;,
      content: &apos;&apos;,
    },
  });

  async function onSubmit(values: z.infer&amp;lt;typeof formSchema&amp;gt;) {
    try {
      const res = await fetch(&apos;/api/posts&apos;, {
        method: &apos;POST&apos;,
        headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
        body: JSON.stringify({
          ...values,
          authorId: props.session.user?.id,
        }),
      });
      const json = await res.json();

      if (!res.ok) {
        toast(json.message);

        return;
      }

      toast(&apos;Post created!&apos;);
      form.reset();
      setOpen(false);
      router.refresh();
    } catch (error) {
      console.error(error);
    }
  }

  return (
    &amp;lt;Dialog open={open} onOpenChange={setOpen}&amp;gt;
      &amp;lt;DialogTrigger asChild&amp;gt;
        &amp;lt;Button&amp;gt;Create post&amp;lt;/Button&amp;gt;
      &amp;lt;/DialogTrigger&amp;gt;
      &amp;lt;DialogContent className=&apos;max-w-[300px]&apos;&amp;gt;
        &amp;lt;DialogHeader className=&apos;text-left&apos;&amp;gt;
          &amp;lt;DialogTitle&amp;gt;Create post&amp;lt;/DialogTitle&amp;gt;
          &amp;lt;DialogDescription&amp;gt;
            Please &amp;lt;strong&amp;gt;do not&amp;lt;/strong&amp;gt; post &amp;lt;strong&amp;gt;NSFW&amp;lt;/strong&amp;gt; content.
          &amp;lt;/DialogDescription&amp;gt;
        &amp;lt;/DialogHeader&amp;gt;
        &amp;lt;Form {...form}&amp;gt;
          &amp;lt;form className=&apos;space-y-4&apos; onSubmit={form.handleSubmit(onSubmit)}&amp;gt;
            &amp;lt;FormField
              control={form.control}
              name=&apos;title&apos;
              render={({ field }) =&amp;gt; (
                &amp;lt;FormItem&amp;gt;
                  &amp;lt;FormLabel&amp;gt;Title&amp;lt;/FormLabel&amp;gt;
                  &amp;lt;FormControl&amp;gt;
                    &amp;lt;Input placeholder=&apos;Hi there!&apos; {...field} /&amp;gt;
                  &amp;lt;/FormControl&amp;gt;
                  &amp;lt;FormMessage /&amp;gt;
                &amp;lt;/FormItem&amp;gt;
              )}
            /&amp;gt;
            &amp;lt;FormField
              control={form.control}
              name=&apos;content&apos;
              render={({ field }) =&amp;gt; (
                &amp;lt;FormItem&amp;gt;
                  &amp;lt;FormLabel&amp;gt;Content&amp;lt;/FormLabel&amp;gt;
                  &amp;lt;FormControl&amp;gt;
                    &amp;lt;Textarea
                      placeholder=&apos;Testing this great app!&apos;
                      {...field}
                    /&amp;gt;
                  &amp;lt;/FormControl&amp;gt;
                  &amp;lt;FormMessage /&amp;gt;
                &amp;lt;/FormItem&amp;gt;
              )}
            /&amp;gt;
            &amp;lt;Button className=&apos;w-full&apos; type=&apos;submit&apos;&amp;gt;
              Post
            &amp;lt;/Button&amp;gt;
          &amp;lt;/form&amp;gt;
        &amp;lt;/Form&amp;gt;
      &amp;lt;/DialogContent&amp;gt;
    &amp;lt;/Dialog&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/app/page.tsx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Haz fetch de los dats con Prisma, como esta página es un server component, podemos hacer fetch directamente.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { authOptions } from &apos;@/libs/auth&apos;;
import { getServerSession } from &apos;next-auth&apos;;
import prisma from &apos;@/libs/db&apos;;
import CreatePost from &apos;@/components/post/create&apos;;
import Post from &apos;@/components/post&apos;;

export default async function Home() {
  const session = await getServerSession(authOptions);

  // You can fetch data to Prisma in server components
  const posts = await prisma.post.findMany({
    include: {
      author: true,
    },
  });

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1 className=&apos;mb-5 font-bold text-xl&apos;&amp;gt;Home&amp;lt;/h1&amp;gt;
      {session ? (
        &amp;lt;&amp;gt;
          &amp;lt;CreatePost session={session} /&amp;gt;
        &amp;lt;/&amp;gt;
      ) : (
        &amp;lt;&amp;gt;
          &amp;lt;p&amp;gt;You are not logged in&amp;lt;/p&amp;gt;
        &amp;lt;/&amp;gt;
      )}
      &amp;lt;h3 className=&apos;text-lg font-semibold mt-10&apos;&amp;gt;Posts&amp;lt;/h3&amp;gt;
      &amp;lt;ul className=&apos;mt-5 space-y-2.5&apos;&amp;gt;
        {posts.length &amp;gt; 0 ? (
          posts.map((post) =&amp;gt; (
            &amp;lt;li key={post.id}&amp;gt;
              &amp;lt;Post {...post} session={session} /&amp;gt;
            &amp;lt;/li&amp;gt;
          ))
        ) : (
          &amp;lt;p&amp;gt;No posts&amp;lt;/p&amp;gt;
        )}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/components/post/item.tsx&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;use client&apos;;

import DeletePost from &apos;./delete&apos;;
import EditPost from &apos;./edit&apos;;
import { TPostProps } from &apos;./types&apos;;

export default function PostItem(props: TPostProps) {
  return (
    &amp;lt;article className=&apos;w-max p-2 border border-slate-500 rounded-md&apos;&amp;gt;
      &amp;lt;header className=&apos;flex justify-between items-center&apos;&amp;gt;
        &amp;lt;h2 className=&apos;font-bold text-lg&apos;&amp;gt;{props.title}&amp;lt;/h2&amp;gt;
        {props.session?.user?.id === props.authorId &amp;amp;&amp;amp; (
          &amp;lt;section className=&apos;space-x-2&apos;&amp;gt;
            &amp;lt;EditPost {...props} /&amp;gt;
            &amp;lt;DeletePost {...props} /&amp;gt;
          &amp;lt;/section&amp;gt;
        )}
      &amp;lt;/header&amp;gt;
      &amp;lt;p&amp;gt;{props.content}&amp;lt;/p&amp;gt;
      &amp;lt;span className=&apos;text-sm&apos;&amp;gt;
        Posted by {props.author?.email || &apos;anon&apos;} at{&apos; &apos;}
        {new Date(props.createdAt).toLocaleString()}
      &amp;lt;/span&amp;gt;
    &amp;lt;/article&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/components/post/edit.tsx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Crea un botón para abrir un diálogo que renderice los datos del post para editar, agrega validación y fetch al endpoint de la API.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;use client&apos;;

import { useState } from &apos;react&apos;;
import { Edit } from &apos;lucide-react&apos;;
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from &apos;@/components/ui/dialog&apos;;
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from &apos;@/components/ui/form&apos;;
import { Input } from &apos;@/components/ui/input&apos;;
import { Textarea } from &apos;@/components/ui/textarea&apos;;
import { useForm } from &apos;react-hook-form&apos;;
import * as z from &apos;zod&apos;;
import { zodResolver } from &apos;@hookform/resolvers/zod&apos;;
import { useRouter } from &apos;next/navigation&apos;;
import { toast } from &apos;sonner&apos;;
import { Button } from &apos;@/components/ui/button&apos;;
import { TPostProps } from &apos;./types&apos;;

const formSchema = z.object({
  title: z.string().min(1).max(100),
  content: z.string().min(1),
});

export default function EditPost(props: TPostProps) {
  const [open, setOpen] = useState(false);
  const router = useRouter();
  const form = useForm&amp;lt;z.infer&amp;lt;typeof formSchema&amp;gt;&amp;gt;({
    resolver: zodResolver(formSchema),
    defaultValues: {
      title: props.title,
      content: props.content,
    },
  });

  async function onSubmit(values: z.infer&amp;lt;typeof formSchema&amp;gt;) {
    try {
      const res = await fetch(&apos;/api/posts&apos;, {
        method: &apos;PUT&apos;,
        headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
        body: JSON.stringify({
          ...values,
          id: props.id,
        }),
      });
      const json = await res.json();

      if (!res.ok) {
        toast(json.message);

        return;
      }

      toast(&apos;Post edited!&apos;);
      form.reset();
      setOpen(false);
      router.refresh();
    } catch (error) {
      console.error(error);
    }
  }

  return (
    &amp;lt;Dialog open={open} onOpenChange={setOpen}&amp;gt;
      &amp;lt;DialogTrigger asChild&amp;gt;
        &amp;lt;Button variant=&apos;secondary&apos; size=&apos;icon&apos;&amp;gt;
          &amp;lt;Edit /&amp;gt;
        &amp;lt;/Button&amp;gt;
      &amp;lt;/DialogTrigger&amp;gt;
      &amp;lt;DialogContent className=&apos;max-w-[300px]&apos;&amp;gt;
        &amp;lt;DialogHeader className=&apos;text-left&apos;&amp;gt;
          &amp;lt;DialogTitle&amp;gt;Edit post&amp;lt;/DialogTitle&amp;gt;
          &amp;lt;DialogDescription&amp;gt;
            Please &amp;lt;strong&amp;gt;do not&amp;lt;/strong&amp;gt; post &amp;lt;strong&amp;gt;NSFW&amp;lt;/strong&amp;gt; content.
          &amp;lt;/DialogDescription&amp;gt;
        &amp;lt;/DialogHeader&amp;gt;
        &amp;lt;Form {...form}&amp;gt;
          &amp;lt;form className=&apos;space-y-4&apos; onSubmit={form.handleSubmit(onSubmit)}&amp;gt;
            &amp;lt;FormField
              control={form.control}
              name=&apos;title&apos;
              render={({ field }) =&amp;gt; (
                &amp;lt;FormItem&amp;gt;
                  &amp;lt;FormLabel&amp;gt;Title&amp;lt;/FormLabel&amp;gt;
                  &amp;lt;FormControl&amp;gt;
                    &amp;lt;Input placeholder=&apos;Hi there!&apos; {...field} /&amp;gt;
                  &amp;lt;/FormControl&amp;gt;
                  &amp;lt;FormMessage /&amp;gt;
                &amp;lt;/FormItem&amp;gt;
              )}
            /&amp;gt;
            &amp;lt;FormField
              control={form.control}
              name=&apos;content&apos;
              render={({ field }) =&amp;gt; (
                &amp;lt;FormItem&amp;gt;
                  &amp;lt;FormLabel&amp;gt;Content&amp;lt;/FormLabel&amp;gt;
                  &amp;lt;FormControl&amp;gt;
                    &amp;lt;Textarea
                      placeholder=&apos;Testing this great app!&apos;
                      {...field}
                    /&amp;gt;
                  &amp;lt;/FormControl&amp;gt;
                  &amp;lt;FormMessage /&amp;gt;
                &amp;lt;/FormItem&amp;gt;
              )}
            /&amp;gt;
            &amp;lt;DialogClose asChild&amp;gt;
              &amp;lt;Button className=&apos;w-full&apos; type=&apos;submit&apos;&amp;gt;
                Edit post
              &amp;lt;/Button&amp;gt;
            &amp;lt;/DialogClose&amp;gt;
          &amp;lt;/form&amp;gt;
        &amp;lt;/Form&amp;gt;
      &amp;lt;/DialogContent&amp;gt;
    &amp;lt;/Dialog&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/components/post/delete.tsx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Crea un botón para abrir un diálogo para eliminar el post, agrega validación y fetch al endpoint de la API.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;use client&apos;;

import { LucideTrash2 } from &apos;lucide-react&apos;;
import { Button } from &apos;@/components/ui/button&apos;;
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from &apos;@/components/ui/dialog&apos;;
import { useRouter } from &apos;next/navigation&apos;;
import { toast } from &apos;sonner&apos;;
import { TPostProps } from &apos;./types&apos;;

export default function DeletePost(props: TPostProps) {
  const router = useRouter();

  async function handleDelete() {
    try {
      const res = await fetch(&apos;/api/posts&apos;, {
        method: &apos;DELETE&apos;,
        headers: { &apos;Content-Type&apos;: &apos;application/json&apos; },
        body: JSON.stringify({
          id: props.id,
        }),
      });
      const json = await res.json();

      if (!res.ok) {
        toast(json.message);

        return;
      }

      toast(&apos;Post deleted!&apos;);
      router.refresh();
    } catch (error) {
      console.error(error);
    }
  }
  return (
    &amp;lt;Dialog&amp;gt;
      &amp;lt;DialogTrigger asChild&amp;gt;
        &amp;lt;Button variant=&apos;destructive&apos; size=&apos;icon&apos;&amp;gt;
          &amp;lt;LucideTrash2 /&amp;gt;
        &amp;lt;/Button&amp;gt;
      &amp;lt;/DialogTrigger&amp;gt;
      &amp;lt;DialogContent className=&apos;max-w-[300px]&apos;&amp;gt;
        &amp;lt;DialogHeader className=&apos;text-left&apos;&amp;gt;
          &amp;lt;DialogTitle&amp;gt;Delete post&amp;lt;/DialogTitle&amp;gt;
          &amp;lt;DialogDescription&amp;gt;
            Are you sure you want to &amp;lt;strong&amp;gt;delete&amp;lt;/strong&amp;gt; this post? This
            action cannot be undone.
          &amp;lt;/DialogDescription&amp;gt;
        &amp;lt;/DialogHeader&amp;gt;
        &amp;lt;footer className=&apos;flex flex-col gap-2&apos;&amp;gt;
          &amp;lt;DialogClose asChild&amp;gt;
            &amp;lt;Button variant=&apos;secondary&apos; className=&apos;w-full&apos;&amp;gt;
              No, keep post
            &amp;lt;/Button&amp;gt;
          &amp;lt;/DialogClose&amp;gt;
          &amp;lt;DialogClose asChild&amp;gt;
            &amp;lt;Button
              onClick={handleDelete}
              variant=&apos;destructive&apos;
              className=&apos;w-full&apos;
            &amp;gt;
              Yes, delete post
            &amp;lt;/Button&amp;gt;
          &amp;lt;/DialogClose&amp;gt;
        &amp;lt;/footer&amp;gt;
      &amp;lt;/DialogContent&amp;gt;
    &amp;lt;/Dialog&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/components/post/types.ts&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type SessionProps = {
  session: any;
};

type TPostProps = {
  author: {
    id: string;
    name: string | null;
    email: string | null;
    emailVerified: Date | null;
    image: string | null;
  } | null;
  id: string;
  createdAt: Date;
  updatedAt: Date;
  title: string;
  content: string;
  authorId: string | null;
  session: any;
};

export type { SessionProps, TPostProps };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/components/sign-out.tsx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Un botón simple para cerrar sesión.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;use client&apos;;

import { signOut } from &apos;next-auth/react&apos;;
import { Button } from &apos;./ui/button&apos;;

export default function SignOut() {
  return &amp;lt;Button onClick={() =&amp;gt; signOut()}&amp;gt;Sign out&amp;lt;/Button&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/components/navbar.tsx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Renderiza el botón de cerrar sesión o iniciar sesión dependiendo si el usuario está autenticado o no.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Link from &apos;next/link&apos;;
import { Button } from &apos;./ui/button&apos;;
import { getServerSession } from &apos;next-auth&apos;;
import { authOptions } from &apos;@/libs/auth&apos;;
import SignOut from &apos;./sign-out&apos;;

export default async function Navbar() {
  const session = await getServerSession(authOptions);

  return (
    &amp;lt;nav className=&apos;w-full p-4 border-b flex justify-between items-center&apos;&amp;gt;
      &amp;lt;section&amp;gt;
        &amp;lt;Button variant=&apos;link&apos; className=&apos;px-0 font-semibold text-lg&apos;&amp;gt;
          &amp;lt;Link href=&apos;/&apos;&amp;gt;Fullstack app&amp;lt;/Link&amp;gt;
        &amp;lt;/Button&amp;gt;
      &amp;lt;/section&amp;gt;
      &amp;lt;section&amp;gt;
        {session ? (
          &amp;lt;SignOut /&amp;gt;
        ) : (
          &amp;lt;Button asChild&amp;gt;
            &amp;lt;Link href=&apos;/auth&apos;&amp;gt;Sign in&amp;lt;/Link&amp;gt;
          &amp;lt;/Button&amp;gt;
        )}
      &amp;lt;/section&amp;gt;
    &amp;lt;/nav&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;src/app/layout.tsx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Agrega tu &lt;strong&gt;Navbar&lt;/strong&gt; y &lt;strong&gt;Toaster&lt;/strong&gt; y algunos estilos.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Inter } from &apos;next/font/google&apos;;
import Navbar from &apos;@/components/navbar&apos;;
import { Toaster } from &apos;@/components/ui/sonner&apos;;
import &apos;./globals.css&apos;;

const inter = Inter({ subsets: [&apos;latin&apos;] });

interface Props extends React.PropsWithChildren {}

export default function RootLayout(props: Props) {
  return (
    &amp;lt;html lang=&apos;en&apos;&amp;gt;
      &amp;lt;body className={inter.className}&amp;gt;
        &amp;lt;Navbar /&amp;gt;
        &amp;lt;main className=&apos;px-4 py-8&apos;&amp;gt;{props.children}&amp;lt;/main&amp;gt;
        &amp;lt;Toaster /&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. Añade funcionalidad CRUD&lt;/h2&gt;
&lt;p&gt;Ahora podemos crear, leer, actualizar y eliminar posts.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;prisma/schema.prisma&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Actualiza tu schema de Prisma con el modelo Post:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;generator client {
  provider = &quot;prisma-client-js&quot;
}

datasource db {
  provider = &quot;postgresql&quot;
  url      = env(&quot;DATABASE_URL&quot;)
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String? @db.Text
  session_state     String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
  posts         Post[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

model Post {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title     String
  content   String
  author    User     @relation(fields: [authorId], references: [id])
  authorId  String
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Genera una nueva migración:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx prisma migrate dev --name add-posts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ahora, create un archivo &lt;code&gt;src/app/api/posts/route.ts&lt;/code&gt; con las funciones asíncronas &lt;strong&gt;GET&lt;/strong&gt;, &lt;strong&gt;PUT&lt;/strong&gt; y &lt;strong&gt;DELETE&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import prisma from &apos;@/libs/db&apos;;
import { NextRequest, NextResponse } from &apos;next/server&apos;;

export async function POST(req: Request) {
  try {
    if (!req.body) {
      return NextResponse.json({
        ok: false,
        status: 400,
        message: &apos;Data required&apos;,
      });
    }

    const json = await req.json();
    const res = await prisma.post.create({
      data: json,
    });

    return NextResponse.json({
      ok: true,
      status: 201,
      data: res,
    });
  } catch (error) {
    if (error instanceof Error) {
      return NextResponse.json({
        ok: false,
        status: 500,
        message: error.message,
      });
    }

    return NextResponse.json({
      ok: false,
      status: 500,
      message: &apos;Internal server error&apos;,
    });
  }
}

export async function PUT(req: NextRequest) {
  try {
    const body = await req.json();
    const res = await prisma.post.update({
      where: { id: body.id },
      data: {
        title: body.title,
        content: body.content,
      },
    });

    return NextResponse.json({
      ok: true,
      status: 200,
      data: res,
    });
  } catch (error) {
    if (error instanceof Error) {
      return NextResponse.json({
        ok: false,
        status: 500,
        message: error.message,
      });
    }

    return NextResponse.json({
      ok: false,
      status: 500,
      message: &apos;Internal server error&apos;,
    });
  }
}

export async function DELETE(req: NextRequest) {
  try {
    const body = await req.json();
    const res = await prisma.post.delete({
      where: { id: body.id },
    });

    return NextResponse.json({
      ok: true,
      status: 200,
      data: res,
    });
  } catch (error) {
    if (error instanceof Error) {
      return NextResponse.json({
        ok: false,
        status: 500,
        message: error.message,
      });
    }

    return NextResponse.json({
      ok: false,
      status: 500,
      message: &apos;Internal server error&apos;,
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Intenta crear un post en el home.&lt;/p&gt;
&lt;p&gt;La página se refrescará y verás el post que creaste, solo tú puedes editarlo o eliminarlo.&lt;/p&gt;
&lt;h2&gt;5. Conclusión&lt;/h2&gt;
&lt;p&gt;Como puedes ver, crear una app fullstack con Next.js es muy fácil.&lt;/p&gt;
&lt;p&gt;Por supuesto, se puede mejorar, agregando validación del lado del servidor para los inputs, agregando paginación para los posts en el home, etc.&lt;/p&gt;
</content:encoded><author>Juan Manzanero</author></item><item><title>La Monotonia de las Redes Sociales</title><link>https://juanmanzanero.com/blog/es/la-monotonia-de-las-redes-sociales</link><guid isPermaLink="true">https://juanmanzanero.com/blog/es/la-monotonia-de-las-redes-sociales</guid><description>La abstracción de la interacción humana mediante software ha causado muchos problemas que antes no existían.</description><pubDate>Mon, 17 Jul 2023 06:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Es evidente que muchas interacciones en Internet ocurren a través de las redes sociales,
lo que te permite enviar solicitudes de amistad, chatear o compartir memes y fotos.&lt;/p&gt;
&lt;p&gt;Sin embargo, la abstracción de la interacción humana mediante software ha causado muchos
problemas que antes no existían.&lt;/p&gt;
&lt;p&gt;Cuando empezó Internet, muchas personas crearon sus propios sitios web porque eso era lo
que debías tener si querías ser popular, y muchos de estos sitios web eran simplemente como
blogs donde los usuarios compartían sus pasatiempos como películas, deportes, libros, videojuegos, etc.&lt;/p&gt;
&lt;p&gt;Esta motivación llevó a la creación de sitios web únicos y personales, donde podías conocer
a alguien y sus gustos o disgustos.&lt;/p&gt;
&lt;p&gt;Ahora, con el auge de las redes sociales a mediados de los años 2000, la gente prefiere conectarse
simplemente buscando un nombre o viendo los amigos de sus amigos, y enviar solicitudes de amistad
para intentar conectarse. Al principio, esto era genial, como cualquier cosa nueva, pero los problemas
comenzaron cuando empresas como Facebook (ahora Meta) o Google (con YouTube) necesitaban
monetizar sus plataformas, principalmente con anuncios.&lt;/p&gt;
&lt;p&gt;Y, por supuesto, eso significa que tenían que suprimir, censurar o prohibir cualquier cosa
que pudiera ser perjudicial para la sociedad, como discursos de odio o retos estúpidos que pudieran
poner en peligro la vida de las personas.&lt;/p&gt;
&lt;p&gt;Pero lo malo de esto es que homogeneizan a casi todos, obligándolos a actuar como los algoritmos
recomiendan usuarios con gustos y comentarios similares, guiando a las personas a actuar como alguien más,
y así sucesivamente.&lt;/p&gt;
&lt;p&gt;Ahora casi todos hacen principalmente dos cosas: publicar fotos sobre sus vidas &quot;perfectas&quot; o compartir memes,
y no me malinterpretes, está bien entrar en las redes sociales y tratar de desconectar de tu trabajo o problemas,
pero usarlas todos los días como una vía de escape instantánea en lugar de enfrentar tus propios
problemas podría ser perjudicial a largo plazo, aislándote de la necesidad de socializar en la vida real,
con personas reales, y pensando que todos tienen una vida perfecta.&lt;/p&gt;
&lt;p&gt;No, TODOS tienen problemas en sus vidas, incluso más que los tuyos, pero los algoritmos de las
redes sociales promueven principalmente &quot;solo vibraciones positivas&quot; y todas esas tonterías que en grandes
cantidades son perjudiciales para nuestras mentes.&lt;/p&gt;
&lt;p&gt;Y no mencionemos la censura y el shadow-banning si publicas algo controvertido, puede ser algo que no se
debería tolerar, como incitar al odio a un grupo, o puede ser algo en lo que no todos estén de acuerdo,
pero podría ser útil reflexionar un poco al respecto, aunque sea controvertido.&lt;/p&gt;
&lt;p&gt;¿Debería todo el mundo poder expresar lo que piensa? Sí, siempre y cuando no promueva el odio o
lastime a otras personas o animales.&lt;/p&gt;
&lt;p&gt;Hay informes de que Twitter promueve el odio en el algoritmo, y Meta sabe que Instagram aumenta la
ansiedad y la depresión en los jóvenes, y de hecho, lo promueve... ya que todos los sentimientos
negativos te mantienen en las redes sociales interactuando con los demás, ya que eso es lo que esas
empresas venden, tus datos y tu tiempo a los anunciantes.&lt;/p&gt;
&lt;p&gt;Recientemente escuché un video que hablaba sobre este tema, y sería genial si volviéramos a los
inicios de Internet, donde las personas creaban contenido como un pasatiempo, en lugar de buscar
validación a través de los me gusta y los comentarios, siendo personas más auténticas en lugar de productos.&lt;/p&gt;
&lt;p&gt;¿Deberían las soluciones de software reemplazar las interacciones humanas? Creo que no, pero es
demasiado tarde para casi todos, pero si estás leyendo esto, comienza por cambiar tu vida primero si
quieres ser honesto contigo mismo.&lt;/p&gt;
&lt;hr /&gt;
&lt;h6&gt;Blog inspirado en &quot;Why does every personal website look like this now?&quot; by Eric Murphy en YouTube.&lt;/h6&gt;
&lt;p&gt;Video de origen (en Inglés):&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe
width=&quot;560&quot;
height=&quot;315&quot;
src=&quot;https://www.youtube-nocookie.com/embed/_x6SCSz7g5I&quot;
title=&quot;YouTube video player&quot;
frameborder=&quot;0&quot;
allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
allowfullscreen&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><author>Juan Manzanero</author></item><item><title>La Privacidad Debe Ser Tomada Más en Serio</title><link>https://juanmanzanero.com/blog/es/la-privacidad-debe-ser-tomada-mas-en-serio</link><guid isPermaLink="true">https://juanmanzanero.com/blog/es/la-privacidad-debe-ser-tomada-mas-en-serio</guid><description>La privacidad es un pilar fundamental de la libertad de expresión.</description><pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;La privacidad es un pilar fundamental de la libertad de expresión.&lt;/p&gt;
&lt;p&gt;Puede que hayas encontrado &lt;em&gt;propaganda&lt;/em&gt; en redes sociales, sobre
“individuos/grupos anónimos” están cometiendo crímenes, hackeos, etc.&lt;/p&gt;
&lt;p&gt;Bueno, como dije, eso es pura &lt;em&gt;propaganda&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Los gobiernos quieren convencerte de que la privacidad y el anonimato significan
crimen.&lt;/p&gt;
&lt;p&gt;No debería decirte esto, pero es una forma de pensar estúpida.&lt;/p&gt;
&lt;p&gt;No porque un criminal use Tor o Monero significa que todos quienes usen las
tecnologías mencionadas son criminales.&lt;/p&gt;
&lt;p&gt;Es como decir que si un criminal usa una cuchara significa que todos quienes
usen una cuchara son iguales criminales, sí; así de estúpido suena.&lt;/p&gt;
&lt;p&gt;La privacidad es un derecho básico, garantiza que aquellos en el poder puedan
ser cuestionados sin que puedan deshacerse de sus críticos, y esto es más
requerido, ya que la censura continúa incrementando.&lt;/p&gt;
&lt;p&gt;Puede que hoy censuren malas palabras, mañana memes del idiota en la silla al
que la gente llama presidente, en 1 mes no podrás hablar de nada más que de
buenas cosas del gobierno.&lt;/p&gt;
&lt;p&gt;Pero como la libertad, todos la quieren, pero no todos van a luchar por ella, al
menos en estos días.&lt;/p&gt;
&lt;p&gt;Aprende sobre &lt;em&gt;PGP&lt;/em&gt;, &lt;em&gt;Tor&lt;/em&gt;, &lt;em&gt;Monero&lt;/em&gt;, y cualquier otra tecnología que te
proteja.&lt;/p&gt;
</content:encoded><author>Juan Manzanero</author></item><item><title>Next Intl Blog Template</title><link>https://juanmanzanero.com/blog/es/next-intl-blog-template</link><guid isPermaLink="true">https://juanmanzanero.com/blog/es/next-intl-blog-template</guid><description>¡Comienza tu blog en múltiples idiomas!</description><pubDate>Mon, 18 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://github.com/juanmanzanero-com/next-intl-blog-template&quot;&gt;GitHub&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://next-intl-blog-template.vercel.app/en&quot;&gt;Website&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Introducción&lt;/h2&gt;
&lt;p&gt;Recientemente actualicé este website, y como sabrás, es un website con &lt;strong&gt;contenido en Inglés y Español&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;No estoy usando un plugin de traducción, en su lugar escribo cada palabra en ambos idiomas.&lt;/p&gt;
&lt;p&gt;Gracias a Next.js y &lt;a href=&quot;https://next-intl-docs.vercel.app/&quot;&gt;next-intl&lt;/a&gt; puedo lograr esto, renderizando rutas para cada idioma en el website, accediendo a un diccionario que contiene el contenido traducido por mí.&lt;/p&gt;
&lt;p&gt;Para los archivos .mdx, creé un directorio para cada idioma, y dentro de esos directorios contiene el contenido en ambos idiomas también.&lt;/p&gt;
&lt;h2&gt;Cómo usar&lt;/h2&gt;
&lt;p&gt;Este template es una extensión de &lt;a href=&quot;https://next-intl-docs.vercel.app/&quot;&gt;next-intl&lt;/a&gt;, revisa la &lt;a href=&quot;https://next-intl-docs.vercel.app/docs/getting-started&quot;&gt;guía de inicio&lt;/a&gt; para aprender lo básico, el propósito del template es crear un layout simple para futuras personalizaciones.&lt;/p&gt;
&lt;h3&gt;Agregar o quitar locales&lt;/h3&gt;
&lt;p&gt;Puedes agregar o remover locales en el archivo &lt;code&gt;src/lang/locales.ts&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export type locales = &apos;en&apos; | &apos;es&apos;;

export const localesList: locales[] = [&apos;en&apos;, &apos;es&apos;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Solo agrega o remueve un locale de la constante &lt;code&gt;locales&lt;/code&gt;, y agrega o remueve el locale de la lista.&lt;/p&gt;
&lt;p&gt;El primer item en &lt;code&gt;localesList&lt;/code&gt; debe ser el locale por default.&lt;/p&gt;
&lt;p&gt;La lista es usada para la generación de rutas estáticas en
&lt;code&gt;src/app/[locale]/layout.tsx&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { localesList } from &apos;@/lang/locales&apos;;

export function generateStaticParams() {
  return localesList.map((locale) =&amp;gt; ({ locale }));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recuerda actualizar el matcher en &lt;code&gt;src/middleware.ts&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//...

export const config = {
  matcher: [&apos;/&apos;, &apos;/(en|es)/:path*&apos;],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Y por supuesto, actualiza los archivos &lt;code&gt;src/lang/[locale].json&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Crear contenido&lt;/h3&gt;
&lt;p&gt;Usa &lt;code&gt;src/content/[locale]&lt;/code&gt; para crear contenido, en el directorio &lt;code&gt;/[locale]/&lt;/code&gt; crea un directorio para cada propósito, por ejemplo: &lt;code&gt;/[locale]/blog&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Dentro crea el archivo .mdx con un nombre único, el nombre será usado como slug para crear la página estática para ese post.&lt;/p&gt;
&lt;p&gt;Para crear una sección de blog, usarás la función &lt;em&gt;getAllContent&lt;/em&gt; en tu ruta, por ejemplo: &lt;code&gt;src/app/[locale]/blog/[slug]/page.tsx&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Mdx } from &apos;@/components&apos;;
import { TParamsLocale, TPage, TSlugLang } from &apos;@/types&apos;;
import { Metadata } from &apos;next&apos;;
import { getAllContent, getContent } from &apos;@/utils/getContent&apos;;

export async function generateStaticParams(
  props: TParamsLocale
): Promise&amp;lt;TSlugLang[]&amp;gt; {
  const blogs = await getAllContent(props.params.locale, &apos;blog&apos;);

  if (!blogs) return [];

  return blogs.map((blog) =&amp;gt; ({
    slug: blog.slug,
    locale: props.params.locale,
  }));
}

//...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esto creará una página estática para cada post de blog.&lt;/p&gt;
&lt;p&gt;Puedes obtener la metadata del archivo &lt;code&gt;.mdx&lt;/code&gt; también.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//...

export async function generateMetadata(props: TPage): Promise&amp;lt;Metadata&amp;gt; {
  const blog = await getContent(props.params.locale, &apos;blog&apos;, props.params.slug);

  if (!blog) return {};

  return {
    title: blog.title,
    //...
  };
}

//...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Entonces, renderiza el contenido usando el componente &lt;em&gt;Mdx&lt;/em&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//...

export default async function Page(props: TPage) {
  const post = await getContent(props.params.locale, &apos;blog&apos;, props.params.slug);

  if (!post) return null;

  return &amp;lt;Mdx code={post.body.code} /&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/juanmanzanero-com/next-intl-blog-template&quot;&gt;Puedes hacer un fork de este template aquí&lt;/a&gt;&lt;/p&gt;
</content:encoded><author>Juan Manzanero</author></item><item><title>Participé en una Hackaton</title><link>https://juanmanzanero.com/blog/es/participe-en-una-hackaton</link><guid isPermaLink="true">https://juanmanzanero.com/blog/es/participe-en-una-hackaton</guid><description>Recientemente participé en un Hackathon de Supabase, formando un equipo con personas de otros países.</description><pubDate>Wed, 16 Aug 2023 06:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recientemente participé en un &lt;a href=&quot;https://supabase.com/blog/supabase-lw8-hackathon&quot;&gt;Hackathon de Supabase&lt;/a&gt;,
formando un equipo con personas de otros países, uno de Bangladesh y otro de Brasil,
utilizando inglés para comunicarnos en Discord.&lt;/p&gt;
&lt;p&gt;La temática del Hackathon era libre, la única regla principal era utilizar Supabase para cualquier función,
como autenticación, como base de datos PostgreSQL o para utilizar vectores en la inteligencia artificial.&lt;/p&gt;
&lt;p&gt;Tuvimos 10 días para construir un producto utilizando cualquier tecnología y subirlo a un repositorio en GitHub.&lt;/p&gt;
&lt;p&gt;Desarrollamos una aplicación de comercio electrónico con un modelo de productos similar a Walmart,
implementando vectores para obtener mejores resultados en las búsquedas.&lt;/p&gt;
&lt;p&gt;Utilizando Supabase, implementamos autenticación y rutas protegidas,
de manera que el usuario deba iniciar sesión para ver recomendaciones y más.&lt;/p&gt;
&lt;p&gt;El usuario puede agregar productos al carrito de compras y revisar sus artículos para guardarlos y
ver recomendaciones, así como los artículos que se compran con frecuencia.&lt;/p&gt;
&lt;p&gt;Mi papel principal se centró en crear la interfaz de usuario utilizando Next.js 13app router,
protegiendo las rutas solo para usuarios autenticados, y creando componentes reutilizables como
tarjetas de producto, y por supuesto, haciendo que el diseño sea adaptable tanto para dispositivos móviles
como para escritorio.&lt;/p&gt;
&lt;p&gt;Utilizamos &lt;a href=&quot;https://ui.shadcn.com/&quot;&gt;Shadcn/UI&lt;/a&gt;, ya que estos componentes ya implementan funcionalidades
con accesibilidad, como modales o barras laterales. Por ejemplo, la barra lateral que aparece cuando estás
en un dispositivo móvil y lo abres con el botón en el encabezado, con una animación suave.&lt;/p&gt;
&lt;p&gt;Presentamos el proyecto a tiempo y estamos esperando los resultados.&lt;/p&gt;
&lt;p&gt;Esta es la primera vez que participo en un Hackathon y realmente lo disfruté,
espero seguir contribuyendo al proyecto en GitHub.&lt;/p&gt;
&lt;p&gt;Es increíble trabajar con personas de otros países, utilizando el inglés aunque no sea nuestra lengua materna,
pero con un propósito en común: crear un gran producto.&lt;/p&gt;
&lt;p&gt;Seguiré buscando participar en más Hackathones en el futuro y contribuir a
proyectos de código abierto (open source) en Github, porque realmente disfruto la sensación de
desarrollar algo grande con más personas.&lt;/p&gt;
&lt;p&gt;Aprendí mucho en estos pocos días, como la integración de Next.js con Supabase para autenticación y
rutas protegidas, utilizando la documentación de Supabase como guía, y utilizando por primera vez Shadcn/UI,
y estoy ansioso por seguir usándolo.&lt;/p&gt;
&lt;p&gt;Me llevó mucho tiempo participar en un Hackathon, ya que antes dudaba de mi experiencia,
pero la realidad es que nunca estaremos listos para nuevos desafíos, porque si ya estás listo,
significa que es demasiado tarde.&lt;/p&gt;
&lt;p&gt;Quiero aprender más sobre el uso de vectores para la inteligencia artificial,
así que investigaré más sobre el tema, ya que la tecnología tiende a avanzar en esa dirección.&lt;/p&gt;
&lt;p&gt;Quién sabe cuál será la próxima gran tendencia tecnológica o cuándo llegará.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://groce-wise.vercel.app/&quot;&gt;Puedes ver el proyecto: Grocewise aquí&lt;/a&gt;&lt;/p&gt;
</content:encoded><author>Juan Manzanero</author></item><item><title>Rewind 2023 y Planes Futuros</title><link>https://juanmanzanero.com/blog/es/rewind-2023-y-planes-futuros</link><guid isPermaLink="true">https://juanmanzanero.com/blog/es/rewind-2023-y-planes-futuros</guid><description>Mi rewind del 2023 y mis planes para el 2024 y más allá.</description><pubDate>Sat, 16 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Mi rewind del 2023 y mis planes para el 2024 y más allá.&lt;/p&gt;
&lt;p&gt;Espero que la estés pasando bien en estas fechas.&lt;/p&gt;
&lt;p&gt;La vida es una sucesión de decisiones, y en retrospectiva, estoy feliz de que este año haya hecho
las correctas.&lt;/p&gt;
&lt;h2&gt;En retrospectiva sobre mi carrera este 2023&lt;/h2&gt;
&lt;p&gt;Crecí drásticamente como Full Stack Developer, aprendiendo nuevas libraries y estableciendo mi tech
stack.&lt;/p&gt;
&lt;p&gt;Incluso comencé con hacking (no lo que sueles escuchar en las noticias), creando side projects
buscando crear una solución.&lt;/p&gt;
&lt;p&gt;Actualicé este sitio, creando nuevas funcionalidades para crear contenido.&lt;/p&gt;
&lt;p&gt;Ahora tengo más confianza en mis habilidades, listo para seguir creciendo y tomando nuevos desafios.&lt;/p&gt;
&lt;h2&gt;Planes futuros para mi carrera este 2024&lt;/h2&gt;
&lt;p&gt;Quiero comenzar a hacer freelancing, estaré creando templates y proyectos demo para vender mis
servicios como Frontend Developer principalmente, pero seguiré aprendiendo sobre Backend y Cloud,
así como seguiré practicando mi Inglés para entrar al mercado estadounidense o europeo.&lt;/p&gt;
&lt;p&gt;Quizás no lo consiga este 2024, pero debo seguir creciendo, ya que cada año que pasa, estaré más
preparado.&lt;/p&gt;
&lt;p&gt;Veré si podría contribuir a algún proyecto Open Source, ya que casí todas las herramientas que uso
son Open Source, y no podría estar aquí sino fuera por la ayuda de proyectos Open Source.&lt;/p&gt;
&lt;p&gt;Honestamente, mi verdadero deseo es trabajar tiempo completo en una startup Software as a Service,
o cualquier startup que se enfoque en Software.&lt;/p&gt;
&lt;p&gt;Lo bueno es que continuo creciendo profesionalmente, y espero (y lo haré) que el siguiente año
alcance más metas.&lt;/p&gt;
&lt;p&gt;Y por supuesto, continuaré con el hacking (de forma creativa, no robando información u otras cosas criminales)
con side projects, crear una fuente extra de ingresos sería genial para mis finanzas.&lt;/p&gt;
&lt;h2&gt;Retrospectiva personal del 2023 y más&lt;/h2&gt;
&lt;p&gt;Este año redescubrí el hobby de leer, y realmente lo disfruto.&lt;/p&gt;
&lt;p&gt;Descubrí mi nuevo libro favorito, &lt;strong&gt;Ready Player One&lt;/strong&gt;, fue una lectura emocionante, el siguiente año
leeré la secuela.&lt;/p&gt;
&lt;p&gt;También comencé a leer &lt;strong&gt;Ikigai&lt;/strong&gt;, para seguir adquiriendo buenos hábitos para una vida larga y feliz,
así como buscar un propósito en la vida.&lt;/p&gt;
&lt;p&gt;Otro libro que comencé a leer es &lt;strong&gt;The Little Book of Common Sense Investing&lt;/strong&gt;, ya que teniendo el buen
hábito de ahorrar, pero quiero que mi dinero siga creciendo más para un retiro digno.&lt;/p&gt;
&lt;p&gt;Estoy gastando menos tiempo en redes sociales,
&lt;a href=&quot;/es/blog/la-monotonia-de-las-redes-sociales&quot;&gt;incluso escribí un artículo sobre esto&lt;/a&gt;,
haciendo cosas que &lt;strong&gt;realmente quiero hacer&lt;/strong&gt; en su lugar.&lt;/p&gt;
&lt;p&gt;Es realmente horrible cuanto tiempo nos roban las redes sociales, manteniéndonos alejados de hacer cosas
que realmente disfrutamos.&lt;/p&gt;
&lt;p&gt;Estoy haciendo ejercicio moderado, pero dentro de casa, quiero ir afuera también, necesito más luz solar.&lt;/p&gt;
&lt;p&gt;Estoy feliz de pasar tiempo con mi familia, incluso si la mayoría del tiempo estoy trabajando o estudiando,
mantengo contacto con mis seres queridos, y continuaré haciéndolo, desde luego.&lt;/p&gt;
&lt;h2&gt;Planes personales futuros para 2024 y más&lt;/h2&gt;
&lt;p&gt;Seguiré leyendo, escribiré más para este website.&lt;/p&gt;
&lt;p&gt;Quiero adquirir nuevos hábitos, como pixel art.&lt;/p&gt;
&lt;p&gt;Retomaré un viejo hobby, &lt;strong&gt;GameDev&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Como Unity ha estado haciendo cosas cuestionables, usaré &lt;a href=&quot;https://godotengine.org/&quot;&gt;Godot&lt;/a&gt; en su lugar.&lt;/p&gt;
&lt;p&gt;No tengo en mente algún proyecto grande o algo así, solo retomar el desarrollar demos sencillas de videojuegos,
estaría genial lanzar un videojuego pequeño, pero terminado.&lt;/p&gt;
&lt;p&gt;Créeme, es muy, &lt;strong&gt;MUY&lt;/strong&gt; difícil desarrollar videojuegos, así que en lugar de abrumarme, mantendré mis
&lt;strong&gt;ambiciones simples&lt;/strong&gt;, pero &lt;strong&gt;constantes&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;¡Felices fiestas!&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://juanmanzanero.com/_astro/2023-complete!.C65X4Dkp_GxmRo.webp&quot; alt=&quot;2023 complete image!&quot; /&gt;&lt;/p&gt;
</content:encoded><author>Juan Manzanero</author></item><item><title>Una Mejor Forma de Consumir Contenido</title><link>https://juanmanzanero.com/blog/es/una-mejor-forma-de-consumir-contenido</link><guid isPermaLink="true">https://juanmanzanero.com/blog/es/una-mejor-forma-de-consumir-contenido</guid><description>Obtén tus noticias sin visitar websites con algoritmos que muestran contenido que no quieres ver.</description><pubDate>Thu, 11 Apr 2024 06:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Obtén tus noticias sin visitar websites con algoritmos que muestran contenido que no quieres ver.&lt;/p&gt;
&lt;h2&gt;Algoritmos que dictan lo que ves&lt;/h2&gt;
&lt;p&gt;Las redes sociales no están diseñadas para mostrarte las noticias más novedosas e importantes,
sino para mostrarte contenido dictado por un algoritmo.&lt;/p&gt;
&lt;p&gt;Y este contenido es normalmente, viral; y viral no quiere decir interesante.&lt;/p&gt;
&lt;p&gt;Normalmente estos algoritmos priorizan contenido que te hacen enojar.&lt;/p&gt;
&lt;p&gt;Contenido que promueve la negatividad obtiene más &quot;clicks&quot; que aquel que promueve la positividad.&lt;/p&gt;
&lt;p&gt;Esa es la razón por la cual Twitter y Facebook están llenos de posts estúpidos e irrelevantes (por lo regular).&lt;/p&gt;
&lt;p&gt;Por supuesto, es genial cuando el algoritmo te muestra contenido que te gusta,
descubriendo nuevas personas y páginas, pero eso no es lo usual.&lt;/p&gt;
&lt;p&gt;Sin mencionar los molestos anuncios y más cosas que quieren que les hagas click.&lt;/p&gt;
&lt;p&gt;Meta (anteriormente Facebook) sabe sobre esto, y promueve en sus productos como Instagram y Facebook,
lo mismo con Twitter.&lt;/p&gt;
&lt;h2&gt;La Solución: News Aggregators (RSS)&lt;/h2&gt;
&lt;p&gt;RSS es un acrónimo de &quot;Really Simple Syndication&quot;.&lt;/p&gt;
&lt;p&gt;Es una tecnología antigua, no muy promovida por las compañías.&lt;/p&gt;
&lt;p&gt;Esto es debido a que cuando lees un post en un RSS Reader, no debes de visitar el website,
y el website no puede mostrarte anuncios usando Google Ads (por ejemplo). No generas tráfico,
tus visitas no cuentan, al menos no si no abres el link del post en tu RSS Reader.&lt;/p&gt;
&lt;p&gt;Lo bueno que casi cualquier RSS Reader te muestra el contenido organizado por fechas,
no por un algoritmo raro que quiere hacerte enojar.&lt;/p&gt;
&lt;p&gt;Para mi website utilizo un script en Node.js que toma todos los archivos &lt;code&gt;.mdx&lt;/code&gt; dentro de &lt;code&gt;content/blog&lt;/code&gt;
y &lt;code&gt;content/portfolio&lt;/code&gt;, entonces genera los RSS Items, aquellos con &lt;code&gt;rss: true&lt;/code&gt; en sus metadatos.&lt;/p&gt;
&lt;h2&gt;Cómo usar un RSS Reader&lt;/h2&gt;
&lt;p&gt;Primero, debes descargar uno.&lt;/p&gt;
&lt;p&gt;Hay muchas opciones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://netnewswire.com/&quot;&gt;NetNewsWire&lt;/a&gt;: un RSS Reader nativo para macOS y iOS, gratis y Open Source,
mi opción favorita como &lt;s&gt;Pecador&lt;/s&gt; usuario de Apple&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apps.kde.org/akregator&quot;&gt;Akregator&lt;/a&gt;: del proyecto KDE para Linux&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.nononsenseapps.feeder.play&quot;&gt;Feeder&lt;/a&gt;: para Android&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ravenreader.app/&quot;&gt;Raven Reader&lt;/a&gt;: aplicación de escritorio multiplataforma&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Agregando Feeds&lt;/h3&gt;
&lt;p&gt;Ahora debes buscar por el link RSS de tu website favorito, ¡&lt;a href=&quot;https://juanmanzanero.com/es/feed.xml&quot;&gt;como este&lt;/a&gt;!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://juanmanzanero.com/es/feed.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si lo abres, verás una página rara con código similar a HTML.&lt;/p&gt;
&lt;p&gt;Una vez copiado, ve a tu RSS app y busca &quot;Agregar feed&quot; o algo similar, y pega el link, ¡y listo!,
ahora obtendrás los últimos posts de mi website.&lt;/p&gt;
&lt;h3&gt;Agregando Feeds de Redes Sociales&lt;/h3&gt;
&lt;p&gt;Puedes agregar incluso feeds de sitios como Reddit o YouTube.&lt;/p&gt;
&lt;h4&gt;Reddit&lt;/h4&gt;
&lt;p&gt;Solo cambia &lt;code&gt;[SUBREDDIT]&lt;/code&gt; por el nombre del subreddit a añadir:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://reddit.com/r/[SUBREDDIT]/new/.rss
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;YouTube&lt;/h4&gt;
&lt;p&gt;Ve a el canal por añadir, luego ve a la pestaña &quot;Acerca de&quot;, da click en &lt;strong&gt;Compartir &amp;gt; Copiar ID del Canal&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Ahora solo cambia &lt;code&gt;[CHANNEL ID]&lt;/code&gt; por el copiado:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://youtube.com/feeds/videos.xml?channel_id=[CHANNEL ID]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Mis Feeds Favoritos&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://juanmanzanero.com/es/feed.xml&quot;&gt;juanmanzanero.com (¡obviamente!)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://apod.com/feed.rss&quot;&gt;Astronomic Picture of the Day (apod)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://feeds2.feedburner.com/EarthSciencePictureoftheDay&quot;&gt;Earth Science Picture of the Day (epod)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ericmurphy.xyz/index.xml&quot;&gt;Erick Murphy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lukesmith.xyz/index.xml&quot;&gt;Luke Smith&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Más Sobre RSS&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.privacytools.io/privacy-rss-feed-readers&quot;&gt;Privacy Tools - RSS Feed Readers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.privacyguides.org/en/news-aggregators/&quot;&gt;Privacy Guides - News Aggregators&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><author>Juan Manzanero</author></item><item><title>Website migrado a Astro</title><link>https://juanmanzanero.com/blog/es/website-migrado-a-astro</link><guid isPermaLink="true">https://juanmanzanero.com/blog/es/website-migrado-a-astro</guid><description>Migré este website a Astro, de tal forma para probarlo, me terminó gustando.</description><pubDate>Wed, 26 Jun 2024 06:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Migré este website a Astro, de tal forma para probarlo, me terminó gustando.&lt;/p&gt;
&lt;p&gt;Probé esta herramienta para construir website estáticos, ya que Vercel ha estado tomando decisiones raras sobre la dirección del desarrollo de Next.js.&lt;/p&gt;
&lt;p&gt;Eso, y que me di cuenta que solo necesito un static website, no necesito de SSR.&lt;/p&gt;
&lt;h2&gt;Static es el camino a seguir&lt;/h2&gt;
&lt;p&gt;Al inicio del Internet, los website eran simples archivos planos de HTML, CSS y JS, no había necesidad de Single Page Application, Server Side Rendering, etc.&lt;/p&gt;
&lt;p&gt;Por supuesto, hay muchos websites que necesitan estas estrategias de rendering, pero para un website como este, solo static es mejor.&lt;/p&gt;
&lt;p&gt;Menos tiempo buildeando, carga rápida, hosting barato, menos JavaScript en el browser.&lt;/p&gt;
&lt;h2&gt;Lo genial sobre Astro&lt;/h2&gt;
&lt;p&gt;Puedes usar cualquier component de cualquier framework.&lt;/p&gt;
&lt;p&gt;Puedes usar components de React, Angular, Vue, Svelete en el mismo proyecto, ya que Astro los compila como HTML/CSS/JS.&lt;/p&gt;
&lt;p&gt;No solo eso, pero puedes usar Astro como si fuera Next.js, con la estrategia de &lt;em&gt;hybrid&lt;/em&gt; rendering, puedes especificar que páginas quieres que sean Server Side Rendered. Obviamente, si vas por este camino, debes hostear tu proyecto en un server.&lt;/p&gt;
&lt;h3&gt;En otras cosas...&lt;/h3&gt;
&lt;p&gt;Estoy planeando en hacer videos para YouTube, hablando de temas que considero necesitamos en la comunidad hispana, temas como cyberSecurity, privacidad, el actual estado de la tecnología, y más.&lt;/p&gt;
&lt;p&gt;Hay muchos canales de YT en Inglés hanlando cobre eso, así que es por eso que pienso hacerlo en Español.&lt;/p&gt;
&lt;p&gt;Aún así necesito mejorar los banners de estos posts...&lt;/p&gt;
</content:encoded><author>Juan Manzanero</author></item></channel></rss>