Files
soft_usite/pkg/services/umind_plantillas_rubro_test.go
T
Lizandro GuarnizoandClaude Opus 5 af71b66bb8 feat(umind): logo propio, plantillas de agente por rubro y página promocional
Logo. Hasta ahora no había: era el texto "uM" en un cuadrado, con tamaño y
esquinas distintas en cada pantalla. Ahora hay un mark — la "u" del nombre con
el punto de pulso integrado — probado a 16px, 20px y 32px sobre claro y oscuro,
que es donde se cae la mayoría de los logos. Reemplaza el texto en el portal,
el header, el sidebar del Studio y la promo, y va también de favicon.

Plantillas por rubro. Un agente recién creado no sabía nada, y escribir el
conocimiento desde cero frente a un campo en blanco es donde la mayoría
abandona. Al crear uno se puede elegir un punto de partida —tienda,
restaurante, consultorio, servicios, peluquería o la base genérica— y arranca
con sus notas ya escritas, listas para editar.

Los valores de ejemplo van entre corchetes a propósito, para que se vea que hay
que reemplazarlos: una nota que parezca dato real termina en boca del asistente.
Un test lo verifica nota por nota. Y la plantilla de salud abre con el límite —
el asistente no da consejo médico ni interpreta síntomas — porque es el único
rubro donde una respuesta inventada hace daño de verdad.

El catálogo vive en código, no en base: es contenido editorial que mejoramos
nosotros, no configuración que cada instalación toca por su lado. Las notas se
cargan en segundo plano porque cada una necesita sus embeddings, y hacer
esperar el alta sería castigar justo al que eligió plantilla.

Con las opciones nuevas el formulario pasaba de largo la pantalla y el botón de
guardar quedaba abajo del borde: el modal ahora tiene su propio scroll.

Y la página promocional de uMind queda servida en /umind.html.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 18:21:46 -05:00

90 lines
3.0 KiB
Go

package services
import (
"strings"
"testing"
)
// El catálogo es contenido que se le muestra al cliente y que además define el
// conocimiento inicial de su agente. Un título vacío o una nota sin marcadores
// para completar significa que alguien va a publicar datos de ejemplo como si
// fueran reales.
func TestCatalogoDeRubrosEstaCompleto(t *testing.T) {
if len(PlantillasRubro) < 3 {
t.Fatalf("el catálogo tiene %d plantillas, muy pocas para ofrecer", len(PlantillasRubro))
}
vistas := map[string]bool{}
for _, p := range PlantillasRubro {
if p.Clave == "" || p.Nombre == "" || p.Descripcion == "" {
t.Errorf("plantilla incompleta: %+v", p.Clave)
}
if vistas[p.Clave] {
t.Errorf("clave duplicada: %q — la segunda nunca se podría elegir", p.Clave)
}
vistas[p.Clave] = true
if len(p.Notas) == 0 {
t.Errorf("la plantilla %q no trae ninguna nota: elegirla sería igual que no elegir nada", p.Clave)
}
for _, n := range p.Notas {
if strings.TrimSpace(n.Titulo) == "" {
t.Errorf("nota sin título en %q", p.Clave)
}
if len(strings.TrimSpace(n.Contenido)) < 40 {
t.Errorf("nota %q de %q demasiado corta: no generaría ni un fragmento", n.Titulo, p.Clave)
}
}
}
}
// Los valores de ejemplo van entre corchetes justamente para que se note que
// hay que reemplazarlos. Una nota sin corchetes se lee como dato real y termina
// en boca del asistente.
func TestLasNotasPidenSerCompletadas(t *testing.T) {
for _, p := range PlantillasRubro {
for _, n := range p.Notas {
if !strings.Contains(n.Contenido, "[") {
t.Errorf("la nota %q de %q no tiene ningún marcador [entre corchetes]: se puede confundir con información real", n.Titulo, p.Clave)
}
}
}
}
// El rubro de salud no puede ofrecerse sin decir explícitamente que el
// asistente no da consejo médico: es el único donde una respuesta inventada
// hace daño de verdad.
func TestSaludAclaraQueNoDaConsejoMedico(t *testing.T) {
p, ok := PlantillaRubroPorClave("salud")
if !ok {
t.Fatal("falta la plantilla de salud")
}
junto := strings.ToLower(p.Notas[0].Titulo + " " + p.Notas[0].Contenido)
if !strings.Contains(junto, "no responde consultas médicas") && !strings.Contains(junto, "no damos consejo") {
t.Error("la primera nota de salud tiene que ser el límite: el asistente no da consejo médico")
}
}
func TestPlantillaRubroPorClave(t *testing.T) {
if _, ok := PlantillaRubroPorClave("no-existe"); ok {
t.Error("una clave inventada no debería resolver")
}
p, ok := PlantillaRubroPorClave("restaurante")
if !ok || p.Nombre == "" {
t.Error("restaurante debería existir en el catálogo")
}
}
func TestResumenNoFiltraElContenido(t *testing.T) {
// El resumen alimenta la pantalla de elección: manda títulos, no el texto
// completo de cada nota.
for _, r := range ResumenPlantillasRubro() {
if _, hay := r["contenido"]; hay {
t.Error("el resumen no debería incluir el contenido de las notas")
}
if r["clave"] == "" || r["nombre"] == "" {
t.Errorf("resumen incompleto: %+v", r)
}
}
}