feat(umind): que el cliente conecte su propia cuenta de IA

El modelo soportaba una config de IA por espacio desde hace varias fases, pero
no había forma de cargarla: ni el cliente desde el portal, ni el staff por él.
Quedaba como una columna que solo se podía llenar tocando la base a mano.

Ahora hay una pantalla — "Tu IA", desde la lista de agentes — donde se conecta
una cuenta de OpenAI, Anthropic, Gemini, Groq, DeepSeek, Qwen u Ollama. Con el
botón "Probar", que manda una consulta real: una clave vencida se descubre ahí
y no cuando un cliente escribe y no le contestan.

El aislamiento es la parte delicada, y va en tres capas:

Las configs globales son del staff. Un cliente puede usarlas —le aparecen en el
selector— pero no editarlas ni borrarlas; el acceso corta antes de mirar
permisos, así que ni siquiera se le confirma que existen. Sin eso, cualquiera
podría cambiarle el proveedor de IA a todos los demás o dejarlos sin servicio.

El tenant sale del alcance ya validado, nunca del body. Si viniera del cuerpo
de la petición, bastaría con cambiar el número para colgarle una config a otro
cliente, y con ella una clave que no es suya.

El tenant no se puede mover en una edición, por lo mismo al revés: sería
regalarle la propia.

La clave se guarda cifrada y no vuelve nunca al navegador — ni al dueño. Solo
sus últimos cuatro caracteres, que alcanzan para reconocer cuál cargó.

Borrar está bloqueado si hay agentes usándola: si no, quedarían apuntando a
algo inexistente y cayendo al proveedor global sin que nadie se entere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-08-20 23:08:43 -05:00
co-authored by Claude Opus 5
parent 195590f5e4
commit 926bbde9fb
13 changed files with 636 additions and 30 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 96 96'%3E%3Crect width='96' height='96' rx='22' fill='%238eb02f'/%3E%3Cpath d='M32,42 V58 A14,14 0 0 0 60,58 V42' fill='none' stroke='%23fff' stroke-width='10' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M60,58 V64' fill='none' stroke='%23fff' stroke-width='10' stroke-linecap='round'/%3E%3Ccircle cx='60' cy='28' r='7' fill='%23fff'/%3E%3C/svg%3E" />
<title>uMind Studio</title>
<script type="module" crossorigin src="/orchestrator/assets/index-Cv_aH8gm.js"></script>
<link rel="stylesheet" crossorigin href="/orchestrator/assets/index-Bk6phDz-.css">
<script type="module" crossorigin src="/orchestrator/assets/index-YAZk1p08.js"></script>
<link rel="stylesheet" crossorigin href="/orchestrator/assets/index-DjoN7J4s.css">
</head>
<!-- Sin clase de fondo: el color lo pone body en style.css desde los tokens,
que son los que cambian con el tema. Una utilidad acá le ganaba a la