diff --git a/index.html b/index.html index 928711b..932fc48 100644 --- a/index.html +++ b/index.html @@ -6,25 +6,20 @@ Portal del Cliente - IntegraRepara + + + + @@ -40,14 +35,6 @@

Conectando...

- -
@@ -57,40 +44,26 @@
-

Portal del Cliente

-

Empresa Reparadora

+

Empresa

- -

Hola, Cliente

+

Hola, Cliente

-
-
+
-
@@ -100,74 +73,44 @@ : 'https://integrarepara-api.integrarepara.es'; let urlToken = ""; + let mapsToInit = []; + let liveMaps = {}; + let liveMarkers = {}; document.addEventListener("DOMContentLoaded", async () => { lucide.createIcons(); - const urlParams = new URLSearchParams(window.location.search); urlToken = urlParams.get('token'); - if (!urlToken) { - showError(); - return; - } + if (!urlToken) return; try { const res = await fetch(`${API_URL}/public/portal/${urlToken}`); const data = await res.json(); - if (!data.ok) throw new Error("Token inválido"); - renderPortal(data.client, data.company, data.services); - - } catch (e) { - console.error(e); - showError(); - } + } catch (e) { console.error(e); } }); - function showError() { - document.getElementById('loader').classList.add('opacity-0', 'pointer-events-none'); - setTimeout(() => { - document.getElementById('loader').classList.add('hidden'); - document.getElementById('errorScreen').classList.remove('hidden'); - document.getElementById('errorScreen').classList.add('flex'); - }, 300); - } - - // LIMPIADOR INTELIGENTE DE TEXTOS DE ASEGURADORAS function summarizeDescription(rawText) { if (!rawText) return "Avería reportada."; - let text = rawText.replace(/\n/g, ' '); const regexCorte = /(\(|\bM\.O\b|\bMATERIAL\b|\bASEGURADO\b|\bTELEFONO\b|\bURGENTE\b)/i; let cutPos = text.search(regexCorte); - - if (cutPos > 5) { - text = text.substring(0, cutPos); - } - + if (cutPos > 5) text = text.substring(0, cutPos); text = text.trim(); text = text.charAt(0).toUpperCase() + text.slice(1).toLowerCase(); - let words = text.split(/\s+/); - if (words.length > 10) { - return words.slice(0, 10).join(" ") + "..."; - } - + if (words.length > 10) return words.slice(0, 10).join(" ") + "..."; if (text.length < 5) return "Reparación general solicitada."; - return text.endsWith('.') ? text : text + "."; } - // Helper para sumar 1 hora y crear la franja 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')}`; + let tm = h * 60 + m + 60; + return `${String(Math.floor(tm / 60)).padStart(2,'0')}:${String(tm % 60).padStart(2,'0')}`; } function formatDate(dateStr) { @@ -176,13 +119,12 @@ 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: 'long' }; - return d.toLocaleDateString('es-ES', opciones); + return d.toLocaleDateString('es-ES', { weekday: 'long', day: 'numeric', month: 'long' }); } catch(e) { return dateStr; } } function renderPortal(client, company, allServices) { - document.getElementById('companyName').innerText = company.name || "Empresa Reparadora"; + document.getElementById('companyName').innerText = company.name || "Empresa"; if (company.logo) { document.getElementById('companyLogo').src = company.logo; document.getElementById('companyLogoContainer').classList.remove('hidden'); @@ -191,77 +133,23 @@ const activeContainer = document.getElementById('activeServicesContainer'); const historyContainer = document.getElementById('historyServicesContainer'); - const historyWrapper = document.getElementById('historyContainerWrapper'); - const noActiveDiv = document.getElementById('noActiveServices'); - - activeContainer.innerHTML = ''; - historyContainer.innerHTML = ''; let hasActive = false; - let hasHistory = false; allServices.forEach(srv => { const isFinalized = srv.status_name === 'Terminado'; const isPendingWorker = (!srv.assigned_worker || srv.assigned_worker === 'Pendiente'); - - // Procesamos la descripción con la "mini-IA" const descLimpia = summarizeDescription(srv.description); - let statusHtml = ''; if (isFinalized) { - // MODO HISTORIAL (Gris y apagado) statusHtml = `
${srv.status_name} - ${formatDate(srv.scheduled_date) || 'Cerrado'} -
- `; - } else if (isPendingWorker) { - // MODO 1: AÚN NO HAY TÉCNICO (Botón de agendar bloqueado) - statusHtml = ` -
-
- - -
-

