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>
90 lines
3.0 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|