Fix AI tab save: dedicated AJAX endpoint /save-ai

Instead of relying on the native HTML form (which couldn't reliably
isolate AI fields), the IA tab now uses an AJAX call identical to
save-per-type. The new /admin/bot-config/save-ai endpoint only
reads and merges AI fields into config_json, preserving menus/flows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Lizandro Guarnizo
2026-07-05 08:28:41 -05:00
co-authored by Claude Sonnet 4.6
parent 5afa846b4e
commit bb4b5dab10
2 changed files with 83 additions and 1 deletions
+36 -1
View File
@@ -3747,7 +3747,7 @@ HTML;
<strong>Requiere bot_type = hybrid y proveedor de IA configurado.</strong>
</div>
</div>
<div style="margin-top:14px"><button type="submit" class="btn-primary">💾 Guardar IA</button></div>
<div style="margin-top:14px"><button type="button" id="saveAiBtn" onclick="saveAiConfig()" class="btn-primary">💾 Guardar IA</button></div>
</div>
<!-- ─── GENERAL ──────────────────────────────────────────────────────── -->
@@ -3842,6 +3842,41 @@ function savePerType() {
});
}
function saveAiConfig() {
const cid = document.querySelector('input[name="company_id"]').value;
const fd = new FormData();
fd.set('company_id', cid);
const qs = sel => document.querySelector(sel);
fd.set('ai_provider', qs('select[name="ai_provider"]')?.value || '');
fd.set('ai_model', qs('select[name="ai_model"]')?.value || 'gpt-4o-mini');
fd.set('ai_temperature', qs('input[name="ai_temperature"]')?.value || '0.7');
fd.set('ai_prompt', qs('textarea[name="ai_prompt"]')?.value || '');
fd.set('gemini_model', qs('select[name="gemini_model"]')?.value || '');
fd.set('claude_model', qs('select[name="claude_model"]')?.value || '');
const oKey = qs('input[name="openai_api_key"]')?.value || '';
const gKey = qs('input[name="gemini_api_key"]')?.value || '';
const cKey = qs('input[name="claude_api_key"]')?.value || '';
if (oKey) fd.set('openai_api_key', oKey);
if (gKey) fd.set('gemini_api_key', gKey);
if (cKey) fd.set('claude_api_key', cKey);
if (qs('input[name="ai_for_media"]')?.checked) fd.set('ai_for_media', '1');
if (qs('input[name="nlu"]')?.checked) fd.set('nlu', '1');
const btn = document.getElementById('saveAiBtn');
if (btn) { btn.textContent = 'Guardando...'; btn.disabled = true; }
fetch('/admin/bot-config/save-ai', { method: 'POST', body: fd })
.then(r => r.json())
.then(d => {
if (btn) { btn.textContent = '💾 Guardar IA'; btn.disabled = false; }
if (d.ok) { btn.textContent = '✅ Guardado'; setTimeout(() => { btn.textContent = '💾 Guardar IA'; }, 2000); }
else alert('❌ Error al guardar: ' + JSON.stringify(d));
})
.catch(e => {
if (btn) { btn.textContent = '💾 Guardar IA'; btn.disabled = false; }
alert('❌ Error de red: ' + e.message);
});
}
function switchTab(tab, btn) {
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-btn').forEach(t => t.classList.remove('active'));
+47
View File
@@ -587,6 +587,53 @@ $routes = [
exit;
})()],
// ─── Admin: guardar solo campos IA (endpoint separado) ───────────────────
['POST', '/admin/bot-config/save-ai', fn() => (function () {
SessionAuth::require();
header('Content-Type: application/json; charset=utf-8');
$companyId = (int)($_POST['company_id'] ?? 0);
if ($companyId === 0) { echo json_encode(['ok' => false, 'error' => 'missing company_id']); exit; }
$company = CompanyRepository::findById($companyId);
if (!$company) { echo json_encode(['ok' => false, 'error' => 'not found']); exit; }
$config = json_decode($company['config_json'] ?? '{}', true) ?: [];
$aiProvider = trim($_POST['ai_provider'] ?? '');
if ($aiProvider !== '') $config['ai_provider'] = $aiProvider; else unset($config['ai_provider']);
$aiModel = trim($_POST['ai_model'] ?? '');
if ($aiModel !== '') $config['ai_model'] = $aiModel;
$aiTemp = trim($_POST['ai_temperature'] ?? '');
if ($aiTemp !== '') $config['ai_temperature'] = (float)$aiTemp;
$aiPrompt = trim($_POST['ai_prompt'] ?? '');
$config['ai_prompt'] = $aiPrompt;
$config['ai_for_media'] = isset($_POST['ai_for_media']);
$config['nlu'] = isset($_POST['nlu']);
$openaiKey = trim($_POST['openai_api_key'] ?? '');
if ($openaiKey !== '') $config['openai_api_key'] = $openaiKey;
$geminiKey = trim($_POST['gemini_api_key'] ?? '');
if ($geminiKey !== '') $config['gemini_api_key'] = $geminiKey;
$geminiModel = trim($_POST['gemini_model'] ?? '');
if ($geminiModel !== '') $config['gemini_model'] = $geminiModel;
$claudeKey = trim($_POST['claude_api_key'] ?? '');
if ($claudeKey !== '') $config['claude_api_key'] = $claudeKey;
$claudeModel = trim($_POST['claude_model'] ?? '');
if ($claudeModel !== '') $config['claude_model'] = $claudeModel;
CompanyRepository::save(['id' => $companyId, 'config_json' => json_encode($config, JSON_UNESCAPED_UNICODE)]);
echo json_encode(['ok' => true]);
exit;
})()],
// ─── Admin: guardar per_type por categoría (endpoint separado) ───────────
['POST', '/admin/bot-config/save-per-type', fn() => (function () {
SessionAuth::require();