From bda5f3a7b50bcf52fb811d4d5f24162eb9172e88 Mon Sep 17 00:00:00 2001 From: marsalva Date: Tue, 17 Feb 2026 22:06:16 +0000 Subject: [PATCH] Actualizar panel.html --- panel.html | 250 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 192 insertions(+), 58 deletions(-) diff --git a/panel.html b/panel.html index 79b23e1..7b3f457 100644 --- a/panel.html +++ b/panel.html @@ -27,20 +27,20 @@
-
+

DASHBOARD

-

Resumen del estado de todos los expedientes y citas de hoy.

+

Resumen del estado de todos los expedientes y métricas de hoy.

-
+
@@ -66,7 +66,7 @@

-

-

Buscando Operario

+

Buscando Operario (WA)

@@ -80,23 +80,74 @@
-

Citas de Hoy

+

Citas Hoy

-

Programadas para hoy

+
+
+

Urgencias

+
+
+

-

+

Atención prioritaria

+
+
-
-
- -

Agenda de Visitas para Hoy

-
+
-
-

Cargando agenda...

+
+ +
+
+
+

Agenda de Visitas para Hoy

+
+
+

Cargando agenda...

+
+
+ +
+
+ +
+
+
+

Avisos Urgentes Activos

+
+
+

Buscando urgencias...

+
+
+
+ +
+ +
+
+
+

Volumen por Aseguradora

+
+
+

Calculando...

+
+
+ +
+
+
+

Últimas 5 Entradas

+
+
+

Cargando actividad...

