Permite anular servicios sin tener que asignártelos primero

solicitar.html: botón "Anular Aviso" en la Bolsa de Trabajo. buscar.html: botón de anular en la pestaña "Sin Asignar" y en "Mis Asignados". asignados.html: botón "Anular Servicio" en la ficha de un servicio ya asignado pero sin cita. En los tres casos se cambia el estado a Anulado directamente, sin tocar assigned_to, para no disparar el WhatsApp de "servicio asignado".
This commit is contained in:
2026-09-07 07:26:24 +00:00
parent 8b2fbe4138
commit 806fa00103
3 changed files with 119 additions and 3 deletions

View File

@@ -444,6 +444,14 @@
</button>
`;
// 🆕 Botón Anular: disponible en ambas pestañas, para no tener que asignarse
// el servicio primero solo para poder cancelarlo (y evitar el WhatsApp de "asignado").
const anularBtn = `
<button onclick="anularServicio(${s.id})" class="bg-white hover:bg-rose-50 border border-rose-200 text-rose-600 font-black py-2.5 px-3 rounded-xl flex items-center justify-center transition-colors" title="Anular">
<i data-lucide="ban" class="w-4 h-4"></i>
</button>
`;
if (currentTab === 'mine') {
if (!isScheduled) {
actionButtons = `
@@ -459,19 +467,20 @@
</button>
` + actionButtons;
}
actionButtons = actionButtons + anularBtn;
} else if (currentTab === 'unassigned') {
// GOAL 2: BOTÓN ASIGNÁRMELO
actionButtons = `
<button onclick="claimService(${s.id})" class="flex-1 bg-emerald-500 hover:bg-emerald-600 text-white font-black py-2.5 rounded-xl flex items-center justify-center gap-1.5 transition-colors text-[10px] uppercase tracking-widest shadow-md">
<i data-lucide="hand" class="w-4 h-4"></i> ¡Asignármelo!
</button>
` + actionButtons;
` + actionButtons + anularBtn;
}
return `
<div class="bg-white p-5 rounded-3xl border border-slate-200 shadow-sm relative overflow-hidden flex flex-col gap-4">
${isUrgent ? '<div class="absolute top-0 right-0 bg-red-500 text-white text-[8px] font-black px-3 py-1 rounded-bl-xl uppercase tracking-widest shadow-sm z-10">Urgente</div>' : ''}
<div class="flex justify-between items-start">
<div class="flex items-center 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 border ${colorData.border}">
@@ -481,7 +490,7 @@
<span class="text-[9px] text-slate-400 font-bold uppercase bg-slate-50 border border-slate-100 px-2.5 py-1.5 rounded-lg">#${s.service_ref}</span>
</div>
</div>
<div>
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-0.5 truncate">${company}</p>
<h3 class="font-black text-slate-800 text-base uppercase leading-tight truncate">${name}</h3>
@@ -553,6 +562,32 @@
}
}
// 🆕 Anula un servicio directamente, esté o no asignado, sin pasar por "Asignármelo" primero
// (así no se le manda al cliente el WhatsApp de "servicio asignado" para algo que se va a cancelar).
async function anularServicio(id) {
if (!confirm("¿Seguro que quieres anular este servicio? No podrás deshacerlo desde aquí.")) return;
const stAnulado = systemStatuses.find(st => st.name.toLowerCase().includes('anula'));
if (!stAnulado) return alert("El sistema no tiene un estado 'Anulado' configurado. Avisa a la oficina.");
try {
const res = await fetch(`${API_URL}/providers/scraped/${id}`, {
method: 'PUT',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify({ status_operativo: String(stAnulado.id) })
});
if (res.ok) {
showToast("Servicio anulado");
loadAllData();
} else {
alert("Error al anular.");
}
} catch (e) {
alert("Error de conexión");
}
}
async function quickCopyPortalLink(serviceId, phone, name, addr, btnElement) {
if (!phone) return alert("El cliente no tiene teléfono guardado");