Actualizar index.html

This commit is contained in:
2026-02-25 21:51:28 +00:00
parent 9be2e3f138
commit 6841d47825

View File

@@ -14,13 +14,6 @@
.blob { position: absolute; filter: blur(60px); z-index: -1; opacity: 0.4; }
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
/* Animación fluida para la barra de ruta del camión */
@keyframes movingLine {
0% { transform: translateX(-100%); }
100% { transform: translateX(200%); }
}
.animate-moving-line { animation: movingLine 2s linear infinite; }
</style>
</head>
<body class="text-slate-800 font-sans antialiased min-h-screen flex flex-col items-center relative overflow-x-hidden">
@@ -82,6 +75,7 @@
: 'https://integrarepara-api.integrarepara.es';
let urlToken = "";
let etasToInit = []; // Para procesar los tiempos de llegada al cargar
document.addEventListener("DOMContentLoaded", async () => {
lucide.createIcons();
@@ -99,20 +93,16 @@
if (!data.ok) throw new Error("Token inválido");
renderPortal(data.client, data.company, data.services);
} catch (e) {
console.error(e);
showError();
}
});
function showError() {
const loader = document.getElementById('loader');
const errorScreen = document.getElementById('errorScreen');
loader.classList.add('opacity-0', 'pointer-events-none');
document.getElementById('loader').classList.add('opacity-0', 'pointer-events-none');
setTimeout(() => {
loader.classList.add('hidden');
errorScreen.classList.remove('hidden');
errorScreen.classList.add('flex');
document.getElementById('loader').classList.add('hidden');
document.getElementById('errorScreen').classList.remove('hidden');
document.getElementById('errorScreen').classList.add('flex');
}, 300);
}
@@ -162,7 +152,7 @@
allServices.forEach(srv => {
const isFinalized = srv.status_name === 'Terminado';
const isPendingWorker = (!srv.assigned_worker || srv.assigned_worker === 'Pendiente');
const raw = srv.raw_data || {};
const descLimpia = summarizeDescription(srv.description);
let statusHtml = '';
@@ -173,21 +163,22 @@
</div>`;
}
else if (srv.status_name === 'Técnico de Camino') {
// NUEVO DISEÑO ANIMADO Y LIMPIO PARA "DE CAMINO" SIN MAPA
// Preparamos la dirección para calcular la ETA
const fullAddr = `${raw["Dirección"] || ""}, ${raw["Código Postal"] || ""} ${raw["Población"] || ""}`;
etasToInit.push({ id: srv.id, address: fullAddr });
// ESTRUCTURA BASE DE LA BARRA DE PROGRESO (SE LLENA CON JS)
statusHtml = `
<div class="bg-indigo-50 border border-indigo-200 p-6 rounded-3xl relative overflow-hidden shadow-inner">
<div class="absolute top-0 left-0 w-full h-1.5 bg-indigo-100 overflow-hidden">
<div class="w-1/2 h-full bg-indigo-500 rounded-full animate-moving-line"></div>
</div>
<div class="flex items-center gap-5 relative z-10">
<div class="w-14 h-14 bg-indigo-500 text-white rounded-2xl flex items-center justify-center shadow-lg shadow-indigo-500/30 animate-pulse shrink-0">
<i data-lucide="truck" class="w-7 h-7"></i>
</div>
<div>
<p class="text-[9px] font-black uppercase tracking-widest text-indigo-400 mb-0.5">En ruta hacia tu domicilio</p>
<h4 class="font-black text-indigo-900 uppercase text-lg leading-none mb-1.5 tracking-tight">¡Técnico en camino!</h4>
<p class="text-xs font-bold text-indigo-600 leading-tight">Prepárate, el técnico está a punto de llegar a tu ubicación.</p>
<div class="flex-1 min-w-0">
<h4 class="font-black text-indigo-900 uppercase text-lg leading-none mb-1.5 tracking-tight">¡En camino!</h4>
<div id="eta-container-${srv.id}">
<p class="text-[10px] font-bold text-indigo-500 flex items-center gap-1.5"><i data-lucide="loader-2" class="w-3 h-3 animate-spin"></i> Calculando ruta...</p>
</div>
</div>
</div>
</div>
@@ -254,8 +245,93 @@
setTimeout(() => {
document.getElementById('loader').classList.add('hidden');
document.getElementById('mainContent').classList.remove('hidden');
// Disparamos el cálculo de ETA para los que estén De Camino
etasToInit.forEach(item => calculateClientETA(item.id, item.address));
}, 300);
}
// ==========================================
// CÁLCULO DE ETA (TIEMPO ESTIMADO) INTELIGENTE
// ==========================================
async function calculateClientETA(serviceId, destAddress) {
const container = document.getElementById(`eta-container-${serviceId}`);
try {
// 1. Obtenemos la ubicación de donde salió el técnico
const res = await fetch(`${API_URL}/public/portal/${urlToken}/location/${serviceId}`);
const data = await res.json();
if (!data.ok || !data.location) {
container.innerHTML = `<p class="text-xs font-bold text-indigo-600 leading-tight">El técnico está conduciendo hacia tu domicilio.</p>`;
return;
}
const wLat = parseFloat(data.location.lat);
const wLon = parseFloat(data.location.lng);
// 2. Buscamos las coordenadas del cliente
let geoRes = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(destAddress + ', España')}`);
let geoData = await geoRes.json();
if (!geoData || geoData.length === 0) {
const parts = destAddress.split(',');
const fallbackDest = parts.length > 1 ? parts[parts.length - 1].trim() : destAddress;
geoRes = await fetch(`https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(fallbackDest + ', España')}`);
geoData = await geoRes.json();
}
if (geoData && geoData.length > 0) {
const cLat = parseFloat(geoData[0].lat);
const cLon = parseFloat(geoData[0].lon);
// 3. Calculamos la distancia en línea recta (Fórmula de Haversine)
const R = 6371;
const dLat = (cLat - wLat) * Math.PI / 180;
const dLon = (cLon - wLon) * Math.PI / 180;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(wLat * Math.PI / 180) * Math.cos(cLat * Math.PI / 180) * Math.sin(dLon/2) * Math.sin(dLon/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
const km = R * c;
// 4. Estimamos los minutos totales (velocidad media ciudad 35km/h + 5min atascos/aparcar)
const totalMins = Math.round((km/35)*60) + 5;
// 5. EL TRUCO MAGICO: Restar los minutos que ya han pasado desde que salió
const startedAt = new Date(data.location.updated_at).getTime();
const now = new Date().getTime();
const diffMins = Math.floor((now - startedAt) / 60000);
let remainingMins = totalMins - diffMins;
if (remainingMins < 1) remainingMins = 1; // Para que no ponga 0 o negativo
// Calculamos el % para la barra visual
let progressPercent = (diffMins / totalMins) * 100;
if (progressPercent > 95) progressPercent = 95;
if (progressPercent < 5) progressPercent = 5;
// Dibujamos la barra visual
container.innerHTML = `
<p class="text-[10px] font-black text-indigo-500 uppercase tracking-widest mb-1.5 flex items-center gap-1.5">
<i data-lucide="clock" class="w-3.5 h-3.5"></i> Llegada en aprox. <span class="text-indigo-700 text-sm ml-0.5">${remainingMins} min</span>
</p>
<div class="w-full bg-indigo-100 rounded-full h-2.5 overflow-hidden shadow-inner">
<div class="bg-indigo-500 h-full rounded-full transition-all duration-1000 relative" style="width: ${progressPercent}%">
<div class="absolute right-0 top-0 bottom-0 w-4 bg-white/40 animate-pulse"></div>
</div>
</div>
<div class="flex justify-between items-center mt-1 px-1">
<p class="text-[8px] font-black uppercase text-indigo-300">Saliendo</p>
<p class="text-[8px] font-black uppercase text-indigo-400">A ${km.toFixed(1)} km</p>
</div>
`;
lucide.createIcons();
} else {
container.innerHTML = `<p class="text-xs font-bold text-indigo-600 leading-tight">El técnico está conduciendo hacia tu domicilio.</p>`;
}
} catch(e) {
container.innerHTML = `<p class="text-xs font-bold text-indigo-600 leading-tight">El técnico está conduciendo hacia tu domicilio.</p>`;
}
}
</script>
</body>
</html>