+
+
+
@@ -122,16 +173,12 @@ async function loadDashboardData(token) { try { - // 1. Pedimos los datos del Panel Operativo (los que ya están asignados o citados) - const resActive = await fetch(`${API_URL}/services/active`, { headers: { "Authorization": `Bearer ${token}` } }); - const dataActive = await resActive.json(); - - // 2. Pedimos los datos de Proveedores (para saber cuántos están en bandeja y en cola) + // 1. Pedimos todos los datos (el endpoint de scraped trae todos los del panel también) const resScraped = await fetch(`${API_URL}/providers/scraped`, { headers: { "Authorization": `Bearer ${token}` } }); const dataScraped = await resScraped.json(); - if (dataActive.ok && dataScraped.ok) { - processDashboard(dataActive.services, dataScraped.services); + if (dataScraped.ok) { + processDashboard(dataScraped.services); } } catch (e) { console.error("Error cargando dashboard:", e); @@ -139,83 +186,88 @@ } } - function processDashboard(activeServices, scrapedServices) { - // Fecha de hoy en formato YYYY-MM-DD para comparar + function processDashboard(allServices) { + // Filtrar SOLO LOS ACTIVOS reales (Ignoramos los archivados) + const activeServices = allServices.filter(s => s.status !== 'archived'); + const todayStr = new Date().toISOString().split('T')[0]; - // --- CÁLCULO DE KPIs --- + // --- CÁLCULO DE KPIs CORREGIDO --- - // 1. SIN ASIGNAR (En bandeja de entrada, status pending, sin mandar a la rueda automatizada) - const unassignedCount = scrapedServices.filter(s => s.status === 'pending' && s.automation_status !== 'in_progress').length; + // 1. Total (Solución al fallo de la suma doble) + const totalActive = activeServices.length; - // 2. EN RUEDA (Buscando operario en automático) - const queueCount = scrapedServices.filter(s => s.status === 'pending' && s.automation_status === 'in_progress').length; + // 2. Sin Asignar + const unassignedCount = activeServices.filter(s => s.status === 'pending' && s.automation_status !== 'in_progress').length; + + // 3. En Rueda + const queueCount = activeServices.filter(s => s.status === 'pending' && s.automation_status === 'in_progress').length; - // 3. ASIGNADOS PERO SIN FECHA (En el Panel Operativo, columna izquierda) - const pendingCount = activeServices.filter(s => s.estado_operativo === 'asignado_operario').length; + // 4. Asignados sin cita (Pendientes) + const pendingCount = activeServices.filter(s => s.status === 'imported' && (!s.raw_data.scheduled_date || s.raw_data.scheduled_date === "")).length; - // 4. CITAS DE HOY (Tienen programada la visita para la fecha de hoy) + // 5. Citas de hoy const todayVisits = activeServices.filter(s => s.raw_data && s.raw_data.scheduled_date === todayStr); - - // 5. TOTAL GENERAL (Todo lo que no está archivado ni terminado de forma permanente) - const totalActive = activeServices.length + queueCount + unassignedCount; - // Pintar los números con animación + // 6. Urgencias (NUEVO) + const urgentVisits = activeServices.filter(s => { + const raw = s.raw_data || {}; + return raw['Urgente'] === 'Sí' || raw['Urgente'] === 'true' || raw['URGENTE'] === 'SI'; + }); + + // Pintar los números animateValue("kpiTotal", totalActive); animateValue("kpiUnassigned", unassignedCount); animateValue("kpiQueue", queueCount); animateValue("kpiPending", pendingCount); animateValue("kpiToday", todayVisits.length); + animateValue("kpiUrgent", urgentVisits.length); - // --- RENDERIZAR AGENDA DE HOY --- + // --- RENDERIZAR WIDGETS --- + renderTodaySchedule(todayVisits); + renderUrgentSchedule(urgentVisits); + renderCompanyDistribution(activeServices); + renderLatestActivity(allServices); // Aquí pasamos todos para ver los recién entrados + } + + // WIDGET 1: Agenda Hoy + function renderTodaySchedule(visits) { const container = document.getElementById("todaySchedule"); - - if (todayVisits.length === 0) { + if (visits.length === 0) { container.innerHTML = `

No hay visitas programadas para hoy

`; - lucide.createIcons(); - return; + lucide.createIcons(); return; } - // Ordenar citas por hora - todayVisits.sort((a, b) => { - const timeA = a.raw_data.scheduled_time || "23:59"; - const timeB = b.raw_data.scheduled_time || "23:59"; - return timeA.localeCompare(timeB); - }); + visits.sort((a, b) => (a.raw_data.scheduled_time || "23:59").localeCompare(b.raw_data.scheduled_time || "23:59")); - container.innerHTML = todayVisits.map(s => { + container.innerHTML = visits.map(s => { const raw = s.raw_data; const time = raw.scheduled_time ? raw.scheduled_time.substring(0,5) : "--:--"; - const op = s.assigned_name || "Sin Asignar"; + const op = s.current_worker_name || raw.assigned_to_name || "Sin Asignar"; const name = raw['Nombre Cliente'] || raw['CLIENTE'] || "Asegurado"; const pop = raw['Población'] || raw['POBLACION-PROVINCIA'] || "Dirección no especificada"; - const ref = s.service_ref; - // Color según estado (por si está trabajando o ya terminó el de hoy) let statusColor = "bg-blue-50 text-blue-600"; if(raw.status_operativo === 'trabajando') statusColor = "bg-amber-50 text-amber-600"; if(raw.status_operativo === 'incidencia') statusColor = "bg-red-50 text-red-600"; if(raw.status_operativo === 'terminado') statusColor = "bg-emerald-50 text-emerald-600"; return ` -
+
-
- ${time} -
+
${time}

${name}

- #${ref} + #${s.service_ref}

${pop}

- `; }).join(''); - lucide.createIcons(); } - // Efecto visual para que los números cuenten de 0 al valor real + // WIDGET 2: Urgencias + function renderUrgentSchedule(visits) { + const container = document.getElementById("urgentSchedule"); + if (visits.length === 0) { + container.innerHTML = ` +
+ +

Todo bajo control. Sin urgencias.

+
`; + lucide.createIcons(); return; + } + + container.innerHTML = visits.map(s => { + const raw = s.raw_data; + const name = raw['Nombre Cliente'] || raw['CLIENTE'] || "Asegurado"; + const op = s.current_worker_name || raw.assigned_to_name || "Buscando Operario..."; + return ` +
+
+

${name}

+

REF: #${s.service_ref}

+
+ ${op} +
`; + }).join(''); + lucide.createIcons(); + } + + // WIDGET 3: Distribución Compañías + function renderCompanyDistribution(activeServices) { + const container = document.getElementById("companyDistribution"); + if (activeServices.length === 0) { container.innerHTML = "

Sin datos

"; return; } + + const counts = {}; + activeServices.forEach(s => { + const raw = s.raw_data || {}; + let comp = (raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || (s.provider === 'MANUAL' ? 'PARTICULAR' : 'OTRA')).toUpperCase().trim(); + if(comp.includes("HOMESERVE")) comp = "HOMESERVE"; + counts[comp] = (counts[comp] || 0) + 1; + }); + + const sorted = Object.entries(counts).sort((a,b) => b[1] - a[1]); + const max = sorted[0][1]; + + container.innerHTML = sorted.map(([comp, count]) => { + const percent = Math.round((count / max) * 100); + return ` +
+
+ ${comp} + ${count} expedientes +
+
+
+
+
`; + }).join(''); + } + + // WIDGET 4: Última Actividad + function renderLatestActivity(allServices) { + const container = document.getElementById("latestActivity"); + // Ordenar por ID o Created At descendente y coger 5 + const latest = allServices.sort((a, b) => b.id - a.id).slice(0, 5); + + if (latest.length === 0) { container.innerHTML = "

Sin actividad reciente

"; return; } + + container.innerHTML = latest.map(s => { + const name = s.raw_data['Nombre Cliente'] || s.raw_data['CLIENTE'] || "Nuevo Cliente"; + const isNew = s.status === 'pending'; + const icon = isNew ? '' : ''; + return ` +
+
${icon}
+
+

REF: #${s.service_ref}

+

${name}

+
+
`; + }).join(''); + lucide.createIcons(); + } + + // Animación de contador de KPIs function animateValue(id, end) { const obj = document.getElementById(id); let start = 0; - const duration = 1000; + const duration = 800; const range = end - start; if(range === 0) { obj.innerText = "0"; return; } const minTimer = 50;