Actualizar servicios.html

This commit is contained in:
2026-02-08 14:31:44 +00:00
parent e71f164c57
commit 4bd5b21561

View File

@@ -6,9 +6,7 @@
<title>Servicios - IntegraRepara</title> <title>Servicios - IntegraRepara</title>
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script> <script src="https://unpkg.com/lucide@latest"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=TU_API_KEY_AQUI&libraries=places&callback=initAutocomplete" async defer></script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAS0JItllN0PxiTlfWJXsRkHvF0BJcOMPs&libraries=places&callback=initAutocomplete" async defer></script>
<style> <style>
.fade-in { animation: fadeIn 0.3s ease-in-out; } .fade-in { animation: fadeIn 0.3s ease-in-out; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@@ -63,13 +61,12 @@
<i data-lucide="arrow-left" class="w-5 h-5"></i> Volver al listado <i data-lucide="arrow-left" class="w-5 h-5"></i> Volver al listado
</button> </button>
<h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center gap-2"> <h2 class="text-2xl font-bold text-gray-800 mb-6 flex items-center gap-2" id="formTitle">
<i data-lucide="file-plus" class="text-green-600"></i> Alta de Nuevo Servicio <i data-lucide="file-plus" class="text-green-600"></i> Alta de Nuevo Servicio
</h2> </h2>
<form onsubmit="createService(event)" class="space-y-6"> <form onsubmit="handleFormSubmit(event)" class="space-y-6">
<input type="hidden" id="editServiceId"> <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
<h3 class="text-lg font-bold text-gray-700 mb-4 border-b pb-2 flex items-center gap-2"> <h3 class="text-lg font-bold text-gray-700 mb-4 border-b pb-2 flex items-center gap-2">
<i data-lucide="user" class="w-5 h-5 text-gray-400"></i> Datos del Cliente <i data-lucide="user" class="w-5 h-5 text-gray-400"></i> Datos del Cliente
</h3> </h3>
@@ -89,9 +86,9 @@
<input type="text" id="sName" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"> <input type="text" id="sName" required class="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
</div> </div>
<div class="md:col-span-2"> <div class="md:col-span-2">
<label class="block text-xs font-bold text-gray-600 mb-1">Dirección y Población (Autocompletar) *</label> <label class="block text-xs font-bold text-gray-600 mb-1">Dirección y Población *</label>
<div class="flex gap-2"> <div class="flex gap-2">
<input type="text" id="sAddress" required placeholder="Escribe para buscar dirección..." class="flex-1 px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none"> <input type="text" id="sAddress" required placeholder="Escribe para buscar..." class="flex-1 px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none">
<select id="sAddressSelect" class="hidden w-1/3 border rounded-lg text-sm bg-gray-50 focus:ring-2 focus:ring-blue-500" onchange="selectAddress(this.value)"> <select id="sAddressSelect" class="hidden w-1/3 border rounded-lg text-sm bg-gray-50 focus:ring-2 focus:ring-blue-500" onchange="selectAddress(this.value)">
<option value="">Otras direcciones...</option> <option value="">Otras direcciones...</option>
</select> </select>
@@ -153,18 +150,12 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
<label class="block text-xs font-bold text-gray-600 mb-1 flex justify-between"> <label class="block text-xs font-bold text-gray-600 mb-1">Notas Internas</label>
<span>Notas Internas</span> <textarea id="sNotesInternal" rows="2" class="w-full px-3 py-2 border bg-yellow-50 border-yellow-200 rounded-lg outline-none text-sm placeholder-yellow-700/50"></textarea>
<span class="text-gray-400 font-normal text-[10px] uppercase">Solo Oficina</span>
</label>
<textarea id="sNotesInternal" rows="2" class="w-full px-3 py-2 border bg-yellow-50 border-yellow-200 rounded-lg outline-none text-sm placeholder-yellow-700/50" placeholder="Código alarma, llaves en portería..."></textarea>
</div> </div>
<div> <div>
<label class="block text-xs font-bold text-gray-600 mb-1 flex justify-between"> <label class="block text-xs font-bold text-gray-600 mb-1">Notas para Cliente</label>
<span>Notas para Cliente</span> <textarea id="sNotesClient" rows="2" class="w-full px-3 py-2 border rounded-lg outline-none text-sm"></textarea>
<span class="text-gray-400 font-normal text-[10px] uppercase">Visible en Parte</span>
</label>
<textarea id="sNotesClient" rows="2" class="w-full px-3 py-2 border rounded-lg outline-none text-sm" placeholder="Recomendaciones, observaciones..."></textarea>
</div> </div>
</div> </div>
</div> </div>
@@ -179,29 +170,24 @@
<input type="checkbox" id="sIsCompany" class="form-checkbox text-blue-600 w-5 h-5" onchange="toggleCompanyFields()"> <input type="checkbox" id="sIsCompany" class="form-checkbox text-blue-600 w-5 h-5" onchange="toggleCompanyFields()">
</label> </label>
</div> </div>
<div id="companyFields" class="hidden mt-4 pt-4 border-t border-gray-100 grid grid-cols-1 md:grid-cols-2 gap-4 animate-slide-in"> <div id="companyFields" class="hidden mt-4 pt-4 border-t border-gray-100 grid grid-cols-1 md:grid-cols-2 gap-4 animate-slide-in">
<div> <div>
<label class="block text-xs font-bold text-gray-600 mb-1">Seleccionar Compañía</label> <label class="block text-xs font-bold text-gray-600 mb-1">Seleccionar Compañía</label>
<div class="flex gap-2"> <div class="flex gap-2">
<select id="sCompanyId" class="flex-1 px-3 py-2 border rounded-lg bg-white outline-none focus:border-blue-500"> <select id="sCompanyId" class="flex-1 px-3 py-2 border rounded-lg bg-white outline-none focus:border-blue-500"><option value="">-- Seleccionar --</option></select>
<option value="">-- Seleccionar --</option> <button type="button" onclick="quickAddCompany()" class="bg-slate-800 text-white px-3 rounded-lg hover:bg-slate-700 transition-colors"><i data-lucide="plus" class="w-4 h-4"></i></button>
</select>
<button type="button" onclick="quickAddCompany()" class="bg-slate-800 text-white px-3 rounded-lg hover:bg-slate-700 transition-colors">
<i data-lucide="plus" class="w-4 h-4"></i>
</button>
</div> </div>
</div> </div>
<div> <div>
<label class="block text-xs font-bold text-gray-600 mb-1">Nº Expediente / Referencia</label> <label class="block text-xs font-bold text-gray-600 mb-1">Nº Expediente</label>
<input type="text" id="sCompanyRef" class="w-full px-3 py-2 border rounded-lg outline-none focus:border-blue-500" placeholder="Ej: SIN-2024-8892"> <input type="text" id="sCompanyRef" class="w-full px-3 py-2 border rounded-lg outline-none focus:border-blue-500">
</div> </div>
</div> </div>
</div> </div>
<div class="pt-2 pb-6"> <div class="pt-2 pb-6">
<button type="submit" id="btnSave" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-4 rounded-xl shadow-lg shadow-blue-500/30 text-lg transition-all flex justify-center items-center gap-3 transform active:scale-95"> <button type="submit" id="btnSave" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-4 rounded-xl shadow-lg shadow-blue-500/30 text-lg transition-all flex justify-center items-center gap-3 transform active:scale-95">
<i data-lucide="save" class="w-6 h-6"></i> CREAR SERVICIO <i data-lucide="save" class="w-6 h-6"></i> GUARDAR
</button> </button>
</div> </div>
</form> </form>
@@ -213,6 +199,16 @@
<h2 class="text-xl font-bold text-gray-900">Detalle del Servicio</h2> <h2 class="text-xl font-bold text-gray-900">Detalle del Servicio</h2>
<button onclick="closeDetailPanel()" class="text-gray-400 hover:text-gray-800 transition-colors"><i data-lucide="x" class="w-6 h-6"></i></button> <button onclick="closeDetailPanel()" class="text-gray-400 hover:text-gray-800 transition-colors"><i data-lucide="x" class="w-6 h-6"></i></button>
</div> </div>
<div class="flex gap-2 mb-6">
<button onclick="editService()" class="flex-1 bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 py-2 rounded-lg text-sm font-bold shadow-sm flex items-center justify-center gap-2">
<i data-lucide="edit-2" class="w-4 h-4"></i> Editar Datos
</button>
<button onclick="deleteService()" class="bg-white border border-red-200 hover:bg-red-50 text-red-600 py-2 px-4 rounded-lg text-sm font-bold shadow-sm" title="Borrar Servicio">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
</div>
<div class="bg-gray-50 p-4 rounded-xl border border-gray-200 mb-6 shadow-sm"> <div class="bg-gray-50 p-4 rounded-xl border border-gray-200 mb-6 shadow-sm">
<div class="flex justify-between items-start mb-2"> <div class="flex justify-between items-start mb-2">
<span id="detailStatusBadge" class="px-2 py-1 rounded text-[10px] font-bold bg-gray-200 text-gray-700 uppercase tracking-wide">ESTADO</span> <span id="detailStatusBadge" class="px-2 py-1 rounded text-[10px] font-bold bg-gray-200 text-gray-700 uppercase tracking-wide">ESTADO</span>
@@ -222,6 +218,7 @@
<p class="text-sm text-gray-500 mb-2" id="detailClient">Cliente</p> <p class="text-sm text-gray-500 mb-2" id="detailClient">Cliente</p>
<div class="flex items-center text-xs text-gray-400 gap-2"><i data-lucide="calendar" class="w-3 h-3"></i> <span id="detailDate">Fecha</span></div> <div class="flex items-center text-xs text-gray-400 gap-2"><i data-lucide="calendar" class="w-3 h-3"></i> <span id="detailDate">Fecha</span></div>
</div> </div>
<div class="mb-8"> <div class="mb-8">
<label class="block text-xs font-bold text-gray-500 uppercase mb-2 tracking-wider">Actualizar Estado</label> <label class="block text-xs font-bold text-gray-500 uppercase mb-2 tracking-wider">Actualizar Estado</label>
<div class="flex gap-2"> <div class="flex gap-2">
@@ -229,8 +226,9 @@
<button onclick="updateStatus()" class="bg-slate-800 text-white px-4 py-2 rounded-lg text-sm hover:bg-slate-700 font-bold shadow-md transition-colors">Guardar</button> <button onclick="updateStatus()" class="bg-slate-800 text-white px-4 py-2 rounded-lg text-sm hover:bg-slate-700 font-bold shadow-md transition-colors">Guardar</button>
</div> </div>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<h3 class="font-bold text-gray-800 mb-6 flex items-center gap-2 border-b pb-2"><i data-lucide="history" class="w-4 h-4 text-gray-400"></i> Historial de Cambios</h3> <h3 class="font-bold text-gray-800 mb-6 flex items-center gap-2 border-b pb-2"><i data-lucide="history" class="w-4 h-4 text-gray-400"></i> Historial</h3>
<div class="relative border-l-2 border-gray-200 ml-3 space-y-8 pb-8 pl-6" id="statusTimeline"></div> <div class="relative border-l-2 border-gray-200 ml-3 space-y-8 pb-8 pl-6" id="statusTimeline"></div>
</div> </div>
</div> </div>
@@ -246,30 +244,22 @@
<script> <script>
let allStatuses = []; let allStatuses = [];
let currentServiceId = null; let currentServiceId = null;
let autocomplete; // Variable para Google Maps let autocomplete;
let currentServiceData = null; // Guardamos datos del servicio actual para edición
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const token = localStorage.getItem("token"); const token = localStorage.getItem("token");
if (!token) window.location.href = "index.html"; if (!token) window.location.href = "index.html";
document.getElementById('sDate').valueAsDate = new Date();
const now = new Date();
document.getElementById('sTime').value = now.toTimeString().slice(0,5);
fetchStatuses(); fetchStatuses();
fetchServices(); fetchServices();
loadCompanies(); loadCompanies();
}); });
// INIT GOOGLE AUTOCOMPLETE (Se llama cuando carga el script de Google)
function initAutocomplete() { function initAutocomplete() {
const input = document.getElementById("sAddress"); const input = document.getElementById("sAddress");
if(input) { if(input) {
autocomplete = new google.maps.places.Autocomplete(input, { types: ['geocode'] }); autocomplete = new google.maps.places.Autocomplete(input, { types: ['geocode'] });
// Evitar que el enter envíe el formulario al seleccionar dirección input.addEventListener('keydown', function(e) { if (e.key === "Enter") e.preventDefault(); });
input.addEventListener('keydown', function(e) {
if (e.key === "Enter") e.preventDefault();
});
} }
} }
@@ -277,23 +267,150 @@
function toggleView(view) { function toggleView(view) {
document.getElementById('servicesListView').classList.add('hidden'); document.getElementById('servicesListView').classList.add('hidden');
document.getElementById('createServiceView').classList.add('hidden'); document.getElementById('createServiceView').classList.add('hidden');
if(view === 'list') { if(view === 'list') {
document.getElementById('servicesListView').classList.remove('hidden'); document.getElementById('servicesListView').classList.remove('hidden');
fetchServices(); fetchServices();
} else { } else {
// RESETEAR FORMULARIO SI ES NUEVO
if(document.getElementById('editServiceId').value !== "") {
document.getElementById('editServiceId').value = ""; // Limpiar ID
document.querySelector('form').reset();
document.getElementById('formTitle').innerHTML = '<i data-lucide="file-plus" class="text-green-600"></i> Alta de Nuevo Servicio';
document.getElementById('sDate').valueAsDate = new Date(); // Reset fecha
lucide.createIcons();
}
document.getElementById('createServiceView').classList.remove('hidden'); document.getElementById('createServiceView').classList.remove('hidden');
window.scrollTo(0,0); window.scrollTo(0,0);
} }
} }
// --- DATOS MAESTROS --- // --- FUNCIONES EDICIÓN Y BORRADO (NUEVAS) ---
async function editService() {
if(!currentServiceId) return;
// Cargar datos completos si no los tenemos
try {
const res = await fetch(`${API_URL}/services/${currentServiceId}`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const json = await res.json();
if(json.ok) {
fillEditForm(json.service);
closeDetailPanel();
document.getElementById('createServiceView').classList.remove('hidden');
document.getElementById('servicesListView').classList.add('hidden');
}
} catch(e) { showToast("Error cargando datos", true); }
}
function fillEditForm(s) {
document.getElementById('editServiceId').value = s.id;
document.getElementById('formTitle').innerHTML = '<i data-lucide="edit-2" class="text-blue-600"></i> Editando Servicio #' + s.id;
document.getElementById('sPhone').value = s.contact_phone;
document.getElementById('sName').value = s.contact_name;
document.getElementById('sAddress').value = s.address;
document.getElementById('sEmail').value = s.email || '';
document.getElementById('sDesc').value = s.description || '';
// Fechas (Cortar la parte de tiempo ISO)
if(s.scheduled_date) document.getElementById('sDate').value = s.scheduled_date.split('T')[0];
if(s.scheduled_time) document.getElementById('sTime').value = s.scheduled_time;
document.getElementById('sDuration').value = s.duration_minutes || 30;
document.getElementById('sUrgent').checked = s.is_urgent;
document.getElementById('sIsCompany').checked = s.is_company;
toggleCompanyFields(); // Mostrar/ocultar panel
if(s.is_company) {
document.getElementById('sCompanyId').value = s.company_id || '';
document.getElementById('sCompanyRef').value = s.company_ref || '';
}
document.getElementById('sNotesInternal').value = s.internal_notes || '';
document.getElementById('sNotesClient').value = s.client_notes || '';
// El estado inicial no se edita aquí, se hace desde el panel de estados
document.getElementById('sCreateStatus').disabled = true;
lucide.createIcons();
}
async function deleteService() {
if(!currentServiceId || !confirm("¿Estás seguro de que quieres borrar este servicio permanentemente?")) return;
try {
const res = await fetch(`${API_URL}/services/${currentServiceId}`, {
method: 'DELETE',
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
});
if(res.ok) {
showToast("Servicio eliminado");
closeDetailPanel();
fetchServices();
} else { showToast("Error al borrar", true); }
} catch(e) { showToast("Error conexión", true); }
}
// --- SUBMIT UNIFICADO (CREAR / EDITAR) ---
async function handleFormSubmit(e) {
e.preventDefault();
const btn = document.getElementById('btnSave');
btn.disabled = true; btn.innerText = "Procesando...";
const editId = document.getElementById('editServiceId').value;
const isEdit = !!editId;
const data = {
phone: document.getElementById('sPhone').value,
name: document.getElementById('sName').value,
address: document.getElementById('sAddress').value,
email: document.getElementById('sEmail').value,
description: document.getElementById('sDesc').value,
scheduled_date: document.getElementById('sDate').value,
scheduled_time: document.getElementById('sTime').value,
duration: document.getElementById('sDuration').value,
is_urgent: document.getElementById('sUrgent').checked,
is_company: document.getElementById('sIsCompany').checked,
company_id: document.getElementById('sCompanyId').value || null,
company_ref: document.getElementById('sCompanyRef').value,
internal_notes: document.getElementById('sNotesInternal').value,
client_notes: document.getElementById('sNotesClient').value
};
if (!isEdit) {
data.status_id = document.getElementById('sCreateStatus').value; // Solo al crear
}
const url = isEdit ? `${API_URL}/services/${editId}` : `${API_URL}/services`;
const method = isEdit ? 'PUT' : 'POST';
try {
const res = await fetch(url, {
method: method,
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify(data)
});
const json = await res.json();
if (json.ok) {
showToast(isEdit ? "✅ Servicio Actualizado" : "✅ Servicio Creado");
toggleView('list');
} else {
showToast("❌ " + (json.error || "Error"), true);
}
} catch (e) {
showToast("Error de conexión", true);
} finally {
btn.disabled = false; btn.innerText = "GUARDAR";
}
}
// --- RESTO DE FUNCIONES (MAESTROS, LISTAR, ETC) ---
async function fetchStatuses() { async function fetchStatuses() {
try { try {
const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json(); const data = await res.json();
if(data.ok) { if(data.ok) {
allStatuses = data.statuses; allStatuses = data.statuses;
// Llenar selector de creación
const createSel = document.getElementById('sCreateStatus'); const createSel = document.getElementById('sCreateStatus');
createSel.innerHTML = ''; createSel.innerHTML = '';
allStatuses.forEach(s => { allStatuses.forEach(s => {
@@ -329,7 +446,41 @@
} catch(e) { alert("Error"); } } catch(e) { alert("Error"); }
} }
// --- LÓGICA DE SERVICIOS --- function toggleCompanyFields() {
const isChecked = document.getElementById('sIsCompany').checked;
const div = document.getElementById('companyFields');
if(isChecked) { div.classList.remove('hidden'); loadCompanies(); } else { div.classList.add('hidden'); }
}
async function searchClientByPhone() {
const phone = document.getElementById('sPhone').value;
if(phone.length < 8) return;
try {
const res = await fetch(`${API_URL}/clients/search?phone=${phone}`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if (data.ok && data.client) {
const c = data.client;
document.getElementById('sName').value = c.full_name;
document.getElementById('sEmail').value = c.email || "";
document.getElementById('clientFoundMsg').classList.remove('hidden');
const addresses = c.addresses || [];
if (addresses.length > 0) {
document.getElementById('sAddress').value = addresses[addresses.length - 1];
const select = document.getElementById('sAddressSelect');
select.innerHTML = '<option value="">Otras direcciones...</option>';
addresses.forEach(addr => select.innerHTML += `<option value="${addr}">${addr}</option>`);
if (addresses.length > 1) select.classList.remove('hidden');
}
} else {
document.getElementById('clientFoundMsg').classList.add('hidden');
document.getElementById('sAddressSelect').classList.add('hidden');
}
} catch (e) { console.error(e); }
}
function selectAddress(val) { if(val) document.getElementById('sAddress').value = val; }
// --- LISTAR Y DETALLES ---
async function fetchServices() { async function fetchServices() {
try { try {
const res = await fetch(`${API_URL}/services`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }); const res = await fetch(`${API_URL}/services`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
@@ -373,94 +524,23 @@
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
} }
function toggleCompanyFields() {
const isChecked = document.getElementById('sIsCompany').checked;
const div = document.getElementById('companyFields');
if(isChecked) { div.classList.remove('hidden'); loadCompanies(); } else { div.classList.add('hidden'); }
}
async function searchClientByPhone() {
const phone = document.getElementById('sPhone').value;
if(phone.length < 8) return;
try {
const res = await fetch(`${API_URL}/clients/search?phone=${phone}`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if (data.ok && data.client) {
const c = data.client;
document.getElementById('sName').value = c.full_name;
document.getElementById('sEmail').value = c.email || "";
document.getElementById('clientFoundMsg').classList.remove('hidden');
const addresses = c.addresses || [];
if (addresses.length > 0) {
document.getElementById('sAddress').value = addresses[addresses.length - 1];
const select = document.getElementById('sAddressSelect');
select.innerHTML = '<option value="">Otras direcciones...</option>';
addresses.forEach(addr => select.innerHTML += `<option value="${addr}">${addr}</option>`);
if (addresses.length > 1) select.classList.remove('hidden');
}
} else {
document.getElementById('clientFoundMsg').classList.add('hidden');
document.getElementById('sAddressSelect').classList.add('hidden');
}
} catch (e) { console.error(e); }
}
function selectAddress(val) { if(val) document.getElementById('sAddress').value = val; }
async function createService(e) {
e.preventDefault();
const btn = document.getElementById('btnSave');
btn.disabled = true; btn.innerText = "Guardando...";
const data = {
phone: document.getElementById('sPhone').value,
name: document.getElementById('sName').value,
address: document.getElementById('sAddress').value,
email: document.getElementById('sEmail').value,
description: document.getElementById('sDesc').value,
scheduled_date: document.getElementById('sDate').value,
scheduled_time: document.getElementById('sTime').value,
duration: document.getElementById('sDuration').value,
is_urgent: document.getElementById('sUrgent').checked,
is_company: document.getElementById('sIsCompany').checked,
company_id: document.getElementById('sCompanyId').value || null,
company_ref: document.getElementById('sCompanyRef').value,
internal_notes: document.getElementById('sNotesInternal').value,
client_notes: document.getElementById('sNotesClient').value,
status_id: document.getElementById('sCreateStatus').value // NUEVO: Estado manual
};
try {
const res = await fetch(`${API_URL}/services`, {
method: 'POST',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify(data)
});
const json = await res.json();
if (json.ok) {
showToast("✅ Servicio Creado");
document.querySelector('form').reset();
toggleView('list');
} else {
showToast("❌ " + json.error, true);
}
} catch (e) { showToast("Error de conexión", true); }
finally { btn.disabled = false; btn.innerHTML = '<i data-lucide="save" class="w-6 h-6"></i> CREAR SERVICIO'; lucide.createIcons(); }
}
async function openDetail(id, client, title, statusName, statusColor, date) { async function openDetail(id, client, title, statusName, statusColor, date) {
currentServiceId = id; currentServiceId = id;
document.getElementById('serviceDetailPanel').classList.remove('hidden'); document.getElementById('serviceDetailPanel').classList.remove('hidden');
document.getElementById('detailTitle').innerText = title || 'Servicio General'; document.getElementById('detailTitle').innerText = title || 'Servicio General';
document.getElementById('detailClient').innerText = client; document.getElementById('detailClient').innerText = client;
document.getElementById('detailId').innerText = `#${id}`; document.getElementById('detailId').innerText = `#${id}`;
document.getElementById('detailDate').innerText = date; document.getElementById('detailDate').innerText = date;
const badge = document.getElementById('detailStatusBadge'); const badge = document.getElementById('detailStatusBadge');
badge.innerText = statusName; badge.innerText = statusName;
badge.className = `px-2 py-1 rounded text-[10px] font-bold text-white uppercase tracking-wide bg-${statusColor}-500`; badge.className = `px-2 py-1 rounded text-[10px] font-bold text-white uppercase tracking-wide bg-${statusColor}-500`;
const sel = document.getElementById('newStatusSelect'); const sel = document.getElementById('newStatusSelect');
sel.innerHTML = ""; sel.innerHTML = "";
allStatuses.forEach(s => sel.innerHTML += `<option value="${s.id}">${s.name}</option>`); allStatuses.forEach(s => sel.innerHTML += `<option value="${s.id}">${s.name}</option>`);
loadTimeline(id); loadTimeline(id);
} }