package services import ( "log" "strings" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // PlantillaRubro es un punto de partida para un agente nuevo: el conocimiento // base que casi todos los negocios de ese rubro necesitan, ya escrito. // // No reemplaza la información real del cliente — la reemplaza el cliente // editando estas notas. Existe porque un agente vacío no sirve para nada el // primer día, y escribir desde cero frente a un campo en blanco es donde la // mayoría abandona. type PlantillaRubro struct { Clave string `json:"clave"` Nombre string `json:"nombre"` Descripcion string `json:"descripcion"` Tono string `json:"tono"` Bienvenida string `json:"mensaje_bienvenida"` Notas []NotaPlanura `json:"notas"` } // NotaPlanura es una nota de conocimiento con su texto de ejemplo. El texto // está escrito para que se note que hay que cambiarlo: valores entre // corchetes, nunca datos inventados que parezcan reales. type NotaPlanura struct { Titulo string `json:"titulo"` Contenido string `json:"contenido"` } // PlantillasRubro es el catálogo. Vive en código y no en base de datos a // propósito: son contenido editorial que cambia cuando lo mejoramos nosotros, // no configuración que cada instalación toca por su lado. var PlantillasRubro = []PlantillaRubro{ { Clave: "generico", Nombre: "Negocio general", Descripcion: "La base que sirve para cualquier rubro: horarios, contacto, formas de pago.", Tono: "amable y directo", Bienvenida: "¡Hola! Contame en qué te puedo ayudar.", Notas: []NotaPlanura{ { Titulo: "Horarios y contacto", Contenido: `Atendemos de [lunes a viernes] de [9:00] a [18:00], y los [sábados] de [9:00] a [13:00]. Los [domingos] y feriados no atendemos. Nos pueden escribir por WhatsApp al [número] o al correo [correo]. Estamos en [dirección completa], [ciudad].`, }, { Titulo: "Formas de pago", Contenido: `Aceptamos [efectivo, tarjeta débito y crédito, transferencia y Nequi]. [No] aceptamos [cheques]. Para transferencias, la cuenta es [banco, tipo de cuenta, número] a nombre de [titular]. El pago se confirma cuando [nos llega el comprobante / se acredita].`, }, { Titulo: "Qué NO hacemos", Contenido: `Escribí acá lo que la gente pregunta seguido y la respuesta es que no. Ejemplos: no hacemos [envíos fuera de la ciudad], no trabajamos con [cierta marca], no atendemos [sin cita previa]. Esto evita que el asistente prometa cosas que después no podés cumplir.`, }, }, }, { Clave: "tienda", Nombre: "Tienda / e-commerce", Descripcion: "Envíos, cambios, garantía y estado de pedidos.", Tono: "cercano y resolutivo", Bienvenida: "¡Hola! ¿Buscás algo en particular o querés saber por un pedido?", Notas: []NotaPlanura{ { Titulo: "Envíos y zonas de cobertura", Contenido: `Hacemos envíos a [ciudades o zonas]. El costo del envío es [$X] y demora [N] días hábiles. Envío gratis por compras superiores a [$X]. A [zonas donde no llegan] no hacemos envíos. El pedido se despacha [el mismo día si se hace antes de las X / al día siguiente].`, }, { Titulo: "Cambios y devoluciones", Contenido: `Se pueden cambiar productos dentro de los [N] días desde la entrega. El producto tiene que estar [sin uso, con etiqueta, en su empaque original]. [No] aceptamos cambios en [productos en promoción / ropa interior / etc.]. El costo del envío del cambio lo cubre [el cliente / nosotros].`, }, { Titulo: "Garantía", Contenido: `Los productos tienen [N meses] de garantía por defectos de fábrica. La garantía no cubre [mal uso, daño por golpes, desgaste normal]. Para hacerla efectiva hace falta [la factura / el número de pedido].`, }, { Titulo: "Estado de un pedido", Contenido: `Si preguntan por un pedido, pediles el [número de pedido] y decíles que alguien del equipo lo revisa y responde en [tiempo]. No inventes estados de pedido: el asistente no tiene acceso al sistema de envíos.`, }, }, }, { Clave: "restaurante", Nombre: "Restaurante / comida", Descripcion: "Menú, reservas, domicilios y restricciones alimentarias.", Tono: "cálido y breve", Bienvenida: "¡Hola! ¿Querés ver el menú, reservar o pedir a domicilio?", Notas: []NotaPlanura{ { Titulo: "Horarios de cocina y domicilios", Contenido: `La cocina atiende de [12:00] a [15:00] y de [19:00] a [23:00]. Los domicilios salen hasta las [22:30]. [Día] cerramos. El domicilio demora entre [N] y [M] minutos según la zona.`, }, { Titulo: "Zonas de domicilio y costo", Contenido: `Llevamos a [barrios o zonas]. El costo del domicilio es [$X] o gratis por pedidos mayores a [$X]. A [zonas] no llegamos. Pedido mínimo: [$X].`, }, { Titulo: "Reservas", Contenido: `Tomamos reservas para grupos de [N] personas o más. Se reserva con [N horas] de anticipación. [Sí/No] cobramos seña. Las mesas se guardan [N] minutos después de la hora reservada.`, }, { Titulo: "Restricciones y alergias", Contenido: `Tenemos opciones [vegetarianas / veganas / sin gluten / sin lactosa]: [cuáles]. Nuestra cocina [sí/no] es libre de [maní, mariscos, gluten] — importante para alergias. Ante una consulta por alergia grave, siempre derivar a una persona del equipo.`, }, }, }, { Clave: "salud", Nombre: "Consultorio / clínica", Descripcion: "Turnos, obras sociales y qué NO puede responder un asistente.", Tono: "profesional y claro", Bienvenida: "Hola, ¿querés sacar un turno o consultar por coberturas?", Notas: []NotaPlanura{ { Titulo: "Límite importante: no damos consejo médico", Contenido: `Este asistente NO responde consultas médicas, no interpreta síntomas, no sugiere tratamientos ni medicamentos, y no dice si algo es urgente. Ante cualquier consulta de salud, la respuesta es derivar a un profesional y ofrecer sacar un turno. Si la persona describe una urgencia, indicar que llame al [número de emergencias] o vaya a la guardia más cercana.`, }, { Titulo: "Turnos", Contenido: `Atendemos con turno previo de [lunes a viernes] de [horario]. Los turnos se sacan por [WhatsApp / teléfono / la web]. La consulta dura aproximadamente [N] minutos. Si no podés venir, avisanos con [N horas] de anticipación.`, }, { Titulo: "Obras sociales y particulares", Contenido: `Trabajamos con [lista de obras sociales / prepagas]. [No] atendemos por [obras sociales específicas]. La consulta particular cuesta [$X]. Traer [documento, credencial, orden] el día del turno.`, }, { Titulo: "Dirección y cómo llegar", Contenido: `Estamos en [dirección], [ciudad]. [Referencias para llegar]. [Hay / no hay] estacionamiento. El consultorio [es / no es] accesible para silla de ruedas.`, }, }, }, { Clave: "servicios", Nombre: "Servicios profesionales", Descripcion: "Para estudios, agencias, técnicos: qué hacen, cómo cotizan, plazos.", Tono: "profesional y concreto", Bienvenida: "Hola, contame qué necesitás y te oriento.", Notas: []NotaPlanura{ { Titulo: "Qué hacemos y qué no", Contenido: `Nos dedicamos a [descripción del servicio]. Trabajamos con [tipo de clientes / rubros]. No hacemos [servicios fuera del alcance] — para eso conviene [alternativa].`, }, { Titulo: "Cómo cotizamos", Contenido: `Cada trabajo se cotiza según [alcance, horas, complejidad]. Un [servicio típico] arranca en [$X]. La cotización es [gratuita] y demora [N] días. Para cotizar necesitamos saber [qué datos]. Nunca des un precio cerrado si no está en esta lista: ofrecé armar una cotización.`, }, { Titulo: "Plazos y forma de trabajo", Contenido: `Un proyecto [típico] lleva entre [N] y [M] [semanas]. Trabajamos con [N]% de anticipo y el resto [contra entrega / en cuotas]. Las entregas se coordinan por [canal].`, }, }, }, { Clave: "belleza", Nombre: "Peluquería / estética", Descripcion: "Servicios, precios, turnos y políticas de cancelación.", Tono: "amable y cercano", Bienvenida: "¡Hola! ¿Querés reservar un turno o consultar precios?", Notas: []NotaPlanura{ { Titulo: "Servicios y precios", Contenido: `[Corte]: desde [$X] — dura [N] minutos. [Color]: desde [$X] — dura [N] minutos. [Otros servicios con precio y duración]. Los precios varían según [largo de pelo / cantidad de producto]; si preguntan por un caso puntual, ofrecé una consulta previa sin cargo.`, }, { Titulo: "Turnos y cancelaciones", Contenido: `Trabajamos con turno previo, de [lunes a sábado] de [horario]. Se reserva por [WhatsApp]. Si no podés venir, avisá con [N horas] de anticipación. [Cobramos / no cobramos] seña para [servicios largos]. Llegar más de [N] minutos tarde puede significar reprogramar el turno.`, }, { Titulo: "Cuidados previos y posteriores", Contenido: `Para [color / alisado], venir con el pelo [lavado / sin lavar]. Después de [servicio], no [lavarse el pelo / mojarse] por [N] horas. Recomendamos [productos] para mantener el resultado.`, }, }, }, } // PlantillaRubroPorClave busca una plantilla del catálogo. func PlantillaRubroPorClave(clave string) (*PlantillaRubro, bool) { for i := range PlantillasRubro { if PlantillasRubro[i].Clave == clave { return &PlantillasRubro[i], true } } return nil, false } // AplicarPlantillaRubro carga las notas de una plantilla como conocimiento del // agente. Cada nota entra como una fuente de tipo "texto" editable: el dueño // abre, reemplaza los corchetes por sus datos y guarda. // // Se ejecuta en segundo plano porque cada nota necesita sus embeddings, y el // agente ya se creó — hacer esperar la respuesta del alta por esto sería // castigar al que elige plantilla. func AplicarPlantillaRubro(agenteID uint, clave string) { plantilla, ok := PlantillaRubroPorClave(clave) if !ok { log.Printf("[UMIND] Plantilla de rubro %q no existe, el agente %d queda vacío", clave, agenteID) return } for _, nota := range plantilla.Notas { doc := &models.UmindDocumento{ AgenteID: agenteID, Tipo: "texto", Origen: nota.Titulo, Contenido: nota.Contenido, Estado: "procesando", } if err := models.CreateUmindDocumento(doc); err != nil { log.Printf("[UMIND] No se pudo crear la nota %q del agente %d: %v", nota.Titulo, agenteID, err) continue } IngestarTexto(agenteID, doc.ID, nota.Contenido) } log.Printf("[UMIND] Agente %d arrancó con la plantilla %q (%d notas)", agenteID, clave, len(plantilla.Notas)) } // ResumenPlantillasRubro devuelve el catálogo sin el contenido completo de las // notas — es lo que necesita la pantalla de elección para mostrar las opciones. func ResumenPlantillasRubro() []map[string]interface{} { out := make([]map[string]interface{}, 0, len(PlantillasRubro)) for _, p := range PlantillasRubro { titulos := make([]string, 0, len(p.Notas)) for _, n := range p.Notas { titulos = append(titulos, n.Titulo) } out = append(out, map[string]interface{}{ "clave": p.Clave, "nombre": p.Nombre, "descripcion": p.Descripcion, "notas": len(p.Notas), "titulos": titulos, "resumen": strings.Join(titulos, " · "), }) } return out }