File size: 13,845 Bytes
3cfed6e
db8a51a
187966e
 
 
 
 
db8a51a
 
 
187966e
 
 
 
 
 
 
 
db8a51a
 
 
 
 
187966e
db8a51a
 
 
3cfed6e
db8a51a
 
 
 
 
 
 
 
 
 
3cfed6e
 
 
 
 
 
 
 
 
 
 
 
db8a51a
3cfed6e
 
db8a51a
3cfed6e
db8a51a
3cfed6e
 
 
db8a51a
 
 
 
 
 
 
 
 
 
 
187966e
3cfed6e
db8a51a
3cfed6e
 
 
 
 
 
db8a51a
3cfed6e
db8a51a
 
 
 
 
3cfed6e
db8a51a
 
 
 
3cfed6e
db8a51a
 
 
 
 
 
 
 
 
 
 
 
3cfed6e
10d4cc8
 
 
 
 
 
 
 
 
 
 
3cfed6e
187966e
 
 
 
db8a51a
 
 
3cfed6e
 
db8a51a
187966e
 
 
db8a51a
 
 
 
 
 
 
 
 
 
187966e
3cfed6e
187966e
 
 
 
 
 
db8a51a
3cfed6e
 
db8a51a
3cfed6e
db8a51a
3cfed6e
10d4cc8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3cfed6e
 
db8a51a
 
187966e
3cfed6e
db8a51a
 
 
 
187966e
 
 
db8a51a
 
187966e
 
db8a51a
 
10d4cc8
 
db8a51a
 
 
187966e
 
 
 
3cfed6e
 
 
187966e
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/* Backend-served support chat widget — pro support-agent UI.
   Embed: <script src="https://BACKEND/widget.js?t=slug" defer></script> */
