<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joshua Terrones</title>
    <description>The latest articles on DEV Community by Joshua Terrones (@joshuaterrones).</description>
    <link>https://dev.to/joshuaterrones</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3057483%2Ffb02916f-8ad2-4e1d-9f34-81e3d8501f6f.webp</url>
      <title>DEV Community: Joshua Terrones</title>
      <link>https://dev.to/joshuaterrones</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/joshuaterrones"/>
    <language>en</language>
    <item>
      <title>Cómo construí un portafolio con datos reales: 4 APIs en una semana</title>
      <dc:creator>Joshua Terrones</dc:creator>
      <pubDate>Thu, 24 Sep 2026 16:58:20 +0000</pubDate>
      <link>https://dev.to/joshuaterrones/como-construi-un-portafolio-con-datos-reales-4-apis-en-una-semana-a</link>
      <guid>https://dev.to/joshuaterrones/como-construi-un-portafolio-con-datos-reales-4-apis-en-una-semana-a</guid>
      <description>&lt;p&gt;Durante meses tuve un portafolio bonito pero vacío. Los proyectos decían "Proyecto 01, Proyecto 02, Proyecto 03", las horas de código decían "000h" y el formulario de contacto no enviaba nada. Un esqueleto precioso que no le decía nada a nadie.&lt;/p&gt;

&lt;p&gt;Este mes decidí cambiarlo. La meta: cero placeholders, todo con datos reales, todo automatizado. Lo que sigue es la bitácora de cómo conecté cuatro servicios distintos en una sola semana y lo que aprendí en el camino.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub API: los proyectos que sí tengo
&lt;/h2&gt;

&lt;p&gt;El primer cambio fue el más visible: que la Home mostrara mis repositorios reales. La API REST de GitHub no expone los repos "pinned" (los que eliges manualmente en tu perfil), así que tuve que usar la API GraphQL, que sí lo permite.&lt;/p&gt;

&lt;p&gt;El resultado es una arquitectura híbrida: GraphQL para los proyectos destacados de la Home, REST para el listado completo en /proyectos. Ambas con caché de 24 horas para no golpear el rate limit.&lt;/p&gt;

&lt;p&gt;Una cosa que aprendí: si cada visita hace un fetch a la API, revientas el límite en un día. Con revalidate: 86400 consumes 3 requests al día en vez de miles. La caché no es opcional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hackatime: las horas que realmente programo
&lt;/h2&gt;

&lt;p&gt;Hackatime es un fork de WakaTime que usa Hack Club para trackear tu actividad en el editor. Registra cuántas horas programas al día, qué lenguajes usas más y mantiene una racha de días activos.&lt;/p&gt;

&lt;p&gt;Conectarlo fue sencillo: una API key, un endpoint y listo. Ahora mi sección "Sobre mí" muestra las horas reales que he programado este año y un desglose por lenguaje. Es una forma más honesta de mostrar actividad que un texto genérico.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity: el blog que dejó de ser un array
&lt;/h2&gt;

&lt;p&gt;Esta fue la parte más larga. Mi blog originalmente era un archivo hardcodeado con tres posts de prueba. Cada vez que quería publicar algo tenía que editar código, commitear, esperar el deploy. Un proceso de 15 minutos por post.&lt;/p&gt;

&lt;p&gt;Sanity es un CMS headless que se embebe dentro de tu propia app de Next.js. Escribes desde /studio, el contenido se guarda en sus servidores y tu blog lo lee vía queries GROQ. Es la diferencia entre editar un archivo y escribir como en Medium.&lt;/p&gt;

&lt;p&gt;Dos detalles que me costaron tiempo: primero, el CORS. Al embeber el Studio en Next.js, el CORS por defecto solo permite localhost:3333 (el puerto del Studio independiente), no el 3000 de tu app. Hay que añadirlo explícitamente con el flag --credentials.&lt;/p&gt;

&lt;p&gt;Segundo, el canonical URL de Dev.to. Dev.to rechaza URLs con http:// (sin S). Si tu dominio está redirigiendo o usas localhost, la sincronización falla con un error 422 que no explica nada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resend: el formulario que ahora sí envía correos
&lt;/h2&gt;

&lt;p&gt;Lo último fue conectar el formulario de contacto. Antes solo simulaba el envío — el botón cambiaba a "Enviado ✓" pero nada llegaba a ningún lado. Con Resend, ahora llegan mensajes reales a mi bandeja de entrada.&lt;/p&gt;

&lt;p&gt;La ventaja de Resend es que se integra muy bien con dominios personalizados. Verificas tu dominio con unos registros DNS, y ya puedes enviar correos desde tu propia dirección. El correo sale desde &lt;a href="mailto:hola@josht.xyz"&gt;hola@josht.xyz&lt;/a&gt; y va directo a mi iCloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que aprendí
&lt;/h2&gt;

&lt;p&gt;Tres cosas que me llevo de esta semana:&lt;/p&gt;

