diff --git a/servicios2.html b/servicios2.html index 4e6101b..ff99070 100644 --- a/servicios2.html +++ b/servicios2.html @@ -68,71 +68,69 @@
- +
-
- +
- +
-
+
Cargando estados...
+ +
+
+
+
+

Sin Asignar

+

0

+
+
+
+
+
+

Sin Cita

+

0

+
+
+
+
+
+

Pte. Inicio

+

0

+
+
+
+
+
+

Trabajando

+

0

+
+
+
+
+
+

Anul/Finaliz

+

0

+
+
+
-
- -
-
-

Sin Asignar

- 0 -
-
-
- -
-
-

Sin Cita

- 0 -
-
-
- -
-
-

Pte. Inicio

- 0 -
-
-
- -
-
-

Trabajando

- 0 -
-
-
- -
-
-

Incidencias

- 0 -
-
-
- +
+
@@ -456,6 +454,7 @@ let systemStatuses = []; let systemGuilds = []; let activeStatusFilter = 'ALL'; + let activeKpiFilter = 'ACTIVE'; // Muestra todos menos los cerrados por defecto const colorDict = { 'gray': { bg: 'bg-slate-100', text: 'text-slate-600', dot: 'bg-slate-500', border: 'border-slate-200' }, @@ -545,10 +544,16 @@ container.innerHTML = html; } + function setKpiFilter(cat) { + if (activeKpiFilter === cat) activeKpiFilter = 'ACTIVE'; + else activeKpiFilter = cat; + renderLists(); + } + function setStatusFilter(id) { activeStatusFilter = id; renderStatusPills(); - renderKanban(); + renderLists(); } async function refreshData() { @@ -558,7 +563,7 @@ if (data.ok) { localData = data.services; updateOperatorFilter(); - renderKanban(); + renderLists(); } } catch (e) { console.error(e); } } @@ -573,26 +578,14 @@ if (html.includes(`value="${currentValue}"`)) opSelect.value = currentValue; } - // 馃 "IA" LIMPIADORA DE TEXTOS function limpiarPaja(texto) { - if (!texto) return "Sin descripci贸n detallada."; - let res = texto.replace(/(\r\n|\n|\r)/gm, " "); - res = res.replace(/\d{2}\/\d{2}\/\d{4}\s*-\s*/g, ' '); - const basura = [ - /Llama asegurad[oa]\s*\d*/gi, /solicita (operario|profesional) para/gi, - /Cobro banco.*?(?=\.|\s-|$)/gi, /El servicio dispone de hasta.*?(?=\.|\s-|$)/gi, - /Servicio asignado a:.*?(?=\.|\s-|$)/gi, /Cambio de estado:.*?(?=\.|\s-|$)/gi, - /en espera de profesional.*?(?=\.|\s-|$)/gi - ]; - basura.forEach(regex => { res = res.replace(regex, ' '); }); - res = res.replace(/\s+/g, ' ').replace(/\s,\s/g, ', ').replace(/^[,\.\-\s]+/, '').trim(); + if (!texto) return "Sin notas de aver铆a."; + let res = texto.replace(/(\r\n|\n|\r)/gm, " ").replace(/\d{2}\/\d{2}\/\d{4}\s*-\s*/g, ' '); + [/Llama asegurad[oa]\s*\d*/gi, /solicita (operario|profesional) para/gi, /Cobro banco.*?(?=\.|\s-|$)/gi, /El servicio dispone de hasta.*?(?=\.|\s-|$)/gi, /Servicio asignado a:.*?(?=\.|\s-|$)/gi, /Cambio de estado:.*?(?=\.|\s-|$)/gi, /en espera de profesional.*?(?=\.|\s-|$)/gi].forEach(rx => res = res.replace(rx, ' ')); + res = res.replace(/\s+/g, ' ').replace(/^[,\.\-\s]+/, '').trim(); return res.length > 5 ? res.charAt(0).toUpperCase() + res.slice(1) : texto; } - // ===================================== - // MOTOR KANBAN E INTELIGENCIA DE ESTADOS - // ===================================== - function getServiceStateInfo(s) { const raw = s.raw_data || {}; const dbStat = raw.status_operativo; @@ -602,7 +595,7 @@ } if (!s.assigned_name && (!dbStat || dbStat === 'sin_asignar')) { - return systemStatuses.find(st => st.name.toLowerCase().includes('pendiente de asignar')) || systemStatuses[0] || {id: 'sin_asignar', name: 'Sin Asignar', color: 'gray'}; + return systemStatuses.find(st => st.name.toLowerCase().includes('pendiente de asignar')) || systemStatuses[0] || {id: 'sin_asignar', name: 'Sin Asignar', color: 'gray', is_final: false}; } const foundById = systemStatuses.find(st => String(st.id) === String(dbStat)); @@ -620,153 +613,169 @@ const citado = systemStatuses.find(st => st.name.toLowerCase().includes('citado')); if(citado) return citado; } - if (stLower === 'de_camino') return systemStatuses.find(st => st.name.toLowerCase().includes('camino')) || {name: 'De Camino', color: 'blue'}; - if (stLower === 'trabajando') return systemStatuses.find(st => st.name.toLowerCase().includes('trabajando')) || {name: 'Trabajando', color: 'emerald'}; - if (stLower === 'incidencia') return systemStatuses.find(st => st.name.toLowerCase().includes('incidencia')) || {name: 'Incidencia', color: 'purple'}; + if (stLower === 'de_camino') return systemStatuses.find(st => st.name.toLowerCase().includes('camino')) || {name: 'De Camino', color: 'blue', is_final: false}; + if (stLower === 'trabajando') return systemStatuses.find(st => st.name.toLowerCase().includes('trabajando')) || {name: 'Trabajando', color: 'emerald', is_final: false}; + if (stLower === 'incidencia') return systemStatuses.find(st => st.name.toLowerCase().includes('incidencia')) || {name: 'Incidencia', color: 'purple', is_final: false}; return { id: 'unknown', name: 'Desconocido', color: 'gray', is_final: false }; } - function renderKanban() { + function renderLists() { + if(systemStatuses.length === 0) return; + const searchTerm = document.getElementById('searchFilter').value.toLowerCase(); const selectedOp = document.getElementById('opFilter').value; const weekValue = document.getElementById('weekFilter').value; - const cols = { c1: [], c2: [], c3: [], c4: [], c5: [] }; + let kpiUnassigned = 0; + let kpiUnscheduled = 0; + let kpiPendingStart = 0; + let kpiWorking = 0; + let kpiFinished = 0; - localData.forEach(s => { - if (s.status === 'archived' || s.provider === 'SYSTEM_BLOCK') return; - + const filteredData = localData.filter(s => { const raw = s.raw_data || {}; - const name = (raw["Nombre Cliente"] || raw["CLIENTE"] || "").toLowerCase(); - const ref = (s.service_ref || "").toLowerCase(); - const pop = (raw["Poblaci贸n"] || "").toLowerCase(); - const phone = (raw["Tel茅fono"] || "").toLowerCase(); - - // Filtro Buscador - if (searchTerm && !name.includes(searchTerm) && !ref.includes(searchTerm) && !pop.includes(searchTerm) && !phone.includes(searchTerm)) return; - - // Filtro Operario - if (selectedOp !== "ALL" && s.assigned_name !== selectedOp) return; - - // Filtro Semana - if (weekValue !== "") { - if (!raw.scheduled_date || !isDateInWeekString(raw.scheduled_date, weekValue)) return; - } - - // INTELIGENCIA DE ESTADOS const stateInfo = getServiceStateInfo(s); - s._stateInfo = stateInfo; // Guardamos para la tarjeta + s._stateInfo = stateInfo; + const stName = (stateInfo.name || "").toLowerCase(); - - // Filtro de Pastillas - if (activeStatusFilter !== "ALL" && String(stateInfo.id) !== activeStatusFilter) return; - - // Ocultar finalizados - if (stateInfo.is_final || stName.includes('finaliza') || stName.includes('anulad') || stName.includes('terminad')) return; - const isWorking = stName.includes('trabaja') || stName.includes('camino'); const hasDate = raw.scheduled_date && raw.scheduled_date.trim() !== ""; - const isIncident = stName.includes('incidencia') || stName.includes('pausa') || stName.includes('espera'); + const isFinal = stateInfo.is_final || stName.includes('finaliza') || stName.includes('anulad') || stName.includes('terminad') || s.status === 'archived'; - // REPARTO KANBAN INTELIGENTE (5 COLUMNAS) - if (!s.assigned_to) { - cols.c1.push(s); - } else if (isIncident) { - cols.c5.push(s); - } else if (!hasDate) { - cols.c2.push(s); - } else if (!isWorking) { - cols.c3.push(s); + let category = 'none'; + + if (isFinal) { kpiFinished++; category = 'finished'; } + else if (!s.assigned_to || stateInfo.id === 'bolsa' || stName.includes('asignar')) { kpiUnassigned++; category = 'unassigned'; } + else if (!hasDate) { kpiUnscheduled++; category = 'unscheduled'; } + else if (!isWorking) { kpiPendingStart++; category = 'pending_start'; } + else { kpiWorking++; category = 'working'; } + + const name = (raw["Nombre Cliente"] || raw["CLIENTE"] || "").toLowerCase(); + const ref = (s.service_ref || "").toLowerCase(); + const phone = (raw["Tel茅fono"] || "").toLowerCase(); + const pop = (raw["Poblaci贸n"] || "").toLowerCase(); + + const matchesSearch = searchTerm === "" || name.includes(searchTerm) || ref.includes(searchTerm) || phone.includes(searchTerm) || pop.includes(searchTerm); + const matchesOp = selectedOp === "ALL" || s.assigned_name === selectedOp; + + let matchesWeek = true; + if (weekValue !== "" && raw.scheduled_date) { + matchesWeek = isDateInWeekString(raw.scheduled_date, weekValue); + } else if (weekValue !== "") { + matchesWeek = false; + } + + let matchesStatus = (activeStatusFilter === "ALL") ? true : String(stateInfo.id) === activeStatusFilter; + + let matchesKpi = false; + if (activeKpiFilter === 'ACTIVE') { + matchesKpi = category !== 'finished'; } else { - cols.c4.push(s); + matchesKpi = category === activeKpiFilter; + } + + return matchesSearch && matchesOp && matchesWeek && matchesStatus && matchesKpi; + }); + + document.getElementById('kpi-unassigned').innerText = kpiUnassigned; + document.getElementById('kpi-unscheduled').innerText = kpiUnscheduled; + document.getElementById('kpi-pending-start').innerText = kpiPendingStart; + document.getElementById('kpi-working').innerText = kpiWorking; + document.getElementById('kpi-finished').innerText = kpiFinished; + + ['unassigned', 'unscheduled', 'pending_start', 'working', 'finished'].forEach(k => { + const box = document.getElementById(`box-${k}`); + if (box) { + if (activeKpiFilter === k) box.classList.add('ring-2', 'ring-blue-500', 'bg-blue-50'); + else box.classList.remove('ring-2', 'ring-blue-500', 'bg-blue-50'); } }); - document.getElementById('count-c1').innerText = cols.c1.length; - document.getElementById('count-c2').innerText = cols.c2.length; - document.getElementById('count-c3').innerText = cols.c3.length; - document.getElementById('count-c4').innerText = cols.c4.length; - document.getElementById('count-c5').innerText = cols.c5.length; + const grid = document.getElementById('servicesGrid'); + grid.innerHTML = filteredData.length > 0 + ? filteredData.map(s => buildGridCard(s)).join('') + : `
+ +

