Crea un chat para tu web /widget N8N

🎯 ¿Quieres tener un Agente de IA en tu sitio web que responda automáticamente con ChatGPT?
En este tutorial te enseño paso a paso cómo crear un asistente virtual inteligente usando ChatGPT + n8n, sin necesidad de programar.

🔌 Compatible con:
✅ WordPress y woocommerce
✅ Shopify
✅ HighLevel

📥 Descarga la plantilla de N8N aquí 👉 DESCARGAR

En el codigo acontinuacion copia  y personaliza tu widget 

				
					
<!-- Widget Chat IA (N8N) -->
<style>
:root{
  --p:#000000;   /* negro */
  --ink:#ffffff; /* blanco */
  --bg:#fff;
  --tx:#1f2937;
  --bd:#e5e7eb;
  --bot:#f8fafc;
  --sh:0 18px 44px rgba(2,6,23,.18);
  --r:16px;
}

#tww,#twbtn{
  all:initial;
  font-family:'Maven Pro', sans-serif;
}

/* Ventana principal */
#tww{
  position:fixed !important;
  right:22px;
  bottom:90px;
  z-index:2147483647 !important;
  width:min(380px,92vw);
  height:620px;            /* altura fija escritorio */
  max-height:80vh;         /* nunca más grande que la pantalla */
  background:var(--bg);
  border:1px solid var(--bd);
  border-radius:var(--r);
  box-shadow:var(--sh);
  display:none;
  overflow:hidden;

  display:flex;
  flex-direction:column;
}
#tww.open{display:flex}

@media(max-width:480px){
  #tww{
    left:10px;
    right:10px;
    bottom:86px;
    width:auto;
    height:75vh;     /* altura proporcional en móviles */
  }
}