Buscando Técnico

-

Estamos asignando al técnico más cercano a tu domicilio.

-

En cuanto esté asignado, se habilitará la opción de agendar la visita aquí mismo.

-
- `; - } else if (srv.appointment_status === 'pending') { - // MODO 2: PENDIENTE DE ACEPTACIÓN POR LA OFICINA - statusHtml = ` -
-
- - -
-

Pendiente de Confirmación

-

Tu solicitud de fecha ha sido enviada. El técnico debe confirmarla en breve.

-
- `; - } else if (srv.status_name === 'Visita Agendada' || (srv.scheduled_date && srv.scheduled_time)) { - // MODO 3: CITA CONFIRMADA (FRANJA DE 1 HORA) - const endT = addOneHour(srv.scheduled_time); - statusHtml = ` -
-
- -
-
- -
-
-

Visita Confirmada

-

${formatDate(srv.scheduled_date)}

-

Llegada aprox: ${srv.scheduled_time} - ${endT}

-
-
- `; - } else if (srv.status_name === 'Técnico de Camino') { - // MODO 5: DE CAMINO + `; + } + else if (srv.status_name === 'Técnico de Camino') { + // MODO MAPA EN VIVO statusHtml = `
@@ -270,166 +158,137 @@

¡Técnico en camino!

-

Llegando en breves momentos a tu domicilio.

+

Sigue su ubicación en tiempo real.

-
-
- -

Señal GPS Próximamente

+ +
+
+
+ +

Buscando satélites...

+
`; - } else if (srv.status_name === 'En Reparación') { - // MODO 8: TRABAJANDO + mapsToInit.push(srv.id); // Guardamos para inicializar el mapa + } + else if (srv.status_name === 'En Reparación') { statusHtml = `
-
- -
+

En Reparación

-

El técnico está trabajando en tu domicilio ahora mismo.

+

El técnico está trabajando en tu domicilio.

-
- `; - } else if (srv.status_name === 'Pausado / Incidencia') { - // MODO 6: INCIDENCIA +
`; + } + else if (srv.status_name === 'Visita Agendada' || (srv.scheduled_date && srv.scheduled_time)) { + const endT = addOneHour(srv.scheduled_time); statusHtml = ` -
-
- +
+
+
+

Visita Confirmada

+

${formatDate(srv.scheduled_date)}

+

Llegada aprox: ${srv.scheduled_time} - ${endT}

-
-

Incidencia / Pausado

-

El servicio está pausado temporalmente. Te avisaremos pronto.

-
-
- `; - } else { - // MODO 4: PENDIENTE DE CITA NORMAL (Tiene técnico, no tiene cita) +
`; + } + else { statusHtml = ` -
-
- - -
-

Pendiente de Cita

-

El técnico necesita saber cuándo puede pasar por tu domicilio para la reparación.

- - -
- `; +
+
+

En Gestión

+
`; } - // ESTRUCTURA DE LA TARJETA MAESTRA UNIFICADA - let cardHtml = ''; - - if (isFinalized) { - // Historial compacto - cardHtml = ` -
-
-
- Exp. #${srv.title.replace('Expediente #', '').replace('🚨 URGENTE: ', '')} -

${descLimpia}

-
-
- ${statusHtml} + let cardHtml = ` +
+
+ Expediente #${srv.title.replace('Expediente #', '').replace('🚨 URGENTE: ', '')} + ${srv.assigned_worker && srv.assigned_worker !== 'Pendiente' ? + `
+

Técnico

+

${srv.assigned_worker.split(' ')[0]}