&lt;p&gt;Primero, la caché no es un detalle opcional. Es la diferencia entre una app que escala y una que se cae en la primera hora.&lt;/p&gt;

&lt;p&gt;Segundo, los errores de CORS son silenciosos y confusos. El login de Sanity me tuvo atrapado en un bucle infinito durante una hora. La solución estaba en un flag de configuración que no aparece en el tutorial básico.&lt;/p&gt;

&lt;p&gt;Tercero, la automatización vale la pena. El tiempo que invertí conectando el webhook de Sanity a Dev.to se paga solo en el segundo post. Ya no copio y pego nada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué viene
&lt;/h2&gt;

&lt;p&gt;Ahora que el sistema funciona, quiero enfocarme en el contenido. Publicar un post por semana sobre lo que voy aprendiendo: APIs, arquitectura, decisiones técnicas. El blog ya no es una barrera, es un espacio donde puedo escribir sin fricción.&lt;/p&gt;

&lt;p&gt;También quiero añadir soporte bilingüe (español/inglés) y on-demand revalidation para que los posts aparezcan al instante. Pero eso es otra semana.&lt;/p&gt;

&lt;p&gt;Por ahora, el portafolio hace lo que debe hacer: muestra trabajo real, cuenta la historia de cómo lo construí y facilita que alguien me contacte. Todo lo demás es refinamiento.&lt;/p&gt;

</description>
      <category>devlog</category>
      <category>webdev</category>
      <category>nextjs</category>
      <category>sanity</category>
    </item>
    <item>
      <title>Automaticé la publicación de mi blog a Dev.to</title>
      <dc:creator>Joshua Terrones</dc:creator>
      <pubDate>Thu, 24 Sep 2026 10:16:16 +0000</pubDate>
      <link>https://dev.to/joshuaterrones/automatice-la-publicacion-de-mi-blog-a-devto-5bl8</link>
      <guid>https://dev.to/joshuaterrones/automatice-la-publicacion-de-mi-blog-a-devto-5bl8</guid>
      <description>&lt;p&gt;Acabo de automatizar el flujo completo de publicación de mi blog. Ahora escribo un post en Sanity Studio, le doy Publish, y en 10 segundos aparece en dos sitios a la vez: en mi portafolio (josht.xyz) y en Dev.to.&lt;/p&gt;

&lt;h2&gt;
  
  
  El flujo completo
&lt;/h2&gt;

&lt;p&gt;Antes tenía que copiar el contenido, pegarlo en el editor de Dev.to, subir las imágenes manualmente y ajustar el formato. Un proceso de 15 minutos por post. Ahora es automático.&lt;/p&gt;

&lt;p&gt;La arquitectura es simple pero efectiva:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Escribo el post en Sanity Studio.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Al publicar, Sanity dispara un webhook con el ID del documento.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mi API Route en Next.js recibe el webhook, consulta el post completo vía GROQ y lo convierte a Markdown.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Llama a la API de Dev.to con el contenido y un canonical_url que apunta a mi dominio.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  El detalle del canonical
&lt;/h2&gt;

&lt;p&gt;El canonical_url es lo más importante de toda la integración. Le dice a Google y a Dev.to que la versión "oficial" del contenido vive en mi sitio, no en Dev.to. Esto evita penalizaciones por contenido duplicado en SEO y me mantiene el tráfico.&lt;/p&gt;

&lt;h2&gt;
  
  
  Los problemas que encontré
&lt;/h2&gt;

&lt;p&gt;Dev.to no acepta URLs con http:// en el canonical_url. Hay que usar siempre https://. Lo descubrí a la mala, con un error 422 que no explicaba nada hasta que leí la documentación con calma.&lt;/p&gt;

&lt;p&gt;También hay que asegurarse de que el dominio no esté redirigiendo. Si josht.xyz redirige a &lt;a href="http://www.josht.xyz" rel="noopener noreferrer"&gt;www.josht.xyz&lt;/a&gt;, el webhook de Sanity falla con un 308 antes de llegar a mi código. Es un detalle que parece obvio pero que cuesta ver si no lo estás esperando.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Por ahora el flujo funciona solo de Sanity a Dev.to. En el futuro quiero añadir lo inverso: que si alguien comenta o reacciona en Dev.to, se refleje de alguna forma en mi sitio. Pero eso es otro proyecto.&lt;/p&gt;

&lt;p&gt;Mientras tanto, ya tengo el sistema que quería. Escribo una vez, publico en dos lugares, sin copiar y pegar.&lt;/p&gt;

