diff --git a/asignados.html b/asignados.html index 8207885..62ef761 100644 --- a/asignados.html +++ b/asignados.html @@ -40,7 +40,7 @@ -
+

Por Agendar

Sin Cita

@@ -53,10 +53,29 @@
-

Buscando pendientes...

+

Sincronizando...

-
@@ -85,7 +104,7 @@

- +
@@ -111,7 +130,6 @@
-

Duración Estimada

@@ -133,21 +151,17 @@

Seleccionar Día

-
-
+

Huecos Disponibles

-
-
+
-
-
@@ -155,7 +169,53 @@ Confirmar Cita
- +
+
+ + @@ -170,7 +230,8 @@ : 'https://integrarepara-api.integrarepara.es'; let localServices = []; // Solo los "Sin Cita" - let globalAllServices = []; // TODOS los servicios para calcular solapamientos + let globalAllServices = []; // TODOS para solapamientos + let pendingRequests = []; // Solicitudes del portal let systemStatuses = []; let bizHours = { m_start: '09:00', m_end: '14:00', a_start: '16:00', a_end: '19:00' }; @@ -188,11 +249,8 @@ const data = await res.json(); if(data.ok && data.config && data.config.portal_settings) { - // Extraemos horario comercial const ps = data.config.portal_settings; if(ps.m_start) bizHours = { m_start: ps.m_start, m_end: ps.m_end, a_start: ps.a_start, a_end: ps.a_end }; - - // Extraemos colores if(ps.app_settings) { theme = ps.app_settings; localStorage.setItem('app_theme', JSON.stringify(theme)); @@ -207,7 +265,7 @@ } document.addEventListener("DOMContentLoaded", async () => { - if (!localStorage.getItem("token") || localStorage.getItem("role") !== 'operario') { + if (!localStorage.getItem("token") || (localStorage.getItem("role") !== 'operario' && localStorage.getItem("role") !== 'operario_cerrado')) { window.location.href = "index.html"; return; } await applyTheme(); @@ -225,23 +283,32 @@ } async function refreshData() { - document.getElementById('servicesList').classList.add('hidden'); + document.getElementById('contentWrapper').classList.add('hidden'); document.getElementById('loader').classList.remove('hidden'); try { - const res = await fetch(`${API_URL}/services/active`, { - headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } - }); - const data = await res.json(); + const headers = { "Authorization": `Bearer ${localStorage.getItem("token")}` }; - if (data.ok) { - globalAllServices = data.services; // Guardamos TODOS para chequear la agenda real + // 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(); + + if (dataActivos.ok && dataReqs.ok) { + globalAllServices = dataActivos.services; + pendingRequests = dataReqs.requests; - // Filtramos los SIN CITA - localServices = data.services.filter(s => { + // 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; + if (raw.status_operativo) { const st = systemStatuses.find(x => String(x.id) === String(raw.status_operativo)); if (st && st.is_final) return false; @@ -249,22 +316,151 @@ return true; }); + renderRequests(); renderServices(); } } catch (e) { alert("Error de conexión"); } finally { document.getElementById('loader').classList.add('hidden'); - document.getElementById('servicesList').classList.remove('hidden'); + document.getElementById('contentWrapper').classList.remove('hidden'); } } + // ========================================== + // RENDERIZADO DE SOLICITUDES PENDIENTES + // ========================================== + function renderRequests() { + const reqSection = document.getElementById('requestsSection'); + const reqList = document.getElementById('requestsList'); + reqList.innerHTML = ''; + + if (pendingRequests.length === 0) { + reqSection.classList.add('hidden'); + return; + } + + reqSection.classList.remove('hidden'); + + pendingRequests.forEach(req => { + const raw = req.raw_data || {}; + const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Cliente"; + const rDate = formatDate(raw.requested_date); + const rTime = addOneHour(raw.requested_time); // Calculamos el tramo para mostrarlo + + reqList.innerHTML += ` +
+
+
+

Cita Solicitada

+

${name}

+

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