No hay servicios que coincidan

+
`; - document.getElementById('col-1').innerHTML = cols.c1.map(s => buildCard(s, 1)).join(''); - document.getElementById('col-2').innerHTML = cols.c2.map(s => buildCard(s, 2)).join(''); - document.getElementById('col-3').innerHTML = cols.c3.map(s => buildCard(s, 3)).join(''); - document.getElementById('col-4').innerHTML = cols.c4.map(s => buildCard(s, 4)).join(''); - document.getElementById('col-5').innerHTML = cols.c5.map(s => buildCard(s, 5)).join(''); - lucide.createIcons(); } - function buildCard(s, colType) { + function buildGridCard(s) { const raw = s.raw_data || {}; - const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado"; - const addr = raw["Direcci贸n"] || "Sin direcci贸n"; - const pop = raw["Poblaci贸n"] || ""; - const comp = (raw["Compa帽铆a"] || raw["Procedencia"] || "Particular").split('-')[0].trim().substring(0,10); + const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado Sin Nombre"; + const addr = raw["Direcci贸n"] || raw["DOMICILIO"] || "---"; + const pop = raw["Poblaci贸n"] || raw["POBLACION-PROVINCIA"] || ""; + const fullAddr = `${addr} ${pop}`.trim(); + const cita = raw.scheduled_date ? `${raw.scheduled_date.split('-').reverse().slice(0,2).join('/')} | ${raw.scheduled_time}` : 'Pte. Cita'; + const companyName = raw['Compa帽铆a'] || raw['COMPA脩IA'] || raw['Procedencia'] || (s.provider === 'MANUAL' ? 'PARTICULAR' : 'ASEGURADORA'); - const guildObj = systemGuilds.find(g => String(g.id) === String(s.guild_id || raw.guild_id)); - const guildName = guildObj ? guildObj.name : 'Reparaci贸n'; + const isUrgent = s.is_urgent === true || (raw['Urgente'] && String(raw['Urgente']).toLowerCase() === 's铆') || (raw['URGENTE'] && String(raw['URGENTE']).toLowerCase() === 'si'); + + const stateInfo = s._stateInfo || {name: 'Desconocido', color: 'gray'}; + const colorData = colorDict[stateInfo.color] || colorDict['gray']; + + let iconEstado = 'tag'; + if(raw.scheduled_date) iconEstado = 'calendar'; + if(stateInfo.name.toLowerCase().includes('camino')) iconEstado = 'car'; + if(stateInfo.name.toLowerCase().includes('reparaci') || stateInfo.name.toLowerCase().includes('trabaja')) iconEstado = 'wrench'; + if(stateInfo.name.toLowerCase().includes('incidencia') || stateInfo.name.toLowerCase().includes('pausa')) iconEstado = 'alert-triangle'; + + const inProgressBadge = s.automation_status === 'in_progress' ? ` En Bolsa` : ''; + + // 馃 IA Y ALERTAS const descLimpia = limpiarPaja(raw["Descripci贸n"] || raw["DESCRIPCION"] || raw["Averia"]); - // Alertas (Candado y Ojos) const hasLock = raw.has_lock === true || String(raw.has_lock) === 'true'; const hasEyes = raw.has_eyes === true || String(raw.has_eyes) === 'true'; let alerts = ''; - if (hasLock) alerts += ``; - if (hasEyes) alerts += ``; + if (hasLock) alerts += ``; + if (hasEyes) alerts += ``; - // Identificar el estado exacto actual leyendo la IA guardada - const stateInfo = s._stateInfo || {name: 'Desconocido', color: 'gray'}; - const cMap = colorDict[stateInfo.color] || colorDict['gray']; - - // Pie de tarjeta din谩mico - let bottomInfo = ''; - if (colType === 1) { - if (s.automation_status === 'in_progress') { - bottomInfo = ` Buscando...`; - } else { - bottomInfo = ` Falta T茅cnico`; - } - } else if (colType === 2) { - bottomInfo = ` -
-
- ${s.assigned_name} -
`; - } else { - const fDate = raw.scheduled_date ? raw.scheduled_date.split('-').reverse().slice(0,2).join('/') : ''; - bottomInfo = ` -
- - ${fDate} | ${raw.scheduled_time || ''} -
-
- ${s.assigned_name ? s.assigned_name.substring(0,2).toUpperCase() : '?'} -
`; - } + // 馃懟 FANTASMA DE CERRADOS + const isFinal = stateInfo.is_final || stateInfo.name.toLowerCase().includes('finaliza') || stateInfo.name.toLowerCase().includes('anulad') || s.status === 'archived'; + const opacityClass = isFinal ? 'opacity-60 grayscale-[40%]' : ''; + const watermark = isFinal ? `
${stateInfo.name.toLowerCase().includes('anulad') ? 'Anulado' : 'Cerrado'}
` : ''; return ` -
- ${s.is_urgent ? '
Urgente
' : ''} +
+ ${watermark} + ${inProgressBadge} -
-
- #${s.service_ref} - ${comp} +
+
+ +
+ ${stateInfo.name} +
+
+ ${isUrgent ? `URGENTE` : `#${s.service_ref}`} +
+ +
+
+ ${companyName} ${alerts}
+

${name}

+

${fullAddr}

-
-

${name}

-

${addr}, ${pop}

+
+

${descLimpia}

- -

${descLimpia}

- -
- ${bottomInfo} + +
+
+
+ ${s.assigned_name || 'Sin asignar'} +
+
+ + ${raw.scheduled_date ? cita.split('|')[0] : 'Pte. Cita'} +
`; }