</description>
      <category>devlog</category>
      <category>webdev</category>
      <category>nextjs</category>
      <category>sanity</category>
    </item>
    <item>
      <title>Conecté mi portafolio a APIs reales: GitHub, Hackatime y Sanity</title>
      <dc:creator>Joshua Terrones</dc:creator>
      <pubDate>Thu, 24 Sep 2026 10:00:31 +0000</pubDate>
      <link>https://dev.to/joshuaterrones/conecte-mi-portafolio-a-apis-reales-github-hackatime-y-sanity-4b27</link>
      <guid>https://dev.to/joshuaterrones/conecte-mi-portafolio-a-apis-reales-github-hackatime-y-sanity-4b27</guid>
      <description>&lt;p&gt;Durante las últimas semanas estuve construyendo mi portafolio personal en Next.js 16. Al principio todo era maquetación pura: los proyectos mostraban "Proyecto 01, Proyecto 02, Proyecto 03", las horas de código decían "000h", y el blog era un array hardcodeado con tres posts de prueba. Un esqueleto precioso, pero vacío.&lt;/p&gt;

&lt;p&gt;Esta semana decidí conectarlo todo a APIs reales. Este post es la bitácora completa del proceso, incluyendo los errores que me costaron más tiempo del que me gustaría admitir.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub API: la primera integración
&lt;/h2&gt;

&lt;p&gt;Lo primero que quise resolver era que la Home mostrara mis proyectos reales en lugar de placeholders. Después de investigar un poco descubrí algo importante: la API REST de GitHub no expone los repositorios "pinned" (los que eliges manualmente en tu perfil). Solo devuelve todos los públicos.&lt;/p&gt;

&lt;p&gt;Para obtener los pinned reales hay que usar la API GraphQL, que sí permite consultar los pinnedItems de un usuario. Así que terminé con una arquitectura híbrida:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GraphQL → para los repos destacados en la Home.&lt;br&gt;
REST → para el listado completo en /proyectos.&lt;br&gt;
Ambas con caché de 24 horas vía next: { revalidate: 86400 }.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Hackatime: las horas que realmente programo
&lt;/h2&gt;

&lt;p&gt;Hackatime es un fork abierto de WakaTime que usa Hack Club para trackear horas de código. Registra tus editors y te da estadísticas reales: cuántas horas programaste, qué lenguajes usas más, racha de días activos.&lt;/p&gt;

&lt;p&gt;La API devuelve un JSON muy limpio. Un ejemplo real de mi cuenta, con el rango del año completo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;total_seconds: 45725 → 12h 42m&lt;br&gt;
TypeScript: 43.29%&lt;br&gt;
Other: 38.81%&lt;br&gt;
XML: 18.6%&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Solo necesité configurar el API key en .env.local y hacer un fetch con la fecha de inicio del año. Los datos se muestran en /sobre-mi con barras de progreso por lenguaje.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity: el blog que ya no es un array
&lt;/h2&gt;

&lt;p&gt;Esta fue la parte más larga. Mi blog originalmente era un archivo posts.ts con tres posts hardcodeados. Funcionaba, pero cada vez que quería publicar algo tenía que editar código, commitear y esperar el deploy.&lt;/p&gt;

&lt;p&gt;Sanity es un CMS headless que se embebe dentro de tu propia app de Next.js. Escribes posts desde /studio, se guardan en sus servidores, y tu blog los lee vía queries GROQ.&lt;/p&gt;

&lt;p&gt;Un detalle que casi me vuelve loco: al embeber el Studio en Next.js, el CORS por defecto solo permite localhost:3333 (el puerto del Studio independiente), no el 3000 de tu app. Hay que añadirlo explícitamente:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;npx sanity cors add &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt; --credentials&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ese flag --credentials es crítico. Sin él, el Studio puede cargar pero el login se queda en un bucle infinito porque el navegador no envía las cookies de sesión.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que aprendí
&lt;/h2&gt;

&lt;p&gt;Tres lecciones que me llevo de esta integración:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;La API REST de GitHub no sirve para repos pinned. Hay que usar GraphQL. Perdí una hora intentando resolverlo con REST antes de leer la documentación oficial.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;La caché no es opcional. Si cada visita hace un fetch a la API, revientas el rate limit en un día. Con revalidate: 86400 consumes 3 requests al día en vez de 3000.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Los errores de CORS son silenciosos y confusos. El login de Sanity no daba ningún mensaje de error claro — solo un bucle infinito. La solución estaba en un flag de configuración que no aparece en el tutorial básico.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Todavía queda trabajo: conectar este blog a Dev.to para publicar automáticamente, añadir un formulario de contacto con Resend, y eventualmente soporte bilingüe (español/inglés) con next-intl.&lt;/p&gt;

&lt;p&gt;Pero por ahora, el portafolio ya no es un esqueleto. Muestra proyectos reales, cuenta las horas que programo de verdad, y tiene un blog que puedo actualizar sin tocar una sola línea de código.&lt;/p&gt;

&lt;p&gt;Eso se siente bien.&lt;/p&gt;

</description>
      <category>devlog</category>
      <category>webdev</category>
      <category>nextjs</category>
      <category>sanity</category>
    </item>
  </channel>
</rss>
