fix(ai-config): editar cualquier config dejaba al agente sin cerebro

es_agente_bot no se podía marcar desde ninguna pantalla —el formulario nunca
mandaba el campo— y el update lo escribía igual con el valor cero. O sea que
guardar cualquier config desde /app/ai-config apagaba el cerebro del bot de
Telegram y del chat del panel, y no había forma de volver a prenderlo salvo
tocando la base.

- El update solo escribe los campos que vinieron en el body.
- El formulario tiene la casilla y el selector de bot de Telegram.
- Marcar una desmarca la anterior: GetAgenteBotAiConfig hace First(), así que
  con dos marcadas ganaba la que estuviera primero en la tabla.

Y el otro comportamiento raro: cuando ningún módulo coincidía, se usaba
"cualquier config activa". Eso podía elegir la de embeddings o la de Whisper,
que no conversan — el error que llegaba era del proveedor y no se parecía en
nada a la causa. Ahora esas quedan excluidas del comodín y, si no queda
ninguna usable, el error dice qué módulo asignar y dónde.

De paso: la etiqueta "IA / vCard" mentía (ese módulo alimenta además soporte,
el chat del panel y las plantillas), el comentario de is_active decía "solo uno
activo a la vez" cuando hace falta uno por módulo, y GetActiveAiConfig no la
usaba nadie.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-17 21:16:15 -05:00
co-authored by Claude Opus 5
parent 6a3a8c9248
commit f5ffed7a13
4 changed files with 116 additions and 33 deletions
+26 -9
View File
@@ -126,6 +126,9 @@ func CreateAiConfigHandler(c *fiber.Ctx) error {
if err := models.CreateAiConfig(&item); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
if item.EsAgenteBot {
models.QuitarAgenteBotSalvo(item.ID)
}
return c.JSON(fiber.Map{"ok": true, "id": item.ID})
}
@@ -153,21 +156,35 @@ func UpdateAiConfigHandler(c *fiber.Ctx) error {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
}
// Qué campos vinieron de verdad. Sin esto, un formulario que no manda
// es_agente_bot lo guardaba en false: editar cualquier config desde el
// panel dejaba al bot de Telegram y al chat del dashboard sin cerebro.
var presentes map[string]json.RawMessage
_ = json.Unmarshal(c.Body(), &presentes)
vino := func(campo string) bool { _, ok := presentes[campo]; return ok }
updates := map[string]interface{}{
"nombre": strings.TrimSpace(req.Nombre),
"provider": strings.ToLower(strings.TrimSpace(req.Provider)),
"base_url": strings.TrimSpace(req.BaseURL),
"model_name": strings.TrimSpace(req.ModelName),
"is_active": req.IsActive,
"notes": req.Notes,
"modulo": models.JoinModulos(strings.Split(req.Modulo, ",")),
"es_agente_bot": req.EsAgenteBot,
"telegram_config_id": req.TelegramConfigID,
"nombre": strings.TrimSpace(req.Nombre),
"provider": strings.ToLower(strings.TrimSpace(req.Provider)),
"base_url": strings.TrimSpace(req.BaseURL),
"model_name": strings.TrimSpace(req.ModelName),
"is_active": req.IsActive,
"notes": req.Notes,
"modulo": models.JoinModulos(strings.Split(req.Modulo, ",")),
}
if vino("es_agente_bot") {
updates["es_agente_bot"] = req.EsAgenteBot
}
if vino("telegram_config_id") {
updates["telegram_config_id"] = req.TelegramConfigID
}
if strings.TrimSpace(req.ApiKey) != "" {
updates["api_key"] = models.CifrarClaveAi(strings.TrimSpace(req.ApiKey))
}
if req.EsAgenteBot && vino("es_agente_bot") {
models.QuitarAgenteBotSalvo(uint(id))
}
if err := models.UpdateAiConfig(uint(id), updates); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}