Actualizar calendario.html

This commit is contained in:
2026-03-28 21:40:38 +00:00
parent 8e6471ac86
commit 4af91e43c6

View File

@@ -317,13 +317,19 @@
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
<div> <div>
<label class="label-modern">Día</label> <label class="label-modern">Día</label>
<input type="date" id="nsDate" class="input-modern" required> <input type="date" id="nsDate" onchange="renderNewServiceAgenda()" class="input-modern" required>
</div> </div>
<div> <div>
<label class="label-modern">Hora</label> <label class="label-modern">Hora</label>
<input type="time" id="nsTime" class="input-modern" required> <input type="time" id="nsTime" class="input-modern" required>
</div> </div>
</div> </div>
<div id="nsAgendaContainer" class="hidden mt-1 text-left bg-blue-50/50 border border-blue-100 rounded-xl p-3">
<p class="text-[9px] font-black text-blue-500 uppercase tracking-widest mb-2 flex items-center gap-1.5"><i data-lucide="map" class="w-3 h-3"></i> Tu ruta para este día</p>
<div id="nsAgendaList" class="flex flex-col gap-2"></div>
</div>
<div> <div>
<label class="label-modern">Tiempo Estimado</label> <label class="label-modern">Tiempo Estimado</label>
<div class="relative"> <div class="relative">
@@ -1343,12 +1349,67 @@
document.getElementById('nsTime').value = ""; document.getElementById('nsTime').value = "";
document.getElementById('nsDate').value = selectedDateStr; document.getElementById('nsDate').value = selectedDateStr;
renderNewServiceAgenda(); // <-- Cargamos la ruta del día
const modal = document.getElementById('newServiceModal'); const modal = document.getElementById('newServiceModal');
modal.style.display = 'flex'; modal.style.display = 'flex';
setTimeout(() => modal.classList.remove('translate-y-full'), 10); setTimeout(() => modal.classList.remove('translate-y-full'), 10);
safeLoadIcons(); safeLoadIcons();
} }
// Dibuja los huecos ocupados para que el operario sepa por dónde se mueve
function renderNewServiceAgenda() {
const dateInput = document.getElementById('nsDate').value;
const agendaContainer = document.getElementById('nsAgendaContainer');
const agendaList = document.getElementById('nsAgendaList');
if (!dateInput) {
agendaContainer.classList.add('hidden');
return;
}
const dayServices = localServices.filter(s => String(s.raw_data.scheduled_date || "").trim() === dateInput);
dayServices.sort((a, b) => {
const tA = String(a.raw_data.scheduled_time || "23:59");
const tB = String(b.raw_data.scheduled_time || "23:59");
return tA.localeCompare(tB);
});
if (dayServices.length === 0) {
agendaList.innerHTML = `<span class="text-[10px] font-bold text-emerald-600 bg-emerald-50 px-3 py-1 rounded-lg border border-emerald-100 text-center">¡Día totalmente libre!</span>`;
} else {
agendaList.innerHTML = dayServices.map(s => {
const time = s.raw_data.scheduled_time || "--:--";
const dur = parseInt(s.raw_data.duration_minutes || 60);
let endTime = "--:--";
if (time.includes(':')) {
let [h, m] = time.split(':').map(Number);
m += dur;
h += Math.floor(m / 60);
m = m % 60;
endTime = `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
}
const city = s.raw_data["Población"] || "Sin Zona";
return `
<div class="bg-white border border-blue-100 rounded-lg p-2 text-xs flex justify-between items-center shadow-sm">
<div class="flex items-center gap-2">
<div class="bg-blue-50 text-blue-600 font-black px-2 py-1 rounded-md text-[10px]">
${time} - ${endTime}
</div>
<span class="font-bold text-slate-700 truncate max-w-[150px]">${city}</span>
</div>
</div>
`;
}).join('');
}
agendaContainer.classList.remove('hidden');
safeLoadIcons();
}
function closeNewServiceModal() { function closeNewServiceModal() {
const modal = document.getElementById('newServiceModal'); const modal = document.getElementById('newServiceModal');
modal.classList.add('translate-y-full'); modal.classList.add('translate-y-full');
@@ -1378,8 +1439,101 @@
} catch (e) {} } catch (e) {}
} }
async function saveAppointment() {
const id = document.getElementById('detId').value;
const date = document.getElementById('dateInput').value;
const time = document.getElementById('timeInput').value;
const duration = parseInt(document.getElementById('durationInput').value) || 60;
let statusMap = document.getElementById('detStatusMap').value;
const selectedSt = systemStatuses.find(st => String(st.id) === String(statusMap));
if (selectedSt && !selectedSt.is_final && !date && !selectedSt.name.toLowerCase().includes('pausa') && !selectedSt.name.toLowerCase().includes('asignar')) {
if(!confirm("No has asignado Fecha para este estado. ¿Deseas continuar?")) return;
}
// 🛑 RADAR ANTI-SOLAPAMIENTO (PERMISIVO)
if (date && time) {
let [newH, newM] = time.split(':').map(Number);
let newStartMin = newH * 60 + newM;
let newEndMin = newStartMin + duration;
const solapamiento = localServices.find(s => {
if (String(s.id) === String(id)) return false;
const sDate = String(s.raw_data.scheduled_date || "").trim();
const sTime = String(s.raw_data.scheduled_time || "").trim();
if (sDate === date && sTime && sTime.includes(':')) {
let [sH, sM] = sTime.split(':').map(Number);
let sDur = parseInt(s.raw_data.duration_minutes || 60);
let sStartMin = sH * 60 + sM;
let sEndMin = sStartMin + sDur;
return (newStartMin < sEndMin && newEndMin > sStartMin);
}
return false;
});
if (solapamiento) {
const horaC = solapamiento.raw_data.scheduled_time;
const zonaC = solapamiento.raw_data["Población"] || "otra zona";
// AHORA ES UN CONFIRM: Te avisa, pero te deja pasar si quieres
const forzar = confirm(`⚠️ SOLAPAMIENTO DETECTADO\n\nYa tienes una cita a las ${horaC} en ${zonaC}.\n\n¿Deseas FORZAR el guardado y agendarla en el mismo hueco?`);
if (!forzar) return; // Si cancela, abortamos
}
}
const btn = document.getElementById('btnSaveAppt');
const originalContent = btn.innerHTML;
btn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Guardando...`;
btn.disabled = true;
try {
const res = await fetch(`${API_URL}/services/set-appointment/${id}`, {
method: 'PUT',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ date, time, duration_minutes: duration, status_operativo: statusMap })
});
if (res.ok) {
showToast("Estado actualizado");
closeModal();
refreshData();
} else { alert("Error al guardar"); }
} catch (e) { alert("Error de conexión"); }
finally { btn.innerHTML = originalContent; btn.disabled = false; safeLoadIcons(); }
}
async function saveNewServiceApp(e) { async function saveNewServiceApp(e) {
e.preventDefault(); e.preventDefault();
const date = document.getElementById('nsDate').value;
const time = document.getElementById('nsTime').value;
const duration = parseInt(document.getElementById('nsDuration').value) || 60;
// 🛑 RADAR ANTI-SOLAPAMIENTO EN CREACIÓN (PERMISIVO)
if (date && time) {
let [newH, newM] = time.split(':').map(Number);
let newStartMin = newH * 60 + newM;
let newEndMin = newStartMin + duration;
const solapamiento = localServices.find(s => {
const sDate = String(s.raw_data.scheduled_date || "").trim();
const sTime = String(s.raw_data.scheduled_time || "").trim();
if (sDate === date && sTime && sTime.includes(':')) {
let [sH, sM] = sTime.split(':').map(Number);
let sDur = parseInt(s.raw_data.duration_minutes || 60);
let sStartMin = sH * 60 + sM;
let sEndMin = sStartMin + sDur;
return (newStartMin < sEndMin && newEndMin > sStartMin);
}
return false;
});
if (solapamiento) {
const horaC = solapamiento.raw_data.scheduled_time;
const zonaC = solapamiento.raw_data["Población"] || "otra zona";
const forzar = confirm(`⚠️ SOLAPAMIENTO DETECTADO\n\nYa tienes una cita a las ${horaC} en ${zonaC}.\n\n¿Deseas FORZAR la creación de este aviso en el mismo hueco?`);
if (!forzar) return;
}
}
const btn = document.getElementById('btnSaveNewApp'); const btn = document.getElementById('btnSaveNewApp');
const originalHTML = btn.innerHTML; const originalHTML = btn.innerHTML;
btn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Procesando...`; btn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i> Procesando...`;
@@ -1397,7 +1551,7 @@
description: document.getElementById('nsDesc').value, description: document.getElementById('nsDesc').value,
guild_id: null, guild_id: null,
assigned_to: myWorkerId, assigned_to: myWorkerId,
duration_minutes: document.getElementById('nsDuration').value, duration_minutes: duration,
is_urgent: false, is_urgent: false,
is_company: false, is_company: false,
company_name: 'Particular', company_name: 'Particular',
@@ -1417,10 +1571,6 @@
const citadoSt = systemStatuses.find(st => st.name.toLowerCase().includes('citado')); const citadoSt = systemStatuses.find(st => st.name.toLowerCase().includes('citado'));
const statusMapId = citadoSt ? String(citadoSt.id) : null; const statusMapId = citadoSt ? String(citadoSt.id) : null;
const date = document.getElementById('nsDate').value;
const time = document.getElementById('nsTime').value;
const duration = document.getElementById('nsDuration').value;
await fetch(`${API_URL}/services/set-appointment/${dataCreate.id}`, { await fetch(`${API_URL}/services/set-appointment/${dataCreate.id}`, {
method: 'PUT', method: 'PUT',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` }, headers: { "Content-Type": "application/json", "Authorization": `Bearer ${token}` },