<?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: Francisco</title>
    <description>The latest articles on DEV Community by Francisco (@fmarchena).</description>
    <link>https://dev.to/fmarchena</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F745267%2Fadc94710-89ef-48db-9679-3af59be4f3f6.jpeg</url>
      <title>DEV Community: Francisco</title>
      <link>https://dev.to/fmarchena</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fmarchena"/>
    <language>en</language>
    <item>
      <title>De bloquear a autocorregir: una demo práctica de guardrails para agentes de IA con Laravel, Grok y OpenSpec</title>
      <dc:creator>Francisco</dc:creator>
      <pubDate>Fri, 01 May 2026 23:30:15 +0000</pubDate>
      <link>https://dev.to/fmarchena/de-bloquear-a-autocorregir-una-demo-practica-de-guardrails-para-agentes-de-ia-con-laravel-grok-y-2djp</link>
      <guid>https://dev.to/fmarchena/de-bloquear-a-autocorregir-una-demo-practica-de-guardrails-para-agentes-de-ia-con-laravel-grok-y-2djp</guid>
      <description>&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;Hace poco estuve leyendo el artículo &lt;strong&gt;“Guardrails para Agentes de IA que se Autocorrigen en Lugar de Bloquear”&lt;/strong&gt;, publicado por &lt;strong&gt;Elizabeth Fuentes L para AWS Español&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;La idea principal me pareció muy interesante: muchos guardrails tradicionales funcionan de forma binaria. Si el agente viola una regla, se bloquea la acción y el usuario tiene que intervenir. Eso es necesario en algunos casos, pero no siempre es la mejor experiencia.&lt;/p&gt;

&lt;p&gt;El artículo plantea una alternativa: en vez de solo bloquear, podemos guiar al agente para que corrija su acción. A ese enfoque se le llama &lt;code&gt;steer&lt;/code&gt;. El agente recibe una corrección, ajusta los parámetros y continúa el flujo.&lt;/p&gt;

&lt;p&gt;Artículo original:&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/aws-espanol/guardrails-para-agentes-de-ia-que-se-autocorrigen-en-lugar-de-bloquear-3n32"&gt;https://dev.to/aws-espanol/guardrails-para-agentes-de-ia-que-se-autocorrigen-en-lugar-de-bloquear-3n32&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Quise llevar esa idea a una demo práctica usando tecnologías que manejo más en mi día a día:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Laravel
Laravel AI SDK
Vue
Docker Compose
MySQL
Grok / xAI
OpenSpec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Importante: esta demo &lt;strong&gt;no implementa directamente Strands Agents ni Agent Control&lt;/strong&gt;. Lo que hice fue tomar el patrón conceptual del artículo y llevarlo a un stack Laravel + Vue + Laravel AI SDK + Grok.&lt;/p&gt;

&lt;p&gt;En lugar de usar &lt;code&gt;Guide()&lt;/code&gt; para que el LLM reintente, implementé un &lt;code&gt;GuardrailEngine&lt;/code&gt; que corrige el payload de forma determinística y devuelve una propuesta corregida al usuario.&lt;/p&gt;

&lt;p&gt;El objetivo fue aprender el patrón, bajarlo a código y demostrar la idea central:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No bloquear por defecto.
Corregir cuando sea seguro.
Bloquear cuando realmente sea necesario.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  El problema
&lt;/h2&gt;

&lt;p&gt;Imaginemos un agente de IA para agendar citas.&lt;/p&gt;

&lt;p&gt;Un usuario escribe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quiero una cita mañana a las 8pm para 3 personas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pero el negocio tiene estas reglas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Horario de atención: 8:00 a.m. a 6:00 p.m.
Máximo de personas por cita: 2
Duración de la cita: 30 minutos
No se pueden agendar días bloqueados
No se pueden duplicar slots ocupados
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un guardrail tradicional podría responder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No puedo agendar esa cita.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso funciona, pero corta el flujo.&lt;/p&gt;

&lt;p&gt;Una mejor experiencia sería:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No puedo agendar a las 8:00 p.m. ni para 3 personas.
Te puedo ofrecer mañana a las 5:30 p.m. para 2 personas.
¿Deseas confirmar?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahí es donde entra la idea de &lt;strong&gt;autocorrección&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Qué construí
&lt;/h2&gt;

&lt;p&gt;Construí una demo funcional donde un usuario puede pedir una cita por chat.&lt;/p&gt;

&lt;p&gt;La IA interpreta la intención, genera un payload estructurado y luego el &lt;code&gt;GuardrailEngine&lt;/code&gt; valida las reglas del negocio.&lt;/p&gt;

&lt;p&gt;El usuario no confirma directamente lo que la IA propone. Primero pasa por una capa de control.&lt;/p&gt;

&lt;p&gt;También agregué un frontadmin para configurar reglas, servicios, días bloqueados y revisar logs.&lt;/p&gt;

&lt;p&gt;En simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;La IA entiende.
El guardrail decide.
El backend ejecuta.
El admin configura.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Arquitectura de la demo
&lt;/h2&gt;