.tw-h{display:flex;justify-content:space-between;gap:10px;padding:12px 14px;border-bottom:1px solid var(--bd)}
.tw-b{display:flex;gap:10px;align-items:center}
.tw-b img{width:28px;height:28px;border-radius:8px}
.tw-t strong{display:block;font-size:14px;color:var(--tx)}
.tw-t i{display:block;font-size:12px;color:#6b7280}

.screen{flex:1;display:flex;flex-direction:column;min-height:0}
.hidden{display:none!important}

.wel{justify-content:center;align-items:center;gap:14px;background:linear-gradient(180deg,#f7f7ff,#fff)}
.wel h3{margin:0 16px;font-size:22px;color:var(--tx);text-align:center}
.ops{display:grid;gap:12px;width:88%;max-width:320px}
.btn{border:1px solid var(--bd);border-radius:14px;padding:14px 16px;font-weight:700;display:flex;align-items:center;gap:10px;cursor:pointer;justify-content:center;box-shadow:var(--sh)}
.btn svg{width:22px;height:22px}
.btn.chat{background:var(--p);color:#fff}

.chat{flex-direction:column}
.tw-m{flex:1;overflow:auto;padding:16px;white-space:pre-wrap;display:flex;flex-direction:column;gap:8px;background:var(--bg)}
.row{display:flex}
.bub{max-width:78%;padding:10px 12px;border-radius:14px;font-size:14px;line-height:1.35}
.u{margin-left:auto;background:var(--p);color:#fff}
.b{background:var(--bot);border:1px solid var(--bd)}
.typ{display:flex;gap:6px;padding:0 16px 8px}
.dot{width:8px;height:8px;border-radius:999px;background:#9aa4b2;opacity:.4;animation:b 1s infinite}
.dot:nth-child(2){animation-delay:.15s}
.dot:nth-child(3){animation-delay:.3s}
@keyframes b{0%,80%,100%{transform:translateY(0)}40%{transform:translateY(-4px)}}
.tw-f{display:flex;gap:8px;padding:14px;border-top:1px solid var(--bd)}
.tw-f input{flex:1;border:1px solid var(--bd);border-radius:12px;padding:12px}
.tw-f button{min-width:52px;border:0;border-radius:12px;background:var(--p);color:#fff;font-weight:600;cursor:pointer;font-size:14px;}

.bub a{color:#2563eb;text-decoration:underline;word-break:break-word}
.bub img{max-width:100%;height:auto;border-radius:10px;display:block;margin-top:6px}

@media(max-width:480px){
  #tww{left:10px;right:10px;bottom:86px;width:auto}
}

/* Posicion icono*/
#twbtn {
  position: fixed !important;
  right: 22px;
  bottom: 22px;
  z-index: 2147483647 !important;
  width: 64px;
  height: 64px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: transparent;
  box-shadow: none;
  cursor: pointer;
}

#twbtn img {
  width: 90%;
  height: 90%;
  object-fit: contain;
  pointer-events: none;
}

/* imagen icono*/
</style>

<div id="twbtn" aria-label="Abrir chat">
  <picture><source srcset="https://ivanrodriguezdigital.com/wp-content/webp-express/webp-images/uploads/2025/09/comentario.png.webp" type="image/webp"><img decoding="async" src="https://ivanrodriguezdigital.com/wp-content/uploads/2025/09/comentario.png" alt="Abrir chat" class="webpexpress-processed"></picture>
</div>
<div id="tww" role="dialog" aria-live="polite">
  <div class="tw-h">
    <div class="tw-b">
      <img id="logo" alt="logo">
      <div class="tw-t"><strong id="title"></strong><i id="sub"></i></div>
    </div>
    <div style="display:flex;gap:8px;align-items:center">
      <button id="back" class="hidden" style="all:unset;cursor:pointer;font-size:14px;border:1px solid var(--bd);padding:4px 8px;border-radius:10px">↩︎Atrás</button>
      <button id="x" style="all:unset;cursor:pointer;font-size:18px" aria-label="Cerrar chat">✕</button>
    </div>
  </div>

  <!-- Pantalla de opciones -->
  <section class="screen wel" id="wel">
    <h3>¡Hola 👋! ¿En qué podemos ayudarte?</h3>
    <div class="ops">
      <button class="btn chat" id="opt-chat" aria-label="Usar chat con asistente IA">Asistente Virtual</button>
    </div>
    <small style="color:#6b7280;margin-top:8px">Atención inmediata a través de nuestro asistente virtual.</small>
  </section>

  <!-- Vista de chat IA -->
  <section class="screen chat hidden" id="chat">
    <div class="tw-m" id="msgs"></div>
    <div class="typ" id="typ"><span class="dot"></span><span class="dot"></span><span class="dot"></span></div>
    <div class="tw-f">
      <input id="q" placeholder="Escribe tu mensaje aquí…" aria-label="Escribe tu mensaje">
      <button id="send" aria-label="Enviar mensaje">Enviar</button>
    </div>
  </section>
</div>

<script>
(() => {
  // ====== CONFIG CHAT URL ======
  const EP = "https://n8n.srv867795.hstgr.cloud/webhook/4ac82bcd-1b53-422c-b7f6-44d02cdac7a1/chat";
  const EP_LEADS = "";
  const BRAND = {
    title: "TU LOGO",
    subtitle: "Normalmente respondemos al instante",
    logo: "https://ivanrodriguezdigital.com/wp-content/uploads/2025/09/TU-LOGO--e1757803368737.png"
  };

  // ====== DOM ready ======
  function ready(fn) {
    if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", fn);
    else fn();
  }

  ready(() => {
    // DOM refs (usamos getElementById para mayor robustez)
    const box = document.getElementById('tww');
    const btn = document.getElementById('twbtn');
    const closeBtn = document.getElementById('x');
    const back = document.getElementById('back');
    const optChat = document.getElementById('opt-chat');
    const msgs = document.getElementById('msgs');
    const typ = document.getElementById('typ');
    const q = document.getElementById('q');
    const send = document.getElementById('send');

    if (!box || !btn) return; // required elements

    // Asegurar que el widget esté en body (evita stacking contexts de padres)
    if (box.parentNode !== document.body) document.body.appendChild(box);
    if (btn.parentNode !== document.body) document.body.appendChild(btn);

    // Aplicar branding
    const $title = document.getElementById('title');
    const $sub = document.getElementById('sub');
    const $logo = document.getElementById('logo');
    if ($title) $title.textContent = BRAND.title;
    if ($sub) $sub.textContent = BRAND.subtitle;
    if ($logo) $logo.src = BRAND.logo;

    // ====== Session & History ======
    const sid = localStorage.getItem("tw-sid") || (localStorage.setItem("tw-sid", crypto.randomUUID()), localStorage.getItem("tw-sid"));
    const key = `tw-h-${sid}`;
    const hist = JSON.parse(localStorage.getItem(key) || "[]");
    const leadKey = `tw-leads-${sid}`;

    function save(t, w) {
      const h = JSON.parse(localStorage.getItem(key) || "[]");
      h.push({ t, w });
      if (h.length > 300) h.shift();
      localStorage.setItem(key, JSON.stringify(h));
    }

    // ====== Helpers ======
    function esc(s) { return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"); }
    function md(raw) {
      let s = esc(raw);
      s = s.replace(/!\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)/g, (_, a, u) => `<img decoding="async" alt="${a||''}" src="${u}">`);
      s = s.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (_, t, u) => `<a href="${u}" target="_blank" rel="noopener noreferrer">${t}</a>`);
      s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
      s = s.replace(/(^|\n)\s*-\s+/g, '$1• ');
      s = s.replace(/\n/g, '<br>');
      return s;
    }
    function bubble(text, who = "b") {
      const row = document.createElement("div"); row.className = "row";
      const b = document.createElement("div"); b.className = `bub ${who === "u" ? "u" : "b"}`;
      b.innerHTML = md(text);
      row.appendChild(b);
      msgs.appendChild(row);
      msgs.scrollTop = msgs.scrollHeight;
    }
    function typing(v) { if (!typ) return; typ.style.display = v ? "flex" : "none"; if (msgs) msgs.scrollTop = msgs.scrollHeight; }

    function detectPhones(text) {
      const re = /(?:\+?57)?\D*(3\d{2})\D*(\d{3})\D*(\d{4})/g;
      const out = []; let m;
      while ((m = re.exec(text)) !== null) { out.push(`57${m[1]}${m[2]}${m[3]}`); }
      return [...new Set(out)];
    }
    function alreadySaved(phone) { const set = new Set(JSON.parse(localStorage.getItem(leadKey) || "[]")); return set.has(phone); }
    function markSaved(phone) { const set = new Set(JSON.parse(localStorage.getItem(leadKey) || "[]")); set.add(phone); localStorage.setItem(leadKey, JSON.stringify([...set])); }
    async function sendLead(phone, lastMsg) {
      if (!EP_LEADS) return;
      try {
        await fetch(EP_LEADS, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sessionId: sid, phone, message: String(lastMsg || "").slice(0, 500), page: location.href, ts: new Date().toISOString() }) });
      } catch (e) { /* ignore */ }
    }

    // ====== Open / Close robustos ======
    function openChat() {
      box.classList.add('open');
      box.style.display = 'flex';            // forzar visible
      box.setAttribute('aria-hidden', 'false');
      // focus input shortly after abrir
      setTimeout(() => { if (q) q.focus(); }, 120);
    }
    function closeChat() {
      box.classList.remove('open');
      box.style.display = 'none';            // forzar oculto
      box.setAttribute('aria-hidden', 'true');
    }

    // Cargar historial (no abrir)
    if (hist.length) hist.forEach(m => bubble(m.t, m.w));

    // Listeners (open/close)
    btn.addEventListener('click', (e) => { e.stopPropagation(); openChat(); });
    if (closeBtn) closeBtn.addEventListener('click', (e) => { e.stopPropagation(); closeChat(); });
    if (back) back.addEventListener('click', (e) => { e.stopPropagation(); showWelcome(); });

    // cerrar con ESC
    document.addEventListener('keydown', (e) => { if (e.key === "Escape") closeChat(); });

    // cerrar al clic fuera (opcional, cómodo en móviles)
    document.addEventListener('click', (e) => {
      if (!box.contains(e.target) && !btn.contains(e.target) && box.classList.contains('open')) {
        closeChat();
      }
    });

    // Mostrar welcome / chat helpers
    function showWelcome() { if (document.getElementById('wel')) { document.getElementById('wel').classList.remove('hidden'); document.getElementById('chat').classList.add('hidden'); back.classList.add('hidden'); } }
    function showChat() { if (document.getElementById('wel')) { document.getElementById('wel').classList.add('hidden'); document.getElementById('chat').classList.remove('hidden'); back.classList.remove('hidden'); } }

    // texto de bienvenida
    if (optChat) optChat.addEventListener('click', (e) => {
      e.stopPropagation();
      showChat();
      if (!hist.length) {
        const g = "¡Hola! Soy el asistente virtual de este negocio digital. ¿En qué puedo ayudarte hoy?";
        bubble(g, "b"); save(g, "b");
      }
    });

    // ====== Chat logic ======
    async function speak(t) {
      if (!t) return;
      bubble(t, "u");
      save(t, "u");
      typing(true);

      // lead si escribe celular
      const phones = detectPhones(t);
      if (phones.length) {
        const phone = phones[0];
        if (!alreadySaved(phone)) { await sendLead(phone, t); markSaved(phone); }
      }

      try {
        const r = await fetch(EP, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ chatInput: t, sessionId: sid, locale: "es", url: location.href }) });
        const d = await r.json().catch(() => ({}));
        const reply = d.reply ?? d.text ?? d.message ?? d.output ?? (Array.isArray(d.messages) && d.messages[0]?.content) ?? JSON.stringify(d);
        bubble(String(reply), "b");
        save(String(reply), "b");
      } catch (e) {
        bubble("⚠️ Error de conexión. Intenta de nuevo.", "b");
      } finally {
        typing(false);
      }
    }

    if (send) send.addEventListener('click', () => {
      const t = (q && q.value) ? q.value.trim() : "";
      if (!t) return;
      if (q) q.value = "";
      speak(t);
    });

    if (q) q.addEventListener('keydown', (e) => { if (e.key === "Enter") { e.preventDefault(); send && send.click(); } });

    // Inicial: oculto (por si CSS estaba diferente)
    closeChat();
  });
})();
</script>

<!-- /Widget Chat IA -->

				
			

Más Tutoriales

Ivan Rodriguez

Empresario Digital

This course was comprehensive and covered everything i needed to know about animation.

“Dui varius sed egestas et scelerisque vitae nulla. Sollicitudin semper vel amet sollicitudin laoreet arcu. Adipiscing euismod auctor velit amet integer erat ut sed porta. Augue eu nunc sem aliquet vulputate. Vulputate libero lorem massa faucibus leo hac tortor.”