feat: rediseña el widget embebible (ícono AI, color configurable, animaciones)

El bubble era un emoji 💬 sobre un color fijo hardcodeado en el JS. Ahora:
- Ícono nuevo (spark/sparkle, el motivo visual estándar de "asistente IA"
  hoy) en vez del emoji, con anillo de pulso al cargar para llamar la
  atención sin ser invasivo.
- Color configurable por tenant (UmindTenant.Color, validado como hex en el
  server) — se aplica en tiempo real vía /widget/:site_key/init, así que
  cambiarlo desde el panel no requiere recopiar el <script>.
- Panel con animación de entrada, botón de cerrar explícito, subtítulo
  "Asistente con IA · uMind" y badge de marca — antes solo tenía el nombre
  del tenant y ningún indicio de qué lo potencia.
- El widget ahora se inicializa al cargar el script (no al primer click), así
  el color/nombre reales ya están listos antes de que el visitante abra el chat.

Selector de color agregado al form de tenant en el orquestador (input
type=color + hex).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-12 20:03:51 -05:00
co-authored by Claude Sonnet 5
parent c2e9d2c5a2
commit 8c1e754ab3
8 changed files with 113 additions and 29 deletions
@@ -48,9 +48,23 @@ type umindTenantReq struct {
AiConfigID *uint `json:"ai_config_id"`
Tono string `json:"tono"`
MensajeBienvenida string `json:"mensaje_bienvenida"`
Color string `json:"color"`
Activo bool `json:"activo"`
}
var umindColorHexRegex = regexp.MustCompile(`^#[0-9a-fA-F]{6}$`)
// colorTenant valida el hex del color de marca del widget; si viene vacío o
// inválido cae al verde por defecto en vez de guardar basura (el valor se
// aplica tal cual como CSS custom property en el widget embebido).
func colorTenant(color string) string {
color = strings.TrimSpace(color)
if umindColorHexRegex.MatchString(color) {
return color
}
return "#8eb02f"
}
func (r umindTenantReq) dominiosLimpios() []string {
var out []string
for _, d := range r.DominiosPermitidos {
@@ -81,6 +95,7 @@ func CreateUmindTenantHandler(c *fiber.Ctx) error {
AiConfigID: req.AiConfigID,
Tono: req.Tono,
MensajeBienvenida: req.MensajeBienvenida,
Color: colorTenant(req.Color),
Activo: true,
CreadoPorID: extraerUserID(c),
}
@@ -106,6 +121,7 @@ func UpdateUmindTenantHandler(c *fiber.Ctx) error {
"ai_config_id": req.AiConfigID,
"tono": req.Tono,
"mensaje_bienvenida": req.MensajeBienvenida,
"color": colorTenant(req.Color),
"activo": req.Activo,
}
if err := models.UpdateUmindTenant(uint(id), updates); err != nil {