&lt;p&gt;La demo está compuesta por:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend Cliente
Backend Laravel
Laravel AI SDK
AppointmentIntentAgent
LaravelAiAppointmentAgent
GuardrailEngine
AppointmentController
Frontadmin
MySQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqerrumq8yj2cuqkq4kx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqerrumq8yj2cuqkq4kx.png" alt="Arquitectura de la demo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Responsabilidades por componente
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Componente&lt;/th&gt;
&lt;th&gt;Responsabilidad&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Frontend Cliente&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Permite al usuario escribir la solicitud y confirmar la cita propuesta.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Frontadmin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Permite configurar reglas, servicios, días bloqueados y revisar logs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ChatController&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recibe el mensaje del usuario y coordina el flujo del agente.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LaravelAiAppointmentAgent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Servicio wrapper que invoca el agente creado con Laravel AI SDK.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AppointmentIntentAgent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Agente estructurado que convierte lenguaje natural en un payload de cita.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GuardrailEngine&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Evalúa reglas y decide &lt;code&gt;ALLOW&lt;/code&gt;, &lt;code&gt;STEER&lt;/code&gt; o &lt;code&gt;BLOCK&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AppointmentController&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Crea la cita solo después de confirmación y validación.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Admin Controllers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exponen endpoints para reglas, servicios, días bloqueados y logs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MySQL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Guarda citas, reglas, servicios, días bloqueados, conversaciones del SDK y auditoría.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Flujo principal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Usuario escribe una solicitud
        ↓
Frontend envía el mensaje al backend
        ↓
ChatController recibe el mensaje
        ↓
LaravelAiAppointmentAgent invoca AppointmentIntentAgent
        ↓
Laravel AI SDK usa xAI / Grok como provider
        ↓
El agente devuelve un payload estructurado
        ↓
GuardrailEngine evalúa reglas de negocio
        ↓
Si es válido: ALLOW
Si es corregible: STEER
Si no puede continuar: BLOCK
        ↓
El usuario confirma la propuesta
        ↓
Laravel crea la cita en MySQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Flujo de administración
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontadmin
        ↓
Admin Controllers
        ↓
MySQL
        ↓
Reglas, servicios, días bloqueados y logs
        ↓
GuardrailEngine usa esas reglas en runtime
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La parte importante es que las reglas no viven solamente en el prompt del agente. Se pueden cambiar desde el frontadmin y el &lt;code&gt;GuardrailEngine&lt;/code&gt; las usa en tiempo de ejecución.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementación con Laravel AI SDK
&lt;/h2&gt;

&lt;p&gt;Inicialmente tenía una integración directa con la API de xAI / Grok usando HTTP.&lt;/p&gt;

&lt;p&gt;Luego migré la extracción de intención a &lt;strong&gt;Laravel AI SDK&lt;/strong&gt;, para trabajar con un agente más alineado al ecosistema Laravel.&lt;/p&gt;

&lt;p&gt;La documentación oficial indica que el SDK se instala así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;composer require laravel/ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luego se publican archivos de configuración, stubs y migraciones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan vendor:publish &lt;span class="nt"&gt;--provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Laravel&lt;/span&gt;&lt;span class="se"&gt;\A&lt;/span&gt;&lt;span class="s2"&gt;i&lt;/span&gt;&lt;span class="se"&gt;\A&lt;/span&gt;&lt;span class="s2"&gt;iServiceProvider"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y se ejecutan las migraciones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En mi caso, esto creó la tabla usada por el SDK para conversaciones de agentes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agent_conversations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Después generé un agente estructurado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan make:agent AppointmentIntentAgent &lt;span class="nt"&gt;--structured&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El objetivo de este agente es claro: &lt;strong&gt;extraer intención&lt;/strong&gt;, no validar reglas de negocio.&lt;/p&gt;

&lt;p&gt;Ejemplo conceptual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AppointmentIntentAgent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'Quiero una cita mañana a las 8pm para 3 personas'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La respuesta esperada es un payload estructurado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"service"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"consulta_general"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"date"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"2026-05-03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"time"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"20:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"people"&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para mantener el controller limpio, creé un servicio wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LaravelAiAppointmentAgent&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;extractPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AppointmentIntentAgent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;'service'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'service'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s1"&gt;'consulta_general'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'date'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'date'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'time'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'time'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;'people'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'people'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Así el flujo queda más ordenado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatController
        ↓
LaravelAiAppointmentAgent
        ↓
AppointmentIntentAgent
        ↓
Laravel AI SDK
        ↓
xAI / Grok
        ↓
Payload estructurado
        ↓
GuardrailEngine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La IA no decide si la cita es válida. Solo transforma lenguaje natural en datos estructurados.&lt;/p&gt;

&lt;p&gt;La validación queda en el &lt;code&gt;GuardrailEngine&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decisiones del guardrail
&lt;/h2&gt;

&lt;p&gt;Implementé tres decisiones principales:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ALLOW&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;La acción es válida y puede continuar.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;STEER&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;La acción tiene errores corregibles. El sistema ajusta los datos y devuelve una propuesta corregida.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;BLOCK&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;La acción no puede continuar porque falta información o hay una regla estricta.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ejemplo de &lt;code&gt;STEER&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Entrada del usuario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quiero una cita mañana a las 8pm para 3 personas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El agente estructurado con Laravel AI SDK genera:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consulta_general"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"people"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El guardrail evalúa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20:00 está fuera del horario permitido
3 personas supera el máximo permitido
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Respuesta del guardrail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STEER"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Payload was corrected."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"corrections"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"17:30:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Requested time is after business hours."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"people"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Requested people count exceeds maximum allowed."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Respuesta para el usuario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;La hora solicitada no cumple las reglas. Te puedo ofrecer 17:30.
El máximo permitido es 2 persona(s).
¿Deseas confirmar esta propuesta?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ese es el punto clave: &lt;strong&gt;el sistema no bloquea de inmediato, sino que corrige cuando es seguro hacerlo&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ejemplo de &lt;code&gt;BLOCK&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;No todo debe autocorregirse.&lt;/p&gt;

&lt;p&gt;Si el usuario no proporciona fecha, el sistema no debe inventarla si la intención no está clara.&lt;/p&gt;

&lt;p&gt;Payload incompleto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consulta_general"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"people"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Respuesta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BLOCK"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Date is required."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mensaje:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Necesito que me indiques la fecha para revisar disponibilidad.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este punto también es importante: &lt;strong&gt;&lt;code&gt;STEER&lt;/code&gt; no reemplaza &lt;code&gt;BLOCK&lt;/code&gt;&lt;/strong&gt;. Los dos se complementan.&lt;/p&gt;

