<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Jorge, autor en Jorge Castrillo</title>
	<atom:link href="https://jorgecastrillo.blog/author/keeg/feed/" rel="self" type="application/rss+xml" />
	<link>https://jorgecastrillo.blog/author/keeg/</link>
	<description>Desarrollo Web, IA y Emprendimiento</description>
	<lastBuildDate>Wed, 21 Jan 2026 15:58:02 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9</generator>
	<item>
		<title>Construyendo Agentes de IA con LangChain y LangGraph: Entorno de desarrollo</title>
		<link>https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-entorno-de-desarrollo/</link>
					<comments>https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-entorno-de-desarrollo/#respond</comments>
		
		<dc:creator><![CDATA[Jorge]]></dc:creator>
		<pubDate>Wed, 21 Jan 2026 15:58:02 +0000</pubDate>
				<category><![CDATA[Guías]]></category>
		<category><![CDATA[Inteligencia Artificial]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[IA]]></category>
		<category><![CDATA[LangGraph]]></category>
		<guid isPermaLink="false">https://jorgecastrillo.blog/?p=140</guid>

					<description><![CDATA[<p>Antes de comenzar con la configuración técnica del entorno, es importante comprender los fundamentos detrás de los agentes de inteligencia artificial y cómo frameworks como LangChain y LangGraph permiten construir flujos de trabajo inteligentes, modulares y escalables. Estos conceptos son clave para aprovechar al máximo las capacidades de los modelos de lenguaje y evitar implementaciones [&#8230;]</p>
<p>La entrada <a href="https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-entorno-de-desarrollo/">Construyendo Agentes de IA con LangChain y LangGraph: Entorno de desarrollo</a> se publicó primero en <a href="https://jorgecastrillo.blog">Jorge Castrillo</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Antes de comenzar con la configuración técnica del entorno, es importante comprender los fundamentos detrás de los agentes de inteligencia artificial y cómo frameworks como LangChain y LangGraph permiten construir flujos de trabajo inteligentes, modulares y escalables. Estos <a href="https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-conceptos/">conceptos son clave</a> para aprovechar al máximo las capacidades de los modelos de lenguaje y evitar implementaciones improvisadas o difíciles de mantener.</p>



<p>Si todavía no leíste la base conceptual, te recomendamos revisar la guía “<a href="https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-conceptos/">Construyendo agentes de IA con LangChain y LangGraph: conceptos</a>”, donde se explican de forma clara los principios, la arquitectura y los casos de uso más comunes de estos frameworks. Este conocimiento previo te va a ayudar a entender mejor cada paso de esta guía y a tomar mejores decisiones técnicas al momento de desarrollar tus propios agentes de IA.</p>



<h2 class="wp-block-heading">Requisitos Previos</h2>



<ul class="wp-block-list">
<li>Ubuntu 24.04 instalado</li>



<li>Editor de código (VS Code, PyCharm, etc.)</li>



<li>Conexión a internet</li>
</ul>



<h2 class="wp-block-heading">Paso 1: Instalar Python 3.11+</h2>



<p>LangGraph requiere Python 3.11 o superior. Verificá tu versión actual:</p>



<pre class="wp-block-code"><code>python3 --version</code></pre>



<p>Si necesitás instalar o actualizar Python:</p>



<pre class="wp-block-code"><code>sudo apt update
sudo apt install python3.11 python3.11-venv python3-pip -y</code></pre>



<h2 class="wp-block-heading">Paso 2: Crear el Directorio del Proyecto</h2>



<pre class="wp-block-code"><code>mkdir mi-proyecto-langgraph
cd mi-proyecto-langgraph</code></pre>



<h2 class="wp-block-heading">Paso 3: Crear y Activar un Entorno Virtual</h2>



<p>Es una buena práctica usar entornos virtuales para aislar las dependencias del proyecto:</p>



<pre class="wp-block-code"><code>python3 -m venv venv
source venv/bin/activate</code></pre>



<p>Deberías ver <code>(venv)</code> al inicio de tu línea de comandos.</p>



<h2 class="wp-block-heading">Paso 4: Instalar LangGraph y Dependencias</h2>



<p>Instalá LangGraph y LangChain:</p>



<pre class="wp-block-code"><code>pip install --upgrade pip
pip install langgraph langchain langchain-core langraph-cli langgraph-api</code></pre>



<h2 class="wp-block-heading">Paso 5: Crear una Cuenta en LangSmith</h2>



<p>LangSmith es una plataforma para monitorear, depurar y evaluar aplicaciones LLM.</p>



<ol class="wp-block-list">
<li>Andá a https://smith.langchain.com/</li>



<li>Hacé clic en <strong>«Sign Up»</strong> o <strong>«Get Started»</strong></li>



<li>Completá el registro con tu correo electrónico y contraseña</li>



<li>Verificá tu correo electrónico si es necesario</li>



<li>Iniciá sesión en tu cuenta de LangSmith</li>
</ol>



<h2 class="wp-block-heading">Paso 6: Generar una API Key de LangSmith</h2>



<ol class="wp-block-list">
<li>Una vez dentro de LangSmith, andá a <strong>Settings</strong> (Configuración) en el menú lateral</li>



<li>Buscá la sección <strong>«API Keys»</strong></li>



<li>Hacé clic en <strong>«Create API Key»</strong> o <strong>«+ New API Key»</strong></li>



<li>Dale un nombre descriptivo a tu clave (ejemplo: «Proyecto LangGraph Local»)</li>



<li>Copiá la API key generada (guardala en un lugar seguro, no podrás verla de nuevo)</li>
</ol>



<h2 class="wp-block-heading">Paso 7: Configurar Variables de Entorno</h2>



<p>Creá un archivo <code>.env</code> en la raíz de tu proyecto para almacenar las credenciales:</p>



<pre class="wp-block-code"><code>touch .env</code></pre>



<p>Abrí el archivo <code>.env</code> con tu editor y agregá:</p>



<pre class="wp-block-code"><code>LANGCHAIN_TRACING_V2=true
LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
LANGCHAIN_API_KEY=tu_api_key_de_langsmith_aqui
LANGCHAIN_PROJECT=mi-proyecto-langgraph</code></pre>



<p>Reemplazá <code>tu_api_key_de_langsmith_aqui</code> con la API key que copiaste.</p>



<h2 class="wp-block-heading">Paso 8: Obtener API Key de un Proveedor LLM</h2>



<p>LangGraph soporta múltiples proveedores de modelos de lenguaje. Seleccioná uno de los siguientes:</p>



<ul class="wp-block-list">
<li><strong>OpenAI</strong> &#8211; Registrate en <a href="https://platform.openai.com/" target="_blank" rel="noopener">platform.openai.com</a> y generá una API key</li>



<li><strong>Anthropic (Claude)</strong> &#8211; Registrate en <a href="https://console.anthropic.com/" target="_blank" rel="noopener">console.anthropic.com</a></li>



<li><strong>Google (Gemini)</strong> &#8211; Obtené credenciales en <a href="https://ai.google.dev/" target="_blank" rel="noopener">ai.google.dev</a></li>



<li><strong>Cohere</strong> &#8211; Registrate en <a href="https://dashboard.cohere.com/" target="_blank" rel="noopener">dashboard.cohere.com</a></li>



<li><strong>Azure OpenAI</strong> &#8211; Configurá en tu portal de Azure</li>
</ul>



<p>Agregá la API key de tu proveedor elegido al archivo <code>.env</code>:</p>



<pre class="wp-block-code"><code># Para OpenAI
OPENAI_API_KEY=tu_api_key_de_openai

# Para Anthropic
ANTHROPIC_API_KEY=tu_api_key_de_anthropic

# Para Google
GOOGLE_API_KEY=tu_api_key_de_google</code></pre>



<h2 class="wp-block-heading">Paso 9: Instalar el Paquete del Proveedor LLM</h2>



<pre class="wp-block-code"><code># Para OpenAI
pip install langchain-openai

# Para Anthropic
pip install langchain-anthropic

# Para Google
pip install langchain-google-genai

# Para Cohere
pip install langchain-cohere</code></pre>



<h2 class="wp-block-heading">Paso 10: Instalar python-dotenv</h2>



<p>Para cargar las variables de entorno desde el archivo <code>.env</code>:</p>



<pre class="wp-block-code"><code>pip install python-dotenv</code></pre>



<h2 class="wp-block-heading">Paso 11: Crear un Archivo de Prueba</h2>



<p>Creá un archivo <code>test_setup.py</code> para verificar la instalación:</p>



<pre class="wp-block-code"><code>from dotenv import load_dotenv
import os

# Cargar variables de entorno
load_dotenv()

# Verificar que las variables están cargadas
print("LANGCHAIN_API_KEY:", "✓ Configurada" if os.getenv("LANGCHAIN_API_KEY") else "✗ No encontrada")
print("OPENAI_API_KEY:", "✓ Configurada" if os.getenv("OPENAI_API_KEY") else "✗ No encontrada")

# Prueba simple de LangGraph
from langgraph.graph import StateGraph
from typing import TypedDict

class State(TypedDict):
    mensaje: str

def nodo_saludo(state: State):
    return {"mensaje": "¡Hola desde LangGraph!"}

# Crear grafo
workflow = StateGraph(State)
workflow.add_node("saludo", nodo_saludo)
workflow.set_entry_point("saludo")
workflow.set_finish_point("saludo")

app = workflow.compile()

# Ejecutar
resultado = app.invoke({"mensaje": ""})
print("\nResultado:", resultado)</code></pre>



<h2 class="wp-block-heading">Paso 12: Ejecutar la Prueba</h2>



<pre class="wp-block-code"><code>python test_setup.py</code></pre>



<p>Si todo está configurado correctamente, deberías ver las confirmaciones de las API keys y el mensaje de saludo.</p>



<h2 class="wp-block-heading">Paso 13: Crear requirements.txt</h2>



<pre class="wp-block-code"><code>pip freeze &gt; requirements.txt</code></pre>



<h2 class="wp-block-heading">Paso 14: Configurar .gitignore</h2>



<p>Si usás Git, creá un archivo <code>.gitignore</code> para evitar subir información sensible:</p>



<pre class="wp-block-code"><code>.env
venv/
__pycache__/
*.pyc
.DS_Store</code></pre>



<h2 class="wp-block-heading">¡Entorno Listo!</h2>



<p>Tu entorno de desarrollo para LangGraph está completamente configurado. Ahora podés empezar a construir tus agentes de IA.</p>



<h3 class="wp-block-heading">Comandos Útiles</h3>



<ul class="wp-block-list">
<li><strong>Activar entorno virtual:</strong> <code>source venv/bin/activate</code></li>



<li><strong>Desactivar entorno virtual:</strong> <code>deactivate</code></li>



<li><strong>Instalar desde requirements.txt:</strong> <code>pip install -r requirements.txt</code></li>



<li><strong>Actualizar dependencias:</strong> <code>pip install --upgrade langgraph langchain</code></li>
</ul>



<h3 class="wp-block-heading">Recursos Adicionales</h3>



<ul class="wp-block-list">
<li><a href="https://langchain-ai.github.io/langgraph/" target="_blank" rel="noopener">Documentación oficial de LangGraph</a></li>



<li><a href="https://docs.smith.langchain.com/" target="_blank" rel="noopener">Documentación de LangSmith</a></li>



<li><a href="https://python.langchain.com/docs/get_started/introduction" target="_blank" rel="noopener">Guía de LangChain</a></li>
</ul>
<p>La entrada <a href="https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-entorno-de-desarrollo/">Construyendo Agentes de IA con LangChain y LangGraph: Entorno de desarrollo</a> se publicó primero en <a href="https://jorgecastrillo.blog">Jorge Castrillo</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-entorno-de-desarrollo/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Construyendo Agentes de IA con LangChain y LangGraph: Conceptos</title>
		<link>https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-conceptos/</link>
					<comments>https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-conceptos/#respond</comments>
		
		<dc:creator><![CDATA[Jorge]]></dc:creator>
		<pubDate>Wed, 14 Jan 2026 22:41:30 +0000</pubDate>
				<category><![CDATA[Guías]]></category>
		<category><![CDATA[Inteligencia Artificial]]></category>
		<category><![CDATA[Agents]]></category>
		<category><![CDATA[LangGraph]]></category>
		<category><![CDATA[TI]]></category>
		<guid isPermaLink="false">https://jorgecastrillo.blog/?p=131</guid>

					<description><![CDATA[<p>Aprende sobre los conceptos básicos antes de adentrarte en la creación de agentes de IA con LangGrap. Usalo para evitar cajas negras.</p>
<p>La entrada <a href="https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-conceptos/">Construyendo Agentes de IA con LangChain y LangGraph: Conceptos</a> se publicó primero en <a href="https://jorgecastrillo.blog">Jorge Castrillo</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Después de haber escrito mi primer agente para Officia Agent con TypeScript, bajo un modelo funcional sin ningún tipo de patrón, me di cuenta de que la tarea de construir un agente de IA no es sencilla.</p>



<p>Luego, investigando y leyendo sobre Agentes de IA, descubrí LangGraph, un framework especializado para construir agentes de IA disponible en dos lenguajes: TypeScript (JavaScript tipado) y Python. Así que me di a la tarea de aprenderlo. Inicié viendo algunos videos en YouTube para visualizar sus capacidades y luego me inscribí en el curso gratuito que ofrecen en <a href="https://academy.langchain.com/">LangChain Academy</a>, altamente recomendado para iniciarse bien en este mundo de construcción de Agentes de IA.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>💡 Si te gusta tener el control y programar, LangGraph es lo que necesitas para construir tus agentes de IA.</p>
</blockquote>



<h2 class="wp-block-heading">¿Qué es LangGraph?</h2>



<p>Utilizado por las compañías más grandes del mundo como Replit, Klarna, Elastic y muchas más, LangGraph es un framework que te permite construir agentes de IA a bajo nivel. Esto quiere decir que vos como desarrollador tenés la libertad de programar al agente con tus propias reglas y tu propia lógica.</p>



<h2 class="wp-block-heading">Beneficios</h2>



<p>Es de código abierto, lo que significa que lo podés desplegar localmente o en tus propios servidores. Por supuesto, LangChain también te provee de un ecosistema avanzado de pago con funcionalidades adicionales.</p>



<p>«El cielo es el límite», y en este caso la capacidad de abstraer un problema y llevarlo a una solución con LangGraph es posible para casi cualquier cosa. No te voy a mentir, será laborioso, pero si estás buscando una solución robusta y estable para construir un Agente de IA, esto es lo que necesitas.</p>



<p>Además, hay más beneficios propios del framework:</p>



<ul class="wp-block-list">
<li><strong>Ejecución Duradera:</strong> Los agentes pueden ejecutarse por largos períodos de tiempo, manteniendo su estado y retomando donde lo dejaron, incluso después de interrupciones.</li>



<li><strong>Human-in-the-loop:</strong> Te permite crear una lógica donde puedas incorporar al humano en la toma de decisiones del grafo. Por ejemplo, antes de agendar una cita importante, el agente puede pausar y pedir confirmación.</li>



<li><strong>Memoria:</strong> Los agentes tienen un State (estado) de memoria a corto plazo que podés usar para el razonamiento continuo, y memoria a largo plazo en cada sesión.</li>



<li><strong>Debugging:</strong> En el ecosistema se te provee de una aplicación web llamada LangSmith que te ayuda a tener visibilidad completa de qué está pasando con tu agente, cómo lo está haciendo, cuánto te está costando y más.</li>
</ul>



<h2 class="wp-block-heading">Caso de Uso: Agente de Gestión de Calendario</h2>



<p>Cuando vas a desarrollar un Agente en LangGraph, lo primero que necesitás hacer es descomponer la tarea en pasos, llamados <strong>Nodos</strong>. Un nodo puede ser un proceso determinista, no determinista (como una llamada a un LLM), o una decisión. Finalmente, debés tener claro que todos los nodos comparten un estado (estructura de objeto) global, que podés consultar y actualizar en cada paso.</p>



<p>Veamos el siguiente proceso con un ejemplo práctico:</p>



<h3 class="wp-block-heading">Piensa qué querés automatizar</h3>



<p>Para este ejemplo, lo que quiero automatizar es el proceso de gestión de calendario con las siguientes capacidades:</p>



<pre class="wp-block-code"><code>Ejemplos de escenarios
-Buscar espacio en la agenda: "Podrías buscar un hueco en mi agenda para el martes"
-Verificar horario: "Revisa si tengo libre el jueves a las 3"
-Agendar cita: "Agendame una reunión para mañana a la 1"
-Cancela una cita: "Cancela la cita del viernes a las 4 de la tarde"
-Reprogramar una cita: "Reprograma la reunión de mañana a las 2 y ponela para la próxima semana"

El Agente debe poder
-Conectarse a mi calendario
-Consultar disponibilidad de horarios
-Agendar una cita
-Cancelar una cita
-Reprogramar una cita</code></pre>



<h3 class="wp-block-heading">Paso 1: Diagrama el flujo</h3>



<p>Comienza identificando cada uno de los pasos en el proceso. Cada paso se convierte en un nodo, una función que hace solo una tarea específica.</p>



<p>Las flechas declaran las rutas posibles, pero la decisión de qué ruta tomar se encuentra dentro de cada nodo o en nodos de decisión especiales.</p>



<p>A esto le podemos llamar <strong>Agente</strong>. Un agente puede ser simple o muy complejo, y el ejemplo anterior lo demuestra. El proceso de agendar una cita, cancelarla, reagendarla o buscar espacios no se puede simplificar en una sola función, y para ello introduzco el concepto de <strong>sub-agentes</strong>, donde cada nodo podría ser un sub-agente con su propia lógica de nodos para intentar resolver una petición del usuario.</p>



<p>Por ejemplo, para nuestro agente de calendario, el flujo podría verse así:</p>



<ul class="wp-block-list">
<li><strong>Inicio</strong> → Recibir mensaje del usuario</li>



<li><strong>Analizar intención</strong> → ¿Qué quiere hacer el usuario? (Nodo LLM)</li>



<li><strong>Decisión de ruta</strong> → ¿Buscar disponibilidad, agendar, cancelar o reprogramar?</li>



<li><strong>Ejecutar acción correspondiente</strong> → Sub-agente específico para cada caso</li>



<li><strong>Confirmar con usuario</strong> → Mostrar resultado</li>
</ul>



<h3 class="wp-block-heading">Paso 2: Identifica cada paso</h3>



<p>Una vez identificado cada paso, ahora tenemos que decidir qué tipo de paso será. Podemos definir 4 tipos de pasos:</p>



<ol class="wp-block-list">
<li><strong>Paso LLM:</strong> Se usa cuando se necesita analizar, entender, generar texto o tomar una decisión de razonamiento. <em>Ejemplo:</em> Conocer la intención del usuario cuando dice «necesito un hueco mañana» → interpretar que quiere buscar disponibilidad.</li>



<li><strong>Paso de Datos:</strong> Usado para extraer información de una fuente de datos externa. <em>Ejemplo:</em> Consultar el calendario mediante la API de Google Calendar para obtener las citas existentes del martes.</li>



<li><strong>Paso de Acción:</strong> Usado para ejecutar una acción que modifica el estado del sistema. <em>Ejemplo:</em> Crear una cita en el calendario mediante una llamada POST a la API.</li>



<li><strong>Paso de Solicitud de Usuario:</strong> Utilizado cuando se necesita la intervención humana. <em>Ejemplo:</em> «Encontré 3 horarios disponibles el martes: 10am, 2pm y 4pm. ¿Cuál preferís?»</li>
</ol>



<p>Para nuestro agente de calendario, tendríamos algo así:</p>



<ul class="wp-block-list">
<li>«Agendame una reunión mañana a la 1» → <strong>Paso LLM</strong> para extraer: acción=agendar, fecha=mañana, hora=1pm</li>



<li>Verificar disponibilidad → <strong>Paso de Datos</strong> consultando el calendario</li>



<li>Si hay conflicto → <strong>Paso de Solicitud de Usuario</strong> «Ya tenés una reunión a la 1pm, ¿querés reprogramarla?»</li>



<li>Si está libre → <strong>Paso de Acción</strong> crear la cita</li>
</ul>



<h3 class="wp-block-heading">Paso 3: Diseña el estado</h3>



<p>El State (estado) es la memoria compartida entre los nodos de tu agente. Pensá en todo lo que tus nodos deberían saber para ejecutar sus tareas de manera armónica.</p>



<p><strong>¿Qué se incluye en el state?</strong> Los datos que son necesarios en varios pasos. Por ejemplo:</p>



<ul class="wp-block-list">
<li>La intención del usuario (agendar, cancelar, buscar)</li>



<li>Fecha y hora solicitadas</li>



<li>Resultado de la consulta al calendario</li>



<li>Mensajes del historial de la conversación</li>



<li>Estado de confirmación (¿el usuario confirmó la acción?)</li>
</ul>



<p><strong>¿Qué no incluir?</strong> Lo contrario, datos que no necesitamos en todos los pasos y que podemos extraer en un momento específico. Por ejemplo:</p>



<ul class="wp-block-list">
<li>El token de autenticación de la API (se puede obtener cuando se necesite)</li>



<li>Detalles internos de la respuesta de la API que no afectan decisiones futuras</li>
</ul>



<p>Un ejemplo de estructura de estado para nuestro agente sería:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
{
  messages: Message&#x5B;],        // Historial de conversación
  intent: &quot;agendar&quot; | &quot;cancelar&quot; | &quot;buscar&quot; | &quot;reprogramar&quot;,
  requestedDate: string,      // &quot;2026-01-15&quot;
  requestedTime: string,      // &quot;13:00&quot;
  availability: TimeSlot&#x5B;],   // Horarios disponibles encontrados
  conflictingEvent: Event | null,  // Si hay conflicto
  needsConfirmation: boolean,
  actionCompleted: boolean
}
</pre></div>


<h3 class="wp-block-heading">Paso 4: Crea los nodos (Funciones)</h3>



<p>Creá las funciones para cada uno de tus nodos, usando el patrón de diseño que mejor se adecúe a tu solución. Cada función recibe el estado actual y devuelve una actualización del estado.</p>



<p>Por ejemplo, el nodo que analiza la intención del usuario:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
async function analyzeIntent(state: AgentState): Promise&lt;Partial&lt;AgentState&gt;&gt; {
  const userMessage = state.messages&#x5B;state.messages.length - 1];
  
  // Llamada al LLM para extraer la intención
  const response = await llm.invoke(&#x5B;
    { role: &quot;system&quot;, content: &quot;Extrae la intención del usuario: agendar, cancelar, buscar o reprogramar&quot; },
    { role: &quot;user&quot;, content: userMessage.content }
  ]);
  
  // Parsear la respuesta
  const { intent, date, time } = parseIntentResponse(response);
  
  return {
    intent,
    requestedDate: date,
    requestedTime: time
  };
}
</pre></div>


<p>Y el nodo que consulta disponibilidad:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
async function checkAvailability(state: AgentState): Promise&lt;Partial&lt;AgentState&gt;&gt; {
  // Consultar el calendario externo
  const events = await calendarAPI.getEvents(state.requestedDate);
  
  // Verificar si el horario solicitado está ocupado
  const conflict = events.find(e =&gt; e.time === state.requestedTime);
  
  if (conflict) {
    return {
      conflictingEvent: conflict,
      needsConfirmation: true
    };
  }
  
  return {
    availability: &#x5B;{ date: state.requestedDate, time: state.requestedTime }],
    conflictingEvent: null
  };
}
</pre></div>


<h3 class="wp-block-heading">Paso 5: Une todo</h3>



<p>Ahora conectás todos tus nodos usando el API de LangGraph. Definís las conexiones entre nodos y las condiciones para cada transición:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import { StateGraph } from &quot;@langchain/langgraph&quot;;

// Crear el grafo
const workflow = new StateGraph({
  channels: agentStateSchema
});

// Agregar nodos
workflow.addNode(&quot;analyzeIntent&quot;, analyzeIntent);
workflow.addNode(&quot;checkAvailability&quot;, checkAvailability);
workflow.addNode(&quot;createAppointment&quot;, createAppointment);
workflow.addNode(&quot;askConfirmation&quot;, askConfirmation);

// Definir las conexiones
workflow.addEdge(&quot;__start__&quot;, &quot;analyzeIntent&quot;);
workflow.addEdge(&quot;analyzeIntent&quot;, &quot;checkAvailability&quot;);

// Agregar lógica condicional
workflow.addConditionalEdges(
  &quot;checkAvailability&quot;,
  (state) =&gt; {
    if (state.conflictingEvent) {
      return &quot;askConfirmation&quot;;
    }
    return &quot;createAppointment&quot;;
  }
);

workflow.addEdge(&quot;createAppointment&quot;, &quot;__end__&quot;);
workflow.addEdge(&quot;askConfirmation&quot;, &quot;__end__&quot;);

// Compilar el grafo
const app = workflow.compile();
</pre></div>


<h3 class="wp-block-heading">Paso 6: Prueba y refina</h3>



<p>Finalmente, probá tu agente con diferentes escenarios y refiná el comportamiento. LangGraph te permite inspeccionar el estado en cada paso, lo que facilita el debugging:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; auto-links: false; title: ; notranslate">
// Ejecutar el agente
const result = await app.invoke({
  messages: &#x5B;{ role: &quot;user&quot;, content: &quot;Agendame una reunión mañana a la 1&quot; }]
});

console.log(&quot;Estado final:&quot;, result);

// Usar LangSmith para ver el trace completo
// Podés visualizar cada nodo ejecutado, su entrada y salida
</pre></div>


<p>Algunos tips para probar:</p>



<ul class="wp-block-list">
<li>Probá casos simples primero: «Tengo libre el martes?»</li>



<li>Luego probá casos con conflictos: «Agendame algo cuando ya tengo una cita»</li>



<li>Probá casos ambiguos: «Necesito reunirme la semana que viene» (sin especificar día ni hora)</li>



<li>Usá LangSmith para visualizar el flujo completo y detectar dónde el agente se confunde</li>
</ul>



<p>Con este enfoque paso a paso, podés construir agentes de IA complejos y robustos que resuelvan problemas reales. LangGraph te da el control total sobre la lógica, mientras te abstrae de la complejidad de manejar el estado y las transiciones entre pasos.</p>



<h2 class="wp-block-heading">Conceptos claves</h2>



<h3 class="wp-block-heading">Estado (State)</h3>



<p>La memoria compartida entre todos los nodos del agente. Contiene toda la información necesaria para que los diferentes pasos del flujo puedan ejecutarse de manera coordinada. Se actualiza conforme el agente avanza por los diferentes nodos.</p>



<h3 class="wp-block-heading">Nodo (Node)</h3>



<p>Una función individual que realiza una tarea específica dentro del flujo del agente. Cada nodo recibe el estado actual, ejecuta su lógica particular, y devuelve una actualización del estado. Los nodos pueden ser de tipo LLM, de datos, de acción o de solicitud al usuario.</p>



<h3 class="wp-block-heading">Arista (Edge)</h3>



<p>Una conexión entre dos nodos que define el flujo de ejecución. Las aristas pueden ser simples (conexión directa) o condicionales (la ruta depende de una condición evaluada en tiempo de ejecución).</p>



<h3 class="wp-block-heading">Grafo (Graph)</h3>



<p>La estructura completa del agente, compuesta por todos los nodos y aristas que definen el flujo de ejecución. LangGraph utiliza esta representación para orquestar la ejecución del agente.</p>



<h3 class="wp-block-heading">Sub-agente</h3>



<p>Un agente completo que funciona como un nodo dentro de otro agente más grande. Permite modularizar lógica compleja dividiendo el problema en sub-problemas más manejables, cada uno con su propio flujo de nodos.</p>



<h3 class="wp-block-heading">LLM (Large Language Model)</h3>



<p>Modelo de lenguaje grande utilizado para tareas que requieren comprensión, razonamiento o generación de texto. En el contexto de agentes, se usa típicamente para analizar intenciones, extraer información o tomar decisiones basadas en lenguaje natural.</p>



<h3 class="wp-block-heading">Nodo LLM</h3>



<p>Un nodo que utiliza un modelo de lenguaje para procesar información. Se usa cuando se necesita analizar, entender, generar texto o tomar decisiones de razonamiento sobre el input del usuario.</p>



<h3 class="wp-block-heading">Nodo de Datos</h3>



<p>Un nodo especializado en consultar información de fuentes externas (APIs, bases de datos, servicios). No modifica datos, solo los recupera y los añade al estado.</p>



<h3 class="wp-block-heading">Nodo de Acción</h3>



<p>Un nodo que ejecuta operaciones que modifican el estado del sistema externo (crear, actualizar, eliminar recursos). Por ejemplo, crear una cita en un calendario o enviar un email.</p>



<h3 class="wp-block-heading">Nodo de Solicitud de Usuario</h3>



<p>Un nodo que requiere intervención humana. Detiene el flujo automático del agente y espera input adicional del usuario antes de continuar con el siguiente paso.</p>



<h3 class="wp-block-heading">Arista Condicional (Conditional Edge)</h3>



<p>Una conexión entre nodos que evalúa una condición basada en el estado actual para determinar qué nodo debe ejecutarse a continuación. Permite crear flujos dinámicos que se adaptan según el contexto.</p>



<h3 class="wp-block-heading">Compilación (Compile)</h3>



<p>El proceso de convertir la definición del grafo (nodos y aristas) en una aplicación ejecutable. Una vez compilado, el grafo puede ser invocado para procesar requests.</p>



<h3 class="wp-block-heading">Invocación (Invoke)</h3>



<p>La acción de ejecutar el agente compilado con un estado inicial. El agente procesará el input siguiendo el flujo definido hasta llegar a un nodo final.</p>



<h3 class="wp-block-heading">LangSmith</h3>



<p>Herramienta de observabilidad y debugging para aplicaciones con LangChain y LangGraph. Permite visualizar el trace completo de ejecución, ver el estado en cada paso, y detectar problemas en el flujo del agente.</p>



<h3 class="wp-block-heading">Intención (Intent)</h3>



<p>La acción o propósito que el usuario quiere realizar, extraída de su mensaje. Por ejemplo: agendar, cancelar, buscar, reprogramar. Identificar correctamente la intención es crucial para dirigir el flujo del agente.</p>



<h3 class="wp-block-heading">Historial de Conversación (Message History)</h3>



<p>El registro de todos los mensajes intercambiados entre el usuario y el agente. Almacenado en el estado, permite al agente mantener contexto de conversaciones multi-turno.</p>



<h3 class="wp-block-heading">StateGraph</h3>



<p>La clase principal de LangGraph para crear agentes basados en grafos con estado. Permite definir la estructura del estado, agregar nodos, conectarlos con aristas, y compilar el grafo resultante.</p>



<h3 class="wp-block-heading">Channels</h3>



<p>Los campos que componen el esquema del estado en LangGraph. Cada channel representa una propiedad específica del estado que puede ser leída y actualizada por los nodos.</p>



<h3 class="wp-block-heading">Reducer</h3>



<p>Una función opcional que controla cómo se combinan múltiples actualizaciones a un mismo campo del estado. Útil cuando varios nodos pueden modificar la misma propiedad y necesitas definir la lógica de merge.</p>



<h2 class="wp-block-heading"><strong>¿Quien soy yo?</strong></h2>



<p>Te cuento quien soy en la siguiente entrada <a href="https://jorgecastrillo.blog/wp-admin/post.php?post=1&amp;action=edit">Por qué inicié mi blog personal sobre tecnología y emprendimiento</a></p>
<p>La entrada <a href="https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-conceptos/">Construyendo Agentes de IA con LangChain y LangGraph: Conceptos</a> se publicó primero en <a href="https://jorgecastrillo.blog">Jorge Castrillo</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jorgecastrillo.blog/construyendo-agentes-de-ia-con-langchain-y-langgraph-conceptos/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Nextjs + Notificaciones Push</title>
		<link>https://jorgecastrillo.blog/nextjs-notificaciones-push/</link>
					<comments>https://jorgecastrillo.blog/nextjs-notificaciones-push/#respond</comments>
		
		<dc:creator><![CDATA[Jorge]]></dc:creator>
		<pubDate>Sun, 11 Jan 2026 20:36:08 +0000</pubDate>
				<category><![CDATA[Desarrollo web]]></category>
		<category><![CDATA[Guías]]></category>
		<category><![CDATA[Nextjs]]></category>
		<category><![CDATA[Push Notifications]]></category>
		<guid isPermaLink="false">https://jorgecastrillo.blog/?p=81</guid>

					<description><![CDATA[<p>Las Push Notifications son una herramienta poderosa que permite a las aplicaciones web notificar a sus usuarios cuando suceda algún evento espacial.</p>
<p>La entrada <a href="https://jorgecastrillo.blog/nextjs-notificaciones-push/">Nextjs + Notificaciones Push</a> se publicó primero en <a href="https://jorgecastrillo.blog">Jorge Castrillo</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Las Push Notifications son una herramienta poderosa que permite a las aplicaciones web notificar a sus usuarios cuando suceda algún evento espacial. </p>



<p>Estas notificaciones aparecen en el dispositivo, lo cual es sumamente util para mantener una comunicación cercana con el usuario y puede mejorar la retención y seguimiento.</p>



<h2 class="wp-block-heading">Componentes necesarios para a utilizar</h2>



<ol class="wp-block-list">
<li><strong>service-worker</strong>:<strong><br></strong>Un service worker es un script que corre de manera independiente en tu web.</li>



<li><strong>Browser APIs</strong>:<strong><br></strong>Es la herramienta que nos va a ayudar a integrar las funciones de Notification API y Permission API para habilitar las notificaciones push.</li>



<li><strong>VAPID Keys</strong> (Voluntary Application Server Identification):<br>Consiste en una llave pública y otra privada que son usadas para firmar y verificar la identidad del remitente.</li>
</ol>



<h2 class="wp-block-heading">Suscripción de service worker</h2>



<p>La suscripción de un service worker consiste en registrar el navegador del usuario para recibir notificaciones push. Este proceso genera un endpoint único para cada usuario, permitiendo que el servidor envía los mensajes a un destino concreto.</p>



<h3 class="wp-block-heading">¿Cómo funciona?</h3>



<ol class="wp-block-list">
<li>Se registra el service worker utilizando <code>navigator.serviceWorker.register()</code></li>



<li>Proceso de suscripción
<ul class="wp-block-list">
<li>Se solicita el permiso del usuario con <code>Notification.requestPermission()</code>.</li>



<li>Si es permitido se realiza las suscripción push con <code>registration.pushManager.subscribe()</code></li>



<li>Lo anterior devuelve un Objeto <code>PushSubscription</code> que contiene el endpoint único del navegador y las llaver de encriptación.</li>
</ul>
</li>



<li>Guardar Objeto <code>PushSubscription</code>, está información es la que vas a usar para enviar los mensaje push a tus usuarios</li>
</ol>



<h2 class="wp-block-heading">Lo que necesitas</h2>



<ul class="wp-block-list">
<li>Framework Nextjs <code>npm create next-app@latest push-notifications --yes</code></li>



<li>Instala web push <code>npm i -web-push</code></li>



<li>Instala los typos web-dev <code>npm i --save-dev @types/web-push</code></li>



<li>VAPID Keys</li>
</ul>



<h2 class="wp-block-heading">Genera VAPID Keys</h2>



<p>Para generar la llave pública y privada que usará tu web app para  push messages puedes usar la siguientes alternativas</p>



<ul class="wp-block-list">
<li>Opción 1: Instala el paquete web-push globalmente <code>npm install -g web-push</code> y luego <code>web-push generate-vapid-keys</code></li>



<li>Opición 2: Ve a https://vapidkeys.com/ ingresa tu email y te llegará un mail con las llaves</li>
</ul>



<p>Luego copia y pega las llave en tus variables de entorno (.env)</p>



<pre class="wp-block-code"><code>NEXT_PUBLIC_VAPID_PUBLIC_KEY=llave_publica
VAPID_PRIVATE_KEY=llave_privada</code></pre>



<h2 class="wp-block-heading">Estructura del proyecto</h2>



<p>Te comparto el repositorio en git para que puedas analizarlo y adaptarlo a tu solución <a href="https://github.com/slothgeek/nextjs-push-notifications">aquí</a></p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="888" height="793" src="https://jorgecastrillo.blog/wp-content/uploads/2026/01/image.png" alt="" class="wp-image-82" srcset="https://jorgecastrillo.blog/wp-content/uploads/2026/01/image.png 888w, https://jorgecastrillo.blog/wp-content/uploads/2026/01/image-300x268.png 300w, https://jorgecastrillo.blog/wp-content/uploads/2026/01/image-768x686.png 768w" sizes="(max-width: 888px) 100vw, 888px" /></figure>



<h2 class="wp-block-heading">Paso a paso</h2>



<h3 class="wp-block-heading">1. Agrega soporte para PWA. Crea el archivo <code>manifest.ts</code> en la carpeta app.</h3>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
import type { MetadataRoute } from &#039;next&#039;
 
export default function manifest(): MetadataRoute.Manifest {
  return {
    name: &#039;Notificaciones Push&#039;,
    short_name: &#039;push-notifications&#039;,
    description: &#039;Notificaciones Push by Jorge Castrillo&#039;,
    start_url: &#039;/&#039;,
    display: &#039;standalone&#039;,
    background_color: &#039;#ffffff&#039;,
    theme_color: &#039;#000000&#039;,
    icons: &#x5B;
      {
        src: &#039;/icon-192x192.png&#039;,
        sizes: &#039;192x192&#039;,
        type: &#039;image/png&#039;,
      },
      {
        src: &#039;/icon-512x512.png&#039;,
        sizes: &#039;512x512&#039;,
        type: &#039;image/png&#039;,
      },
    ],
  }
}
</pre></div>


<p>Cambia el nombre, la descripción y el nombre corto a los de tu app. Tambien te comparto una herramienta muy útil para generar todos los tamaños para tus favicon https://realfavicongenerator.net/</p>



<h3 class="wp-block-heading">2. Crear los Server Actions</h3>



<p>Crea el archivo <code>actions.ts</code> en la carpeta app. Para este ejemplo particular usaremos server actions de nextjs. Tendremos unicamente 3 funciones.</p>



<ul class="wp-block-list">
<li><strong>subscribeUser</strong>: Acá es donde debes almacenar el objeto de suscripción que contiene el endpoint qye utilizar web-push y las key para enviar los mensajes push</li>



<li><strong>unsubscribeUser</strong>: Eliminar la suscripción de tu sistema de almacenamiento</li>



<li><strong>sendNotification</strong>:  Función para enviar las notificaciones. En este ejemplo esta función recibe 2 parámetros (mensaje y suscripción). Cuando apliques a tu proyecto, lo ideal es que la suscripción la obtengas desde tu sistema de almacenamiento, de momento la optenemos del navegador.</li>
</ul>



<p>Importamos webpush, lo inicializamos con las variables de entorno y lo usamos para enviar notificaciones desde la función <code>sendNotification</code></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&#039;use server&#039;
 
import webpush from &#039;web-push&#039;
import type { PushSubscription } from &#039;web-push&#039;

type SerializedSubscription = {
  endpoint: string
  keys: {
    p256dh: string
    auth: string
  } | null
}

type PushMessage = {
    title: string
    body: string
    url: string
}
 
webpush.setVapidDetails(
  &#039;mailto:castrillodev@gmail.com&#039;,
  process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!,
  process.env.VAPID_PRIVATE_KEY!
)
 
let subscription: SerializedSubscription | null = null
 
export async function subscribeUser(sub: SerializedSubscription) {
  subscription = sub
  // En un entorno de producción, se almacenaría la suscripción en una base de datos
  // Por ejemplo: await db.subscriptions.create({ data: sub })
  return { success: true }
}
 
export async function unsubscribeUser() {
  subscription = null
  // En un entorno de producción, se eliminaría la suscripción de la base de datos
  // Por ejemplo: await db.subscriptions.delete({ where: { ... } })
  return { success: true }
}
 
export async function sendNotification(message: PushMessage, sub: SerializedSubscription) {
  if (!sub || !sub.keys) {
    throw new Error(&#039;No hay suscripción disponible&#039;)
  }
 
  // Convertir el objeto serializado al formato que espera web-push
  const pushSubscription: PushSubscription = {
    endpoint: sub.endpoint,
    keys: {
      p256dh: sub.keys.p256dh,
      auth: sub.keys.auth,
    },
  }

  try {
    await webpush.sendNotification(
      pushSubscription,
      JSON.stringify({
        title: message.title,
        body: message.body,
        icon: &#039;/icon.png&#039;,
        url: message.url,
      })
    )
    return { success: true }
  } catch (error) {
    console.error(&#039;Error al enviar la notificación:&#039;, error)
    return { success: false, error: &#039;Error al enviar la notificación&#039; }
  }
}
</pre></div>


<h3 class="wp-block-heading">3. Crear el service worker</h3>



<p>Creas el archivo <code>sw.js</code> en la carpeta public. Habilitamos 2 listeners, uno para el evento push y el otro para notificationClick. En Cada evento podemos obtener los datos de la notificación.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
self.addEventListener(&#039;push&#039;, function (event) {
    if (event.data) {
        const data = event.data.json()
        const options = {
            body: data.body,
            icon: data.icon || &#039;/icon.png&#039;,
            badge: &#039;/badge.png&#039;,
            vibrate: &#x5B;100, 50, 100],
            data: {
                dateOfArrival: Date.now(),
                primaryKey: &#039;2&#039;,
            },
        }
        event.waitUntil(self.registration.showNotification(data.title, options))
    }
})

self.addEventListener(&#039;notificationclick&#039;, function (event) {
    console.log(&#039;Notificación clicada.&#039;)
    event.notification.close()

    // Los datos están en event.notification.data, no en event.data
    const data = event.notification.data
    const url = data?.url || &#039;https://jorgecastrillo.blog&#039;
    event.waitUntil(clients.openWindow(url))
})
</pre></div>


<h3 class="wp-block-heading">5. Componente de Notificaciones</h3>



<p>Una vez que tenemos los actions y el service worker ya podemos crear nuestro componente de pruebas para notificaciones push. Crea el archivo push.tsx en app</p>



<p>El componente tiene las siguiente funciones</p>



<ol class="wp-block-list">
<li>Revisa si el navegador soporta notificaciones push.</li>



<li>Obtenemos la suscripción push del navegador</li>



<li>Luego tenemos tres acciones
<ul class="wp-block-list">
<li><strong>subscribeToPush</strong> acá cargamos el service worker del navegador, luego registramos la suscripción push usando la llave pública y luego asignamos el valor al estado subscription</li>



<li><strong>unsubscribeFromPush</strong> Elimina la suscripción con <code>await subscription?.unsubscribe()</code></li>



<li><strong>sendTestNotification</strong> Envía el mensaje</li>
</ul>
</li>



<li>Imprimimos la interfaz y listo</li>
</ol>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: jscript; title: ; notranslate">
&#039;use client&#039;

import { useState, useEffect } from &#039;react&#039;
import { subscribeUser, unsubscribeUser, sendNotification } from &#039;./actions&#039;

const VAPID_PUBLIC_KEY = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY!

function urlBase64ToUint8Array(base64String: string) {
    const padding = &#039;=&#039;.repeat((4 - (base64String.length % 4)) % 4)
    const base64 = (base64String + padding).replace(/-/g, &#039;+&#039;).replace(/_/g, &#039;/&#039;)

    const rawData = window.atob(base64)
    const outputArray = new Uint8Array(rawData.length)

    for (let i = 0; i &lt; rawData.length; ++i) {
        outputArray&#x5B;i] = rawData.charCodeAt(i)
    }
    return outputArray
}

export default function PushNotificationManager() {
    const &#x5B;isSupported, setIsSupported] = useState(false)
    const &#x5B;subscription, setSubscription] = useState&lt;PushSubscription | null&gt;(
        null
    )
    const &#x5B;title, setTitle] = useState(&#039;&#039;)
    const &#x5B;message, setMessage] = useState(&#039;&#039;)
    const &#x5B;url, setUrl] = useState(&#039;&#039;)

    useEffect(() =&gt; {
        if (&#039;serviceWorker&#039; in navigator &amp;&amp; &#039;PushManager&#039; in window) {
            setIsSupported(true)
            registerServiceWorker()
        }
    }, &#x5B;])

    async function registerServiceWorker() {
        const registration = await navigator.serviceWorker.register(&#039;/sw.js&#039;, {
            scope: &#039;/&#039;,
            updateViaCache: &#039;none&#039;,
        })
        const sub = await registration.pushManager.getSubscription()
        setSubscription(sub)
    }

    async function subscribeToPush() {
        if (!VAPID_PUBLIC_KEY) {
            alert(&#039;Error: La clave VAPID pública no está configurada. Por favor, configura NEXT_PUBLIC_VAPID_PUBLIC_KEY en tu archivo .env.local&#039;)
            return
        }
        const registration = await navigator.serviceWorker.ready
        const sub = await registration.pushManager.subscribe({
            userVisibleOnly: true,
            applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
        })
        setSubscription(sub)
        const serializedSub = JSON.parse(JSON.stringify(sub))
        await subscribeUser(serializedSub)
    }

    async function unsubscribeFromPush() {
        await subscription?.unsubscribe()
        setSubscription(null)
        await unsubscribeUser()
    }

    async function sendTestNotification() {
        if (subscription) {
            console.log(&#039;Enviando notificación de prueba:&#039;, message)
            const serializedSub = JSON.parse(JSON.stringify(subscription))
            await sendNotification({ title, body: message, url }, serializedSub)
            setMessage(&#039;&#039;)
        }
    }

    if (!isSupported) {
        return &lt;p&gt;Push notifications are not supported in this browser.&lt;/p&gt;
    }

    return (
        &lt;div className=&quot;flex flex-col gap-4 items-start justify-start w-full&quot;&gt;
            {subscription ? (
                &lt;&gt;
                    &lt;p&gt;Estas suscrito a notificaciones push.&lt;/p&gt;
                    &lt;button
                        onClick={unsubscribeFromPush}
                        className=&#039;bg-red-500 text-white py-2 px-4 rounded-md cursor-pointer&#039;
                    &gt;
                        Cancelar suscripción
                    &lt;/button&gt;
                    &lt;div className=&#039;border-t border-gray-300 w-full&#039;&gt;&lt;/div&gt;
                    &lt;h2 className=&#039;text-lg font-bold&#039;&gt;Enviar notificación de prueba&lt;/h2&gt;
                    &lt;input
                        type=&quot;text&quot;
                        placeholder=&quot;Ingrese el titulo de la notificación&quot;
                        className=&#039;w-full max-w-xs p-2 rounded-md border border-gray-300 bg-white placeholder:text-gray-500 text-black&#039;
                        value={title}
                        onChange={(e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; setTitle(e.target.value)}
                    /&gt;
                    &lt;textarea
                        placeholder=&quot;Ingrese el mensaje de la notificación&quot;
                        className=&#039;w-full p-2 rounded-md border border-gray-300 bg-white placeholder:text-gray-500 text-black&#039;
                        value={message}
                        onChange={(e: React.ChangeEvent&lt;HTMLTextAreaElement&gt;) =&gt; setMessage(e.target.value)}
                    &gt;{message}&lt;/textarea&gt;
                    &lt;input
                        type=&quot;text&quot;
                        placeholder=&quot;Ingrese la url de la notificación&quot;
                        className=&#039;w-full p-2 rounded-md border border-gray-300 bg-white placeholder:text-gray-500 text-black&#039;
                        value={url}
                        onChange={(e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; setUrl(e.target.value)}
                    /&gt;
                    &lt;button
                        onClick={sendTestNotification}
                        className=&#039;bg-blue-500 text-white py-2 px-4 rounded-md cursor-pointer&#039;
                    &gt;
                        Enviar prueba
                    &lt;/button&gt;
                &lt;/&gt;
            ) : (
                &lt;&gt;
                    &lt;p&gt;No estás suscrito a notificaciones push.&lt;/p&gt;
                    &lt;button
                        onClick={subscribeToPush}
                        className=&#039;bg-blue-500 text-white py-2 px-4 rounded-md cursor-pointer&#039;
                    &gt;
                        Suscribirme
                    &lt;/button&gt;
                &lt;/&gt;
            )}
        &lt;/div&gt;
    )
}


</pre></div>


<h2 class="wp-block-heading">Conclusión</h2>



<p>Con esta guia tienes un punto de partida completo para poder implementar notificaciones push en tu proyecto de nextjs. Si prestas atención y entiendes bien el flujo te darás cuenta que esta misma lógica la podrías implementar en otros frameworks de programación.</p>



<p>Espero les sea de mucha utilidad, al menos para mi, lo es y lo estaré consultando cada que lo necesite. </p>



<p><strong><em>Gracias Jorge del pasado.</em></strong></p>
<p>La entrada <a href="https://jorgecastrillo.blog/nextjs-notificaciones-push/">Nextjs + Notificaciones Push</a> se publicó primero en <a href="https://jorgecastrillo.blog">Jorge Castrillo</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jorgecastrillo.blog/nextjs-notificaciones-push/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Planning en Desarrollo de Software: Evita Perder Miles de Dólares</title>
		<link>https://jorgecastrillo.blog/planning-desarrollo-software-evitar-perdidas/</link>
					<comments>https://jorgecastrillo.blog/planning-desarrollo-software-evitar-perdidas/#respond</comments>
		
		<dc:creator><![CDATA[Jorge]]></dc:creator>
		<pubDate>Wed, 07 Jan 2026 17:20:00 +0000</pubDate>
				<category><![CDATA[Guías]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[Historias]]></category>
		<category><![CDATA[Planning]]></category>
		<category><![CDATA[software]]></category>
		<guid isPermaLink="false">https://jorgecastrillo.blog/?p=61</guid>

					<description><![CDATA[<p>Descubre por qué planificar antes de desarrollar software es crucial. Historia real de emprendedoras que perdieron miles por no hacerlo. Guía completa 2026.</p>
<p>La entrada <a href="https://jorgecastrillo.blog/planning-desarrollo-software-evitar-perdidas/">Planning en Desarrollo de Software: Evita Perder Miles de Dólares</a> se publicó primero en <a href="https://jorgecastrillo.blog">Jorge Castrillo</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>¿Estás a punto de invertir en el desarrollo de una página web o aplicación? Detente. Si no planificas correctamente, podrías unirte a las miles de personas que pierden dinero y tiempo en proyectos de software fallidos.</p>



<p>En este artículo te compartiré exactamente qué hacer antes de arrancar a desarrollar una web o app y cómo la etapa de planning en el desarrollo de software te ahorra dolores de cabeza y, sobre todo, dinero.</p>



<p>Al final te cuento una <strong>historia de terror real</strong> de dos emprendedoras que perdieron miles de dólares por contratar desarrollo sin planificar.</p>



<h2 class="wp-block-heading">¿Por Qué Planificar el Desarrollo de Software es Crítico para tu Negocio?</h2>



<p>Me gustaría hablarte desde la experiencia. Desde que arranqué en el fascinante mundo del emprendimiento tecnológico, he visto un patrón repetitivo en negocios emergentes y establecidos.</p>



<p>Lo más común es que las empresas empiecen a pensar en tener una página web o, cuando están más avanzadas, implementar soluciones tipo SAAS o crear una aplicación a la medida porque nada en el mercado se adapta a su operación.</p>



<h3 class="wp-block-heading">Las 5 Preguntas Fundamentales Antes de Desarrollar Software</h3>



<p>Antes de contratar un SAAS, crear una página web o desarrollar una solución de software a la medida, debes hacerte estas preguntas:</p>



<ol class="wp-block-list">
<li><strong>¿Cuáles son los cuellos de botella en los procesos de mi negocio?</strong></li>



<li><strong>¿Cuáles son mis dolores de cabeza operativos?</strong></li>



<li><strong>¿Qué procesos son repetitivos en mi negocio?</strong></li>



<li><strong>¿Esta solución me ayudará a ahorrar tiempo y dinero?</strong></li>



<li><strong>¿Cuál es el objetivo real de implementar esta solución de software?</strong></li>
</ol>



<h3 class="wp-block-heading">Por Qué Estas Preguntas Son Cruciales</h3>



<p>Tu negocio, para funcionar y ser rentable, necesita activos. Si una solución de software se va a volver un pasivo, algo no cuadra ¿cierto?</p>



<p>Esa inversión en software que vas a realizar tiene que ser con sentido y propósito. Tiene que volverse un activo importante en tu organización, ya sea para ahorrar dinero o generar más ingresos.</p>



<h4 class="wp-block-heading">Mi Postura sobre las Webs Informativas</h4>



<p>Siempre he sido crítico de las páginas web puramente informativas, aunque me ha tocado hacerlas porque el cliente lo quiere a pesar de mi asesoría.</p>



<p>Seamos claros: una web informativa se vuelve un pasivo con el tiempo. Una página web tiene que tener un propósito, una intención clara. ¿De qué nos sirve crear una página web informativa que no convierte nada? Lo veo como simple ego empresarial.</p>



<h2 class="wp-block-heading">¿Qué es el Planning en Desarrollo de Software?</h2>



<p>Cuando ya te has respondido las preguntas anteriores y estás decidido a implementar una solución de software en tu negocio, ya sea una página web o una solución a la medida, es momento de hacer un <strong>documento de planning</strong>.</p>



<h3 class="wp-block-heading">Definición de Planning de Software</h3>



<p>El Planning en la etapa previa al desarrollo no es más que un <strong>documento que define las reglas de juego</strong> a la hora de crear la solución.</p>



<p>En esta etapa, el profesional experto en tecnologías debe acompañar y escuchar al cliente y stakeholders para crear un documento de planificación ajustado y certero.</p>



<h3 class="wp-block-heading">Qué Incluye un Planning de Software Profesional</h3>



<ul class="wp-block-list">
<li><strong>Reuniones y Análisis de Procesos</strong> &#8211; Reuniones virtuales o presenciales, identificar y entender los procesos, mapear dolores y cuellos de botella</li>



<li><strong>Establecimiento de KPIs</strong> &#8211; Métricas de éxito del proyecto e indicadores de rendimiento</li>



<li><strong>Casos de Uso</strong> &#8211; Descripción detallada de escenarios y flujos de usuario</li>



<li><strong>Definición de Requerimientos</strong> &#8211; Funcionales y no funcionales, priorización de features</li>



<li><strong>Diseño de Arquitectura</strong> &#8211; Estructura de datos y patrón de diseño recomendado</li>



<li><strong>Stack de Tecnología</strong> &#8211; Análisis de tecnologías apropiadas y selección de herramientas</li>



<li><strong>Prototipo Visual</strong> &#8211; Wireframes en baja fidelidad y validación de flujos</li>



<li><strong>Tiempos de Desarrollo</strong> &#8211; Cronograma realista y fases del proyecto</li>



<li><strong>Costos del Proyecto</strong> &#8211; Presupuesto detallado y ROI esperado</li>
</ul>



<h3 class="wp-block-heading">La Analogía de la Construcción</h3>



<p>Es como hacer una casa: antes de arrancar a construirla, necesitas los planos estructurales, eléctricos, mecánicos, civiles, topográficos y ambientales.</p>



<p><strong>Bonus:</strong> Si tienes un proyecto de construcción de vivienda o comercial, te invito a ver la página que hice para Daniel Chavarría, un crack que lleva tu proyecto de construcción de 0 a 100: <a href="https://www.danielconstruye.cr/" target="_blank" rel="noreferrer noopener">danielconstruye</a><a href="https://www.danielconstruye.cr/">.cr</a></p>



<h2 class="wp-block-heading">¿Cuánto Cuesta el Planning y Por Qué Vale la Pena?</h2>



<p>El planning tiene un costo, sí, no es gratuito. El precio va a depender del tamaño de la solución, pero te aseguro que te evitará dolores de cabeza a futuro.</p>



<h3 class="wp-block-heading">Beneficios Comprobados del Planning</h3>



<ul class="wp-block-list">
<li>✓ <strong>Reducción de riesgo del proyecto</strong> hasta en un 70%</li>



<li>✓ <strong>Claridad técnica total</strong> antes de invertir miles</li>



<li>✓ <strong>Decisiones correctas de arquitectura</strong> desde el inicio</li>



<li>✓ <strong>Evitar rehacer el sistema</strong> 2-3 veces</li>



<li>✓ <strong>Base para cotizar correctamente</strong> el desarrollo</li>



<li>✓ <strong>Ahorro significativo de dinero</strong> a largo plazo</li>
</ul>



<h2 class="wp-block-heading">Conclusión: No Cometas Este Error Común</h2>



<p><strong>Recomendación personal:</strong> Antes de pagar por desarrollar una solución de software, planifica por favor.</p>



<p>He hablado con dueños de negocio que por no planificar han tirado a la basura miles de dólares. Una reunión de 45 minutos NO es suficiente para que un profesional de TI entienda y genere una solución de software que realmente resuelva.</p>



<p>Planifica bien tu próxima solución de software, desde una web «informativa» hasta una aplicación a la medida.</p>



<h2 class="wp-block-heading">Historia de Terror: Cuando No Planificar Cuesta Miles de Dólares</h2>



<p>Hace 3 años conocí a 2 emprendedoras que querían crear una web de tiquetera para eventos. <strong>Spoiler alert:</strong> no planificaron.</p>



<h3 class="wp-block-heading">Primer Intento Fallido</h3>



<p>Se acercaron a mí porque la solución que les habían comenzado a desarrollar no funcionaba. El proceso de compra era deficiente, las notificaciones por email no llegaban correctamente y los tiquetes se perdían en el sistema.</p>



<p>Lamentablemente no les pude ayudar en esa ocasión porque no tenía tiempo disponible. Estaba desarrollando otra solución que me consumía mucho tiempo.</p>



<h3 class="wp-block-heading">Segundo Intento: Mismo Error, Peor Resultado</h3>



<p>El año pasado (2025) volvieron a contactarme con el <strong>mismo problema</strong>, pero con una solución de software distinta y programador distinto. Un completo desastre.</p>



<p>De recordarlo me llevo las manos a la cabeza e inhalo profundamente por la congoja.</p>



<h3 class="wp-block-heading">El Desastre Técnico</h3>



<p>Otra vez, el «experto» que se supone tenía que guiarlas y darles un servicio de calidad NO PLANIFICÓ.</p>



<p>Se puso a montar un WordPress con 300 plugins tratando de resolver el problema y terminó creando un frankenstein técnico, y no te imagines el frankenstein que interpreta Jacob Elordi, de haber sido ese, no estaría tan mal.</p>



<p><strong>Importante:</strong> No digo que WordPress sea malo, al contrario, es buenísimo. Pero sin planificación de desarrollo puede terminar no siendo la herramienta ideal para la solución. El stack de tecnología se define en la etapa de planning.</p>



<h3 class="wp-block-heading">El Resultado Final</h3>



<p>Las emprendedoras perdieron <strong>miles de dólares</strong> y el profesional en TI quedó con una reputación fatal en la comunidad, siendo eventualmente expuesto.</p>



<p><strong>Puras pérdidas por no planificar.</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Preguntas Frecuentes sobre Planning de Software</h2>



<p><strong>¿Cuánto tiempo toma un planning de software?</strong><br>Depende de la complejidad, pero típicamente entre 2-4 semanas para proyectos medianos.</p>



<p><strong>¿Es obligatorio hacer planning para cualquier desarrollo?</strong><br>No es obligatorio legalmente, pero es altamente recomendado para cualquier inversión seria en software.</p>



<p><strong>¿Puedo hacer el planning yo mismo?</strong><br>Puedes intentarlo, pero un profesional experimentado identificará aspectos técnicos y riesgos que podrías pasar por alto.</p>



<p><strong>¿Quien soy yo?</strong><br>Te cuento quien soy en la siguiente entrada <a href="https://jorgecastrillo.blog/wp-admin/post.php?post=1&amp;action=edit">Por qué inicié mi blog personal sobre tecnología y emprendimiento</a></p>
<p>La entrada <a href="https://jorgecastrillo.blog/planning-desarrollo-software-evitar-perdidas/">Planning en Desarrollo de Software: Evita Perder Miles de Dólares</a> se publicó primero en <a href="https://jorgecastrillo.blog">Jorge Castrillo</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jorgecastrillo.blog/planning-desarrollo-software-evitar-perdidas/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Por qué inicié mi blog personal sobre tecnología y emprendimiento</title>
		<link>https://jorgecastrillo.blog/inicie-blog-personal-tecnologia-emprendimiento/</link>
					<comments>https://jorgecastrillo.blog/inicie-blog-personal-tecnologia-emprendimiento/#comments</comments>
		
		<dc:creator><![CDATA[Jorge]]></dc:creator>
		<pubDate>Mon, 05 Jan 2026 14:04:40 +0000</pubDate>
				<category><![CDATA[Anécdotas]]></category>
		<category><![CDATA[Personal]]></category>
		<category><![CDATA[Emprender]]></category>
		<category><![CDATA[TI]]></category>
		<guid isPermaLink="false">https://jorgecastrillo.blog/?p=1</guid>

					<description><![CDATA[<p>Desde hace mucho tiempo tenía la idea de comenzar un blog personal. Un espacio propio donde pudiera guardar experiencias, aprendizajes y anécdotas. Le he dado muchas vueltas a este asunto, haciéndome preguntas como: ¿de qué voy a hablar?, ¿mi contenido tendrá valor?, ¿alguien lo leerá? Un sinfín de dudas que, durante años, me mantuvieron paralizado. [&#8230;]</p>
<p>La entrada <a href="https://jorgecastrillo.blog/inicie-blog-personal-tecnologia-emprendimiento/">Por qué inicié mi blog personal sobre tecnología y emprendimiento</a> se publicó primero en <a href="https://jorgecastrillo.blog">Jorge Castrillo</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Desde hace mucho tiempo tenía la idea de comenzar un blog personal. Un espacio propio donde pudiera guardar experiencias, aprendizajes y anécdotas. Le he dado muchas vueltas a este asunto, haciéndome preguntas como: ¿de qué voy a hablar?, ¿mi contenido tendrá valor?, ¿alguien lo leerá? Un sinfín de dudas que, durante años, me mantuvieron paralizado.</p>



<p>Sin embargo, hoy me levanté con una decisión clara: voy a hacer el blog de una vez por todas. Sin preocuparme demasiado por el diseño o el nombre. Simplemente ponerlo a funcionar, escribir la primera entrada y ver qué sucede.</p>



<p>Y aquí estoy. Un lunes 5 de enero de 2026, dando forma a esta primera publicación y dando el primer paso en este proyecto personal.</p>



<p>Debo dar un agradecimiento indirecto a <a href="https://www.linkedin.com/in/enzo-cavalie/" target="_blank" rel="noopener">Enzo Cavalie</a> de <a href="https://startupeable.com/" target="_blank" rel="noopener">Startupeable</a> y a su último podcast junto a Iván Landaso titulado “<a href="https://startupeable.com/jme">El Apalancamiento Sin Permiso: Cómo Escalar sin Capital ni Conexiones</a>”. Ese episodio fue el empujón final que necesitaba para iniciar este blog.</p>



<h2 class="wp-block-heading">Objetivos del blog</h2>



<ul class="wp-block-list">
<li>Hablar sobre inteligencia artificial y tecnología</li>



<li>Documentar guías prácticas y aprendizajes</li>



<li>Compartir experiencias reales de emprendimiento</li>
</ul>



<h2 class="wp-block-heading">Me presento</h2>



<p>Mi nombre es Jorge Castrillo. Mi historia es una más entre muchas, nada extraordinaria. Soy de una zona rural de Costa Rica llamada <strong>La Cruz</strong>, en la provincia de Guanacaste. De niño soñaba con ser futbolista profesional (spoiler: no lo logré).</p>



<p>Con el tiempo descubrí que tenía habilidades para el canto. A los 14 años hice un amigo que tocaba guitarra y comenzó a enseñarme. Fue amor a primera vista (La Guitarra). Practicaba todos los días hasta que logré tocar mi primera canción (no la recuerdo, pero nunca olvidaré esa sensación). Eso me dio el valor para pedirle una guitarra a mis padres, y una de mis tías, Evelyn (gracias infinitas), me regaló una. Hasta el día de hoy, la conservo.</p>



<p>Pasó el tiempo y, como buen muchacho de pueblo, tocaba guitarra y cantaba con bastante confianza. Luego llegó el momento de tomar decisiones importantes. A los 17 años entendí que no sería futbolista. Entonces decidí que quería ser músico y, al mismo tiempo, salir de mi pueblo y conocer el mundo.</p>



<p>Me inscribí en la Universidad Nacional de Costa Rica para estudiar Música con énfasis en guitarra acústica. No logré entrar. No sabía leer música; había aprendido de forma empírica, con amigos y tardes interminables viendo atardeceres.</p>



<p>Ese golpe fue duro, pero tenía un plan B: Ingeniería en Sistemas de Información. En ese momento no tenía muy claro qué aprendería, solo sabía que era computación, algo que siempre me había apasionado.</p>



<p>Comencé a estudiar y, aunque pasaron muchas cosas interesantes que quizás cuente en futuras entradas, no terminé la carrera. Llegué hasta tercer año y con ese conocimiento empecé a trabajar en el área de TI.</p>



<p>Hoy llevo más de 15 años trabajando en tecnología, enfocado principalmente en desarrollo web. He fundado cuatro empresas; una no funcionó y tres siguen activas. A continuación te cuento brevemente sobre ellas.</p>



<h3 class="wp-block-heading"><a href="https://train-in.cr/" target="_blank" rel="noopener">Train-In Formación Virtual</a></h3>



<p>Empresa fundada junto a tres amigos, enfocada en el desarrollo de soluciones educativas en línea. Entre los servicios que ofrecemos están:</p>



<ul class="wp-block-list">
<li>Desarrollo de e-learning a la medida</li>



<li>Animaciones 2D y 3D</li>



<li>Hosting especializado en Moodle</li>



<li>Instalación y personalización de plataformas Moodle</li>
</ul>



<h3 class="wp-block-heading"><a href="https://www.slothgeek.com/" target="_blank" rel="noopener">Slothgeek Soluciones Web</a></h3>



<p>En el año 2020 comencé a profundizar en WordPress y decidí fundar junto con Caro (mi esposa) <strong>Slothgeek</strong>, una agencia digital enfocada en ayudar a pequeñas y medianas empresas a establecer su presencia en línea.</p>



<ul class="wp-block-list">
<li>Desarrollo de sitios web WordPress a la medida</li>



<li>Desarrollo de tiendas en línea con WordPress y Shopify</li>



<li>Desarrollo de plugins</li>



<li>Desarrollo de plantillas personalizadas</li>



<li>Soporte y mantenimiento WordPress</li>



<li>WordPress Headless</li>



<li>Desarrollo de aplicaciones web a la medida</li>
</ul>



<h3 class="wp-block-heading">iSmart Comply</h3>



<p>Startup que fundé junto a cinco socios más. Era una aplicación que conectaba abogados con clientes, ofrecía cursos en línea, plantillas legales y servicios profesionales. Fracasó. En retrospectiva, no resolvía un problema real ni atacaba un dolor claro. Aun así, fue una experiencia invaluable.</p>



<h3 class="wp-block-heading"><a href="https://www.officia.app/">Agente Officia</a></h3>



<p>A finales de 2024 inicié este proyecto a partir de una necesidad personal: agendar una cita con mi barbero sin tener que esperar horas una respuesta por WhatsApp.</p>



<p>Investigando descubrí que este problema es común en negocios que trabajan con citas. Programé un MVP, se lo presenté a mi barbero y comenzó a usarlo. La aplicación ofrece reservas en línea, gestión de agendas y un asistente virtual conectado a WhatsApp que responde y agenda citas automáticamente.</p>



<p><strong>Hoy son decenas de negocios y cientos de personas que ya no sufren tener que agendar una cita.</strong></p>



<h3 class="wp-block-heading">Habilidades técnicas</h3>



<ul class="wp-block-list">
<li><strong>CMS:</strong> WordPress, Strapi</li>



<li><strong>LMS:</strong> Moodle, LearnDash</li>



<li><strong>Frontend:</strong> React, Next.js, Astro, HTML5, CSS3, Tailwind CSS</li>



<li><strong>Backend:</strong> Node.js, Express, WordPress, Strapi 5, Supabase, LangGraph</li>



<li><strong>Bases de datos:</strong> MongoDB, PostgreSQL, MySQL</li>



<li><strong>Automatización:</strong> WhatsApp API, integración con OpenAI</li>
</ul>



<h3 class="wp-block-heading">Habilidades blandas</h3>



<ul class="wp-block-list">
<li>Liderazgo y organización</li>



<li>Comunicación efectiva</li>



<li>Compromiso y constancia</li>
</ul>



<h2 class="wp-block-heading">Aterricemos</h2>



<p>Al escribir todo esto me doy cuenta de algo: sé más de lo que pensaba. Durante años di por sentado todo lo que aprendí porque siempre lo guardé para mí. Hoy quiero compartirlo con el mundo. Tal vez a alguien le sirva. Tal vez este blog sea útil para alguien más que esté empezando.</p>
<p>La entrada <a href="https://jorgecastrillo.blog/inicie-blog-personal-tecnologia-emprendimiento/">Por qué inicié mi blog personal sobre tecnología y emprendimiento</a> se publicó primero en <a href="https://jorgecastrillo.blog">Jorge Castrillo</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://jorgecastrillo.blog/inicie-blog-personal-tecnologia-emprendimiento/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
