Actualizar servicios.html

This commit is contained in:
2026-02-15 22:56:33 +00:00
parent 9a3d63c6bf
commit 14bc95d132

View File

@@ -20,33 +20,31 @@
<div id="header-container"></div> <div id="header-container"></div>
<main class="flex-1 overflow-y-auto p-6 no-scrollbar text-left"> <main class="flex-1 overflow-y-auto p-6 no-scrollbar text-left">
<div class="flex justify-between items-center mb-8">
<div class="flex justify-between items-center mb-8 text-left">
<div> <div>
<h2 class="text-2xl font-black text-slate-800 tracking-tight">PANEL OPERATIVO</h2> <h2 class="text-2xl font-black text-slate-800 tracking-tight">PANEL OPERATIVO</h2>
<p class="text-sm text-slate-500 font-medium">Gestión de expedientes aceptados y control de citas.</p> <p class="text-sm text-slate-500 font-medium">Gestión de expedientes aceptados y control de citas.</p>
</div> </div>
<button onclick="openCreateModal()" class="bg-slate-900 hover:bg-blue-600 text-white px-6 py-3 rounded-2xl shadow-xl flex items-center gap-3 font-black text-xs uppercase tracking-widest transition-all active:scale-95 text-left"> <button onclick="openCreateModal()" class="bg-slate-900 hover:bg-blue-600 text-white px-6 py-3 rounded-2xl shadow-xl flex items-center gap-3 font-black text-xs uppercase tracking-widest transition-all active:scale-95">
<i data-lucide="plus-circle" class="w-5 h-5"></i> Nuevo Servicio <i data-lucide="plus-circle" class="w-5 h-5"></i> Nuevo Servicio
</button> </button>
</div> </div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 text-left"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div class="space-y-4">
<div class="space-y-4 text-left"> <div class="flex items-center gap-2 px-2">
<div class="flex items-center gap-2 px-2 text-left">
<div class="w-2 h-2 rounded-full bg-blue-500"></div> <div class="w-2 h-2 rounded-full bg-blue-500"></div>
<h3 class="font-black text-slate-400 uppercase text-[10px] tracking-widest text-left">Asignados a Operario (Pendiente Cita)</h3> <h3 class="font-black text-slate-400 uppercase text-[10px] tracking-widest">Asignados a Operario (Pendiente Cita)</h3>
</div> </div>
<div id="pending-list" class="space-y-3 text-left"></div> <div id="pending-list" class="space-y-3"></div>
</div> </div>
<div class="space-y-4 text-left"> <div class="space-y-4">
<div class="flex items-center gap-2 px-2 text-left"> <div class="flex items-center gap-2 px-2">
<div class="w-2 h-2 rounded-full bg-emerald-500"></div> <div class="w-2 h-2 rounded-full bg-emerald-500"></div>
<h3 class="font-black text-slate-400 uppercase text-[10px] tracking-widest text-left">Servicios Citados / En Curso</h3> <h3 class="font-black text-slate-400 uppercase text-[10px] tracking-widest">Servicios Citados / En Curso</h3>
</div> </div>
<div id="assigned-list" class="space-y-3 text-left"></div> <div id="assigned-list" class="space-y-3"></div>
</div> </div>
</div> </div>
</main> </main>
@@ -54,33 +52,36 @@
</div> </div>
<div id="createModal" class="fixed inset-0 bg-slate-900/80 hidden z-[100] flex items-center justify-center backdrop-blur-sm p-4 text-left"> <div id="createModal" class="fixed inset-0 bg-slate-900/80 hidden z-[100] flex items-center justify-center backdrop-blur-sm p-4 text-left">
<div class="bg-white rounded-[2.5rem] shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col fade-in text-left"> <div class="bg-white rounded-[2.5rem] shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col fade-in">
<div class="p-8 border-b flex justify-between items-center bg-slate-50 text-left"> <div class="p-8 border-b flex justify-between items-center bg-slate-50">
<h3 class="font-black text-slate-800 uppercase tracking-tighter text-xl text-left">Alta de Expediente Manual</h3> <h3 class="font-black text-slate-800 uppercase tracking-tighter text-xl">Alta de Expediente Manual</h3>
<button onclick="closeCreateModal()" class="text-slate-400 hover:text-red-500 transition-colors text-left"><i data-lucide="x"></i></button> <button onclick="closeCreateModal()" class="text-slate-400 hover:text-red-500 transition-colors"><i data-lucide="x"></i></button>
</div> </div>
<form onsubmit="saveNewService(event)" class="p-8 overflow-y-auto no-scrollbar grid grid-cols-1 md:grid-cols-2 gap-6 text-left"> <form onsubmit="saveNewService(event)" class="p-8 overflow-y-auto no-scrollbar grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-4 text-left"> <div class="space-y-4">
<div class="bg-blue-50/50 p-6 rounded-3xl border border-blue-100 space-y-4 text-left"> <div class="bg-blue-50/50 p-6 rounded-3xl border border-blue-100 space-y-4">
<input type="tel" id="nPhone" placeholder="Teléfono Cliente" class="w-full border-none px-4 py-3 rounded-xl shadow-sm font-bold text-left outline-none" required> <h4 class="font-black text-blue-400 uppercase text-[10px] tracking-widest">Información del Cliente</h4>
<input type="text" id="nName" placeholder="Nombre completo" class="w-full border-none px-4 py-3 rounded-xl shadow-sm font-bold text-left outline-none" required> <input type="tel" id="nPhone" placeholder="Teléfono" class="w-full bg-white border-none px-4 py-3 rounded-xl shadow-sm font-bold outline-none" required>
<input type="text" id="nAddr" placeholder="Dirección completa" class="w-full border-none px-4 py-3 rounded-xl shadow-sm font-bold text-left outline-none" required> <input type="text" id="nName" placeholder="Nombre completo" class="w-full bg-white border-none px-4 py-3 rounded-xl shadow-sm font-bold outline-none" required>
<input type="text" id="nAddr" placeholder="Dirección completa" class="w-full bg-white border-none px-4 py-3 rounded-xl shadow-sm font-bold outline-none" required>
</div> </div>
<textarea id="nDesc" placeholder="Avería detectada..." rows="4" class="w-full bg-slate-50 border-none px-4 py-3 rounded-3xl text-sm font-medium text-left outline-none"></textarea> <textarea id="nDesc" placeholder="Descripción de la avería..." rows="4" class="w-full bg-slate-50 border-none px-4 py-3 rounded-3xl text-sm font-medium outline-none"></textarea>
</div> </div>
<div class="space-y-6 text-left"> <div class="space-y-6">
<select id="nGuild" class="w-full bg-slate-100 p-4 rounded-2xl font-bold text-left outline-none" onchange="loadOps(this.value)"> <select id="nGuild" class="w-full bg-slate-100 p-4 rounded-2xl font-bold outline-none" onchange="loadOps(this.value)">
<option value="">Seleccionar Gremio...</option> <option value="">Seleccionar Gremio...</option>
</select> </select>
<div class="bg-slate-900 p-6 rounded-[2rem] text-white space-y-4 text-left"> <div class="bg-slate-900 p-6 rounded-[2rem] text-white space-y-4">
<button type="submit" name="action" value="auto" class="w-full bg-blue-600 hover:bg-blue-500 p-4 rounded-2xl flex items-center justify-between text-left"> <button type="submit" name="action" value="auto" class="w-full bg-blue-600 hover:bg-blue-500 p-4 rounded-2xl flex items-center justify-between text-left transition-all">
<div><p class="font-black uppercase text-xs">Mandar a la Cola</p><p class="text-[10px] text-blue-200">WhatsApp Automático</p></div> <div><p class="font-black uppercase text-xs">Mandar a la Cola</p><p class="text-[10px] text-blue-200">WhatsApp Automático</p></div>
<i data-lucide="zap" class="w-6 h-6"></i> <i data-lucide="zap" class="w-6 h-6"></i>
</button> </button>
<div class="bg-slate-800 p-4 rounded-2xl space-y-3 text-left"> <div class="bg-slate-800 p-4 rounded-2xl space-y-3">
<p class="font-black uppercase text-[10px] text-slate-500 text-center">Asignar a Mano:</p> <p class="font-black uppercase text-[10px] text-slate-500 text-center">Asignar a Mano:</p>
<select id="nWorker" class="w-full bg-slate-700 border-none text-white px-3 py-2 rounded-xl text-sm text-left"></select> <select id="nWorker" class="w-full bg-slate-700 border-none text-white px-3 py-2 rounded-xl text-sm outline-none">
<button type="submit" name="action" value="manual" class="w-full bg-white text-slate-900 font-black py-3 rounded-xl text-xs uppercase text-left flex justify-center">Confirmar Asignación</button> <option value="">-- Primero elija gremio --</option>
</select>
<button type="submit" name="action" value="manual" class="w-full bg-white text-slate-900 font-black py-3 rounded-xl text-xs uppercase hover:bg-emerald-400 transition-colors">Confirmar Directo</button>
</div> </div>
</div> </div>
</div> </div>
@@ -89,26 +90,36 @@
</div> </div>
<div id="detailModal" class="fixed inset-0 bg-slate-900/90 hidden z-[120] flex items-center justify-center backdrop-blur-sm p-4 text-left"> <div id="detailModal" class="fixed inset-0 bg-slate-900/90 hidden z-[120] flex items-center justify-center backdrop-blur-sm p-4 text-left">
<div class="bg-white rounded-[2.5rem] shadow-2xl w-full max-w-lg overflow-hidden flex flex-col fade-in text-left"> <div class="bg-white rounded-[2.5rem] shadow-2xl w-full max-w-lg overflow-hidden flex flex-col fade-in">
<div class="p-6 border-b flex justify-between items-center bg-slate-50 text-left"> <div class="p-6 border-b flex justify-between items-center bg-slate-50">
<span class="text-[10px] font-black bg-blue-600 text-white px-3 py-1 rounded-full uppercase text-left">Expediente #<span id="detRef"></span></span> <span class="text-[10px] font-black bg-blue-600 text-white px-3 py-1 rounded-full uppercase">Expediente #<span id="detRef"></span></span>
<button onclick="closeDetailModal()" class="text-slate-400 hover:text-red-500 text-left"><i data-lucide="x"></i></button> <button onclick="closeDetailModal()" class="text-slate-400 hover:text-red-500"><i data-lucide="x"></i></button>
</div> </div>
<div class="p-8 space-y-6 text-left"> <div class="p-8 space-y-6">
<input type="hidden" id="detId"> <input type="hidden" id="detId">
<div class="grid grid-cols-2 gap-4 text-left"> <div class="grid grid-cols-2 gap-4">
<div class="text-left"><p class="text-[9px] font-black text-slate-400 uppercase">Asegurado</p><p id="detName" class="font-black text-slate-800 text-sm uppercase text-left"></p><p id="detPhone" class="text-xs text-blue-600 font-bold text-left"></p></div> <div><p class="text-[9px] font-black text-slate-400 uppercase">Asegurado</p><p id="detName" class="font-black text-slate-800 text-sm uppercase"></p><p id="detPhone" class="text-xs text-blue-600 font-bold"></p></div>
<div class="text-left"><p class="text-[9px] font-black text-slate-400 uppercase">Operario</p><p id="detWorker" class="font-bold text-slate-700 text-xs uppercase text-left"></p></div> <div><p class="text-[9px] font-black text-slate-400 uppercase">Operario</p><p id="detWorker" class="font-bold text-slate-700 text-xs uppercase"></p></div>
</div> </div>
<div class="text-left"><p class="text-[9px] font-black text-slate-400 uppercase text-left">Dirección</p><p id="detAddr" class="text-xs font-medium text-slate-600 uppercase text-left"></p></div> <div><p class="text-[9px] font-black text-slate-400 uppercase">Dirección</p><p id="detAddr" class="text-xs font-medium text-slate-600 uppercase"></p></div>
<div class="bg-blue-50 p-6 rounded-3xl border border-blue-100 space-y-4 text-left"> <div class="bg-blue-50 p-6 rounded-3xl border border-blue-100 space-y-4">
<h4 class="text-[10px] font-black text-blue-500 uppercase tracking-widest flex items-center gap-2 text-left"><i data-lucide="calendar" class="w-3 h-3 text-left"></i> Agendar Visita</h4> <h4 class="text-[10px] font-black text-blue-500 uppercase tracking-widest flex items-center gap-2"><i data-lucide="calendar" class="w-3 h-3"></i> Agendar Visita</h4>
<div class="grid grid-cols-2 gap-3 text-left"> <div class="grid grid-cols-2 gap-3">
<input type="date" id="dateInput" class="w-full bg-white border-none p-3 rounded-xl text-xs font-bold text-left outline-none"> <input type="date" id="dateInput" class="w-full bg-white border-none p-3 rounded-xl text-xs font-bold shadow-sm outline-none">
<input type="time" id="timeInput" class="w-full bg-white border-none p-3 rounded-xl text-xs font-bold text-left outline-none"> <input type="time" id="timeInput" class="w-full bg-white border-none p-3 rounded-xl text-xs font-bold shadow-sm outline-none">
</div> </div>
<button onclick="saveAppointment()" class="w-full bg-blue-600 text-white font-black py-4 rounded-2xl text-xs uppercase tracking-widest hover:bg-slate-900 transition-all text-left flex justify-center">Guardar y Mover a Citados</button> <div class="space-y-2 pt-2">
<p class="text-[9px] font-black text-slate-400 uppercase ml-1">Estado de la Visita</p>
<select id="detStatusMap" class="w-full bg-white border-none p-3 rounded-xl text-xs font-bold shadow-sm outline-none">
<option value="citado">CITADO (Visita programada)</option>
<option value="de_camino">DE CAMINO</option>
<option value="trabajando">TRABAJANDO</option>
<option value="incidencia">CON INCIDENCIA</option>
<option value="terminado">TERMINADO</option>
</select>
</div>
<button onclick="saveAppointment()" class="w-full bg-blue-600 text-white font-black py-4 rounded-2xl text-xs uppercase tracking-widest hover:bg-slate-900 transition-all">Guardar Cambios</button>
</div> </div>
</div> </div>
</div> </div>
@@ -136,12 +147,16 @@
} }
function renderLists() { function renderLists() {
// Filtros basados en el campo estado_operativo del servidor
const pending = localData.filter(s => s.estado_operativo === 'asignado_operario'); const pending = localData.filter(s => s.estado_operativo === 'asignado_operario');
const assigned = localData.filter(s => s.estado_operativo === 'citado'); const assigned = localData.filter(s => s.estado_operativo === 'citado');
document.getElementById('pending-list').innerHTML = pending.map(s => cardTemplate(s, 'blue', 'Asignado')).join(''); document.getElementById('pending-list').innerHTML = pending.length > 0
document.getElementById('assigned-list').innerHTML = assigned.map(s => cardTemplate(s, 'emerald', 'Citado')).join(''); ? pending.map(s => cardTemplate(s, 'blue', 'Asignado')).join('')
: '<p class="text-center py-10 text-slate-300 text-xs font-bold uppercase">Sin pendientes</p>';
document.getElementById('assigned-list').innerHTML = assigned.length > 0
? assigned.map(s => cardTemplate(s, 'emerald', 'En Curso')).join('')
: '<p class="text-center py-10 text-slate-300 text-xs font-bold uppercase">Sin servicios citados</p>';
lucide.createIcons(); lucide.createIcons();
} }
@@ -149,20 +164,20 @@
const raw = s.raw_data; const raw = s.raw_data;
const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado"; const name = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado";
const addr = raw["Dirección"] || raw["DOMICILIO"] || "---"; const addr = raw["Dirección"] || raw["DOMICILIO"] || "---";
const cita = raw.scheduled_date ? `${raw.scheduled_date} | ${raw.scheduled_time}` : 'Pendiente'; const cita = raw.scheduled_date ? `${raw.scheduled_date} | ${raw.scheduled_time}` : 'Pendiente Cita';
return ` return `
<div class="bg-white p-5 rounded-[2rem] border border-slate-100 shadow-sm card-hover text-left flex items-start justify-between gap-4 cursor-pointer" onclick="openDetail(${s.id})"> <div class="bg-white p-5 rounded-[2rem] border border-slate-100 shadow-sm card-hover text-left flex items-start justify-between gap-4 cursor-pointer" onclick="openDetail(${s.id})">
<div class="space-y-1 text-left"> <div class="space-y-1">
<span class="text-[8px] font-black bg-${color}-100 text-${color}-600 px-2 py-0.5 rounded-full uppercase text-left">${label}</span> <span class="text-[8px] font-black bg-${color}-100 text-${color}-600 px-2 py-0.5 rounded-full uppercase">${label}</span>
<h4 class="font-black text-slate-800 uppercase text-xs leading-tight text-left mt-1">${name}</h4> <h4 class="font-black text-slate-800 uppercase text-xs leading-tight mt-1">${name}</h4>
<p class="text-[10px] text-slate-400 font-bold uppercase truncate max-w-[200px] text-left">${addr}</p> <p class="text-[10px] text-slate-400 font-bold uppercase truncate max-w-[200px]">${addr}</p>
<div class="flex items-center gap-3 mt-3 text-left"> <div class="flex items-center gap-3 mt-3">
<div class="flex items-center gap-1.5 text-left"><i data-lucide="user" class="w-3 h-3 text-slate-400"></i><span class="text-[9px] font-black text-slate-600 uppercase text-left">${s.assigned_name || 'Sin asignar'}</span></div> <div class="flex items-center gap-1.5"><i data-lucide="user" class="w-3 h-3 text-slate-400"></i><span class="text-[9px] font-black text-slate-600 uppercase">${s.assigned_name || 'Sin asignar'}</span></div>
${raw.scheduled_date ? `<div class="flex items-center gap-1.5 text-left text-blue-600"><i data-lucide="calendar" class="w-3 h-3 text-left"></i><span class="text-[9px] font-black uppercase text-left">${cita}</span></div>` : ''} ${raw.scheduled_date ? `<div class="flex items-center gap-1.5 text-blue-600"><i data-lucide="calendar" class="w-3 h-3"></i><span class="text-[9px] font-black uppercase">${cita}</span></div>` : ''}
</div> </div>
</div> </div>
<div class="text-right text-slate-300 text-left"><i data-lucide="chevron-right"></i></div> <div class="text-slate-300"><i data-lucide="chevron-right"></i></div>
</div>`; </div>`;
} }
@@ -178,6 +193,10 @@
document.getElementById('detWorker').innerText = s.assigned_name || "Pendiente"; document.getElementById('detWorker').innerText = s.assigned_name || "Pendiente";
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 || "";
// Cargar mapa de estados guardado
document.getElementById('detStatusMap').value = raw.status_operativo || 'citado';
document.getElementById('detailModal').classList.remove('hidden'); document.getElementById('detailModal').classList.remove('hidden');
lucide.createIcons(); lucide.createIcons();
} }
@@ -186,12 +205,14 @@
const id = document.getElementById('detId').value; const id = document.getElementById('detId').value;
const date = document.getElementById('dateInput').value; const date = document.getElementById('dateInput').value;
const time = document.getElementById('timeInput').value; const time = document.getElementById('timeInput').value;
const statusMap = document.getElementById('detStatusMap').value;
if(!date || !time) return alert("Indica fecha y hora"); if(!date || !time) return alert("Indica fecha y hora");
const res = await fetch(`${API_URL}/services/set-appointment/${id}`, { const res = await fetch(`${API_URL}/services/set-appointment/${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({ date, time }) body: JSON.stringify({ date, time, status_operativo: statusMap })
}); });
if(res.ok) { closeDetailModal(); refreshPanel(); } if(res.ok) { closeDetailModal(); refreshPanel(); }
} }
@@ -207,12 +228,14 @@
guild_id: document.getElementById('nGuild').value, guild_id: document.getElementById('nGuild').value,
assigned_to: document.getElementById('nWorker').value || null assigned_to: document.getElementById('nWorker').value || null
}; };
const res = await fetch(`${API_URL}/services/manual-high`, { try {
method: 'POST', const res = await fetch(`${API_URL}/services/manual-high`, {
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, method: 'POST',
body: JSON.stringify({ ...data, mode: action }) headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
}); body: JSON.stringify({ ...data, mode: action })
if (res.ok) { closeCreateModal(); refreshPanel(); } });
if (res.ok) { closeCreateModal(); refreshPanel(); }
} catch(e) { alert("Error al guardar"); }
} }
function closeDetailModal() { document.getElementById('detailModal').classList.add('hidden'); } function closeDetailModal() { document.getElementById('detailModal').classList.add('hidden'); }
@@ -222,13 +245,15 @@
async function loadGuilds() { async function loadGuilds() {
const res = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const res = await fetch(`${API_URL}/guilds`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json(); const data = await res.json();
if(data.ok) document.getElementById('nGuild').innerHTML += data.guilds.map(g => `<option value="${g.id}">${g.name}</option>`).join(''); if(data.ok) document.getElementById('nGuild').innerHTML = '<option value="">Seleccionar Gremio...</option>' + data.guilds.map(g => `<option value="${g.id}">${g.name}</option>`).join('');
} }
async function loadOps(gid) { async function loadOps(gid) {
const sel = document.getElementById('nWorker');
if(!gid) { sel.innerHTML = '<option value="">-- Primero elija gremio --</option>'; return; }
const res = await fetch(`${API_URL}/operators?guild_id=${gid}`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const res = await fetch(`${API_URL}/operators?guild_id=${gid}`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json(); const data = await res.json();
if(data.ok) document.getElementById('nWorker').innerHTML = data.operators.map(o => `<option value="${o.id}">${o.full_name}</option>`).join(''); if(data.ok) sel.innerHTML = '<option value="">Seleccionar operario...</option>' + data.operators.map(o => `<option value="${o.id}">${o.full_name}</option>`).join('');
} }
</script> </script>
</body> </body>