&lt;p&gt;El artículo original también lo explica así: los bloqueos son útiles para reglas estrictas, mientras que &lt;code&gt;steer&lt;/code&gt; funciona mejor para errores corregibles como ajustar parámetros, redactar información sensible o corregir formatos.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reglas configurables desde el frontadmin
&lt;/h2&gt;

&lt;p&gt;Una parte que quise destacar fue que las reglas no estuvieran dentro del prompt.&lt;/p&gt;

&lt;p&gt;Desde el frontadmin se pueden cambiar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hora inicio
Hora fin
Máximo de personas
Duración de cita
Permitir autocorrección
Servicios activos
Días bloqueados
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por ejemplo, si el admin cambia:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hora fin: 17:00
Máximo personas: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El mismo mensaje:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quiero una cita mañana a las 8pm para 3 personas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ahora se corrige a algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;16:30
1 persona
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto demuestra una idea muy importante: &lt;strong&gt;las reglas del negocio pueden cambiar sin tocar el prompt del agente&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Dónde entra Grok?
&lt;/h2&gt;

&lt;p&gt;Grok entra como proveedor del modelo usado por Laravel AI SDK.&lt;/p&gt;

&lt;p&gt;Antes lo estaba llamando directamente por HTTP. Ahora el flujo pasa por un agente estructurado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Laravel AI SDK
        ↓
AppointmentIntentAgent
        ↓
xAI / Grok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Su trabajo es convertir lenguaje natural en un payload estructurado.&lt;/p&gt;

&lt;p&gt;Usuario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quiero una cita mañana a las 8pm para 3 personas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El agente devuelve:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"consulta_general"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"people"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pero Grok no decide si eso se puede ejecutar.&lt;/p&gt;

&lt;p&gt;Esa responsabilidad queda en el &lt;code&gt;GuardrailEngine&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Laravel AI SDK estructura.
Grok interpreta.
Guardrail controla.
Laravel ejecuta.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto ayuda a separar responsabilidades y evita que el modelo sea quien tenga la última palabra sobre reglas de negocio.&lt;/p&gt;




&lt;h2&gt;
  
  
  ¿Dónde entra OpenSpec?
&lt;/h2&gt;

&lt;p&gt;Usé OpenSpec para definir el cambio antes de implementarlo.&lt;/p&gt;

&lt;p&gt;La demo quedó documentada con:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proposal.md
design.md
tasks.md
specs/ai-guardrails/spec.md
specs/appointment-booking/spec.md
specs/admin-rules/spec.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto me ayudó a mantener trazabilidad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requisito → Diseño → Implementación → Prueba
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por ejemplo, el requisito decía:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;El sistema debe evaluar todas las acciones generadas por la IA antes de llamar al API de citas.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y eso terminó implementado en el flujo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatController
↓
LaravelAiAppointmentAgent
↓
AppointmentIntentAgent
↓
GuardrailEngine
↓
AppointmentController
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Diferencia con Agent Control y Strands
&lt;/h2&gt;

&lt;p&gt;El artículo original trabaja el concepto desde &lt;strong&gt;Strands Agents&lt;/strong&gt; y &lt;strong&gt;Agent Control&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En ese enfoque, el agente puede recibir una instrucción correctiva mediante algo similar a &lt;code&gt;Guide()&lt;/code&gt;. Luego el agente reintenta la acción con los parámetros corregidos.&lt;/p&gt;

&lt;p&gt;En mi demo hice una adaptación más sencilla:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Laravel AI SDK genera un payload estructurado.
GuardrailEngine valida el payload.
Si es corregible, el backend devuelve corrected_payload.
El usuario confirma.
Laravel ejecuta.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Es decir, no hay un reintento automático del LLM guiado por &lt;code&gt;Guide()&lt;/code&gt;. La corrección ocurre de forma determinística en el backend.&lt;/p&gt;

&lt;p&gt;Aun así, el patrón conceptual se mantiene:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IA propone.
Guardrail evalúa.
Sistema corrige o bloquea.
Backend ejecuta solo si corresponde.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Casos probados
&lt;/h2&gt;

&lt;p&gt;Probé estos escenarios:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALLOW  → cita válida
STEER  → hora fuera de horario
STEER  → demasiadas personas
STEER  → día bloqueado
STEER  → slot ocupado con alternativa disponible
BLOCK  → falta fecha
BLOCK  → servicio inválido
BLOCK  → autocorrección desactivada
BLOCK  → slot ocupado sin alternativa disponible
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uno de los más interesantes fue el de slot ocupado.&lt;/p&gt;

&lt;p&gt;En lugar de bloquear inmediatamente, el guardrail busca el siguiente horario disponible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Slot solicitado ocupado
↓
Buscar siguiente slot disponible
↓
Si existe: STEER
↓
Si no existe: BLOCK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso se acerca mucho a la idea del blog original: si el agente puede corregirse, no detengas el flujo.&lt;/p&gt;




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

&lt;p&gt;La principal lección fue esta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;No todo error del agente debe terminar en bloqueo.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hay errores que se pueden corregir de forma segura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hora fuera de horario
Cantidad mayor al máximo
Formato incorrecto
Día bloqueado
Slot ocupado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pero también hay casos donde el sistema debe detenerse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Falta información crítica
Servicio no existe
No hay disponibilidad
Autocorrección desactivada
Regla estricta de negocio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La clave está en clasificar bien las reglas.&lt;/p&gt;

