Prepara el terreno para que el cliente cargue su propia cuenta de IA sin
que su clave quede en texto plano ni que el selector le muestre las de
los demás.
- ClaveEnClaro() descifra con fallback a texto plano: las filas viejas se
leen igual y quedan cifradas al primer guardado, sin script ni downtime.
utils.Decrypt hace panic (no devuelve error) con entrada que no es un
ciphertext válido, así que el fallback va sobre recover — eso mismo es
el mecanismo de detección de "todavía está en claro".
- Migrados TODOS los lectores: uMind (chat y embeddings), bot de Telegram,
Whisper, Query Runner, streaming de IA y Landing Generator. Un lector
sin migrar mandaría el ciphertext como API key.
- AiConfig gana TenantID (null = global del staff) y
GetAiConfigSelectPorTenants para acotar el selector.
- Test de los 4 casos del fallback, incluido hex válido que no descifra.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
El fix anterior de Gemini (22efb9c) solo tocó providerDefaultURL, que usan
el chat/embeddings/whisper reales. TestAiConfigHandler (el botón "Probar
conexión" del panel de AI Config) tenía una tercera copia independiente del
mismo mapeo, sin caso para Gemini ni Deepseek — por eso el chat ya
funcionaba con Gemini pero la prueba de conexión seguía fallando.
Se exporta providerDefaultURL a services.ProviderDefaultURL y el controller
la reusa, en vez de mantener una copia más que se desactualiza cada vez que
se agrega un proveedor.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Endpoint GET /ai-config/:id/test que llama /api/tags (Ollama) o /v1/models (OpenAI-compat/Anthropic)
- Botón "Probar" en cada fila de la tabla con modal de resultado JSON
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>