(function () {
  "use strict";

  var me = document.currentScript;
  if (!me) {
    var ss = document.getElementsByTagName("script");
    for (var i = ss.length - 1; i >= 0; i--) {
      if (ss[i].src && ss[i].src.indexOf("/widget.js") !== -1) { me = ss[i]; break; }
    }
  }
  var SRC = me ? new URL(me.src) : null;
  var BACKEND = SRC ? SRC.origin : "";
  var TENANT = (SRC && SRC.searchParams.get("t")) || "default";
  var QS = "?t=" + encodeURIComponent(TENANT);
  var SESSION_KEY = "ssb_session_id_" + TENANT;

  var cfg = {
    brand_name: "Asistente", brand_color: "#e8491d",
    welcome: "¡Hola! ¿En qué puedo ayudarte?",
    assistant_name: "Asistente", avatar_url: ""
  };

  function getSid() { try { return localStorage.getItem(SESSION_KEY) || null; } catch (e) { return null; } }
  function setSid(id) { try { if (id) localStorage.setItem(SESSION_KEY, id); } catch (e) {} }

  function shade(hex, p) {
    try {
      var n = parseInt(hex.replace("#", ""), 16);
      var r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;
      r = Math.max(0, Math.min(255, r + Math.round(255 * p / 100)));
      g = Math.max(0, Math.min(255, g + Math.round(255 * p / 100)));
      b = Math.max(0, Math.min(255, b + Math.round(255 * p / 100)));
      return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
    } catch (e) { return hex; }
  }

  // Professional "support agent" (person + headset) icon — clear & not a photo.
  var ICON_AGENT = '<svg viewBox="0 0 24 24"><path d="M21 12.3C21 6.9 16.8 3.2 12 3.2 7.3 3.2 3 6.85 3 12.38c-.6.34-1 .98-1 1.72v2c0 1.1.9 2 2 2h1v-6.1c0-3.87 3.13-7 7-7s7 3.13 7 7V19h-8v2h8c1.1 0 2-.9 2-2v-1.22c.59-.31 1-.92 1-1.64v-2.3c0-.7-.41-1.31-1-1.62zM9 14c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm6 0c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1zm3-3.5c-.96-2.28-3.21-3.89-5.84-3.89-2.63 0-4.88 1.61-5.84 3.89C7.55 8.99 9.68 8 12.05 8c2.36 0 4.5.99 5.95 2.5z"/></svg>';
  var ICON_SEND = '<svg viewBox="0 0 24 24"><path d="M3 11l18-8-8 18-2.5-7.5L3 11z"/></svg>';

  function fillAvatar(el) {
    if (cfg.avatar_url) {
      el.style.background = shade(cfg.brand_color, 40) + " url('" + cfg.avatar_url + "') center/cover no-repeat";
      el.innerHTML = "";
    } else {
      el.classList.add("ssb-av-icon");
      el.innerHTML = ICON_AGENT;
    }
  }

  function esc(s) { return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); }
  function md(text) {
    var lines = esc(text).split(/\n/), out = [], inList = false;
    for (var i = 0; i < lines.length; i++) {
      var ln = lines[i], bullet = ln.match(/^\s*[-•]\s+(.*)$/);
      if (bullet) { if (!inList) { out.push("<ul>"); inList = true; } out.push("<li>" + inline(bullet[1]) + "</li>"); }
      else { if (inList) { out.push("</ul>"); inList = false; } out.push(ln.trim() === "" ? "<br>" : "<div>" + inline(ln) + "</div>"); }
    }
    if (inList) out.push("</ul>");
    return out.join("");
  }
  function inline(s) {
    s = s.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
    s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
    s = s.replace(/`([^`]+)`/g, "<code>$1</code>");
    return s;
  }

  function injectStyles() {
    var c = cfg.brand_color, c2 = shade(c, -18);
    var css =
      ".ssb-av{background:linear-gradient(135deg," + shade(c, 8) + "," + c2 + ");border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;overflow:hidden}" +
      ".ssb-av-icon svg{width:64%;height:64%;fill:#fff}" +
      "#ssb-launch{position:fixed;bottom:20px;right:20px;width:62px;height:62px;border-radius:50%;cursor:pointer;z-index:99998;border:0;background:linear-gradient(135deg," + c + "," + c2 + ");box-shadow:0 8px 24px rgba(0,0,0,.28);display:flex;align-items:center;justify-content:center;transition:transform .2s}" +
      "#ssb-launch:hover{transform:scale(1.06)}#ssb-launch svg{width:32px;height:32px;fill:#fff}" +
      "#ssb-launch .ssb-dot{position:absolute;top:5px;right:5px;width:13px;height:13px;border-radius:50%;background:#22c55e;border:2px solid #fff}" +
      "#ssb-launch .ssb-pulse{position:absolute;inset:0;border-radius:50%;animation:ssbpulse 2.2s infinite}" +
      "@keyframes ssbpulse{0%{box-shadow:0 0 0 0 " + c + "66}70%{box-shadow:0 0 0 16px " + c + "00}100%{box-shadow:0 0 0 0 " + c + "00}}" +
      "#ssb-panel{position:fixed;bottom:20px;right:20px;width:380px;max-width:94vw;height:600px;max-height:80vh;background:#fff;border-radius:18px;box-shadow:0 18px 60px rgba(0,0,0,.3);display:none;flex-direction:column;overflow:hidden;z-index:99999;font-family:system-ui,-apple-system,Segoe UI,sans-serif;opacity:0;transform:translateY(16px) scale(.98);transition:opacity .22s,transform .22s}" +
      "#ssb-panel.open{display:flex;opacity:1;transform:none}" +
      "#ssb-head{background:linear-gradient(135deg," + c + "," + c2 + ");color:#fff;padding:14px 16px;display:flex;align-items:center;gap:12px}" +
      "#ssb-head .ssb-av{width:44px;height:44px;border:2px solid rgba(255,255,255,.7)}" +
      "#ssb-head .ssb-name{font-weight:700;font-size:15px;line-height:1.2}" +
      "#ssb-head .ssb-status{font-size:12px;opacity:.92;display:flex;align-items:center;gap:5px;margin-top:2px}" +
      "#ssb-head .ssb-status i{width:8px;height:8px;border-radius:50%;background:#22e07b;display:inline-block;animation:ssblive 2s infinite}" +
      "@keyframes ssblive{0%{box-shadow:0 0 0 0 #22e07b88}70%{box-shadow:0 0 0 6px #22e07b00}100%{box-shadow:0 0 0 0 #22e07b00}}" +
      "#ssb-head .ssb-x{margin-left:auto;background:transparent;border:0;color:#fff;font-size:24px;cursor:pointer;opacity:.85;line-height:1}" +
      "#ssb-msgs{flex:1;overflow-y:auto;padding:16px 14px;background:#f4f5f7;display:flex;flex-direction:column;gap:10px}" +
      ".ssb-row{display:flex;align-items:flex-end;gap:8px;max-width:88%}" +
      ".ssb-row.user{align-self:flex-end;flex-direction:row-reverse}.ssb-row.bot{align-self:flex-start}" +
      ".ssb-row .ssb-av{width:28px;height:28px}" +
      ".ssb-b{padding:10px 13px;border-radius:16px;font-size:14px;line-height:1.5;word-wrap:break-word}" +
      ".ssb-row.bot .ssb-b{background:#fff;color:#1a1a1a;border:1px solid #e6e6e9;border-bottom-left-radius:5px;box-shadow:0 1px 2px rgba(0,0,0,.04)}" +
      ".ssb-row.user .ssb-b{background:" + c + ";color:#fff;border-bottom-right-radius:5px}" +
      ".ssb-b a{color:inherit;text-decoration:underline}.ssb-row.bot .ssb-b a{color:" + c2 + "}" +
      ".ssb-b ul{margin:4px 0;padding-left:18px}.ssb-b li{margin:2px 0}.ssb-b code{background:#eee;padding:1px 4px;border-radius:4px;font-size:13px}" +
      ".ssb-typing{display:flex;gap:4px;padding:12px 14px}.ssb-typing span{width:7px;height:7px;border-radius:50%;background:#bbb;animation:ssbbounce 1.2s infinite}" +
      ".ssb-typing span:nth-child(2){animation-delay:.2s}.ssb-typing span:nth-child(3){animation-delay:.4s}" +
      "@keyframes ssbbounce{0%,60%,100%{transform:translateY(0);opacity:.5}30%{transform:translateY(-5px);opacity:1}}" +
      "#ssb-form{display:flex;align-items:center;gap:8px;padding:10px;border-top:1px solid #ececef;background:#fff}" +
      "#ssb-input{flex:1;border:1px solid #e0e0e4;border-radius:22px;padding:11px 15px;font-size:14px;outline:none}" +
      "#ssb-input:focus{border-color:" + c + "}" +
      "#ssb-send{width:42px;height:42px;border-radius:50%;border:0;background:" + c + ";color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;flex:none}#ssb-send svg{width:19px;height:19px;fill:#fff}" +
      "#ssb-foot{text-align:center;font-size:11px;color:#9aa;padding:6px;background:#fff}" +
      ".ssb-cards{align-self:flex-start;display:flex;flex-direction:column;gap:8px;max-width:92%;margin-left:36px}" +
      ".ssb-card{display:flex;gap:10px;background:#fff;border:1px solid #e6e6e9;border-radius:14px;padding:8px;box-shadow:0 1px 3px rgba(0,0,0,.05)}" +
      ".ssb-card img{width:60px;height:60px;object-fit:cover;border-radius:10px;flex:none;background:#f0f0f2}" +
      ".ssb-card .ssb-ci{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}" +
      ".ssb-card .ssb-ct{font-size:13px;font-weight:600;color:#1a1a1a;line-height:1.25}" +
      ".ssb-card .ssb-cp{font-size:14px;font-weight:700;color:" + c2 + "}" +
      ".ssb-card .ssb-cs{font-size:11px;font-weight:600}.ssb-card .ssb-in{color:#1a7f37}.ssb-card .ssb-out{color:#b00}" +
      ".ssb-card .ssb-cb{display:flex;gap:6px;margin-top:2px}" +
      ".ssb-card a.ssb-add{background:" + c + ";color:#fff;font-size:12px;font-weight:600;padding:6px 10px;border-radius:8px;text-decoration:none;text-align:center}" +
      ".ssb-card a.ssb-view{font-size:12px;color:#666;text-decoration:underline;padding:6px 4px;align-self:center}";
    var st = document.createElement("style"); st.textContent = css; document.head.appendChild(st);
  }

  function build() {
    injectStyles();
    var name = cfg.assistant_name || cfg.brand_name;

    var launch = document.createElement("button");
    launch.id = "ssb-launch"; launch.setAttribute("aria-label", "Abrir chat de soporte");
    launch.innerHTML = '<span class="ssb-pulse"></span>' + ICON_AGENT + '<span class="ssb-dot"></span>';

    var panel = document.createElement("div");
    panel.id = "ssb-panel";
    panel.innerHTML =
      '<div id="ssb-head"><div class="ssb-av"></div><div>' +
      '<div class="ssb-name">' + esc(name) + '</div>' +
      '<div class="ssb-status"><i></i>En línea · Responde en segundos</div></div>' +
      '<button class="ssb-x" aria-label="Cerrar">&times;</button></div>' +
      '<div id="ssb-msgs"></div>' +
      '<form id="ssb-form"><input id="ssb-input" type="text" autocomplete="off" placeholder="Escribe tu mensaje…" />' +
      '<button id="ssb-send" type="submit" aria-label="Enviar">' + ICON_SEND + '</button></form>' +
      '<div id="ssb-foot">Asistente con IA</div>';

    document.body.appendChild(launch);
    document.body.appendChild(panel);
    fillAvatar(panel.querySelector("#ssb-head .ssb-av"));

    var msgs = panel.querySelector("#ssb-msgs");
    var form = panel.querySelector("#ssb-form");
    var input = panel.querySelector("#ssb-input");
    var greeted = false;

    function row(who) {
      var r = document.createElement("div"); r.className = "ssb-row " + who;
      if (who === "bot") { var a = document.createElement("div"); a.className = "ssb-av"; fillAvatar(a); r.appendChild(a); }
      var b = document.createElement("div"); b.className = "ssb-b"; r.appendChild(b);
      msgs.appendChild(r); msgs.scrollTop = msgs.scrollHeight; return b;
    }
    function botMsg(t) { var b = row("bot"); b.innerHTML = md(t); msgs.scrollTop = msgs.scrollHeight; return b; }
    function money(p) { return p == null ? "" : (Math.round(p * 100) / 100).toLocaleString("es-ES", { minimumFractionDigits: 2 }) + " €"; }
    function renderCards(products) {
      if (!products || !products.length) return;
      var wrap = document.createElement("div"); wrap.className = "ssb-cards";
      products.slice(0, 6).forEach(function (p) {
        var card = document.createElement("div"); card.className = "ssb-card";
        var img = p.image ? '<img src="' + esc(p.image) + '" alt="" />' : '<img alt="" />';
        var stock = p.available ? '<span class="ssb-cs ssb-in">● En stock</span>' : '<span class="ssb-cs ssb-out">● Agotado</span>';
        var add = (p.available && p.add_url) ? '<a class="ssb-add" href="' + esc(p.add_url) + '" target="_blank" rel="noopener">Añadir al carrito</a>' : "";
        var view = p.url ? '<a class="ssb-view" href="' + esc(p.url) + '" target="_blank" rel="noopener">Ver</a>' : "";
        card.innerHTML = img + '<div class="ssb-ci"><div class="ssb-ct">' + esc(p.title || "") + '</div>' +
          '<div class="ssb-cp">' + money(p.price) + '</div>' + stock +
          '<div class="ssb-cb">' + add + view + '</div></div>';
        wrap.appendChild(card);
      });
      msgs.appendChild(wrap); msgs.scrollTop = msgs.scrollHeight;
    }
    function userMsg(t) { row("user").textContent = t; msgs.scrollTop = msgs.scrollHeight; }
    function typing() { var b = row("bot"); b.classList.add("ssb-typing"); b.innerHTML = "<span></span><span></span><span></span>"; msgs.scrollTop = msgs.scrollHeight; return b; }

    function toggle() {
      panel.classList.toggle("open");
      if (panel.classList.contains("open")) { if (!greeted) { botMsg(cfg.welcome); greeted = true; } input.focus(); }
    }
    launch.addEventListener("click", toggle);
    panel.querySelector(".ssb-x").addEventListener("click", toggle);

    form.addEventListener("submit", function (e) {
      e.preventDefault();
      var text = input.value.trim(); if (!text) return;
      userMsg(text); input.value = "";
      var t = typing();
      fetch(BACKEND + "/chat" + QS, {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ message: text, session_id: getSid() })
      }).then(function (r) { return r.json(); }).then(function (d) {
        setSid(d.session_id); t.classList.remove("ssb-typing"); t.innerHTML = md(d.reply || "…");
        renderCards(d.products); msgs.scrollTop = msgs.scrollHeight;
      }).catch(function () {
        t.classList.remove("ssb-typing"); t.textContent = "Lo siento, ha habido un problema. Inténtalo de nuevo.";
      });
    });
  }

  function start() {
    fetch(BACKEND + "/widget-config" + QS).then(function (r) { return r.json(); })
      .then(function (d) { if (d) cfg = Object.assign(cfg, d); }).catch(function () {})
      .then(function () { if (document.body) build(); else document.addEventListener("DOMContentLoaded", build); });
  }
  start();
})();