feat: bucles de crecimiento y activación de uMind
Cuatro cosas que ya estaban a medio construir y no se usaban como canal. 1. El badge del widget ahora es un enlace con UTM. Cada cliente ya te estaba dando exposición en su sitio y no se capitalizaba. El host sale de la URL del propio script, así funciona igual en cualquier entorno. 2. Reporte del mes por agente en Excel: conversaciones atendidas, con qué las abrió el visitante, y el consumo desglosado. Es lo que el cliente necesita para justificar el gasto puertas adentro — un panel al que hay que entrar no sirve para eso, un archivo que se reenvía sí. Reusa el generador de xlsx del cronograma. 3. Aviso automático de agentes sin base de conocimiento (cron diario). Un agente sin fuentes responde de memoria e inventa datos, el cliente concluye que el producto no sirve y se va. Es el punto donde más gente se cae y se detecta solo. Se avisa UNA vez por agente, usando el log de auditoría como registro de envío para no necesitar tabla nueva ni convertir el recordatorio en spam. 4. Tarjeta de uMind en el dashboard del portal: atajo si el cliente ya lo tiene, oferta si no. Es el punto de contacto más barato que hay — ya entró, ya confía, y el cobro se suma a la factura que ya recibe. El mensaje lidera con notas de voz y fotos, que es lo más difícil de copiar de lo que tenemos. La consulta de conversaciones agrupa en SQL en vez de traerse el historial entero para agrupar en Go. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
cfa91a5c09
commit
8ef49c5169
@@ -1,6 +1,8 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -268,3 +270,39 @@ func GetUmindSesiones(agenteID uint, limit int) ([]UmindMensaje, error) {
|
||||
`, agenteID, limit).Scan(&items).Error
|
||||
return items, err
|
||||
}
|
||||
|
||||
// SesionResumen es una conversación vista desde el reporte: cuándo empezó,
|
||||
// cuántos mensajes tuvo y con qué la abrió el visitante. El primer mensaje es
|
||||
// lo más útil del conjunto — muestra qué le preguntan de verdad al negocio.
|
||||
type SesionResumen struct {
|
||||
SessionID string `json:"session_id"`
|
||||
Inicio time.Time `json:"inicio"`
|
||||
Mensajes int64 `json:"mensajes"`
|
||||
PrimerMensaje string `json:"primer_mensaje"`
|
||||
}
|
||||
|
||||
// GetUmindSesionesRango agrupa los mensajes en conversaciones dentro de un
|
||||
// rango. Una sola consulta: el primer mensaje del visitante se saca con una
|
||||
// subconsulta correlacionada en vez de traer todos los mensajes y agrupar en
|
||||
// Go, que en un agente con tráfico sería traerse el historial entero.
|
||||
func GetUmindSesionesRango(agenteID uint, desde, hasta time.Time) ([]SesionResumen, error) {
|
||||
var items []SesionResumen
|
||||
err := app.Http.Database.DB.Raw(`
|
||||
SELECT
|
||||
m.session_id,
|
||||
MIN(m.created_at) AS inicio,
|
||||
COUNT(*) AS mensajes,
|
||||
COALESCE((
|
||||
SELECT p.content FROM umind_mensajes p
|
||||
WHERE p.session_id = m.session_id AND p.agente_id = m.agente_id
|
||||
AND p.role = 'user' AND p.deleted_at IS NULL
|
||||
ORDER BY p.created_at ASC LIMIT 1
|
||||
), '') AS primer_mensaje
|
||||
FROM umind_mensajes m
|
||||
WHERE m.agente_id = ? AND m.created_at >= ? AND m.created_at < ? AND m.deleted_at IS NULL
|
||||
GROUP BY m.session_id, m.agente_id
|
||||
ORDER BY inicio DESC
|
||||
LIMIT 2000
|
||||
`, agenteID, desde, hasta).Scan(&items).Error
|
||||
return items, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user