Configuración IA: combo buscable para compañías detectadas y deduplicación de repetidos

This commit is contained in:
2026-08-28 21:10:24 +00:00
parent 272994ea08
commit bc519a76aa

View File

@@ -247,15 +247,23 @@
<label class="block text-sm font-medium text-gray-700">Condiciones de Cobertura por Compañía (Opcional)</label> <label class="block text-sm font-medium text-gray-700">Condiciones de Cobertura por Compañía (Opcional)</label>
<p class="text-xs text-gray-500 mb-2">Cuando el cliente pregunte por costes, materiales o qué cubre el seguro, la IA usará el texto de la compañía que coincida con el expediente. La palabra clave se busca dentro del campo "Compañía" del expediente (sin importar mayúsculas ni el resto del texto alrededor).</p> <p class="text-xs text-gray-500 mb-2">Cuando el cliente pregunte por costes, materiales o qué cubre el seguro, la IA usará el texto de la compañía que coincida con el expediente. La palabra clave se busca dentro del campo "Compañía" del expediente (sin importar mayúsculas ni el resto del texto alrededor).</p>
<div id="detectedCompaniesBox" class="hidden mb-3 p-3 bg-white border border-slate-200 rounded-lg"> <div id="detectedCompaniesBox" class="hidden mb-3">
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2">Compañías vistas en tus expedientes (pulsa para añadir)</p> <p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-2">Buscar compañía vista en tus expedientes</p>
<div id="detectedCompaniesList" class="flex flex-wrap gap-1.5"></div> <div class="relative">
<i data-lucide="search" class="w-3.5 h-3.5 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none"></i>
<input type="text" id="companySearchInput" autocomplete="off"
class="w-full border border-slate-300 rounded-lg pl-8 pr-3 py-2 text-xs font-bold text-slate-700 outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Escribe para buscar (ej: santander, repsol...)"
oninput="renderCompanyOptions(this.value)"
onfocus="renderCompanyOptions(this.value)">
<div id="companyOptionsList" class="hidden absolute z-20 mt-1 w-full max-h-56 overflow-y-auto bg-white border border-slate-200 rounded-lg shadow-lg"></div>
</div>
</div> </div>
<div id="coverageRulesList" class="space-y-3"></div> <div id="coverageRulesList" class="space-y-3"></div>
<button type="button" onclick="addCoverageRule()" class="mt-2 text-xs font-bold text-blue-600 hover:text-blue-800 flex items-center gap-1"> <button type="button" onclick="addCoverageRule()" class="mt-2 text-xs font-bold text-blue-600 hover:text-blue-800 flex items-center gap-1">
<i data-lucide="plus-circle" class="w-4 h-4"></i> Añadir compañía <i data-lucide="plus-circle" class="w-4 h-4"></i> Añadir compañía manualmente
</button> </button>
</div> </div>
@@ -1220,30 +1228,68 @@
if (window.lucide) lucide.createIcons(); if (window.lucide) lucide.createIcons();
} }
// Saca las compañías tal cual aparecen en tus expedientes recientes, para que // Quita mayúsculas y tildes para poder comparar/filtrar compañías sin que
// puedas pulsarlas y usarlas como palabra clave sin tener que adivinar el formato. // "Santander", "SANTANDER" o " Santander " cuenten como cosas distintas.
const normalizarTextoCfg = (str) => String(str || "").toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").trim();
let detectedCompaniesCache = [];
// Saca las compañías tal cual aparecen en tus expedientes recientes, deduplicadas
// (por texto normalizado, para que variantes de mayúsculas/tildes/espacios no salgan
// repetidas), para poder buscarlas y añadirlas como palabra clave sin adivinar el formato.
async function loadDetectedCompanies() { async function loadDetectedCompanies() {
try { try {
const res = await fetch(`${API_URL}/services/active`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const res = await fetch(`${API_URL}/services/active`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json(); const data = await res.json();
if (!data.ok) return; if (!data.ok) return;
const seen = new Set(); const seen = new Map(); // clave normalizada -> primer texto original visto
(data.services || []).forEach(s => { (data.services || []).forEach(s => {
const raw = s.raw_data || {}; const raw = s.raw_data || {};
const c = (raw["Compañía"] || raw["COMPAÑIA"] || "").toString().trim(); const c = (raw["Compañía"] || raw["COMPAÑIA"] || "").toString().trim();
if (c) seen.add(c); if (!c) return;
const key = normalizarTextoCfg(c);
if (!seen.has(key)) seen.set(key, c);
}); });
const box = document.getElementById('detectedCompaniesBox'); detectedCompaniesCache = [...seen.values()].sort((a, b) => a.localeCompare(b, 'es'));
const list = document.getElementById('detectedCompaniesList'); document.getElementById('detectedCompaniesBox').classList.toggle('hidden', detectedCompaniesCache.length === 0);
if (seen.size === 0) { box.classList.add('hidden'); return; }
list.innerHTML = [...seen].sort().map(c => `
<button type="button" onclick='addCoverageRule(${JSON.stringify(c)})' class="text-[11px] font-bold bg-slate-100 hover:bg-blue-100 hover:text-blue-700 text-slate-600 px-2.5 py-1 rounded-lg transition-colors">${c}</button>
`).join('');
box.classList.remove('hidden');
if (window.lucide) lucide.createIcons();
} catch (e) { console.error("Error cargando compañías detectadas", e); } } catch (e) { console.error("Error cargando compañías detectadas", e); }
} }
// Filtra el desplegable del combo de búsqueda mientras el usuario escribe.
function renderCompanyOptions(query) {
const list = document.getElementById('companyOptionsList');
const q = normalizarTextoCfg(query);
const matches = detectedCompaniesCache.filter(c => normalizarTextoCfg(c).includes(q));
if (matches.length === 0) {
list.innerHTML = `<div class="px-3 py-2 text-xs text-slate-400 italic">Sin coincidencias</div>`;
} else {
list.innerHTML = matches.map(c => `
<button type="button" onclick='selectDetectedCompany(${JSON.stringify(c)})' class="w-full text-left px-3 py-2 text-xs font-bold text-slate-600 hover:bg-blue-50 hover:text-blue-700 transition-colors border-b border-slate-50 last:border-0">${c}</button>
`).join('');
}
list.classList.remove('hidden');
}
// Al elegir una compañía del combo, se añade debajo su fila de condiciones lista
// para rellenar, y se limpia/cierra el buscador.
function selectDetectedCompany(company) {
addCoverageRule(company);
const input = document.getElementById('companySearchInput');
input.value = "";
document.getElementById('companyOptionsList').classList.add('hidden');
const rows = document.querySelectorAll('#coverageRulesList .coverage-rule-row');
const lastRow = rows[rows.length - 1];
if (lastRow) lastRow.querySelector('.cr-text').focus();
}
document.addEventListener('click', (e) => {
const box = document.getElementById('detectedCompaniesBox');
const optionsList = document.getElementById('companyOptionsList');
if (box && optionsList && !box.contains(e.target)) {
optionsList.classList.add('hidden');
}
});
async function saveWaSettings() { async function saveWaSettings() {
const coverageRules = [...document.querySelectorAll('#coverageRulesList .coverage-rule-row')] const coverageRules = [...document.querySelectorAll('#coverageRulesList .coverage-rule-row')]
.map(row => ({ .map(row => ({