+
` : '' + }
- `; - } else { - // Tarjeta Principal Activa (La que te gusta pero ordenada para no ocultar el botón) - cardHtml = ` -
- -
-
- Expediente #${srv.title.replace('Expediente #', '').replace('🚨 URGENTE: ', '')} -
- ${srv.assigned_worker && srv.assigned_worker !== 'Pendiente' ? - `
-

Técnico

-

${srv.assigned_worker.split(' ')[0]}

-
` : '' - } -
- -
- ${statusHtml} -
- -
-
-

Motivo de la Visita

-

${descLimpia}

-
-
-
-
-

${client.addresses[0] || "Dirección no especificada"}

-
-
-
-

${client.phone}

-
-
-
- - ${!isPendingWorker ? ` -
- - -
- ` : ''} - +
${statusHtml}
+
+

Motivo de la Visita

+

${descLimpia}

- `; - } +
+ `; - if (isFinalized) { - historyContainer.innerHTML += cardHtml; - hasHistory = true; - } else { - activeContainer.innerHTML += cardHtml; - hasActive = true; - } + if (isFinalized) historyContainer.innerHTML += cardHtml; + else { activeContainer.innerHTML += cardHtml; hasActive = true; } }); - // GESTIÓN DE VISIBILIDAD DE CONTENEDORES - if (!hasActive) { - activeContainer.classList.add('hidden'); - noActiveDiv.classList.remove('hidden'); - } else { - activeContainer.classList.remove('hidden'); - noActiveDiv.classList.add('hidden'); - } - - if (hasHistory) { - historyWrapper.classList.remove('hidden'); - } + if (!hasActive) document.getElementById('noActiveServices').classList.remove('hidden'); lucide.createIcons(); - - // Quitar loader + document.getElementById('loader').classList.add('opacity-0', 'pointer-events-none'); - setTimeout(() => document.getElementById('loader').classList.add('hidden'), 300); - document.getElementById('mainContent').classList.remove('hidden'); + setTimeout(() => { + document.getElementById('loader').classList.add('hidden'); + document.getElementById('mainContent').classList.remove('hidden'); + + // INICIALIZAMOS LOS MAPAS + mapsToInit.forEach(id => initLiveMap(id)); + }, 300); } - function startBooking(serviceId) { - window.location.href = `cita.html?token=${urlToken}&service=${serviceId}`; + // ========================================== + // LÓGICA DE MAPA EN TIEMPO REAL + // ========================================== + function initLiveMap(serviceId) { + if(!document.getElementById(`map-${serviceId}`)) return; + + // 1. Configuramos el mapa de Leaflet + liveMaps[serviceId] = L.map(`map-${serviceId}`, { zoomControl: false, attributionControl: false }).setView([40.416775, -3.703790], 6); + L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png').addTo(liveMaps[serviceId]); + + // 2. Creamos un icono HTML personalizado (un puntito azul que brilla) + const carIcon = L.divIcon({ + className: 'custom-div-icon', + html: `
`, + iconSize: [20, 20], + iconAnchor: [10, 10] + }); + + liveMarkers[serviceId] = L.marker([40.416775, -3.703790], {icon: carIcon}); + + // 3. Ejecutamos la búsqueda de coordenadas cada 10 segundos + fetchWorkerLocation(serviceId); + setInterval(() => fetchWorkerLocation(serviceId), 10000); } - function requestNewService() { - alert("Módulo de nuevo aviso particular en desarrollo. Próximamente el cliente podrá crear su propia avería desde aquí."); + async function fetchWorkerLocation(serviceId) { + try { + const res = await fetch(`${API_URL}/public/portal/${urlToken}/location/${serviceId}`); + const data = await res.json(); + + if (data.ok && data.location) { + const loader = document.getElementById(`map-loader-${serviceId}`); + if(loader) loader.classList.add('hidden'); // Ocultar pantalla de "Buscando satélites" + + const lat = parseFloat(data.location.lat); + const lng = parseFloat(data.location.lng); + + // Si el marcador no está en el mapa, lo añadimos + if (!liveMaps[serviceId].hasLayer(liveMarkers[serviceId])) { + liveMarkers[serviceId].addTo(liveMaps[serviceId]); + liveMaps[serviceId].setView([lat, lng], 15); + } else { + // Si ya está, lo movemos suavemente + liveMarkers[serviceId].setLatLng([lat, lng]); + liveMaps[serviceId].flyTo([lat, lng], 16, { animate: true, duration: 1.5 }); + } + } + } catch(e) {} }