+
+ +
+ `; + }); + lucide.createIcons(); + } + + function openApproveModal(id) { + const req = pendingRequests.find(r => r.id === id); + if(!req) return; + const raw = req.raw_data || {}; + + document.getElementById('appId').value = id; + document.getElementById('appRef').innerText = `Exp. #${req.service_ref || "S/R"}`; + document.getElementById('appName').innerText = raw["Nombre Cliente"] || "Cliente"; + + 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'); + const content = document.getElementById('approveModalContent'); + modal.classList.remove('hidden'); + void modal.offsetWidth; + modal.classList.remove('opacity-0'); + content.classList.remove('translate-y-full'); + } + + 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 = ``; + btn.disabled = true; + + try { + const res = await fetch(`${API_URL}/agenda/requests/${id}/approve`, { + method: 'POST', + 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(); + } + } + + 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; + btn.innerHTML = ``; + btn.disabled = true; + + try { + const res = await fetch(`${API_URL}/agenda/requests/${id}/reject`, { + 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(); + } + } + + // ========================================== + // RENDERIZADO DE LISTADO NORMAL (SIN CITA) + // ========================================== function renderServices() { const container = document.getElementById('servicesList'); + const noDateSec = document.getElementById('noDateSection'); if (localServices.length === 0) { container.innerHTML = ` -
+
@@ -272,9 +468,19 @@

No tienes avisos pendientes de agendar.

`; lucide.createIcons(); + + // Si tampoco hay peticiones, ocultamos el título "Pendientes de fecha" + if (pendingRequests.length === 0) { + noDateSec.querySelector('h2').classList.add('hidden'); + } else { + noDateSec.classList.add('hidden'); + } return; } + noDateSec.classList.remove('hidden'); + noDateSec.querySelector('h2').classList.remove('hidden'); + container.innerHTML = localServices.map(s => { const raw = s.raw_data || {}; const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado"; @@ -327,6 +533,26 @@ let min = (m % 60).toString().padStart(2, '0'); return `${h}:${min}`; } + + function addOneHour(timeStr) { + if(!timeStr) return ""; + let [h, m] = timeStr.split(':').map(Number); + let totalMins = h * 60 + m + 60; + let newH = Math.floor(totalMins / 60); + let newM = totalMins % 60; + return `${String(newH).padStart(2,'0')}:${String(newM).padStart(2,'0')}`; + } + + function formatDate(dateStr) { + if (!dateStr) return ""; + try { + const parts = dateStr.split('-'); + if(parts.length !== 3) return dateStr; + const d = new Date(parts[0], parts[1]-1, parts[2]); + const opciones = { weekday: 'long', day: 'numeric', month: 'short' }; + return d.toLocaleDateString('es-ES', opciones); + } catch(e) { return dateStr; } + } function buildDayCarousel() { const container = document.getElementById('dayCarousel'); @@ -419,8 +645,8 @@ let startMins = timeToMins(startStr); let endMins = timeToMins(endStr); - // Saltos de 15 minutos - for (let m = startMins; m + duration <= endMins; m += 15) { + // 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); const isSelected = timeStr === pickerSelectedTime; @@ -447,7 +673,7 @@ grid.classList.remove('hidden'); } - // Si la hora que estaba seleccionada ya no sale (porque ampliaron la duración), la borramos + // Si la hora que estaba seleccionada ya no sale, la borramos if (pickerSelectedTime && !grid.innerHTML.includes(`'${pickerSelectedTime}'`)) { pickerSelectedTime = ""; } @@ -456,7 +682,7 @@ } // ========================================== - // APERTURA DE MODAL Y GUARDADO + // APERTURA DE MODAL Y GUARDADO (MANUAL) // ========================================== function openActionModal(id) { const s = localServices.find(x => x.id === id); @@ -487,9 +713,9 @@ content.classList.remove('translate-y-full'); } - function closeModal() { - const modal = document.getElementById('actionModal'); - const content = document.getElementById('modalContent'); + 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'); @@ -537,7 +763,7 @@ if (res.ok) { showToast("Cita Agendada en Calendario"); - closeModal(); + closeModal('actionModal', 'modalContent'); refreshData(); } else { alert("Error al guardar la cita.");