&lt;p&gt;También me gustó mucho trabajar con Laravel AI SDK porque permite encapsular la interacción con el modelo en clases de agentes. Eso hace que el código sea más mantenible y más cercano a cómo normalmente organizamos responsabilidades en Laravel.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Esta demo me ayudó a entender mejor cómo llevar los guardrails más allá del “permitir o bloquear”.&lt;/p&gt;

&lt;p&gt;El patrón final quedó así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Usuario pide algo
↓
Laravel AI SDK invoca el agente
↓
IA interpreta y genera payload
↓
Guardrail evalúa
↓
ALLOW / STEER / BLOCK
↓
Usuario confirma
↓
Backend ejecuta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No es una implementación idéntica a Agent Control, pero sí aplica la misma idea central del artículo original: &lt;strong&gt;usar guardrails para guiar al agente cuando la corrección es segura, y bloquear solo cuando realmente es necesario&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Para mí, este enfoque tiene mucho valor en aplicaciones reales: citas, reservas, soporte, cotizaciones, DevOps, APIs internas y cualquier flujo donde una IA pueda intentar ejecutar acciones.&lt;/p&gt;

&lt;p&gt;Lo más importante es mantener clara la separación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;La IA entiende.
El guardrail decide.
El backend ejecuta.
El admin configura.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Código fuente
&lt;/h2&gt;

&lt;p&gt;El código de la demo está disponible en GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/fmarchena/appointment-agent-demo/tree/main" rel="noopener noreferrer"&gt;https://github.com/fmarchena/appointment-agent-demo/tree/main&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En el repositorio se incluye:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Laravel API
Laravel AI SDK
Vue frontend
Docker Compose
MySQL
GuardrailEngine
Integración con xAI / Grok
OpenSpec con proposal, design, tasks y specs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Referencias
&lt;/h2&gt;

&lt;p&gt;Artículo base:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Guardrails para Agentes de IA que se Autocorrigen en Lugar de Bloquear&lt;/strong&gt; — Elizabeth Fuentes L, AWS Español.&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/aws-espanol/guardrails-para-agentes-de-ia-que-se-autocorrigen-en-lugar-de-bloquear-3n32"&gt;https://dev.to/aws-espanol/guardrails-para-agentes-de-ia-que-se-autocorrigen-en-lugar-de-bloquear-3n32&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Documentación relacionada:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel AI SDK&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://laravel.com/docs/13.x/ai-sdk" rel="noopener noreferrer"&gt;https://laravel.com/docs/13.x/ai-sdk&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Laravel&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://laravel.com/" rel="noopener noreferrer"&gt;https://laravel.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;xAI / Grok API&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://docs.x.ai/" rel="noopener noreferrer"&gt;https://docs.x.ai/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenSpec&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://openspec.dev/" rel="noopener noreferrer"&gt;https://openspec.dev/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>ai</category>
      <category>openspec</category>
      <category>guardrails</category>
    </item>
    <item>
      <title> Automatización de Publicaciones en LinkedIn con Make - Segunda Parte</title>
      <dc:creator>Francisco</dc:creator>
      <pubDate>Sat, 22 Feb 2025 08:20:12 +0000</pubDate>
      <link>https://dev.to/fmarchena/automatizacion-de-publicaciones-en-linkedin-con-make-segunda-parte-5cpd</link>
      <guid>https://dev.to/fmarchena/automatizacion-de-publicaciones-en-linkedin-con-make-segunda-parte-5cpd</guid>
      <description>&lt;p&gt;Dando seguimiento al artículo &lt;a href="https://dev.to/fmarchena/automatizacion-de-publicaciones-en-linkedin-con-make-primera-parte-4jbn"&gt;Automatización de publicaciones en LinkedIn parte 1&lt;/a&gt;, donde exploramos la creación de una cuenta en el servicio Make y la configuración de Google Sheets (GS), en esta segunda parte profundizaremos en la integración con los servicios de &lt;a href="https://www.perplexity.ai/" rel="noopener noreferrer"&gt;Perplexity&lt;/a&gt;, &lt;a href="https://platform.openai.com" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt; y &lt;a href="https://www.linkedin.com" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. Además, compartiré algunas recomendaciones clave para optimizar el proceso.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Módulo de Perplexity&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Utilizaremos el módulo de Perplexity para realizar búsquedas en Internet a partir de la URL extraída del paso anterior.&lt;/p&gt;

&lt;p&gt;Para ello, será necesario &lt;a href="https://www.perplexity.ai/" rel="noopener noreferrer"&gt;crear una cuenta&lt;/a&gt;. Una vez creada, será imprescindible adquirir créditos; el mínimo disponible es de $3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcss8nflu1gy9r8na7rr5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcss8nflu1gy9r8na7rr5.gif" alt="Gif Api" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Después de obtener los créditos, se requiere generar una &lt;a href="https://www.perplexity.ai/settings/api" rel="noopener noreferrer"&gt;clave API&lt;/a&gt; para vincular el servicio con Make. Esto solo será necesario una vez, a menos que deseemos separar los proyectos con diferentes claves API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generar API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc68m176157h9843la971.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc68m176157h9843la971.gif" alt="Generando API" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Colocar la clave API generada&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qxhhalbwnk82w8cpjkt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qxhhalbwnk82w8cpjkt.png" alt="Clave de API Generada" width="444" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota: Esto también será necesario para utilizar OpenAI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;El consumo de tokens dependerá del modelo utilizado. En este caso, estoy empleando &lt;code&gt;llama-3.1-sonar-small-128k-online&lt;/code&gt;. Si deseas consultar los modelos disponibles, puedes revisar el siguiente &lt;a href="https://docs.perplexity.ai/guides/model-cards" rel="noopener noreferrer"&gt;enlace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Para procesar el texto del enlace, se define un límite máximo de tokens a gastar. El prompt utilizado es:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**Necesito que me resumas el siguiente artículo en español: {{1.0}}**&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Donde &lt;code&gt;{{1.0}}&lt;/code&gt; es el parámetro de salida del módulo trigger en el paso anterior. &lt;code&gt;0&lt;/code&gt; representa la columna A de la hoja de GS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Datos de entrada&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnb2hg512ytak5h7b54c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnb2hg512ytak5h7b54c.png" alt="Datos de entrada Perplexity" width="800" height="1049"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hasta el momento, he realizado aproximadamente entre 15 y 20 ejecuciones (~30,000 tokens) con un costo total de solo $0.08. Para más detalles sobre precios, consulta &lt;a href="https://docs.perplexity.ai/guides/pricing" rel="noopener noreferrer"&gt;aquí&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Datos de salida importantes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86u2b0u6q9o2rg3r2nj4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86u2b0u6q9o2rg3r2nj4.png" alt="Datos de salida" width="800" height="1197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Uso de tokens:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt Tokens (49):&lt;/strong&gt; Tokens utilizados en la entrada (prompt enviado).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminación Tokens (759):&lt;/strong&gt; Tokens generados en la respuesta.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total Tokens (808):&lt;/strong&gt; Suma de los tokens utilizados en la entrada y en la respuesta.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contenido:&lt;/strong&gt; Contiene el resumen y traducción del artículo procesado.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Módulo de OpenAI&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Usaremos el módulo de OpenAI para la redacción automatizada de publicaciones.&lt;/p&gt;

