{"id":48,"date":"2025-11-09T18:24:26","date_gmt":"2025-11-09T18:24:26","guid":{"rendered":"https:\/\/pulki.es\/blog\/?p=48"},"modified":"2025-11-09T18:24:26","modified_gmt":"2025-11-09T18:24:26","slug":"desarrollando-un-asistente-virtual-con-whatsapp-y-openai-parte-3","status":"publish","type":"post","link":"https:\/\/pulki.es\/blog\/index.php\/2025\/11\/09\/desarrollando-un-asistente-virtual-con-whatsapp-y-openai-parte-3\/","title":{"rendered":"Desarrollando un asistente virtual con WhatsApp y OpenAI (Parte 3)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">En esta publicaci\u00f3n veremos c\u00f3mo responder de forma sistem\u00e1tica a mensajes de WhatsApp usando la API de OpenAI. En la <a href=\"https:\/\/pulki.es\/blog\/index.php\/2025\/11\/02\/desarrollando-un-asistente-virtual-con-whatsapp-y-openai-parte-2\/\">entrega anterior<\/a> aprendimos a recibir y enviar mensajes con WhatsApp. Ahora ajustaremos ligeramente ese c\u00f3digo para incorporar respuestas de OpenAI, con y sin contexto. Por simplicidad, comenzaremos por la variante sin contexto.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Enviando mensajes sin contexto&#8230;<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En la Parte 2 de esta serie, expusimos el siguiente bloque de c\u00f3digo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>foreach ($payload&#91;'entry'] as $entry) {\nforeach ($entry&#91;'changes'] ?? &#91;] as $change) {\nif (($change&#91;'field'] ?? '') !== 'messages') continue;\n$v = $change&#91;'value'] ?? &#91;];\nforeach ($v&#91;'messages'] ?? &#91;] as $msg) {\n  $mid  = $msg&#91;'id']   ?? null;   \/\/ message id (wamid-...)\n  $from = $msg&#91;'from'] ?? null;   \/\/ wa_id del cliente (E.164 sin '+')\n  $type = $msg&#91;'type'] ?? 'text';\n \/\/ mensaje del cliente\n  $text = ($type === 'text') ? (string)($msg&#91;'text']&#91;'body'] ?? '') : '';\n     }\n   }\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Como explicamos en dicha publicaci\u00f3n, el mensaje del usuario estar\u00e1 almacenado en la variable $text. Puesto que el <em>payload<\/em> que env\u00eda Meta puede contener m\u00faltiples mensajes, es conveniente insertar nuestra respuesta dentro de los ciclos. Por este motivo, incluiremos las siguientes l\u00edneas justo bajo la variable $text:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$payload = &#91;\n    'model'             => 'gpt-5-mini',\n    'text'              => $text,\n    'reasoning'         => &#91;'effort' => 'low'],\n    'instructions'      => $instructions,\n    'max_output_tokens' => 1000,\n];<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">En este payload, que es el que enviaremos a OpenAI, incluimos el texto que el usuario nos envi\u00f3. Adem\u00e1s, explicitamos la clave &#8216;instructions&#8217;, que contiene una variable tipo <em>string<\/em> que define c\u00f3mo se comportar\u00e1 nuestro Asistente con el cliente. En la pr\u00e1ctica, hay libertad absoluta para elegir este texto. No obstante, para efectos de esta publicaci\u00f3n, ser\u00e1: $instructions = \u00abTen una conversaci\u00f3n amable y amena con el usuario.\u00bb Luego, enviamos el siguiente <em>request<\/em> a <em>responses<\/em> de OpenAI:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$ch = curl_init('https:\/\/api.openai.com\/v1\/responses');\ncurl_setopt_array($ch, &#91;\n    CURLOPT_HTTPHEADER     =&gt; &#91;\n        'Content-Type: application\/json',\n        'Authorization: Bearer ' . $openaiApiKey,\n    ],\n    CURLOPT_POST           =&gt; true,\n    CURLOPT_POSTFIELDS     =&gt; json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),\n    CURLOPT_RETURNTRANSFER =&gt; true,\n    CURLOPT_TIMEOUT        =&gt; 120,\n]);\n$res = curl_exec($ch);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Donde $openaiApiKey es un string que contiene la API key de OpenAI y $res es el resultado de nuestra consulta. En dicha variable, el texto generado por OpenAI puede ser obtenido accediendo a la siguiente ruta:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$texto = $res&#91;'output']&#91;0]&#91;'content']&#91;0]&#91;'text'];<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Finalmente, utilizando la funci\u00f3n callWhatsApp de la <a href=\"https:\/\/pulki.es\/blog\/index.php\/2025\/11\/02\/desarrollando-un-asistente-virtual-con-whatsapp-y-openai-parte-2\/\">Parte 2<\/a>, llamamos la API de Meta con el texto extra\u00eddo para devolverlo al usuario.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong><em>Enviando mensajes con contexto&#8230;<\/em><\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si queremos a\u00f1adir contexto, tenemos dos opciones: la primera es utilizar <em><a href=\"https:\/\/platform.openai.com\/docs\/api-reference\/conversations\/create\">conversations<\/a><\/em> de OpenAI y la segunda es enviar el chat completo a la API antes de generar una respuesta nueva. En esta entrada expondremos la primera alternativa, debido a que la segunda es trivial. Dicho esto, primero debemos crear una conversaci\u00f3n:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    $ch = curl_init('https:\/\/api.openai.com\/v1\/conversations');\n    curl_setopt_array($ch, &#91;\n        CURLOPT_HTTPHEADER     => &#91;\n            'Content-Type: application\/json',\n            'Authorization: Bearer ' . $openaiApiKey,\n        ],\n        CURLOPT_POST           => true,\n        CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),\n        CURLOPT_RETURNTRANSFER => true,\n        CURLOPT_TIMEOUT        => 60,\n    ]);\n    $res  = curl_exec($ch);\n    $http = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);\n\n    curl_close($ch);\n\n    if ($res &amp;&amp; $http >= 200 &amp;&amp; $http &lt; 300) {\n        $j = json_decode($res, true);\n        if (is_array($j) &amp;&amp; !empty($j&#91;'id'])) {\n            $convId = (string)$j&#91;'id'];\n        }\n    }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Este bloque debe ejecutarse en un archivo separado \u2014no en <em>webhook.php<\/em>\u2014. Si lo dej\u00e1ramos en el webhook, cada llamada de Meta crear\u00eda una conversaci\u00f3n nueva y, por tanto, el chatbot no tendr\u00eda memoria. Por ello, crea la conversaci\u00f3n en un script independiente, toma el identificador $convid y gu\u00e1rdalo en <em>webhook.php<\/em>. Finalmente, incluye el conversation_id en el <em>payload<\/em> que enviar\u00e1s a OpenAI:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$payload = &#91;\n    'model'             => 'gpt-5-mini',\n    'conversation'      => $convId,\n    'text'              => $text,\n    'reasoning'         => &#91;'effort' => 'low'],\n    'instructions'      => $instructions,\n    'max_output_tokens' => 1000,\n];<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Luego, debemos llevar a cabo el mismo <em>request<\/em> que en el apartado anterior; as\u00ed obtendremos una respuesta con memoria.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Jos\u00e9 Miguel Mu\u00f1oz Urra \u2013\u00a0<a href=\"mailto:jmunozu@pulki.es\">jmunozu@pulki.es<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>En esta publicaci\u00f3n veremos c\u00f3mo responder de forma sistem\u00e1tica a mensajes de WhatsApp usando la API de OpenAI. En la entrega anterior aprendimos a recibir y enviar mensajes con WhatsApp. Ahora ajustaremos ligeramente ese c\u00f3digo para incorporar respuestas de OpenAI, con y sin contexto. Por simplicidad, comenzaremos por la variante sin contexto. Enviando mensajes sin [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-48","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/posts\/48","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/comments?post=48"}],"version-history":[{"count":1,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/posts\/48\/revisions"}],"predecessor-version":[{"id":49,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/posts\/48\/revisions\/49"}],"wp:attachment":[{"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/media?parent=48"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/categories?post=48"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pulki.es\/blog\/index.php\/wp-json\/wp\/v2\/tags?post=48"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}