From 725542dc343fecf1529e69479cfb9d1134deda8c Mon Sep 17 00:00:00 2001 From: marsalva Date: Tue, 24 Feb 2026 22:14:16 +0000 Subject: [PATCH] Actualizar asignados.html --- asignados.html | 208 ++++++++++++++++++++++++------------------------- 1 file changed, 103 insertions(+), 105 deletions(-) diff --git a/asignados.html b/asignados.html index 0dbd370..466bde4 100644 --- a/asignados.html +++ b/asignados.html @@ -28,7 +28,7 @@ /* Carrusel de fechas */ .day-chip { transition: all 0.2s; border: 2px solid transparent; } - .day-chip.active { background-color: var(--primary) !important; color: white !important; border-color: var(--primary) !important; transform: scale(1.05); box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1); } + .day-chip.active { background-color: var(--primary) !important; color: white !important; border-color: var(--primary) !important; transform: scale(1.05); shadow: 0 4px 6px -1px rgba(0,0,0,0.1); } .day-chip.inactive { background-color: #f8fafc; color: #64748b; border-color: #e2e8f0; } /* Chips de Horas */ @@ -182,8 +182,9 @@ -
-

Fecha Solicitada por el Cliente

+
+
+

Fecha Solicitada por el Cliente

--/--/----

--:--

@@ -193,7 +194,7 @@

¿Cuánto tiempo vas a tardar?

- @@ -211,7 +212,7 @@ -
@@ -229,9 +230,9 @@ ? 'http://localhost:3000' : 'https://integrarepara-api.integrarepara.es'; - let localServices = []; // Solo los "Sin Cita" - let globalAllServices = []; // TODOS para solapamientos - let pendingRequests = []; // Solicitudes del portal + let localServices = []; + let globalAllServices = []; + let pendingRequests = []; let systemStatuses = []; let bizHours = { m_start: '09:00', m_end: '14:00', a_start: '16:00', a_end: '19:00' }; @@ -239,7 +240,6 @@ let pickerSelectedDate = ""; let pickerSelectedTime = ""; - // --- SISTEMA DE TEMA DINÁMICO --- async function applyTheme() { try { let theme = JSON.parse(localStorage.getItem('app_theme')); @@ -261,7 +261,7 @@ document.documentElement.style.setProperty('--secondary', theme.secondary); document.documentElement.style.setProperty('--app-bg', theme.bg); } - } catch (e) { console.warn("Usando tema y horario por defecto"); } + } catch (e) { console.warn("Usando tema por defecto"); } } document.addEventListener("DOMContentLoaded", async () => { @@ -279,7 +279,7 @@ const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const data = await res.json(); if (data.ok) systemStatuses = data.statuses; - } catch (e) { console.error("Error estados:", e); } + } catch (e) { } } async function refreshData() { @@ -289,11 +289,9 @@ try { const headers = { "Authorization": `Bearer ${localStorage.getItem("token")}` }; - // 1. Cargar servicios activos del operario const resActivos = await fetch(`${API_URL}/services/active`, { headers }); const dataActivos = await resActivos.json(); - // 2. Cargar peticiones de cita pendientes de este operario const resReqs = await fetch(`${API_URL}/agenda/requests`, { headers }); const dataReqs = await resReqs.json(); @@ -301,11 +299,9 @@ globalAllServices = dataActivos.services; pendingRequests = dataReqs.requests; - // Filtrar la lista normal de "Sin Cita" localServices = globalAllServices.filter(s => { if (s.provider === 'SYSTEM_BLOCK') return false; const raw = s.raw_data || {}; - // Si ya tiene fecha o si tiene una solicitud pendiente, no lo mostramos en el listado base if (raw.scheduled_date && raw.scheduled_date.trim() !== "") return false; if (raw.appointment_status === 'pending') return false; @@ -348,21 +344,19 @@ const rDate = formatDate(raw.requested_date); const rTime = addOneHour(raw.requested_time); - // Extraer descripción y teléfono const desc = raw["Descripción"] || raw["DESCRIPCION"] || "Sin notas de avería."; const rawPhone = raw["Teléfono"] || raw["TELEFONOS"] || raw["TELEFONO"] || ""; const matchPhone = rawPhone.toString().match(/[6789]\d{8}/); const safePhone = matchPhone ? matchPhone[0] : ""; - // DISEÑO ACTUALIZADO CON ACENTOS VERDES reqList.innerHTML += ` -
-
+
+
-

Cita Solicitada

+

Cita Solicitada

${name}

-

${rDate} | ${raw.requested_time} - ${rTime}

+

${rDate} | ${raw.requested_time} - ${rTime}

@@ -370,10 +364,10 @@
- -
@@ -383,10 +377,9 @@ lucide.createIcons(); } - // Helper rápido para el botón de llamada function quickCallInline(phone) { if (phone) window.location.href = `tel:+34${phone}`; - else alert("No hay un teléfono válido guardado para este cliente."); + else alert("No hay un teléfono válido guardado."); } function openApproveModal(id) { @@ -400,8 +393,6 @@ document.getElementById('appDate').innerText = formatDate(raw.requested_date); document.getElementById('appTime').innerText = `Llegada aprox: ${raw.requested_time} - ${addOneHour(raw.requested_time)}`; - - // Reseteamos el selector de duración a 1 hora por defecto document.getElementById('appDurationInput').value = "60"; const modal = document.getElementById('approveModal'); @@ -415,7 +406,6 @@ async function approveRequest() { const id = document.getElementById('appId').value; const duration = document.getElementById('appDurationInput').value; - const btn = document.getElementById('btnApprove'); const originalContent = btn.innerHTML; btn.innerHTML = ``; @@ -427,26 +417,17 @@ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({ duration: duration }) }); - if (res.ok) { showToast("Cita Aceptada"); closeModal('approveModal', 'approveModalContent'); refreshData(); - } else { - alert("Error al confirmar."); - } - } catch (e) { - alert("Error de conexión"); - } finally { - btn.innerHTML = originalContent; - btn.disabled = false; - lucide.createIcons(); - } + } else { alert("Error al confirmar."); } + } catch (e) { alert("Error de conexión"); } + finally { btn.innerHTML = originalContent; btn.disabled = false; lucide.createIcons(); } } async function rejectRequest() { if(!confirm("¿Seguro que quieres rechazar este horario? El cliente recibirá un mensaje para elegir otro.")) return; - const id = document.getElementById('appId').value; const btn = document.getElementById('btnReject'); const originalContent = btn.innerHTML; @@ -458,26 +439,27 @@ method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` } }); - if (res.ok) { showToast("Cita Rechazada"); closeModal('approveModal', 'approveModalContent'); refreshData(); - } else { - alert("Error al rechazar."); - } - } catch (e) { - alert("Error de conexión"); - } finally { - btn.innerHTML = originalContent; - btn.disabled = false; - lucide.createIcons(); - } + } else { alert("Error al rechazar."); } + } catch (e) { alert("Error de conexión"); } + finally { btn.innerHTML = originalContent; btn.disabled = false; lucide.createIcons(); } } // ========================================== // RENDERIZADO DE LISTADO NORMAL (SIN CITA) // ========================================== + function calculateDelayDays(createdAtStr) { + if (!createdAtStr) return 0; + const createdDate = new Date(createdAtStr); + const now = new Date(); + const diffTime = Math.abs(now - createdDate); + const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); + return diffDays; + } + function renderServices() { const container = document.getElementById('servicesList'); const noDateSec = document.getElementById('noDateSection'); @@ -492,12 +474,8 @@

