Actualizar servicios.html

This commit is contained in:
2026-02-21 11:37:47 +00:00
parent 283bc1e3c0
commit 0796d14098

View File

@@ -260,6 +260,24 @@
</div> </div>
</div> </div>
<div id="panelEnBolsa" class="bg-amber-50/40 p-6 rounded-[1.5rem] border border-amber-200 flex flex-col justify-center items-center shadow-sm h-full hidden text-center space-y-4">
<div class="w-16 h-16 bg-amber-100 text-amber-500 rounded-full flex items-center justify-center animate-pulse shadow-inner">
<i data-lucide="bot" class="w-8 h-8"></i>
</div>
<div>
<h4 class="font-black text-slate-800 text-lg uppercase tracking-tight">Servicio en Bolsa</h4>
<p class="text-xs text-slate-500 mt-2 font-medium">El robot está buscando operario automáticamente. Por favor, no interfieras a menos que sea urgente.</p>
</div>
<div class="w-full space-y-3 mt-6">
<button onclick="stopAutomation()" class="w-full bg-rose-500 hover:bg-rose-600 text-white font-black py-3.5 rounded-xl text-xs uppercase transition-colors shadow-md flex justify-center items-center gap-2">
<i data-lucide="octagon" class="w-4 h-4"></i> Detener y Asignar Manual
</button>
<button onclick="closeDetailModal()" class="w-full bg-white border border-slate-200 text-slate-600 font-bold py-3.5 rounded-xl text-xs uppercase hover:bg-slate-50 transition-colors">
Dejar que el Robot siga
</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -379,7 +397,7 @@
// 1. Prioridad Máxima: Bolsa de Trabajo / Robot WhatsApp // 1. Prioridad Máxima: Bolsa de Trabajo / Robot WhatsApp
if (!s.assigned_name && (s.automation_status === 'in_progress' || s.automation_status === 'failed')) { if (!s.assigned_name && (s.automation_status === 'in_progress' || s.automation_status === 'failed')) {
return { id: 'bolsa', name: s.automation_status === 'in_progress' ? 'Buscando Operario' : 'Fallo en Bolsa', color: s.automation_status === 'in_progress' ? 'amber' : 'red', isBlocked: true, is_final: false }; return { id: 'bolsa', name: s.automation_status === 'in_progress' ? 'Buscando Operario' : 'Fallo en Bolsa', color: s.automation_status === 'in_progress' ? 'amber' : 'red', isBlocked: false, is_final: false };
} }
// 2. Si viene limpio del scraper sin estado -> Pendiente de Asignar // 2. Si viene limpio del scraper sin estado -> Pendiente de Asignar
@@ -513,17 +531,15 @@
if(stateInfo.name.toLowerCase().includes('reparaci') || stateInfo.name.toLowerCase().includes('trabaja')) iconEstado = 'wrench'; if(stateInfo.name.toLowerCase().includes('reparaci') || stateInfo.name.toLowerCase().includes('trabaja')) iconEstado = 'wrench';
if(stateInfo.name.toLowerCase().includes('incidencia') || stateInfo.name.toLowerCase().includes('pausado')) iconEstado = 'alert-triangle'; if(stateInfo.name.toLowerCase().includes('incidencia') || stateInfo.name.toLowerCase().includes('pausado')) iconEstado = 'alert-triangle';
const isBlocked = stateInfo.isBlocked; const inProgressBadge = s.automation_status === 'in_progress' ? `<span class="absolute -top-3 -right-3 bg-amber-400 text-white px-3 py-1 rounded-full text-[10px] font-black uppercase shadow-lg border-2 border-white flex items-center gap-1 animate-pulse z-10"><i data-lucide="bot" class="w-3 h-3"></i> En Bolsa</span>` : '';
const clickAction = isBlocked ? `shakeCard(this, '${s.automation_status}'); event.stopPropagation();` : `openDetail(${s.id})`;
const cursorStyle = isBlocked ? 'cursor-not-allowed' : 'cursor-pointer';
return ` return `
<div class="bg-white p-5 rounded-3xl border border-slate-200 shadow-sm card-hover text-left flex flex-col justify-between relative ${cursorStyle}" onclick="${clickAction}"> <div class="bg-white p-5 rounded-3xl border border-slate-200 shadow-sm card-hover text-left flex flex-col justify-between relative cursor-pointer" onclick="openDetail(${s.id})">
${inProgressBadge}
<div class="space-y-3"> <div class="space-y-3">
<div class="flex items-start justify-between w-full gap-2"> <div class="flex items-start justify-between w-full gap-2">
<span class="text-[9px] font-black ${colorData.bg} ${colorData.text} px-2.5 py-1.5 rounded-lg uppercase tracking-wider flex items-center gap-1.5 truncate border ${colorData.border}"> <span class="text-[9px] font-black ${colorData.bg} ${colorData.text} px-2.5 py-1.5 rounded-lg uppercase tracking-wider flex items-center gap-1.5 truncate border ${colorData.border}">
${isBlocked ? `<span class="w-1.5 h-1.5 bg-current rounded-full pulse-slow opacity-70 shrink-0"></span>` : `<div class="w-2 h-2 rounded-full ${colorData.dot}"></div>`} <div class="w-2 h-2 rounded-full ${colorData.dot}"></div>
<span class="truncate">${stateInfo.name}</span> <span class="truncate">${stateInfo.name}</span>
</span> </span>
${isUrgent ? `<span class="bg-red-500 text-white px-2 py-1 rounded-lg text-[9px] font-black uppercase shadow-sm animate-pulse shrink-0">🔥 URGENTE</span>` : `<span class="text-[10px] text-slate-400 font-bold uppercase bg-slate-50 border border-slate-100 px-2.5 py-1 rounded-lg shrink-0">#${s.service_ref}</span>`} ${isUrgent ? `<span class="bg-red-500 text-white px-2 py-1 rounded-lg text-[9px] font-black uppercase shadow-sm animate-pulse shrink-0">🔥 URGENTE</span>` : `<span class="text-[10px] text-slate-400 font-bold uppercase bg-slate-50 border border-slate-100 px-2.5 py-1 rounded-lg shrink-0">#${s.service_ref}</span>`}
@@ -546,7 +562,7 @@
<div class="bg-slate-100 p-1.5 rounded-lg text-slate-500 shrink-0"><i data-lucide="hard-hat" class="w-3.5 h-3.5"></i></div> <div class="bg-slate-100 p-1.5 rounded-lg text-slate-500 shrink-0"><i data-lucide="hard-hat" class="w-3.5 h-3.5"></i></div>
<span class="text-[10px] font-black text-slate-600 uppercase truncate" title="${s.assigned_name || 'Sin asignar'}">${s.assigned_name || 'Sin asignar'}</span> <span class="text-[10px] font-black text-slate-600 uppercase truncate" title="${s.assigned_name || 'Sin asignar'}">${s.assigned_name || 'Sin asignar'}</span>
</div> </div>
${raw.scheduled_date && !isBlocked ? ` ${raw.scheduled_date ? `
<div class="flex items-center gap-1.5 text-blue-600 shrink-0 ml-2 bg-blue-50 px-2 py-1 rounded-md border border-blue-100"> <div class="flex items-center gap-1.5 text-blue-600 shrink-0 ml-2 bg-blue-50 px-2 py-1 rounded-md border border-blue-100">
<i data-lucide="${iconEstado}" class="w-3.5 h-3.5"></i> <i data-lucide="${iconEstado}" class="w-3.5 h-3.5"></i>
<span class="text-[9px] font-black uppercase">${cita}</span> <span class="text-[9px] font-black uppercase">${cita}</span>
@@ -616,17 +632,23 @@
const stateInfo = s._stateInfo; const stateInfo = s._stateInfo;
// Mostrar u ocultar paneles de asignación dependiendo del estado // --- LÓGICA DE VISIBILIDAD DE PANELES MODIFICADA ---
if (s.assigned_name && stateInfo.id !== 'bolsa' && !stateInfo.name.toLowerCase().includes('asignar') && !stateInfo.name.toLowerCase().includes('desasignado')) { if (s.automation_status === 'in_progress') {
document.getElementById('panelEnBolsa').classList.remove('hidden');
document.getElementById('panelAsignado').classList.add('hidden');
document.getElementById('panelSinAsignar').classList.add('hidden');
}
else if (s.assigned_name && stateInfo.id !== 'bolsa' && !stateInfo.name.toLowerCase().includes('asignar') && !stateInfo.name.toLowerCase().includes('desasignado')) {
document.getElementById('panelEnBolsa').classList.add('hidden');
document.getElementById('panelAsignado').classList.remove('hidden'); document.getElementById('panelAsignado').classList.remove('hidden');
document.getElementById('panelSinAsignar').classList.add('hidden'); document.getElementById('panelSinAsignar').classList.add('hidden');
document.getElementById('detWorker').innerText = s.assigned_name; document.getElementById('detWorker').innerText = s.assigned_name;
document.getElementById('dateInput').value = raw.scheduled_date || ""; document.getElementById('dateInput').value = raw.scheduled_date || "";
document.getElementById('timeInput').value = raw.scheduled_time || ""; document.getElementById('timeInput').value = raw.scheduled_time || "";
document.getElementById('detStatusMap').value = stateInfo.id; document.getElementById('detStatusMap').value = stateInfo.id;
} else { } else {
document.getElementById('panelEnBolsa').classList.add('hidden');
document.getElementById('panelAsignado').classList.add('hidden'); document.getElementById('panelAsignado').classList.add('hidden');
document.getElementById('panelSinAsignar').classList.remove('hidden'); document.getElementById('panelSinAsignar').classList.remove('hidden');
@@ -644,6 +666,24 @@
lucide.createIcons(); lucide.createIcons();
} }
async function stopAutomation() {
const id = document.getElementById('detId').value;
if(!confirm("¿Deseas cancelar la búsqueda del robot para asignar este servicio manualmente?")) return;
try {
await fetch(`${API_URL}/providers/scraped/${id}`, {
method: 'PUT',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ automation_status: 'manual' })
});
showToast("Bolsa detenida. Ya puedes asignar el servicio.");
closeDetailModal();
refreshPanel();
} catch (e) {
alert("Error al detener el automatismo.");
}
}
async function saveAppointment() { async function saveAppointment() {
const id = document.getElementById('detId').value; const id = document.getElementById('detId').value;
const date = document.getElementById('dateInput').value; const date = document.getElementById('dateInput').value;
@@ -711,19 +751,31 @@
const name = select.options[select.selectedIndex].text; const name = select.options[select.selectedIndex].text;
const estadoAsignado = systemStatuses.find(st => st.name.toLowerCase() === 'asignado') || systemStatuses[1]; const estadoAsignado = systemStatuses.find(st => st.name.toLowerCase() === 'asignado') || systemStatuses[1];
// 1. PRIMERA LLAMADA: Detenemos la bolsa de trabajo automática
await fetch(`${API_URL}/providers/scraped/${id}`, {
method: 'PUT',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ automation_status: 'completed' })
});
// 2. SEGUNDA LLAMADA: Guardamos el operario y el estado "Asignado"
await fetch(`${API_URL}/providers/scraped/${id}`, { await fetch(`${API_URL}/providers/scraped/${id}`, {
method: 'PUT', method: 'PUT',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ body: JSON.stringify({
automation_status: 'completed',
assigned_to: assigned_to, assigned_to: assigned_to,
assigned_name: name, assigned_to_name: name,
guild_id: guild_id, guild_id: guild_id,
status_operativo: estadoAsignado ? String(estadoAsignado.id) : 'asignado_operario' status_operativo: estadoAsignado ? String(estadoAsignado.id) : 'asignado_operario'
}) })
}); });
closeDetailModal(); showToast("Asignado correctamente"); refreshPanel();
} catch (e) { alert("Error"); } closeDetailModal();
showToast("Asignado y notificado correctamente");
refreshPanel();
} catch (e) {
alert("Error en la asignación manual");
}
} }
async function saveNewService(e) { async function saveNewService(e) {