diff --git a/servicios.html b/servicios.html index 866d72a..d6e0a20 100644 --- a/servicios.html +++ b/servicios.html @@ -65,17 +65,17 @@
-
+
-

En Curso

-

0

+

Espera Cliente

+

0

-
+
-

Finalizados

-

0

+

Incidencia

+

0

@@ -86,12 +86,16 @@ -
+
+
+ +
+
@@ -314,6 +318,23 @@ setInterval(refreshPanel, 20000); }); + // Helpers de Fechas para el filtro de semanas + function getWeekNumber(d) { + d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())); + d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay()||7)); + var yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1)); + var weekNo = Math.ceil(( ( (d - yearStart) / 86400000) + 1)/7); + return { year: d.getUTCFullYear(), week: weekNo }; + } + + function isDateInWeekString(dateStr, weekStr) { + if (!dateStr || !weekStr) return false; + const targetDate = new Date(dateStr); + const { year, week } = getWeekNumber(targetDate); + const targetWeekStr = `${year}-W${String(week).padStart(2, '0')}`; + return targetWeekStr === weekStr; + } + // 1. CARGAMOS LOS ESTADOS DEL SISTEMA async function loadStatuses() { try { @@ -371,7 +392,9 @@ }); const data = await res.json(); if (data.ok) { - localData = data.services; + // FILTRAR BLOQUEOS PARA QUE NO ENSUCIEN EL PANEL NI LAS ESTADÍSTICAS + localData = data.services.filter(s => s.provider !== 'SYSTEM_BLOCK'); + updateOperatorFilter(); renderLists(); } @@ -388,44 +411,34 @@ if (html.includes(`value="${currentValue}"`)) opSelect.value = currentValue; } - // ========================================== - // 🚀 LÓGICA INTELIGENTE DE ENRUTAMIENTO (ESTADOS) - VERSIÓN OPCIÓN 2 + // ========================================== + // 🚀 LÓGICA INTELIGENTE DE ENRUTAMIENTO (ESTADOS) // ========================================== function getServiceStateInfo(s) { const raw = s.raw_data || {}; - const dbStat = raw.status_operativo; // Aquí viene el ID del estado o texto antiguo + const dbStat = raw.status_operativo; - // 1. Prioridad Máxima: Bolsa de Trabajo / Robot WhatsApp if (!s.assigned_name && (s.automation_status === 'in_progress' || s.automation_status === 'failed')) { return { id: 'bolsa', name: s.automation_status === 'in_progress' ? 'Buscando Operario' : 'Fallo en Bolsa', color: s.automation_status === 'in_progress' ? 'amber' : 'red', isBlocked: false, is_final: false }; } - // 2. Si viene limpio del scraper sin estado -> Pendiente de Asignar if (!s.assigned_name && (!dbStat || dbStat === 'sin_asignar')) { const found = systemStatuses.find(st => st.name.toLowerCase().includes('pendiente de asignar')) || systemStatuses[0]; return { ...found, isBlocked: false }; } - // 3. Match Directo por ID (Si ya se guardó con el nuevo sistema de IDs numéricos) const foundById = systemStatuses.find(st => String(st.id) === String(dbStat)); if (foundById) return { ...foundById, isBlocked: false }; - // 4. Lógica de "Asignado" vs "Esperando Cliente" (Si tiene operario pero NO fecha) if (s.assigned_name && (!raw.scheduled_date || raw.scheduled_date === "")) { const asignado = systemStatuses.find(st => st.name.toLowerCase() === 'asignado'); const esperando = systemStatuses.find(st => st.name.toLowerCase().includes('esperando')); - // Si en BD es el texto antiguo 'esperando_cliente' if (dbStat === 'esperando_cliente' && esperando) return { ...esperando, isBlocked: false }; - - // Si es el texto antiguo 'asignado_operario' o no tiene estado definido pero sí tiene operario if ((dbStat === 'asignado_operario' || !dbStat) && asignado) return { ...asignado, isBlocked: false }; - - // Fallback por defecto si no encaja en lo anterior if (asignado) return { ...asignado, isBlocked: false }; } - // 5. Fallbacks históricos de texto (Para que los servicios antiguos no se rompan) const stLower = String(dbStat).toLowerCase(); if (stLower === 'citado' || (s.assigned_name && raw.scheduled_date && !dbStat)) { const citado = systemStatuses.find(st => st.name.toLowerCase().includes('citado')); @@ -437,7 +450,6 @@ if (stLower === 'desasignado') return { ...systemStatuses.find(st => st.name.toLowerCase().includes('desasignado')), isBlocked: false }; if (stLower === 'terminado' || stLower === 'finalizado') return { ...systemStatuses.find(st => st.is_final), isBlocked: false }; - // Fallback de seguridad return { id: 'unknown', name: 'Desconocido', color: 'gray', isBlocked: false, is_final: false }; } @@ -446,11 +458,12 @@ const searchTerm = document.getElementById('searchFilter').value.toLowerCase(); const selectedOp = document.getElementById('opFilter').value; + const weekValue = document.getElementById('weekFilter').value; // Ej: "2023-W12" let kpiUnassigned = 0; let kpiScheduled = 0; - let kpiActive = 0; - let kpiFinished = 0; + let kpiWaiting = 0; + let kpiIncident = 0; const filteredData = localData.filter(s => { const raw = s.raw_data || {}; @@ -461,44 +474,59 @@ const comp = (raw["Compañía"] || raw["COMPAÑIA"] || raw["Procedencia"] || "").toLowerCase(); const ref = (s.service_ref || "").toLowerCase(); const assigned = s.assigned_name || ""; + const dateRaw = raw.scheduled_date || ""; - // Calculamos el estado real y lo inyectamos const stateInfo = getServiceStateInfo(s); s._stateInfo = stateInfo; - // Lógica de KPIs de suma agrupada + // --- NUEVO REPARTO DE KPIs --- const stName = stateInfo.name.toLowerCase(); + + // 1. SIN ASIGNAR if (stateInfo.id === 'bolsa' || stName.includes('pendiente de asignar') || stName.includes('desasignado')) { kpiUnassigned++; - } else if (stateInfo.is_final || stName.includes('terminado') || stName.includes('anulado') || stName.includes('finalizado')) { - kpiFinished++; - } else if (stName === 'asignado' || stName.includes('esperando') || stName.includes('pendiente de cita') || stName.includes('citado')) { + } + // 2. INCIDENCIA (Roba prioridad a Terminados en el dashboard) + else if (stName.includes('incidencia') || stName.includes('pausa')) { + kpiIncident++; + } + // 3. AGENDADOS (Debe tener fecha) + else if (dateRaw !== "") { kpiScheduled++; - } else { - kpiActive++; // De Camino, Trabajando, Incidencia y los personalizados + } + // 4. ESPERA CLIENTE / EN CURSO (Tiene operario pero no fecha) + else if (!stateInfo.is_final && !stName.includes('terminado')) { + kpiWaiting++; } - // Aplicar Filtros Visuales + // --- FILTROS VISUALES (Buscador, Operario, Semana, Estado) --- const matchesSearch = searchTerm === "" || name.includes(searchTerm) || ref.includes(searchTerm) || addr.includes(searchTerm) || pop.includes(searchTerm) || phone.includes(searchTerm) || comp.includes(searchTerm); const matchesOp = selectedOp === "ALL" || assigned === selectedOp; + let matchesWeek = true; + if (weekValue !== "") { + // Si hemos filtrado por semana, mostramos los de esa semana O los que no tengan fecha para no perderlos de vista + if (dateRaw) { + matchesWeek = isDateInWeekString(dateRaw, weekValue); + } + } + let matchesStatus = false; if (activeStatusFilter === "ALL") { - // Ocultamos los Finalizados por defecto en la vista general para limpiar la pantalla if (stateInfo.is_final && searchTerm === "") matchesStatus = false; else matchesStatus = true; } else { matchesStatus = String(stateInfo.id) === activeStatusFilter; } - return matchesSearch && matchesOp && matchesStatus; + return matchesSearch && matchesOp && matchesWeek && matchesStatus; }); // Actualizamos Dashboards Superiores document.getElementById('kpi-unassigned').innerText = kpiUnassigned; document.getElementById('kpi-scheduled').innerText = kpiScheduled; - document.getElementById('kpi-active').innerText = kpiActive; - document.getElementById('kpi-finished').innerText = kpiFinished; + document.getElementById('kpi-waiting').innerText = kpiWaiting; + document.getElementById('kpi-incident').innerText = kpiIncident; const grid = document.getElementById('servicesGrid'); grid.innerHTML = filteredData.length > 0 @@ -632,7 +660,6 @@ const stateInfo = s._stateInfo; - // --- LÓGICA DE VISIBILIDAD DE PANELES MODIFICADA --- if (s.automation_status === 'in_progress') { document.getElementById('panelEnBolsa').classList.remove('hidden'); document.getElementById('panelAsignado').classList.add('hidden'); @@ -692,7 +719,6 @@ const selectedSt = systemStatuses.find(st => String(st.id) === String(statusMap)); - // Avisar si guarda sin fecha en un estado que debería tenerla if (selectedSt && !selectedSt.is_final && !date && !selectedSt.name.toLowerCase().includes('pausa') && !selectedSt.name.toLowerCase().includes('asignar')) { if(!confirm("No has asignado Fecha para este estado. ¿Deseas continuar?")) return; } @@ -751,14 +777,12 @@ const name = select.options[select.selectedIndex].text; const estadoAsignado = systemStatuses.find(st => st.name.toLowerCase() === 'asignado') || systemStatuses[1]; - // 1. PRIMERA LLAMADA: Detenemos la bolsa de trabajo automática await fetch(`${API_URL}/providers/scraped/${id}`, { method: 'PUT', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({ automation_status: 'completed' }) }); - // 2. SEGUNDA LLAMADA: Guardamos el operario y el estado "Asignado" await fetch(`${API_URL}/providers/scraped/${id}`, { method: 'PUT', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },