Dos piezas que se necesitan mutuamente. ARCHIVOS — a nivel espacio y no de agente, porque los papeles son del negocio: si mañana crea un segundo agente, sus contratos no se mudan. Subir, descargar, borrar, cuota por plan, y un botón "usar como conocimiento" que lo manda por la ingesta que ya existe — un clic, no volver a subir el mismo PDF. Nunca se sirven por el estático: salen por su endpoint, que valida el tenant. La cuota se mira antes de escribir, porque rechazar después de copiar 25 MB al disco es cobrarle el espacio igual. PENDIENTES — no es un motor de workflows, es una tabla y una regla: lo que sale hacia afuera pedido por alguien que no es el dueño no se ejecuta, se encola. El eje es quién está del otro lado, no qué tan peligrosa suena la acción. En el widget público escribe cualquiera: ahí toda acción espera. En un canal interno el dueño ya autorizó al escribirlo, y mandarlo a aprobar su propio pedido sería fricción sin ninguna seguridad a cambio. (El plan decía aprobar siempre el envío de correo; esto es más flojo a propósito y la propiedad que importa se mantiene entera: desde un canal público no sale nada sin una persona.) Cuatro decisiones que valen más que el esquema: - Se guarda el payload EXACTO y se ejecuta eso. Aprobar no vuelve a llamar al modelo: si regenerara, aprobarías una cosa y saldría otra, y la diferencia aparecería recién en la mano del cliente. Hay un test que lo vigila. - Dos personas mirando la misma bandeja pueden aprobar a la vez; el reclamo es un UPDATE condicional, así la cotización no sale dos veces. - Vencen a los 7 días. Una cotización aprobada tres semanas tarde llega con precios de otro mes: es peor que ninguna. - Al cliente nunca se le dice "rechazado" ni se le menciona una aprobación interna — se le dice que le responde alguien del equipo. También con test. El correo de aviso lleva un enlace al panel CON login: un enlace que ejecuta algo irreversible sin autenticar es un enlace que reenviado por error firma. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
95 lines
5.2 KiB
Go
95 lines
5.2 KiB
Go
package routes
|
|
|
|
import (
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/sujit-baniya/fiber-boilerplate/rest/controllers"
|
|
)
|
|
|
|
// RegistrarRutasUmind monta los mismos handlers de uMind bajo dos prefijos:
|
|
// /app/umind para el staff y /portal/umind para el cliente. Una sola
|
|
// implementación, dos historias de autenticación — duplicar los handlers sería
|
|
// duplicar también las chances de que uno se olvide un chequeo de alcance.
|
|
//
|
|
// scope decide QUÉ tenants ve el que llama (UmindScopeStaff/UmindScopePortal).
|
|
// escritura middleware extra para POST/PUT/DELETE (SoloAdmin del lado staff;
|
|
// nil del lado del cliente, que ya está acotado por su scope).
|
|
//
|
|
// El CRUD de tenants NO se registra acá a propósito: crear/borrar tenants y
|
|
// asignarles cliente y plan es del staff, el cliente solo administra lo que
|
|
// hay adentro.
|
|
func RegistrarRutasUmind(g fiber.Router, scope fiber.Handler, escritura fiber.Handler) {
|
|
// Concatena el scope con el middleware de escritura, si lo hay. El scope
|
|
// va SIEMPRE, también en las lecturas.
|
|
w := func(h ...fiber.Handler) []fiber.Handler {
|
|
out := []fiber.Handler{scope}
|
|
if escritura != nil {
|
|
out = append(out, escritura)
|
|
}
|
|
return append(out, h...)
|
|
}
|
|
r := func(h ...fiber.Handler) []fiber.Handler {
|
|
return append([]fiber.Handler{scope}, h...)
|
|
}
|
|
|
|
g.Get("/umind/tenants", r(controllers.GetUmindTenants)...)
|
|
g.Get("/umind/agentes", r(controllers.GetUmindAgentesHandler)...)
|
|
g.Post("/umind/agentes", w(controllers.CreateUmindAgenteHandler)...)
|
|
g.Put("/umind/agentes/:id", w(controllers.UpdateUmindAgenteHandler)...)
|
|
g.Post("/umind/agentes/:id/duplicar", w(controllers.DuplicarUmindAgenteHandler)...)
|
|
g.Delete("/umind/agentes/:id", w(controllers.DeleteUmindAgenteHandler)...)
|
|
|
|
g.Get("/umind/plantillas-rubro", r(controllers.GetPlantillasRubroHandler)...)
|
|
g.Get("/umind/documentos", r(controllers.GetUmindDocumentosHandler)...)
|
|
g.Post("/umind/documentos", w(controllers.CreateUmindDocumentoHandler)...)
|
|
g.Post("/umind/documentos/texto", w(controllers.CreateUmindTextoHandler)...)
|
|
g.Post("/umind/documentos/archivo", w(controllers.CreateUmindArchivoHandler)...)
|
|
g.Get("/umind/documentos/:id/fragmentos", r(controllers.GetFragmentosDocumentoHandler)...)
|
|
g.Put("/umind/documentos/:id", w(controllers.ActualizarUmindDocumentoHandler)...)
|
|
g.Post("/umind/documentos/:id/reprocesar", w(controllers.ReprocesarUmindDocumentoHandler)...)
|
|
g.Delete("/umind/documentos/:id", w(controllers.DeleteUmindDocumentoHandler)...)
|
|
|
|
g.Get("/umind/sesiones", r(controllers.GetUmindSesionesHandler)...)
|
|
g.Get("/umind/historial", r(controllers.GetUmindHistorialHandler)...)
|
|
|
|
g.Get("/umind/tools", r(controllers.GetUmindHerramientasHandler)...)
|
|
g.Post("/umind/tools", w(controllers.CreateUmindHerramientaHandler)...)
|
|
g.Put("/umind/tools/:id", w(controllers.UpdateUmindHerramientaHandler)...)
|
|
g.Delete("/umind/tools/:id", w(controllers.DeleteUmindHerramientaHandler)...)
|
|
|
|
g.Get("/umind/canales", r(controllers.GetUmindCanalesHandler)...)
|
|
g.Post("/umind/canales", w(controllers.CreateUmindCanalHandler)...)
|
|
g.Put("/umind/canales/:id", w(controllers.UpdateUmindCanalHandler)...)
|
|
g.Delete("/umind/canales/:id", w(controllers.DeleteUmindCanalHandler)...)
|
|
|
|
g.Post("/umind/chat", r(controllers.UmindChatPruebaHandler)...)
|
|
|
|
g.Get("/umind/conexiones", r(controllers.GetUmindConexionesHandler)...)
|
|
g.Get("/umind/conexiones/conectar", w(controllers.UmindConectarHandler)...)
|
|
g.Get("/umind/acciones", w(controllers.GetUmindAccionesHandler)...)
|
|
g.Post("/umind/acciones/:id/aprobar", w(controllers.AprobarUmindAccionHandler)...)
|
|
g.Post("/umind/acciones/:id/rechazar", w(controllers.RechazarUmindAccionHandler)...)
|
|
g.Get("/umind/archivos", w(controllers.GetUmindArchivosHandler)...)
|
|
g.Post("/umind/archivos", w(controllers.CreateUmindArchivoRepoHandler)...)
|
|
g.Get("/umind/archivos/:id/descargar", w(controllers.DownloadUmindArchivoHandler)...)
|
|
g.Post("/umind/archivos/:id/conocimiento", w(controllers.UsarArchivoComoConocimientoHandler)...)
|
|
g.Delete("/umind/archivos/:id", w(controllers.DeleteUmindArchivoHandler)...)
|
|
g.Get("/umind/avisos", w(controllers.GetUmindAvisosHandler)...)
|
|
g.Delete("/umind/avisos/:id", w(controllers.DeleteUmindAvisoHandler)...)
|
|
g.Post("/umind/conexiones/imap", w(controllers.CrearConexionImapHandler)...)
|
|
g.Post("/umind/conexiones/:id/probar", w(controllers.ProbarConexionImapAgenteHandler)...)
|
|
g.Delete("/umind/conexiones/:id", w(controllers.DeleteUmindConexionHandler)...)
|
|
|
|
g.Get("/umind/eventos", r(controllers.GetUmindEventosHandler)...)
|
|
g.Get("/umind/uso", r(controllers.GetUmindUsoHandler)...)
|
|
g.Get("/umind/ai-configs", r(controllers.GetUmindAiConfigsHandler)...)
|
|
|
|
// La cuenta de IA propia del cliente: la carga él, queda atada a su espacio
|
|
// y nadie más la ve. Las configs globales del staff no se tocan desde acá.
|
|
g.Get("/umind/ai-propia", r(controllers.ListarAiConfigsPropiasHandler)...)
|
|
g.Post("/umind/ai-propia", w(controllers.CrearAiConfigPropiaHandler)...)
|
|
g.Put("/umind/ai-propia/:id", w(controllers.ActualizarAiConfigPropiaHandler)...)
|
|
g.Delete("/umind/ai-propia/:id", w(controllers.BorrarAiConfigPropiaHandler)...)
|
|
g.Post("/umind/ai-propia/:id/probar", w(controllers.ProbarAiConfigPropiaHandler)...)
|
|
g.Get("/umind/reporte.xlsx", r(controllers.GetUmindReporteHandler)...)
|
|
}
|