@@ -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}
-
Operario
@@ -225,15 +277,97 @@
`;
}).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;