&lt;p&gt;Para comenzar, es necesario &lt;a href="https://platform.openai.com/docs/overview" rel="noopener noreferrer"&gt;crear una cuenta&lt;/a&gt; y adquirir créditos. En este caso, la inversión mínima fue de $10, pero el consumo por generación de contenido es bastante bajo (~$0.01 por artículo).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4isppt64hffx9u69kye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4isppt64hffx9u69kye.png" alt="OpenAI" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Luego, se debe generar una &lt;a href="https://platform.openai.com/docs/guides/authentication" rel="noopener noreferrer"&gt;clave API&lt;/a&gt; para conectar el servicio con Make. La primera vez será necesario crear un proyecto y posteriormente generar la clave API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generar API y ubicar el Organization ID&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhwlea0r58h4diqwbz4r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmhwlea0r58h4diqwbz4r.gif" alt="API Key y Organization ID" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Colocar la clave API y Organization ID&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3hwysyj8g5gpihc6dn3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3hwysyj8g5gpihc6dn3.png" alt="Colocar API" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Definir el contenido del prompt en el módulo de OpenAI:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;**Actúa como un gerente de redes sociales y genera una publicación de LinkedIn. La publicación debe involucrar a la audiencia con una introducción atractiva, proporcionar detalles esenciales y fomentar la interacción a través de 'me gusta', comentarios y compartidos. Termina con un llamado a la acción claro y al final coloca la fuente. Incluye hashtags opcionales [#Hashtag1, #Hashtag2]. Aquí hay un resumen del artículo que quiero que readaptes: {{4.choices[].message.content}}**&lt;/code&gt; .&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Nota: El prompt puede ser ajustado según las necesidades específicas del usuario.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Resultado de ejecución:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8x8ueb02fz06kpexoha6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8x8ueb02fz06kpexoha6.png" alt="Resultado de ejecución" width="800" height="1913"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Módulo de LinkedIn&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Para automatizar la publicación en LinkedIn, utilizaremos el módulo correspondiente en Make.&lt;/p&gt;