No tienes avisos pendientes de agendar.

`; lucide.createIcons(); - - if (pendingRequests.length === 0) { - noDateSec.querySelector('h2').classList.add('hidden'); - } else { - noDateSec.classList.add('hidden'); - } + if (pendingRequests.length === 0) noDateSec.querySelector('h2').classList.add('hidden'); + else noDateSec.classList.add('hidden'); return; } @@ -511,23 +489,39 @@ const pop = raw["Población"] || ""; const isUrgent = s.is_urgent; const company = raw["Compañía"] || raw["Procedencia"] || "Particular"; + + const calledTimes = parseInt(raw.called_times || 0); + const delayDays = calculateDelayDays(s.created_at); + + let delayHtml = ''; + if (delayDays > 1) { + delayHtml = `Retraso: ${delayDays} días`; + } return ` -
+
${isUrgent ? '
Urgente
' : ''} -
- +
+
+ +
+
+

${company} ${delayHtml}

+

${name}

+

+ ${addr}, ${pop} +

+
+
+ +
-
-

${company}

-

${name}

-

- ${addr}, ${pop} -

-
-
- + +
+
`; @@ -536,6 +530,34 @@ lucide.createIcons(); } + async function markClientNotFound(serviceId, currentTimes) { + const btn = event.currentTarget; + const originalHtml = btn.innerHTML; + btn.innerHTML = ` Enviando...`; + btn.disabled = true; + + try { + const res = await fetch(`${API_URL}/services/not-found/${serviceId}`, { + method: 'POST', + headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } + }); + + const data = await res.json(); + if (data.ok) { + showToast(`Aviso enviado. Intentos: ${data.called_times}`); + refreshData(); // Recargamos para actualizar el numerito + } else { + alert("Error al registrar la llamada."); + btn.innerHTML = originalHtml; + btn.disabled = false; + } + } catch (e) { + alert("Error de conexión"); + btn.innerHTML = originalHtml; + btn.disabled = false; + } + } + // ========================================== // LÓGICA DEL MOTOR DE AGENDAMIENTO INTELIGENTE // ========================================== @@ -546,17 +568,8 @@ return `${y}-${m}-${d}`; } - function timeToMins(t) { - let [h, m] = t.split(':').map(Number); - return h * 60 + m; - } - - function minsToTime(m) { - let h = Math.floor(m / 60).toString().padStart(2, '0'); - let min = (m % 60).toString().padStart(2, '0'); - return `${h}:${min}`; - } - + function timeToMins(t) { let [h, m] = t.split(':').map(Number); return h * 60 + m; } + function minsToTime(m) { let h = Math.floor(m / 60).toString().padStart(2, '0'); let min = (m % 60).toString().padStart(2, '0'); return `${h}:${min}`; } function addOneHour(timeStr) { if(!timeStr) return ""; let [h, m] = timeStr.split(':').map(Number); @@ -565,7 +578,6 @@ let newM = totalMins % 60; return `${String(newH).padStart(2,'0')}:${String(newM).padStart(2,'0')}`; } - function formatDate(dateStr) { if (!dateStr) return ""; try { @@ -582,19 +594,16 @@ container.innerHTML = ""; let today = new Date(); - for (let i = 0; i < 14; i++) { // 14 días a futuro + for (let i = 0; i < 14; i++) { let d = new Date(today); d.setDate(today.getDate() + i); - - if (d.getDay() === 0) continue; // Saltar domingos + if (d.getDay() === 0) continue; const isoDate = toISODate(d); const dayName = d.toLocaleDateString('es-ES', { weekday: 'short' }).replace('.', '').substring(0,3); const dayNum = d.getDate(); - // Seleccionar hoy por defecto if (!pickerSelectedDate) pickerSelectedDate = isoDate; - const isSelected = isoDate === pickerSelectedDate; container.innerHTML += ` @@ -608,15 +617,15 @@ function selectPickerDate(isoDate) { pickerSelectedDate = isoDate; - pickerSelectedTime = ""; // Resetear hora al cambiar de día - buildDayCarousel(); // Repintar para actualizar estilos + pickerSelectedTime = ""; + buildDayCarousel(); renderTimeSlots(); checkSaveButton(); } function selectPickerTime(timeStr) { pickerSelectedTime = timeStr; - renderTimeSlots(); // Repintar para estilos + renderTimeSlots(); checkSaveButton(); } @@ -648,7 +657,6 @@ function isOverlapping(startMins, endMins, occupiedRanges) { for(let r of occupiedRanges) { - // Hay solapamiento si mi inicio es antes de que él acabe, Y mi fin es después de que él empiece if(startMins < r.end && endMins > r.start) return true; } return false; @@ -663,12 +671,10 @@ grid.innerHTML = ""; let slotsGenerated = 0; - // Función interna para recorrer tramos const genSlotsForPeriod = (startStr, endStr) => { let startMins = timeToMins(startStr); let endMins = timeToMins(endStr); - // Saltos de 30 minutos (igual que en buscar.html) for (let m = startMins; m + duration <= endMins; m += 30) { if (!isOverlapping(m, m + duration, occupied)) { const timeStr = minsToTime(m); @@ -683,20 +689,18 @@ } }; - // Generar Mañana y Tarde genSlotsForPeriod(bizHours.m_start, bizHours.m_end); genSlotsForPeriod(bizHours.a_start, bizHours.a_end); if (slotsGenerated === 0) { msg.classList.remove('hidden'); grid.classList.add('hidden'); - pickerSelectedTime = ""; // Reset forzado + pickerSelectedTime = ""; } else { msg.classList.add('hidden'); grid.classList.remove('hidden'); } - // Si la hora que estaba seleccionada ya no sale, la borramos if (pickerSelectedTime && !grid.innerHTML.includes(`'${pickerSelectedTime}'`)) { pickerSelectedTime = ""; } @@ -704,9 +708,6 @@ checkSaveButton(); } - // ========================================== - // APERTURA DE MODAL Y GUARDADO (MANUAL) - // ========================================== function openActionModal(id) { const s = localServices.find(x => x.id === id); if (!s) return; @@ -721,17 +722,16 @@ const matchPhone = rawPhone.toString().match(/[6789]\d{8}/); document.getElementById('detPhoneRaw').value = matchPhone ? matchPhone[0] : ""; - // INICIALIZAR EL AGENDADOR document.getElementById('durationInput').value = "60"; - pickerSelectedDate = ""; // Reset - pickerSelectedTime = ""; // Reset - buildDayCarousel(); // Esto selecciona 'hoy' automáticamente y pinta los huecos + pickerSelectedDate = ""; + pickerSelectedTime = ""; + buildDayCarousel(); checkSaveButton(); const modal = document.getElementById('actionModal'); const content = document.getElementById('modalContent'); modal.classList.remove('hidden'); - void modal.offsetWidth; // Reflow + void modal.offsetWidth; modal.classList.remove('opacity-0'); content.classList.remove('translate-y-full'); } @@ -739,10 +739,8 @@ function closeModal(modalId, contentId) { const modal = document.getElementById(modalId); const content = document.getElementById(contentId); - modal.classList.add('opacity-0'); content.classList.add('translate-y-full'); - setTimeout(() => { modal.classList.add('hidden'); }, 300); }