{"id":211309,"date":"2026-09-25T10:24:13","date_gmt":"2026-09-25T10:24:13","guid":{"rendered":"https:\/\/sinch.com\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/"},"modified":"2026-09-25T10:24:13","modified_gmt":"2026-09-25T10:24:13","slug":"como-cree-stack-comunicaciones-cursor-sinch-plugin","status":"publish","type":"blog","link":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/","title":{"rendered":"C\u00f3mo cre\u00e9 un stack de comunicaciones en Cursor con el plugin de Sinch"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Una integraci\u00f3n de comunicaciones rara vez es lenta por el env\u00edo en s\u00ed. El tiempo se invierte en los formatos de la carga \u00fatil, en un\u00a0esquema de autenticaci\u00f3n diferente para cada producto y en la distancia entre lo que funciona en el entorno de pruebas y\u00a0lo que funciona en el tel\u00e9fono que tienes en la mano.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">He creado suficientes integraciones de este tipo con las API de Sinch como para saber en qu\u00e9 se invierte ese tiempo, y eso es lo que hace que el\u00a0<a href=\"https:\/\/cursor.com\/marketplace\/sinch\/sinch-cursor-plugin\" target=\"_blank\" rel=\"noreferrer noopener\">plugin de Sinch para Cursor<\/a> merece la pena probarlo\u00a0como es debido: puedo saber si el c\u00f3digo que devuelve un agente es correcto. As\u00ed que me tom\u00e9 una tarde\u00a0y un banco de prueba sobre el que desarrollar.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">RockBank es ese banco de prueba: un prestamista ficticio cuyos clientes ignoran los recordatorios de pago por correo directo.\u00a0El objetivo era trasladar los recordatorios a los canales que la gente realmente lee y mantener toda la\u00a0mensajer\u00eda detr\u00e1s de una \u00fanica ruta de env\u00edo y un \u00fanico punto de conexi\u00f3n de webhooks, de modo que a\u00f1adir un canal sea un cambio de configuraci\u00f3n\u00a0en lugar de una reescritura completa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lo que hace el flujo<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Un recordatorio de pago por SMS enviado a trav\u00e9s de la API de conversaci\u00f3n de Sinch<\/li>\n\n\n\n<li>El mismo recordatorio actualizado a RCS, con un remitente verificado y un bot\u00f3n de \u201cPagar ahora\u201d en el hilo<\/li>\n\n\n\n<li>Un recordatorio de voz automatizado si el pago sigue pendiente<\/li>\n\n\n\n<li>Verificaci\u00f3n de identidad con un c\u00f3digo de acceso de un solo uso antes de que se procese el pago<\/li>\n\n\n\n<li>Un email de confirmaci\u00f3n a trav\u00e9s de Mailgun una vez que se ha completado el pago<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Eso es mensajer\u00eda, voz, verificaci\u00f3n y email detr\u00e1s de una ruta de env\u00edo y un webhook entrante.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lo que requer\u00eda el desarrollo<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El c\u00f3digo de RockBank no est\u00e1 publicado, as\u00ed que no se trata de clonar y ejecutar. A continuaci\u00f3n se muestra la configuraci\u00f3n de la cuenta\u00a0que asumen los fragmentos de c\u00f3digo, y la misma configuraci\u00f3n que necesitar\u00edas para desarrollar tu propia versi\u00f3n:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Cursor con el plugin de Sinch instalado y un n\u00famero de tel\u00e9fono de prueba que te pertenezca<\/li>\n\n\n\n<li>Una aplicaci\u00f3n de la API de conversaci\u00f3n: ID de proyecto, ID de clave, secreto de clave e ID de aplicaci\u00f3n<\/li>\n\n\n\n<li>Credenciales separadas por producto: una clave y un secreto de aplicaci\u00f3n de voz con un n\u00famero asignado,\u00a0y una clave y un secreto de aplicaci\u00f3n de verificaci\u00f3n, que a su vez es una aplicaci\u00f3n independiente<\/li>\n\n\n\n<li>Un agente RCS aprobado para tu mercado, con SMS configurado en la misma aplicaci\u00f3n como alternativa. La aprobaci\u00f3n del operador\u00a0lleva tiempo, as\u00ed que empieza pronto. Un remitente de SMS no es un sustituto: sin un agente aprobado,\u00a0cada recordatorio llega como texto sin formato.<\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/10dlc-registration\/\" target=\"_blank\" rel=\"noreferrer noopener\">Registro de 10DLC<\/a> en marcha si env\u00edas a\u00a0n\u00fameros de EE. UU. Tienes m\u00e1s informaci\u00f3n al respecto en la secci\u00f3n Posibles obst\u00e1culos.<\/li>\n<\/ul>\n\n\n\r\n    <nav data-content-type=\"longform\" class=\"toc-block longform-spacings px-5 py-6 px-md-6 px-lg-7 py-md-7 bg-light fs-sm rounded-lg\" aria-labelledby=\"toc-title-2258\"><p class=\"h5 m-0\" id=\"toc-title-2258\">\u00cdndice<\/p><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">01<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#lo-que-hace-el-flujo\">Lo que hace el flujo<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">02<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#lo-que-requeria-el-desarrollo\">Lo que requer\u00eda el desarrollo<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">03<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#configurar-el-plugin\">Configurar el plugin<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">04<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#crear-la-ruta-de-envio-en-la-api-de-conversacion\">Crear la ruta de env\u00edo en la API de conversaci\u00f3n<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">05<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#realizar-pruebas-desde-el-editor\">Realizar pruebas desde el editor<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">06<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#conectar-el-webhook-entrante\">Conectar el webhook entrante<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">07<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#anadir-rcs\">A\u00f1adir RCS<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">08<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#voz-verificacion-y-email\">Voz, verificaci\u00f3n y email<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">09<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#posibles-obstaculos\">Posibles obst\u00e1culos<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">10<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#el-resultado-final\">El resultado final<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">11<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#pruebalo\">Pru\u00e9balo<\/a><\/div><\/div><div class=\"d-flex mt-3\"><div class=\"w-auto fw-bold text-accent d-flex me-2\">12<\/div><div class=\"d-flex flex-column\"><a class=\"fw-bold scrollme link-body-color text-accent\" href=\"#recursos-adicionales\">Recursos adicionales<\/a><\/div><\/div><\/nav>\n\n\n<h2 class=\"wp-block-heading\">Configurar el plugin<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Inst\u00e1lalo desde el marketplace de Cursor o desde la paleta de comandos:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-light\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p><!-- \/wp:paragraph --><\/p><p><!-- \/wp:paragraph --><\/p><p class=\"mb-0\">\/add-plugin sinch-cursor-plugin<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">El plugin tiene tres partes, y cada una realiza un trabajo diferente:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Las <strong>Skills<\/strong> cargan el conocimiento de la API de Sinch en el contexto del agente antes de que escriba el c\u00f3digo: puntos de conexi\u00f3n, esquemas de autenticaci\u00f3n, formas de la carga \u00fatil y propiedades del canal. Esta es la parte que decide si el c\u00f3digo generado se compila frente a la API real o frente a una invenci\u00f3n que parece plausible.<\/li>\n\n\n\n<li>Los <strong>comandos<\/strong> (como <code>\/send-message<\/code> y <code>\/list-webhooks<\/code>) llaman a las API de Sinch directamente desde el chat, sin c\u00f3digo de por medio.<\/li>\n\n\n\n<li><strong>Los servidores MCP<\/strong>, concretamente dos. <code>sinch<\/code> ejecuta <code>npx -y @sinch\/mcp<\/code> localmente y conecta al agente a tu cuenta activa para que pueda enviar mensajes e inspeccionar la configuraci\u00f3n mientras trabajas. <code>sinch-docs<\/code> es remoto, en <code>developers.sinch.com\/mcp<\/code>, para buscar en la documentaci\u00f3n. Solo el primero acepta credenciales.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Las credenciales son la \u00fanica parte de la configuraci\u00f3n en la que vale la pena ser precisos. Hay cinco variables que van en un bloque <code>env<\/code> en <code>~\/.cursor\/settings.json<\/code>, y hay que reiniciar Cursor para que se apliquen:<\/p>\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar al portapapeles\" data-clipboard-success-label=\"\u00a1Copiado!\" data-clipboard-error-label=\"No se ha podido copiar\" data-tooltip=\"Copiar al portapapeles\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar al portapapeles\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    {\r\n  &quot;env&quot;: {\r\n    &quot;CONVERSATION_PROJECT_ID&quot;: &quot;your-project-id&quot;,\r\n    &quot;CONVERSATION_KEY_ID&quot;: &quot;your-key-id&quot;,\r\n    &quot;CONVERSATION_KEY_SECRET&quot;: &quot;your-key-secret&quot;,\r\n    &quot;CONVERSATION_REGION&quot;: &quot;us&quot;,\r\n    &quot;CONVERSATION_APP_ID&quot;: &quot;your-app-id&quot;\r\n  }\r\n}\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Si el servidor sigue indicando que faltan credenciales, exporta las mismas cinco variables en el\u00a0perfil de tu shell y sal de la aplicaci\u00f3n de manera definitiva para volver a iniciarla: se ejecuta como <code>npx -y @sinch\/mcp<\/code> y hereda el entorno\u00a0del shell desde el que se inici\u00f3 Cursor.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Estas cinco variables son del servidor MCP, y cubren la API de conversaci\u00f3n, por lo que la clave y el secreto de voz\u00a0nunca entran en Cursor. Las llamadas de voz solo se realizan cuando se ejecuta tu propio c\u00f3digo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Confirma la conexi\u00f3n antes de escribir nada:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-light\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">\/send-message &#8211;to=+15551234567 &#8211;message=\u00bbRockBank test\u00bb<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">El mismo comando admite una cadena alternativa, que sirve como una previsualizaci\u00f3n muy \u00fatil de lo que har\u00e1 la ruta de env\u00edo\u00a0m\u00e1s adelante:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-light\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">\/send-message &#8211;to=+15551234567 &#8211;message=\u00bbRockBank test\u00bb &#8211;fallback=RCS,SMS<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<h2 class=\"wp-block-heading\">Crear la ruta de env\u00edo en la API de conversaci\u00f3n<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La forma de la ruta de env\u00edo es la decisi\u00f3n de la que depende todo lo dem\u00e1s. La API de conversaci\u00f3n toma\u00a0un cuerpo de mensaje gen\u00e9rico y lo transcodifica por canal, y trata la selecci\u00f3n de canales y la alternativa\u00a0como datos en la solicitud en lugar de ramificaciones en tu c\u00f3digo. Para RockBank ese es todo el dise\u00f1o:\u00a0el recordatorio se env\u00eda por RCS con SMS como respaldo, la voz se une m\u00e1s tarde y el sitio de la llamada nunca se entera\u00a0de nada de esto.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Merece la pena poner esto en el prompt en lugar de dejar que se deduzca. P\u00eddele a un agente que \u201cenv\u00ede un mensaje de texto a la clientela cuando venza un pago\u201d y lo normal es que obtengas una ruta de env\u00edo adaptada a un \u00fanico canal,\u00a0porque eso es lo que has pedido. En su lugar, indica la intenci\u00f3n y d\u00e9jale el mecanismo a la skill:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Implementa la ruta de env\u00edo de recordatorios mediante la API de conversaci\u00f3n de Sinch. Env\u00eda RCS con SMS como alternativa, y mant\u00e9n la interfaz estable para que a\u00f1adir un canal m\u00e1s adelante no cambie los sitios de llamada. Adem\u00e1s, gestiona los recibos de entrega y reg\u00edstralos.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Sin nombres de campos, sin puntos de conexi\u00f3n ni esquemas de autenticaci\u00f3n. La skill <code>conversation-api<\/code> ya explica c\u00f3mo funciona la alternativa:\u00a0a\u00f1ade una matriz <code>channel_priority_order<\/code> y haz una lista con cada identidad de canal en el destinatario. Esa\u00a0divisi\u00f3n es la que debes buscar en tus propios prompts. Describes el comportamiento que quieres y las\u00a0restricciones que tiene que cumplir, y la skill se encarga de la carga \u00fatil.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Esa \u00faltima frase hace un trabajo real. Sin ella, se obtiene una ruta de env\u00edo sin observabilidad y descubres\u00a0c\u00f3mo va la primera vez que un cliente dice que el recordatorio nunca lleg\u00f3. Con ella, el agente tiene\u00a0que tener en cuenta qu\u00e9 ocurre despu\u00e9s de que <code>messages:send<\/code> devuelva un 200, que es donde residen los fallos\u00a0m\u00e1s interesantes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Lo que devolvi\u00f3 es un m\u00e9todo privado que toma cualquier cuerpo de mensaje de la API de conversaci\u00f3n, con la alternativa\u00a0expresada como <code>channel_priority_order<\/code> y ambas identidades de canal en el destinatario:<\/p>\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar al portapapeles\" data-clipboard-success-label=\"\u00a1Copiado!\" data-clipboard-error-label=\"No se ha podido copiar\" data-tooltip=\"Copiar al portapapeles\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar al portapapeles\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    async def _send(\r\n    self,\r\n    to: str,\r\n    message: dict[str, Any],\r\n    channel_properties: dict[str, str] | None = None,\r\n) -&gt; dict[str, Any]:\r\n    &quot;&quot;&quot;Send any Conversation API message over RCS, falling back to SMS.&quot;&quot;&quot;\r\n    properties = {&quot;SMS_SENDER&quot;: self.sms_sender} if self.sms_sender else {}\r\n    properties.update(channel_properties or {})\r\n\r\n    payload: dict[str, Any] = {\r\n        &quot;app_id&quot;: self.app_id,\r\n        &quot;recipient&quot;: {\r\n            &quot;identified_by&quot;: {\r\n                &quot;channel_identities&quot;: [\r\n                    {&quot;channel&quot;: &quot;RCS&quot;, &quot;identity&quot;: to},\r\n                    {&quot;channel&quot;: &quot;SMS&quot;, &quot;identity&quot;: to},\r\n                ]\r\n            }\r\n        },\r\n        &quot;channel_priority_order&quot;: [&quot;RCS&quot;, &quot;SMS&quot;],\r\n        &quot;message&quot;: message,\r\n    }\r\n    if properties:\r\n        payload[&quot;channel_properties&quot;] = properties\r\n\r\n    path = f&quot;\/v1\/projects\/{self.project_id}\/messages:send&quot;\r\n    response = await self.client.post(path, auth=self._auth, json=payload)\r\n    if response.is_error:\r\n        raise SinchClientError(\r\n            f&quot;messages:send failed ({response.status_code}): {response.text}&quot;\r\n        )\r\n    return response.json()\r\n\r\n\r\nasync def send_reminder(self, to: str, text: str) -&gt; dict[str, Any]:\r\n    return await self._send(to, {&quot;text_message&quot;: {&quot;text&quot;: text}})\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Cada canal a partir de este cambia el cuerpo del <code>message<\/code> que entrega a <code>_send<\/code>, y nada m\u00e1s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La propiedad del canal <code>SMS_SENDER<\/code> est\u00e1 ah\u00ed porque el SMS necesita un origen a menos que la aplicaci\u00f3n tenga uno\u00a0predeterminado configurado. Eso tambi\u00e9n vino de la skill.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Realizar pruebas desde el editor<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Para esto sirve la parte del servidor MCP. Con el cliente escrito y el archivo todav\u00eda abierto, ped\u00ed\u00a0un env\u00edo real:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p><!-- \/wp:paragraph --><\/p><p class=\"mb-0\">Env\u00eda un recordatorio de prueba a trav\u00e9s de este servicio a mi n\u00famero, con fecha de vencimiento dentro de tres d\u00edas y un importe de 240\u00a0$.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Un mensaje en tiempo real a trav\u00e9s de una cuenta activa, desde el editor:<\/p>\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar al portapapeles\" data-clipboard-success-label=\"\u00a1Copiado!\" data-clipboard-error-label=\"No se ha podido copiar\" data-tooltip=\"Copiar al portapapeles\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar al portapapeles\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    {\r\n  &quot;message_id&quot;: &quot;01KY292FGM58WNNBETKE1Q4NF8&quot;,\r\n  &quot;accepted_time&quot;: &quot;2026-07-21T12:05:38.964Z&quot;\r\n}\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Esa respuesta significa aceptado, no entregado. La entrega se realiza de forma as\u00edncrona y aparece en el\u00a0webhook, por lo que el siguiente paso es asegurarse de que el webhook est\u00e1 realmente suscrito a ella.<br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conectar el webhook entrante<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un solo punto de conexi\u00f3n transporta ambas direcciones de tr\u00e1fico: <code>MESSAGE_DELIVERY<\/code> para los recibos y\u00a0<code>MESSAGE_INBOUND<\/code> para las respuestas de la clientela. Reg\u00edstralo con un comando:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-light\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">\/create-webhook &#8211;target=https:\/\/rockbank.example.com\/sinch\/callbacks \\<br>  &#8211;triggers=MESSAGE_DELIVERY,MESSAGE_INBOUND &#8211;secret=$SINCH_WEBHOOK_SECRET<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">El destino debe ser HTTPS y accesible desde Internet, as\u00ed que, para el trabajo local, coloca un t\u00fanel delante\u00a0de tu gestor y registra la URL del t\u00fanel. El secreto es opcional en la API, pero p\u00e1salo: es\u00a0lo que firma las devoluciones de llamada, tiene que coincidir con el valor que verifica tu gestor y no hay\u00a0ninguna firma que comprobar sin \u00e9l. Una aplicaci\u00f3n admite hasta cinco webhooks, y volver a registrar el mismo destino\u00a0devuelve un 400.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Luego comprueba a qu\u00e9 est\u00e1 suscrita realmente la aplicaci\u00f3n, que no siempre es lo que crees que has pedido:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-light\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">\/list-webhooks<br>\/list-webhook-triggers<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Merece la pena hacerlo antes de confiar en cualquier proceso posterior. Un webhook registrado solo con <code>MESSAGE_INBOUND<\/code>\u00a0acepta tu registro sin problemas, y tu gestor de recibos de entrega se queda ah\u00ed y nunca se ejecuta.\u00a0La ruta de env\u00edo parece correcta y el registro de entregas se mantiene vac\u00edo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El propio gestor necesita una comprobaci\u00f3n de firma antes de mirar el cuerpo:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Escribe el gestor FastAPI para el punto de conexi\u00f3n <code>\/sinch\/callbacks<\/code>. Verifica la firma HMAC-SHA256 que env\u00eda Sinch en cada devoluci\u00f3n de llamada antes de procesar el cuerpo, rechaza las solicitudes con una marca de tiempo caducada o reutilizada, y enruta los recibos de entrega y los mensajes entrantes a gestores separados.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Sinch firma <a href=\"https:\/\/developers.sinch.com\/docs\/conversation\/callbacks\" target=\"_blank\" rel=\"noreferrer noopener\">las devoluciones de llamada<\/a> con HMAC-SHA256\u00a0sobre el cuerpo sin procesar, el nonce y la marca de tiempo:<\/p>\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar al portapapeles\" data-clipboard-success-label=\"\u00a1Copiado!\" data-clipboard-error-label=\"No se ha podido copiar\" data-tooltip=\"Copiar al portapapeles\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar al portapapeles\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    import base64\r\nimport hashlib\r\nimport hmac\r\nimport time\r\n\r\nMAX_CALLBACK_AGE_SECONDS = 300\r\n\r\n@router.post(&quot;\/sinch\/callbacks&quot;)\r\nasync def handle_callback(request: Request) -&gt; Response:\r\n    raw_body = await request.body()\r\n    signature = request.headers.get(&quot;x-sinch-webhook-signature&quot;, &quot;&quot;)\r\n    nonce = request.headers.get(&quot;x-sinch-webhook-signature-nonce&quot;, &quot;&quot;)\r\n    timestamp = request.headers.get(&quot;x-sinch-webhook-signature-timestamp&quot;, &quot;&quot;)\r\n\r\n    try:\r\n        age = abs(time.time() - int(timestamp))\r\n    except ValueError:\r\n        raise HTTPException(status_code=401, detail=&quot;Bad timestamp&quot;)\r\n    if age &gt; MAX_CALLBACK_AGE_SECONDS:\r\n        raise HTTPException(status_code=401, detail=&quot;Stale callback&quot;)\r\n\r\n    signed_data = raw_body + b&quot;.&quot; + nonce.encode() + b&quot;.&quot; + timestamp.encode()\r\n    expected = base64.b64encode(\r\n        hmac.new(\r\n            settings.webhook_secret.encode(), signed_data, hashlib.sha256\r\n        ).digest()\r\n    ).decode()\r\n    if not hmac.compare_digest(expected, signature):\r\n        raise HTTPException(status_code=401, detail=&quot;Invalid signature&quot;)\r\n\r\n    payload = json.loads(raw_body)\r\n    if &quot;message_delivery_report&quot; in payload:\r\n        await record_delivery(payload[&quot;message_delivery_report&quot;])\r\n    elif &quot;message&quot; in payload:\r\n        await record_inbound_message(payload[&quot;message&quot;])\r\n    return Response(status_code=200)\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Hay algunas cosas que hay que hacer bien aqu\u00ed. La cadena firmada es el cuerpo sin procesar, el nonce y la marca de tiempo\u00a0unidos por puntos, en ese orden, y el resumen es base64 en lugar de hexadecimal. Calc\u00falalo sobre los bytes\u00a0sin procesar, no sobre un diccionario reserializado, o nunca coincidir\u00e1. La clave es el secreto que configuraste al\u00a0crear el webhook, y <code>x-sinch-webhook-signature-algorithm<\/code> te indica qu\u00e9 algoritmo se us\u00f3\u00a0(<code>HmacSHA256<\/code> a d\u00eda de hoy).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una firma v\u00e1lida por s\u00ed sola no indica que la solicitud sea reciente. Cualquiera que capture una devoluci\u00f3n de llamada\u00a0firmada puede enviarla de nuevo y el HMAC se validar\u00e1 de todos modos; para eso est\u00e1n la marca de tiempo y el nonce:\u00a0Sinch describe el nonce como \u00fanico por devoluci\u00f3n de llamada exactamente con este fin. La\u00a0ventana de cinco minutos anterior es mi propia elecci\u00f3n, no un valor documentado, y es la parte f\u00e1cil. Si\u00a0necesitas un procesamiento estricto de una sola vez, guarda en cach\u00e9 los nonces que ya hayas aceptado durante el tiempo que dure esa\u00a0ventana y rechaza las repeticiones. Tambi\u00e9n merece la pena tener un manejo idempotente, porque Sinch realiza reintentos con un\u00a0retroceso exponencial y, de hecho, ver\u00e1s el mismo recibo dos veces de forma leg\u00edtima, pero la idempotencia no es una\u00a0protecci\u00f3n contra la repetici\u00f3n: evita que una devoluci\u00f3n de llamada repetida corrompa tu estado, pero nunca te dir\u00e1 que\u00a0se ha repetido.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A\u00f1adir RCS<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Como la ruta de env\u00edo ya es independiente del canal, RCS es un tipo de mensaje y una propiedad del canal:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Actualiza el recordatorio a una tarjeta enriquecida RCS con el remitente verificado de RockBank, un resumen del pago y un bot\u00f3n de \u201cPagar ahora\u201d que abra nuestra p\u00e1gina de pago alojada. Mant\u00e9n SMS como alternativa para los dispositivos que no sean compatibles con RCS.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">La tarjeta es un <code>card_message<\/code> que incluye una opci\u00f3n <code>url_message<\/code>, la cual se entrega al mismo <code>_send<\/code> de\u00a0antes:<\/p>\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar al portapapeles\" data-clipboard-success-label=\"\u00a1Copiado!\" data-clipboard-error-label=\"No se ha podido copiar\" data-tooltip=\"Copiar al portapapeles\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar al portapapeles\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                        async def send_reminder_card(    \r\n        self,    \r\n        to: str,    \r\n        due_date: date,    \r\n        amount: Decimal,    \r\n        account_last4: str,    \r\n        payment_url: str,    \r\n    ) -&gt; dict[str, Any]:    \r\n        card: dict[str, Any] = {    \r\n            &quot;title&quot;: f&quot;Payment due {due_date:%d %b}&quot;,    \r\n            &quot;description&quot;: f&quot;Amount: ${amount:,.2f}. Account ending {account_last4}.&quot;,    \r\n            &quot;choices&quot;: [    \r\n                {&quot;url_message&quot;: {&quot;title&quot;: &quot;Pay now&quot;, &quot;url&quot;: payment_url}}    \r\n            ],    \r\n        }    \r\n        return await self._send(    \r\n            to,    \r\n            {&quot;card_message&quot;: card},    \r\n            channel_properties={&quot;RCS_WEBVIEW_MODE&quot;: &quot;TALL&quot;},    \r\n        )   \r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\"><code>channel_properties<\/code> es un \u00fanico mapa plano, por lo que la clave RCS y el <code>SMS_SENDER<\/code> que necesita la alternativa acaban\u00a0en el mismo diccionario. Esa fusi\u00f3n es la raz\u00f3n por la que <code>_send<\/code> toma las propiedades como argumento en lugar de\u00a0construirlas de forma integrada. <code>RCS_WEBVIEW_MODE<\/code> controla qu\u00e9 parte de la pantalla ocupa la p\u00e1gina de pago cuando\u00a0se abre: <code>FULL<\/code>, <code>HALF<\/code> o <code>TALL<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Hay dos detalles sobre RCS en los que es f\u00e1cil equivocarse:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>RCS no procesa el pago.<\/strong> El bot\u00f3n de \u201cPagar ahora\u201d es una acci\u00f3n URL. Abre la\u00a0p\u00e1gina de pago alojada en una vista web sobre el hilo, la clientela paga ah\u00ed y el control vuelve a la\u00a0conversaci\u00f3n. Lo que te aporta RCS es el remitente verificado, la tarjeta de marca y una clientela que nunca\u00a0sale de su aplicaci\u00f3n de mensajer\u00eda. El pago se sigue procesando a trav\u00e9s de tu proveedor de pagos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Los fallos en la alternativa llegan tarde.<\/strong> Cada canal que nombres en <code>channel_priority_order<\/code> tiene que estar\u00a0configurado en la aplicaci\u00f3n o la solicitud se rechazar\u00e1 de plano, lo cual es el caso f\u00e1cil de depurar. El caso\u00a0m\u00e1s dif\u00edcil es el de un canal configurado que luego falla al realizar la entrega: <code>messages:send<\/code> devuelve un 200 y el\u00a0resultado aparece m\u00e1s tarde como una devoluci\u00f3n de llamada <code>MESSAGE_DELIVERY<\/code>, con <code>SWITCHING_CHANNEL<\/code> marcando el punto\u00a0en el que se activ\u00f3 la alternativa. Otro motivo para configurar bien los disparadores de webhooks antes de depender de la\u00a0ruta alternativa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Voz, verificaci\u00f3n y email<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Cada una de las cuatro partes restantes se resolvi\u00f3 con un prompt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Voz.<\/strong> Si el recordatorio no recibe respuesta y el pago sigue pendiente, RockBank llama. Una\u00a0llamada de texto a voz no necesita ning\u00fan servidor de flujos de llamadas, solo un POST a la API de voz:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">A\u00f1ade un recordatorio de voz para los pagos que sigan pendientes dos d\u00edas despu\u00e9s de que se enviara el mensaje. Usa una llamada de texto a voz a trav\u00e9s de la API de voz de Sinch con nuestras credenciales de la aplicaci\u00f3n de voz, y establece el identificador de llamadas en nuestro n\u00famero registrado.<\/p><\/div>\r\n    <\/div>\r\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar al portapapeles\" data-clipboard-success-label=\"\u00a1Copiado!\" data-clipboard-error-label=\"No se ha podido copiar\" data-tooltip=\"Copiar al portapapeles\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar al portapapeles\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    async def place_reminder_call(self, to: str, message: str) -&gt; dict[str, Any]:\r\n    &quot;&quot;&quot;Place a text-to-speech reminder call via the Voice API.&quot;&quot;&quot;\r\n    tts: dict[str, Any] = {\r\n        &quot;destination&quot;: {&quot;type&quot;: &quot;number&quot;, &quot;endpoint&quot;: to},\r\n        &quot;cli&quot;: self.caller_id,\r\n        &quot;locale&quot;: &quot;en-US&quot;,\r\n        &quot;text&quot;: message,\r\n    }\r\n    response = await self.client.post(\r\n        &quot;\/calling\/v1\/callouts&quot;,\r\n        auth=(self.voice_app_key, self.voice_app_secret),\r\n        json={&quot;method&quot;: &quot;ttsCallout&quot;, &quot;ttsCallout&quot;: tts},\r\n    )\r\n    if response.is_error:\r\n        raise SinchClientError(\r\n            f&quot;Voice callout failed ({response.status_code}): {response.text}&quot;\r\n        )\r\n    return response.json()\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">F\u00edjate en la diferencia de autenticaci\u00f3n: la voz utiliza una clave y un secreto de aplicaci\u00f3n, no las credenciales\u00a0a nivel de proyecto que emplea la API de conversaci\u00f3n. Producto diferente, esquema de autenticaci\u00f3n diferente, y las Skills\u00a0cubren ambos, as\u00ed que no hubo que explicar ninguno de los dos en el prompt.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Para una llamada telef\u00f3nica, <code>destination<\/code> es <code>{\"type\": \"number\", \"endpoint\": \"+46...\"}<\/code> en E.164, y la\u00a0respuesta te devuelve un <code>callId<\/code>. La referencia de la API indica que el par\u00e1metro <code>cli<\/code> es opcional, pero config\u00faralo de todos modos con un\u00a0n\u00famero verificado o uno asignado desde tu panel de control: sin un identificador de llamadas \u00fatil, puedes recibir un ID de llamada\u00a0de una llamada que en realidad nunca lleg\u00f3 al tel\u00e9fono.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Verificaci\u00f3n.<\/strong> Un c\u00f3digo de acceso de un solo uso antes de que la p\u00e1gina de pago acepte nada:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Antes de que la p\u00e1gina de pago acepte nada, verifica al cliente con un c\u00f3digo de acceso de un solo uso por SMS a trav\u00e9s de la API de verificaci\u00f3n de Sinch. Exp\u00f3n el m\u00e9todo para que podamos cambiarlo en cada solicitud.<\/p><\/div>\r\n    <\/div>\r\n\n\n    <div data-content-type=\"longform\" class=\"code-snippet longform-spacings rounded-lg overflow-hidden shadow \" data-count=\"1\">\r\n        <ul class=\"nav nav-buttons code-snippet__tabs longform-except position-relative d-flex gap-2 flex-wrap p-3\" role=\"tablist\">\r\n\r\n                            \r\n                    \r\n                        <li class=\"nav-item\" role=\"presentation\">\r\n                            <button class=\"nav-link active\" data-bs-toggle=\"tab\" data-bs-target=\"#code_0\" type=\"button\" role=\"tab\" aria-controls=\"code_0\" aria-selected=\"true\">\r\n                                Shell                            <\/button>\r\n                        <\/li>\r\n\r\n                    \r\n                            \r\n        <\/ul>\r\n        <div class=\"code-snippet__tab-content tab-content\">\r\n\r\n                            \r\n                    <div class=\"tab-pane show active\" id=\"code_0\" role=\"tabpanel\" aria-labelledby=\"code_0\">\r\n                        <div class=\"code-snippet__code-wrapper\">\r\n                            <div class=\"nav nav-buttons code-snippet__copy-wrapper\">\r\n                                <button type=\"button\" class=\"nav-link code-snippet__copy\" data-clipboard-target=\"#code_0_content\" data-clipboard-label=\"Copiar al portapapeles\" data-clipboard-success-label=\"\u00a1Copiado!\" data-clipboard-error-label=\"No se ha podido copiar\" data-tooltip=\"Copiar al portapapeles\" data-tooltip-placement=\"left\" role=\"tooltip\" aria-label=\"Copiar al portapapeles\">\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--default\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/file-stack.svg\"><\/svg>\r\n                                    <svg class=\"code-snippet__copy-icon code-snippet__copy-icon--success\" width=\"24\" height=\"24\" aria-hidden=\"true\" data-url=\"https:\/\/sinch.com\/wp-content\/plugins\/sinch-core\/assets\/icons\/sinch\/badge-check.svg\"><\/svg>\r\n                                <\/button>\r\n                            <\/div>\r\n                            <pre class=\"w-100 h-100 m-0 line-numbers language-shell\">\r\n                                <code id=\"code_0_content\" class=\"language-shell\">\r\n\r\n                                    async def start(self, phone: str, method: str = &quot;sms&quot;) -&gt; dict[str, Any]:\r\n    response = await self.client.post(\r\n        &quot;\/verification\/v1\/verifications&quot;,\r\n        auth=self._verification_auth,\r\n        json={&quot;identity&quot;: {&quot;type&quot;: &quot;number&quot;, &quot;endpoint&quot;: phone}, &quot;method&quot;: method},\r\n    )\r\n    ...\r\n\r\nasync def verify(self, reference: str, code: str) -&gt; dict[str, Any]:\r\n    path = f&quot;\/verification\/v1\/verifications\/id\/{quote(reference, safe=&#039;&#039;)}&quot;\r\n    response = await self.client.put(\r\n        path,\r\n        auth=self._verification_auth,\r\n        json={&quot;method&quot;: &quot;sms&quot;, &quot;sms&quot;: {&quot;code&quot;: code}},\r\n    )\r\n    ...\r\n                                <\/code>\r\n                            <\/pre>\r\n                        <\/div>\r\n                    <\/div>\r\n                                    <\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\"><code>_verification_auth<\/code> es un tercer conjunto de credenciales: la verificaci\u00f3n se autentica con la clave y\u00a0el secreto de su propia aplicaci\u00f3n en el panel de control, as\u00ed que no son ni las claves a nivel de proyecto que usa la API de conversaci\u00f3n\u00a0ni las de la aplicaci\u00f3n de voz.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>start<\/code> devuelve un <code>id<\/code>, y frente a ese ID es como presentas el c\u00f3digo en el informe. Los valores del m\u00e9todo son la parte que\u00a0merece la pena conocer: <code>sms<\/code> para un c\u00f3digo de acceso por mensaje, <code>callout<\/code> para uno que se lee en una llamada de tel\u00e9fono, adem\u00e1s de\u00a0<code>flashcall<\/code>, <code>seamless<\/code> y <code>whatsapp<\/code>. El cuerpo del informe utiliza el mismo m\u00e9todo como clave, por lo que ser\u00e1\u00a0<code>{\"method\": \"sms\", \"sms\": {\"code\": \"1234\"}}<\/code> para el mensaje y\u00a0<code>{\"method\": \"callout\", \"callout\": {\"code\": \"1234\"}}<\/code> para la llamada.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Las verificaciones caducan al cabo de unos minutos, as\u00ed que debes tratar un error 400 en el informe como \u201ciniciar una nueva\u201d en lugar de\u00a0como un intento fallido. Cambiar a un cliente a <code>callout<\/code> cuando el mensaje no llega consiste en una segunda llamada de\u00a0<code>start<\/code> con un m\u00e9todo distinto, y decidir cu\u00e1ndo ofrecerlo forma parte de la l\u00f3gica de la aplicaci\u00f3n: cu\u00e1nto tiempo\u00a0esperar, cu\u00e1ntos intentos permitir y si es el cliente quien lo solicita o si lo decides en su nombre.\u00a0Vale la pena dise\u00f1arlo a conciencia en lugar de dejarlo en manos de un bucle de reintentos.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Email.<\/strong> La confirmaci\u00f3n se env\u00eda a trav\u00e9s de Mailgun, que est\u00e1 en el mismo plugin:<\/p>\n\n\n    <div data-content-type=\"longform\"  class=\"callout text-body-color px-5 py-6 px-md-6 px-lg-7 py-md-7 longform-spacings rounded-lg bg-sand\" data-theme=\"light\">\r\n\r\n        <div class=\"content-body\"> <p class=\"mb-0\">Cuando se confirme un pago o un plan de pago, env\u00eda un email de confirmaci\u00f3n a trav\u00e9s de Mailgun con el importe, la fecha y un n\u00famero de referencia.<\/p><\/div>\r\n    <\/div>\r\n\n\n\n<p class=\"wp-block-paragraph\">Mailgun supone un cuarto conjunto de credenciales y es la excepci\u00f3n: se autentica por dominio y por clave en lugar de por aplicaci\u00f3n, como ocurre con los otros tres. Se trata de una autenticaci\u00f3n b\u00e1sica frente a <code>POST \/v3\/{domain}\/messages<\/code>, con <code>api<\/code> como nombre de usuario y una clave de API como contrase\u00f1a. <code>o:testmode=yes<\/code> acepta un env\u00edo sin llegar a entregarlo, lo cual es \u00fatil saberlo mientras configuras la confirmaci\u00f3n.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Posibles obst\u00e1culos<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Empieza el registro de 10DLC antes de escribir el c\u00f3digo de env\u00edo.<\/strong> El env\u00edo de SMS a n\u00fameros de EE. UU. lo requiere,\u00a0la aprobaci\u00f3n tarda d\u00edas y no hay velocidad del agente capaz de comprimir eso. Es el motivo m\u00e1s habitual\u00a0por el que un desarrollo de una tarde se acaba convirtiendo en el desarrollo de la semana siguiente.<\/li>\n\n\n\n<li><strong>Un error de coincidencia de regiones parece un problema de autenticaci\u00f3n.<\/strong> Todas las URL de la API de conversaci\u00f3n son espec\u00edficas de cada regi\u00f3n. Si la\u00a0<code>CONVERSATION_REGION<\/code> no coincide con el lugar en el que se encuentra la aplicaci\u00f3n, obtendr\u00e1s un 404 o un error que apunta a\u00a0tus credenciales. Comprueba la regi\u00f3n en el panel de control antes de volver a generar una clave que es perfectamente v\u00e1lida.<\/li>\n\n\n\n<li><strong>Prueba la ruta alternativa de manera tan exhaustiva como la ruta principal.<\/strong> La disponibilidad de RCS var\u00eda en funci\u00f3n del mercado, el operador\u00a0y el dispositivo, y tu propio tel\u00e9fono solo demuestra que la ruta principal funciona. Haz un env\u00edo a un dispositivo que no tenga RCS y confirma\u00a0que el SMS llega realmente. <code>messages:transcode<\/code> permite previsualizar c\u00f3mo se degrada una tarjeta sin enviarla.<\/li>\n\n\n\n<li><strong>El servidor MCP realiza acciones reales en una cuenta real.<\/strong> Esa es la idea, y eso implica\u00a0apuntarlo a un n\u00famero de prueba en lugar de a una lista, y pensar en qu\u00e9 credenciales se encuentran\u00a0en el perfil de tu shell.<\/li>\n\n\n\n<li><strong>Mant\u00e9n al agente dentro de la ra\u00edz del proyecto.<\/strong> Durante una ejecuci\u00f3n, el agente busc\u00f3 fuera de la carpeta\u00a0abierta, encontr\u00f3 una implementaci\u00f3n m\u00e1s antigua de la misma funci\u00f3n en un directorio hermano y la copi\u00f3\u00a0en lugar de usar las Skills. El c\u00f3digo funcion\u00f3. Pero tambi\u00e9n era el c\u00f3digo equivocado. Abre el proyecto en el que pretendas\u00a0trabajar.<\/li>\n\n\n\n<li><strong>Lee lo que ha escrito el agente.<\/strong> El valor de un agente que escribe c\u00f3digo de integraci\u00f3n real, en lugar de\u00a0devolver un resultado opaco, es que el c\u00f3digo est\u00e1 justo ah\u00ed para poder revisarlo. La brecha en el disparador de webhooks\u00a0y la ausencia del identificador de llamadas eran visibles en el diff.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>El resultado final<\/strong><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Desde el punto de vista del cliente: llega un recordatorio tres d\u00edas antes del vencimiento del pago. En un dispositivo compatible\u00a0con RCS, se trata de una tarjeta de marca de un remitente verificado con un bot\u00f3n de \u201cPagar ahora\u201d. Lo tocan, se verifican\u00a0con un c\u00f3digo de acceso, pagan sin salir del hilo y reciben un email de confirmaci\u00f3n. Si el pago sigue\u00a0pendiente, la experiencia contin\u00faa con un recordatorio de voz automatizado.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Al final, el recorrido abarca mensajer\u00eda, verificaci\u00f3n, voz y email, y todo se ha dise\u00f1ado mediante el mismo flujo de trabajo\u00a0de Cursor. El tiempo que me ahorr\u00f3 el agente fue tiempo de lectura de documentos: descubrir las formas de la carga \u00fatil y\u00a0los esquemas de autenticaci\u00f3n en cuatro productos diferentes, adem\u00e1s de los ciclos de implementaci\u00f3n y comprobaci\u00f3n que suele haber entre el momento en el que una\u00a0carga \u00fatil es err\u00f3nea y el momento en el que yo me doy cuenta de ello. Escribir nunca fue la parte lenta.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pru\u00e9balo<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Instala el plugin desde el <a href=\"https:\/\/cursor.com\/marketplace\/sinch\/sinch-cursor-plugin\" target=\"_blank\" rel=\"noreferrer noopener\">marketplace de Cursor<\/a>,\u00a0exporta las credenciales, vuelve a iniciar Cursor y dale al agente el prompt de la API de conversaci\u00f3n de antes\u00a0con un n\u00famero de prueba. El plugin es de c\u00f3digo abierto y est\u00e1 en\u00a0<a href=\"https:\/\/github.com\/sinch\/sinch-plugins\" target=\"_blank\" rel=\"noreferrer noopener\">sinch-plugins en GitHub<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Recursos adicionales<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/cursor.com\/marketplace\/sinch\/sinch-cursor-plugin\" target=\"_blank\" rel=\"noreferrer noopener\">Plugin de Sinch Cursor en el marketplace de Cursor<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/sinch\/sinch-plugins\" target=\"_blank\" rel=\"noreferrer noopener\">sinch-plugins en GitHub<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/conversation\/\" target=\"_blank\" rel=\"noreferrer noopener\">Documentaci\u00f3n de la API de conversaci\u00f3n de Sinch<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/voice\/\" target=\"_blank\" rel=\"noreferrer noopener\">Documentaci\u00f3n de la API de voz de Sinch<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developers.sinch.com\/docs\/verification\/\" target=\"_blank\" rel=\"noreferrer noopener\">Documentaci\u00f3n de la API de verificaci\u00f3n de Sinch<\/a><\/li>\n<\/ul>\n","protected":false},"author":93,"featured_media":210360,"menu_order":0,"template":"","meta":{"_acf_changed":false,"footnotes":""},"tags":[],"blog_category":[344],"class_list":["post-211309","blog","type-blog","status-publish","has-post-thumbnail","hentry","blog_category-desarrolladores"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>C\u00f3mo crear un stack de comunicaciones en Cursor con Sinch<\/title>\n<meta name=\"description\" content=\"C\u00f3mo he creado un stack de comunicaci\u00f3n con los clientes en Cursor usando el plugin de Sinch, desde recordatorios por RCS y SMS hasta voz, verificaci\u00f3n y email.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/\" \/>\n<meta property=\"og:locale\" content=\"es_ES\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"C\u00f3mo crear un stack de comunicaciones en Cursor con Sinch\" \/>\n<meta property=\"og:description\" content=\"C\u00f3mo he creado un stack de comunicaci\u00f3n con los clientes en Cursor usando el plugin de Sinch, desde recordatorios por RCS y SMS hasta voz, verificaci\u00f3n y email.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/\" \/>\n<meta property=\"og:site_name\" content=\"Sinch\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/wearesinch\/\" \/>\n<meta property=\"og:image\" content=\"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1296\" \/>\n\t<meta property=\"og:image:height\" content=\"788\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:site\" content=\"@WeAreSinch\" \/>\n<meta name=\"twitter:label1\" content=\"Tiempo de lectura\" \/>\n\t<meta name=\"twitter:data1\" content=\"11 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"BlogPosting\",\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/\"},\"author\":{\"name\":\"Gunnar Grosch\",\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/#\\\/schema\\\/person\\\/b2bde1c2728a21bc953ea0e129936c08\"},\"headline\":\"C\u00f3mo cre\u00e9 un stack de comunicaciones en Cursor con el plugin de Sinch\",\"datePublished\":\"2026-09-25T10:24:13+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/\"},\"wordCount\":3274,\"publisher\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\",\"inLanguage\":\"es\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/\",\"url\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/\",\"name\":\"C\u00f3mo crear un stack de comunicaciones en Cursor con Sinch\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\",\"datePublished\":\"2026-09-25T10:24:13+00:00\",\"description\":\"C\u00f3mo he creado un stack de comunicaci\u00f3n con los clientes en Cursor usando el plugin de Sinch, desde recordatorios por RCS y SMS hasta voz, verificaci\u00f3n y email.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/#primaryimage\",\"url\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\",\"contentUrl\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2026\\\/09\\\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png\",\"width\":1296,\"height\":788},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/como-cree-stack-comunicaciones-cursor-sinch-plugin\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/sinch.com\\\/es\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Blog\",\"item\":\"https:\\\/\\\/sinch.com\\\/es\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"C\u00f3mo cre\u00e9 un stack de comunicaciones en Cursor con el plugin de Sinch\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/#website\",\"url\":\"https:\\\/\\\/sinch.com\\\/es\\\/\",\"name\":\"Sinch\",\"description\":\"Mensajer\u00eda SMS, Voz, Email, Video y API de verificaci\u00f3n | Sinch\",\"publisher\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/sinch.com\\\/es\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/#organization\",\"name\":\"Sinch\",\"url\":\"https:\\\/\\\/sinch.com\\\/es\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2024\\\/09\\\/Thumbnail-Logo-Honey.png\",\"contentUrl\":\"https:\\\/\\\/sinch.com\\\/wp-content\\\/uploads\\\/2024\\\/09\\\/Thumbnail-Logo-Honey.png\",\"width\":800,\"height\":496,\"caption\":\"Sinch\"},\"image\":{\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/wearesinch\\\/\",\"https:\\\/\\\/x.com\\\/WeAreSinch\",\"https:\\\/\\\/www.linkedin.com\\\/company\\\/sinch\",\"https:\\\/\\\/www.youtube.com\\\/channel\\\/UCZZ2u_B2afTxA0v-xcgfsaw\",\"https:\\\/\\\/www.instagram.com\\\/wearesinch\\\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/sinch.com\\\/es\\\/#\\\/schema\\\/person\\\/b2bde1c2728a21bc953ea0e129936c08\",\"name\":\"Gunnar Grosch\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g\",\"caption\":\"Gunnar Grosch\"},\"description\":\"Gunnar Grosch is a Principal Developer Advocate at Sinch and an engineer with over 25 years of experience building software, scaling developer communities, and making complex technology accessible. He shares code, insights, and lessons through conference talks, blog posts, and open source.\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"C\u00f3mo crear un stack de comunicaciones en Cursor con Sinch","description":"C\u00f3mo he creado un stack de comunicaci\u00f3n con los clientes en Cursor usando el plugin de Sinch, desde recordatorios por RCS y SMS hasta voz, verificaci\u00f3n y email.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/","og_locale":"es_ES","og_type":"article","og_title":"C\u00f3mo crear un stack de comunicaciones en Cursor con Sinch","og_description":"C\u00f3mo he creado un stack de comunicaci\u00f3n con los clientes en Cursor usando el plugin de Sinch, desde recordatorios por RCS y SMS hasta voz, verificaci\u00f3n y email.","og_url":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/","og_site_name":"Sinch","article_publisher":"https:\/\/www.facebook.com\/wearesinch\/","og_image":[{"width":1296,"height":788,"url":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_site":"@WeAreSinch","twitter_misc":{"Tiempo de lectura":"11 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"BlogPosting","@id":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/#article","isPartOf":{"@id":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/"},"author":{"name":"Gunnar Grosch","@id":"https:\/\/sinch.com\/es\/#\/schema\/person\/b2bde1c2728a21bc953ea0e129936c08"},"headline":"C\u00f3mo cre\u00e9 un stack de comunicaciones en Cursor con el plugin de Sinch","datePublished":"2026-09-25T10:24:13+00:00","mainEntityOfPage":{"@id":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/"},"wordCount":3274,"publisher":{"@id":"https:\/\/sinch.com\/es\/#organization"},"image":{"@id":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/#primaryimage"},"thumbnailUrl":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","inLanguage":"es"},{"@type":"WebPage","@id":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/","url":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/","name":"C\u00f3mo crear un stack de comunicaciones en Cursor con Sinch","isPartOf":{"@id":"https:\/\/sinch.com\/es\/#website"},"primaryImageOfPage":{"@id":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/#primaryimage"},"image":{"@id":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/#primaryimage"},"thumbnailUrl":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","datePublished":"2026-09-25T10:24:13+00:00","description":"C\u00f3mo he creado un stack de comunicaci\u00f3n con los clientes en Cursor usando el plugin de Sinch, desde recordatorios por RCS y SMS hasta voz, verificaci\u00f3n y email.","breadcrumb":{"@id":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/#primaryimage","url":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","contentUrl":"https:\/\/sinch.com\/wp-content\/uploads\/2026\/09\/SI-Blog-how-i-built-communications-stack-cursor-sinch-plugin.png","width":1296,"height":788},{"@type":"BreadcrumbList","@id":"https:\/\/sinch.com\/es\/blog\/como-cree-stack-comunicaciones-cursor-sinch-plugin\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/sinch.com\/es\/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https:\/\/sinch.com\/es\/blog\/"},{"@type":"ListItem","position":3,"name":"C\u00f3mo cre\u00e9 un stack de comunicaciones en Cursor con el plugin de Sinch"}]},{"@type":"WebSite","@id":"https:\/\/sinch.com\/es\/#website","url":"https:\/\/sinch.com\/es\/","name":"Sinch","description":"Mensajer\u00eda SMS, Voz, Email, Video y API de verificaci\u00f3n | Sinch","publisher":{"@id":"https:\/\/sinch.com\/es\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/sinch.com\/es\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"},{"@type":"Organization","@id":"https:\/\/sinch.com\/es\/#organization","name":"Sinch","url":"https:\/\/sinch.com\/es\/","logo":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/sinch.com\/es\/#\/schema\/logo\/image\/","url":"https:\/\/sinch.com\/wp-content\/uploads\/2024\/09\/Thumbnail-Logo-Honey.png","contentUrl":"https:\/\/sinch.com\/wp-content\/uploads\/2024\/09\/Thumbnail-Logo-Honey.png","width":800,"height":496,"caption":"Sinch"},"image":{"@id":"https:\/\/sinch.com\/es\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/wearesinch\/","https:\/\/x.com\/WeAreSinch","https:\/\/www.linkedin.com\/company\/sinch","https:\/\/www.youtube.com\/channel\/UCZZ2u_B2afTxA0v-xcgfsaw","https:\/\/www.instagram.com\/wearesinch\/"]},{"@type":"Person","@id":"https:\/\/sinch.com\/es\/#\/schema\/person\/b2bde1c2728a21bc953ea0e129936c08","name":"Gunnar Grosch","image":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/secure.gravatar.com\/avatar\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/8f177695c01082754db3f0858d534fdaceedba31f74932d7de39991e5e9d7e8d?s=96&d=mm&r=g","caption":"Gunnar Grosch"},"description":"Gunnar Grosch is a Principal Developer Advocate at Sinch and an engineer with over 25 years of experience building software, scaling developer communities, and making complex technology accessible. He shares code, insights, and lessons through conference talks, blog posts, and open source."}]}},"_links":{"self":[{"href":"https:\/\/sinch.com\/es\/wp-json\/wp\/v2\/blog\/211309","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sinch.com\/es\/wp-json\/wp\/v2\/blog"}],"about":[{"href":"https:\/\/sinch.com\/es\/wp-json\/wp\/v2\/types\/blog"}],"author":[{"embeddable":true,"href":"https:\/\/sinch.com\/es\/wp-json\/wp\/v2\/users\/93"}],"version-history":[{"count":0,"href":"https:\/\/sinch.com\/es\/wp-json\/wp\/v2\/blog\/211309\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sinch.com\/es\/wp-json\/wp\/v2\/media\/210360"}],"wp:attachment":[{"href":"https:\/\/sinch.com\/es\/wp-json\/wp\/v2\/media?parent=211309"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sinch.com\/es\/wp-json\/wp\/v2\/tags?post=211309"},{"taxonomy":"blog_category","embeddable":true,"href":"https:\/\/sinch.com\/es\/wp-json\/wp\/v2\/blog_category?post=211309"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}