&lt;p&gt;Pasos a seguir:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkel236pb1nhca1iggl5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhkel236pb1nhca1iggl5.png" alt="Añadir conexión LinkedIn" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Añadir conexión de LinkedIn.&lt;/li&gt;
&lt;li&gt;Seleccionar el tipo de conexión.&lt;/li&gt;
&lt;li&gt;Asignar un nombre a la conexión.&lt;/li&gt;
&lt;li&gt;Guardar y autenticarse en LinkedIn.&lt;/li&gt;
&lt;li&gt;Conceder permisos a Make.&lt;/li&gt;
&lt;li&gt;Configurar el texto de la publicación, seleccionando la salida del módulo OpenAI &lt;code&gt;{{7.choices[].message.content}}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ajustar la visibilidad de la publicación (&lt;strong&gt;pública&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Ejecutar el flujo.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Una vez completado el proceso, la publicación en LinkedIn tendrá el siguiente formato:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz57jy6tq7841kl6826jn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz57jy6tq7841kl6826jn.png" alt="Resultado final" width="576" height="866"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusión&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Esta segunda parte del proceso de automatización de publicaciones en LinkedIn demuestra cómo herramientas como Make pueden simplificar y optimizar la creación de contenido en redes sociales. La plataforma es bastante intuitiva, aunque la integración con algunos servicios, como Google Suite, puede requerir configuraciones adicionales que pueden ser algo complejas debido a la documentación desactualizada.&lt;/p&gt;

&lt;p&gt;Me ha gustado la amplia variedad de servicios que Make ofrece incluso en su plan gratuito, lo que lo convierte en una opción viable para diferentes niveles de automatización. En futuras actualizaciones, planeo mejorar el flujo de trabajo incluyendo la generación de imágenes adjuntas a las publicaciones y explorando la posibilidad de agregar un paso de aprobación antes de la publicación final.&lt;/p&gt;

</description>
      <category>make</category>
    </item>
    <item>
      <title>Automatización de Publicaciones en LinkedIn con Make - Primera Parte</title>
      <dc:creator>Francisco</dc:creator>
      <pubDate>Sun, 19 Jan 2025 15:00:00 +0000</pubDate>
      <link>https://dev.to/fmarchena/automatizacion-de-publicaciones-en-linkedin-con-make-primera-parte-4jbn</link>
      <guid>https://dev.to/fmarchena/automatizacion-de-publicaciones-en-linkedin-con-make-primera-parte-4jbn</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Problema que queremos resolver: Ahorro de tiempo en la gestión de publicaciones en LinkedIn&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Para profesionales, emprendedores y empresas, LinkedIn es una plataforma esencial para compartir contenido, interactuar con su red y fortalecer su presencia en línea. Sin embargo, la gestión manual de publicaciones puede ser un desafío, especialmente cuando se busca mantener consistencia y optimizar el alcance.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;¿El problema principal?&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Falta de tiempo para programar publicaciones manualmente.&lt;/li&gt;
&lt;li&gt;Inconsistencia en el calendario de publicaciones.&lt;/li&gt;
&lt;li&gt;Dificultad para centralizar procesos en una sola herramienta.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aquí es donde &lt;strong&gt;Make&lt;/strong&gt; (anteriormente Integromat) se convierte en una solución poderosa al automatizar la creación y publicación de contenido en LinkedIn.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Make&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Para resolver el problema que queremos abordar, utilizaremos la suscripción gratuita, ya que permite tener hasta 2 escenarios activos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgz15hc7tg51jdy3t9htd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgz15hc7tg51jdy3t9htd.png" alt="Interfaz de Make" width="800" height="490"&gt;&lt;/a&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Imagen tomada en enero 2025&lt;/em&gt; - &lt;a href="https://www.make.com/en/pricing" rel="noopener noreferrer"&gt;https://www.make.com/en/pricing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Componentes base&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Escenario: un escenario es el espacio donde se crea y gestiona un flujo de trabajo automatizado. Es el "proyecto" que contiene todas las conexiones y configuraciones necesarias para resolver un problema o automatizar una tarea.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flujo: el flujo es el conjunto de acciones y procesos que ocurren dentro de un escenario, desde el inicio hasta el final. Es el camino que los datos recorren para completar una tarea.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Módulos: son las "&lt;strong&gt;piezas&lt;/strong&gt;" que componen un flujo dentro del escenario. Cada módulo realiza una acción específica, como obtener datos, procesarlos, aplicar condiciones o enviarlos a otro servicio.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Módulo Trigger (Disparador)&lt;/em&gt;: el módulo inicial que activa el flujo. Ejemplo: "Watch New Rows" en Google Sheets.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Módulo de Procesamiento&lt;/em&gt;: Transforman o filtran los datos a medida que avanzan. Ejemplo: Formatear texto o aplicar cálculos.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;Módulo de Acción (Salida)&lt;/em&gt;: Envía datos al destino final. Ejemplo: Publicar en LinkedIn.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conexiones: son los vínculos entre los módulos y los servicios externos. Representan la autorización o autenticación necesaria para que Make pueda interactuar con servicios como Google Drive, LinkedIn o OpenAI.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Requerimientos principales&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Como requerimientos principales, necesitaremos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Una cuenta de &lt;strong&gt;Gmail&lt;/strong&gt; para utilizar Google Drive y Google Sheets.&lt;/li&gt;
&lt;li&gt;Una cuenta de &lt;strong&gt;LinkedIn&lt;/strong&gt; para registrar los posts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perplexity&lt;/strong&gt; y &lt;strong&gt;OpenAI (Token)&lt;/strong&gt; para generar el contenido final del post.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nota&lt;/strong&gt;:&lt;br&gt;
Make ofrece variedad de servicios  a los cuales podemos utilizar  los requerimientos anteriores no son las únicas opciones disponibles pero para efectos de esta serie de  post estaremos utilizando esos. &lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Pasos&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1. Crear un escenario nuevo&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Make trabaja principalmente con escenarios, donde se crea el flujo. En estos escenarios es posible agrupar o guardar diferentes flujos dentro de carpetas.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;2. Configurar módulos&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Los escenarios se construyen con 2 o más módulos, que representan los servicios disponibles en Make.  &lt;/p&gt;

&lt;p&gt;El punto inicial, conocido como &lt;strong&gt;módulo disparador&lt;/strong&gt; (&lt;em&gt;trigger&lt;/em&gt;), tiene funciones especiales para iniciar el flujo. Para este proyecto, utilizaremos el servicio &lt;strong&gt;Google Sheets (GS)&lt;/strong&gt;, que contiene una hoja con los enlaces que serán procesados por el escenario.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Módulo Trigger: Google Sheets&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;El módulo disparador (&lt;em&gt;trigger&lt;/em&gt;) se activa cuando se añade un nuevo registro en el archivo que deseamos observar. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configuración inicial:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Vincula o selecciona una cuenta de Google que tenga el archivo a utilizar.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpullb4pwqpsun98lx3z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgpullb4pwqpsun98lx3z.png" alt="Añadir conexión" width="719" height="284"&gt;&lt;/a&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Selecciona el método de búsqueda del archivo:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Buscar por ruta:&lt;/strong&gt; Navega hasta el archivo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buscar en todo:&lt;/strong&gt; Busca en todas las carpetas del Drive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Colocar manualmente:&lt;/strong&gt; Proporciona la URL generada por Google Sheets.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3epr55pq6sv1q55dmdfv.png" alt="Método de búsqueda" width="352" height="144"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Selecciona el tipo de Drive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mi Drive.&lt;/li&gt;
&lt;li&gt;Compartidos conmigo.&lt;/li&gt;
&lt;li&gt;Google Shared Drive (disponible en planes Business y Enterprise).
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ufax60pk3j5erxa223s.png" alt="Tipo de Drive" width="800" height="342"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Configuración final:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Selecciona el archivo mediante la navegación (en este caso, “Buscar por ruta”).&lt;/li&gt;
&lt;li&gt;Indica el nombre de la hoja donde se encuentran los datos a procesar.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configura los detalles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Si la tabla tiene cabecera.&lt;/li&gt;
&lt;li&gt;Desde qué columna inicia y termina la cabecera.&lt;/li&gt;
&lt;li&gt;El límite de registros que se analizarán en cada ejecución.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi8gdqn6huf2xd8c0hidt.png" alt="Configuración del archivo" width="350" height="382"&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ejecuta el flujo manualmente la primera vez para obtener los parámetros de salida necesarios para los siguientes módulos.&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhk9ekop3e3u721lnypgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhk9ekop3e3u721lnypgf.png" alt="Ejecutar manualmente" width="800" height="874"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;De la salida del módulo, lo más relevante será el enlace obtenido:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy7nbaehhlo2ysigeuoa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy7nbaehhlo2ysigeuoa.png" alt="Salida del módulo" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Configuraciones adicionales&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;El escenario puede programarse para ejecutarse de manera automática en diferentes horarios. Esto se realiza configurando el ícono del reloj en el módulo disparador.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Opciones de programación:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A intervalos regulares (cada X minutos).&lt;/li&gt;
&lt;li&gt;Una vez (por ejemplo: &lt;strong&gt;'18/01/2025 23:47'&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;Todos los días (a una hora específica).&lt;/li&gt;
&lt;li&gt;Días de la semana (puedes elegir uno o más días y horas específicas).&lt;/li&gt;
&lt;li&gt;Días del mes.&lt;/li&gt;
&lt;li&gt;En fechas específicas.&lt;/li&gt;
&lt;li&gt;A demanda (útil para pruebas).
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkmabd6fo9mh0g6lzc3af.png" alt="Configuración de programación" width="800" height="613"&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Próximos pasos&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;En publicaciones futuras, profundizare  en las configuración de los siguientes elementos clave para completar el flujo de automatización:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Perplexity:&lt;/strong&gt; Cómo configurar este servicio para generar ideas o información relevante que pueda incluirse en los posts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI:&lt;/strong&gt; Detalles sobre la generación de contenido con un token personalizado y cómo integrarlo en Make.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn:&lt;/strong&gt; La configuración específica para publicar automáticamente el contenido generado en tu perfil .&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*&lt;em&gt;¡No te pierdas estas guías para completar la automatización y maximizar tu presencia en LinkedIn!&lt;br&gt;
*&lt;/em&gt;&lt;/p&gt;

</description>
      <category>make</category>
      <category>ai</category>
      <category>google</category>
      <category>automation</category>
    </item>
    <item>
      <title>Taller Kinesis Data Firehose y S3</title>
      <dc:creator>Francisco</dc:creator>
      <pubDate>Sun, 19 Feb 2023 04:36:58 +0000</pubDate>
      <link>https://dev.to/fmarchena/taller-kinesis-data-firehose-y-s3-3cbp</link>
      <guid>https://dev.to/fmarchena/taller-kinesis-data-firehose-y-s3-3cbp</guid>
      <description>&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fylxh4huhi7r5b8wee7tv.png" alt="Arquitectura del  Taller de Python + Kinesis Firehose + S3" width="572" height="301"&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt; Arquitectura del  Taller de Python + Kinesis Firehose + S3 &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;En este taller, aprenderemos cómo procesar archivos CSV con AWS utilizando Kinesis Data Firehose y S3. Además, utilizaremos Python como fuente de datos desde una maquina externa para enviar los datos al flujo de Firehose.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Requisitos previos:&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Una cuenta de AWS con acceso a Kinesis Data Firehose y S3.&lt;/li&gt;
&lt;li&gt;Python 3.x instalado en local.&lt;/li&gt;
&lt;li&gt;Tener instalada la liberias boto3 y pandas&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Paso 1: Configurar el bucket de S3&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lo primero que debemos hacer es crear un bucket de S3 donde almacenaremos nuestros datos. Para hacer esto, vamos a la consola de AWS y seleccionamos S3. Luego, hacemos clic en el botón "Crear bucket" y seguimos las instrucciones para nombrar el bucket y establecer las opciones de configuración.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyu7d5k25q1lb0g372ydi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyu7d5k25q1lb0g372ydi.png" alt="Crear Bucket s3" width="442" height="302"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paso 2: Configurar el flujo de Kinesis Data Firehose&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A continuación, creamos un flujo de Kinesis Data Firehose que recibirá los datos de nuestra fuente de datos y los almacenará en nuestro bucket de S3. Para hacer esto, vamos a la consola de AWS y seleccionamos Kinesis Data Firehose. Luego, hacemos clic en "Crear flujo de Firehose" y seguimos las instrucciones para nombrar nuestro flujo y establecer las opciones de configuración.&lt;/p&gt;

&lt;p&gt;Para la opciones de configuración:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Seleccionaremos en origen como Direct PUT&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Destino S3&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Nombre de la secuencia de entrada cualquier nombre para el ejemplo colocare taller1-csv. El nombre que se coloque se convertira en el identificacion del servicio el cual recibira la trama que enviemos mas adelante. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prefijo de bucket de S3&lt;br&gt;
data/input/firehose/movie/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prefijo de salida de error de bucket S3&lt;br&gt;
data/input/firehose/error&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Los prefijos crear las carpetas necesarias de no existir al momento de recibir la trama o caer en error.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Tamaño del búfer 1 MiB (datos)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Intervalo de almacenamiento en el búfer 60 segundos&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Para las configuraciones del búfer para que la trama sea procesada debe cumplir cualquiera de las dos opciones el tamaño o el intervalo&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdif71cqydsb37oqr07s0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdif71cqydsb37oqr07s0.png" alt="Imagen de configuración" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fleqkowzkr7sjlhjqu1ws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fleqkowzkr7sjlhjqu1ws.png" alt=" " width="800" height="1085"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paso 3: Configurar la fuente de datos&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Antes de configurar la fuente de datos requerimosPara poder conectarnos a servicio de AWS desde una maquina externa necesitaremos  crear una clave de acceso para esto se debe realizar los siguientes pasos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Haz clic en tu nombre de usuario en la esquina superior derecha de la página y selecciona "My Security Credentials" (Mis credenciales de seguridad).&lt;br&gt;
Si se te solicita, ingresa tu nombre de usuario y contraseña de AWS nuevamente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;En la pestaña "Access keys" (Claves de acceso), haz clic en "Create New Access Key" (Crear nueva clave de acceso).&lt;br&gt;
Haz clic en el botón "Download Key File" (Descargar archivo de clave) para descargar la clave de acceso en un archivo .csv. También puedes copiar y pegar la clave de acceso y la clave secreta en un lugar seguro.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Asegúrate de guardar el archivo de la clave de acceso de manera segura, ya que no se mostrará nuevamente. Si pierdes la clave de acceso, deberás crear una nueva.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0cl1yerw2jwiewubiev0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0cl1yerw2jwiewubiev0.png" alt="Acceso a Clave" width="262" height="285"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Una vez que hemos creada la clave de acceso y nuestro flujo de Kinesis Data Firehose, debemos configurar la fuente de datos para que sepa de dónde tomar los datos. En este caso, utilizaremos Python como fuente de datos. Para hacer esto, escribiremos un script Python que lea los datos del archivo CSV y los envíe al flujo de Kinesis Data Firehose utilizando la biblioteca Boto3 de AWS para interactuar con los servicios de AWS desde nuestro código Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
import time
import json
import pandas as pd

AWS_ACCESS_KEY = #credenciales
AWS_SECRET_KEY = #credenciales
REGION_NAME = #Region

DeliveryStreamName = 'taller1-csv' #paso 2
firehose = boto3.client('firehose',aws_access_key_id=AWS_ACCESS_KEY,
    aws_secret_access_key=AWS_SECRET_KEY,
    region_name=REGION_NAME
)


record = {}
bad_lines = []
column_names = ["MovieID", "YearOfRelease", "Title" ]
df = pd.read_csv("movie_titles.csv",  encoding = "ISO-8859-1" , names=column_names , error_bad_lines=False)
for index, row in df.iterrows():
 record = {'MovieID':row[0],
    'YearOfRelease':row[1],
    'Title':row[2] 
    }
 response = firehose.put_record(
        DeliveryStreamName = DeliveryStreamName,
        Record = {
            'Data': json.dumps(record)
        }
    )      
print('Dato de movie enviado a Kinesis Data Firehose : \n' + str(record))
time.sleep(.5)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Paso 4: Enviar los datos al flujo de Kinesis Data Firehose&lt;/strong&gt;&lt;br&gt;
Ahora, podemos ejecutar nuestro script Python y enviar los datos al flujo de Kinesis Data Firehose. Los datos serán transformados y almacenados en nuestro bucket de S3 según las configuraciones establecidas en los pasos anteriores.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paso 5: Verificar los datos almacenados en S3&lt;/strong&gt;&lt;br&gt;
Finalmente, podemos verificar que nuestros datos se han almacenado correctamente en nuestro bucket de S3. Podemos acceder a nuestro bucket desde la consola de AWS y verificar que el archivo CSV se ha transformado en el formato deseado.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv207gcn6u9g4n079tykf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv207gcn6u9g4n079tykf.png" alt="Resultado" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusión&lt;/strong&gt;&lt;br&gt;
En este taller, hemos aprendido cómo procesar archivos CSV utilizando AWS con Kinesis Data Firehose y S3. Además, hemos utilizado Python como fuente de datos para enviar los datos al flujo de Firehose. AWS ofrece una amplia gama de servicios y herramientas para procesar y analizar datos, lo que hace que sea fácil y escalable trabajar con grandes volúmenes de datos en la nube.&lt;/p&gt;

&lt;p&gt;** Despligue con Cloudformation**&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Description: Stack Lab Firehose
Parameters:
  NombreBucket:
    Description: bucketS3
    Type: String
    Default: aws-firehose
Resources:
  deliverystream:
    DependsOn:
      - deliveryPolicy
    Type: AWS::KinesisFirehose::DeliveryStream
    Properties:
      DeliveryStreamName: taller1-csv
      ExtendedS3DestinationConfiguration:
        BucketARN: !Join 
          - ''
          - - 'arn:aws:s3:::'
            - !Ref s3bucket
        BufferingHints:
          IntervalInSeconds: '60'
          SizeInMBs: '1'
        CompressionFormat: UNCOMPRESSED
        Prefix: data/input/firehose/movie/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/hour=!{timestamp:HH}/
        ErrorOutputPrefix: data/input/firehose/error
        RoleARN: !GetAtt deliveryRole.Arn
  s3bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Join [ -, [ !Ref NombreBucket, !Ref AWS::AccountId , 'movie' ] ]
  deliveryRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: ''
            Effect: Allow
            Principal:
              Service: firehose.amazonaws.com
            Action: 'sts:AssumeRole'
            Condition:
              StringEquals:
                'sts:ExternalId': !Ref 'AWS::AccountId'
  deliveryPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: firehose_delivery_policy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action:
              - 's3:AbortMultipartUpload'
              - 's3:GetBucketLocation'
              - 's3:GetObject'
              - 's3:ListBucket'
              - 's3:ListBucketMultipartUploads'
              - 's3:PutObject'
            Resource:
              - !Join 
                - ''
                - - 'arn:aws:s3:::'
                  - !Ref s3bucket
              - !Join 
                - ''
                - - 'arn:aws:s3:::'
                  - !Ref s3bucket
                  - '*'
      Roles:
        - !Ref deliveryRole
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
