Actualizar clientes.html

This commit is contained in:
2026-02-28 18:17:54 +00:00
parent 1abe39d5ed
commit 67f79de51c

View File

@@ -22,20 +22,17 @@
<div class="flex-1 flex flex-col h-full min-w-0"> <div class="flex-1 flex flex-col h-full min-w-0">
<div id="header-container"></div> <div id="header-container"></div>
<main class="flex-1 flex flex-row overflow-hidden"> <main class="flex-1 flex flex-row overflow-hidden relative">
<div class="w-full md:w-1/3 bg-white border-r border-gray-200 flex flex-col z-10 shadow-lg"> <div class="w-full md:w-1/3 bg-white border-r border-gray-200 flex flex-col z-10 shadow-lg h-full">
<div class="p-4 border-b border-gray-100 bg-white"> <div class="p-4 border-b border-gray-100 bg-white">
<div class="flex justify-between items-center mb-3"> <div class="flex justify-between items-center mb-3">
<h2 class="text-lg font-bold text-gray-800">Cartera de Clientes</h2> <h2 class="text-lg font-bold text-gray-800">Cartera de Clientes</h2>
<button onclick="openNewClientModal()" class="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded-lg shadow transition-colors" title="Nuevo Cliente">
<i data-lucide="user-plus" class="w-5 h-5"></i>
</button>
</div> </div>
<div class="relative"> <div class="relative">
<i data-lucide="search" class="w-4 h-4 absolute left-3 top-3 text-gray-400"></i> <i data-lucide="search" class="w-4 h-4 absolute left-3 top-3 text-gray-400"></i>
<input type="text" id="searchInput" onkeyup="searchClients()" placeholder="Buscar por nombre, tlf..." class="w-full pl-10 pr-4 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all"> <input type="text" id="searchInput" oninput="debounceSearch()" placeholder="Buscar por nombre, teléfono..." class="w-full pl-10 pr-4 py-2 bg-gray-50 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 transition-all">
</div> </div>
</div> </div>
@@ -51,73 +48,62 @@
<i data-lucide="users" class="w-10 h-10 text-gray-400"></i> <i data-lucide="users" class="w-10 h-10 text-gray-400"></i>
</div> </div>
<p class="text-lg font-medium">Selecciona un cliente</p> <p class="text-lg font-medium">Selecciona un cliente</p>
<p class="text-sm">o crea uno nuevo para ver sus detalles</p> <p class="text-sm">o búscalo en el listado para ver su historial</p>
</div> </div>
<div id="clientContent" class="hidden flex-col h-full"> <div id="clientContent" class="hidden flex-col h-full fade-in">
<div class="bg-white p-6 border-b border-gray-200 shadow-sm shrink-0"> <div class="bg-white p-6 border-b border-gray-200 shadow-sm shrink-0">
<div class="flex justify-between items-start"> <div class="flex justify-between items-start">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center text-2xl font-bold text-blue-600" id="detailAvatar"> <div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center text-2xl font-black text-blue-600 uppercase shadow-inner border border-blue-200" id="detailAvatar">
A C
</div> </div>
<div> <div>
<h1 class="text-2xl font-bold text-gray-800 leading-tight" id="detailName">Nombre Cliente</h1> <h1 class="text-2xl font-black text-gray-800 leading-tight uppercase" id="detailName">Nombre Cliente</h1>
<div class="flex items-center gap-3 mt-1 text-sm text-gray-500"> <div class="flex items-center gap-3 mt-1 text-sm font-bold text-gray-500">
<span class="flex items-center gap-1"><i data-lucide="phone" class="w-3 h-3"></i> <span id="detailPhone">...</span></span> <span class="flex items-center gap-1"><i data-lucide="phone" class="w-3 h-3 text-blue-500"></i> <span id="detailPhone">...</span></span>
<span class="hidden md:flex items-center gap-1"><i data-lucide="mail" class="w-3 h-3"></i> <span id="detailEmail">...</span></span>
</div> </div>
</div> </div>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<a id="btnCall" href="#" class="p-2 text-gray-500 hover:bg-gray-100 rounded-lg transition" title="Llamar"><i data-lucide="phone" class="w-5 h-5"></i></a> <a id="btnCall" href="#" class="p-2 text-gray-500 hover:text-blue-600 hover:bg-blue-50 rounded-lg transition" title="Llamar"><i data-lucide="phone" class="w-5 h-5"></i></a>
<a id="btnWhatsapp" href="#" target="_blank" class="p-2 text-green-600 hover:bg-green-50 rounded-lg transition" title="WhatsApp"><i data-lucide="message-circle" class="w-5 h-5"></i></a> <a id="btnWhatsapp" href="#" target="_blank" class="p-2 text-gray-500 hover:text-emerald-600 hover:bg-emerald-50 rounded-lg transition" title="WhatsApp"><i data-lucide="message-circle" class="w-5 h-5"></i></a>
<button onclick="saveNotes()" class="bg-slate-800 text-white px-4 py-2 rounded-lg text-sm font-bold shadow hover:bg-slate-700 transition flex items-center gap-2">
<i data-lucide="save" class="w-4 h-4"></i> Guardar Notas
</button>
</div> </div>
</div> </div>
</div> </div>
<div class="flex-1 overflow-y-auto p-6 space-y-6"> <div class="flex-1 overflow-y-auto p-6 space-y-6">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div class="grid grid-cols-1 gap-6">
<div class="bg-white p-5 rounded-[1.5rem] shadow-sm border border-gray-100">
<div class="bg-white p-5 rounded-xl shadow-sm border border-gray-100"> <h3 class="text-xs font-black text-gray-400 uppercase tracking-widest mb-4 flex items-center gap-2">
<h3 class="text-sm font-bold text-gray-400 uppercase tracking-wider mb-4 flex items-center gap-2"> <i data-lucide="map-pin" class="w-4 h-4 text-rose-500"></i> Direcciones Encontradas
<i data-lucide="map-pin" class="w-4 h-4"></i> Direcciones
</h3> </h3>
<div id="addressList" class="space-y-3"> <div id="addressList" class="space-y-3">
</div> </div>
</div>
<div class="bg-white p-5 rounded-xl shadow-sm border border-gray-100 flex flex-col">
<h3 class="text-sm font-bold text-gray-400 uppercase tracking-wider mb-2 flex items-center gap-2">
<i data-lucide="sticky-note" class="w-4 h-4"></i> Notas Privadas
</h3>
<textarea id="detailNotes" class="w-full flex-1 p-3 bg-yellow-50 border border-yellow-100 rounded-lg text-sm text-gray-700 focus:outline-none resize-none" placeholder="Escribe notas sobre este cliente..."></textarea>
</div> </div>
</div> </div>
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden"> <div class="bg-white rounded-[1.5rem] shadow-sm border border-gray-100 overflow-hidden">
<div class="p-4 border-b border-gray-100 bg-gray-50 flex justify-between items-center"> <div class="p-5 border-b border-gray-100 bg-gray-50/50 flex justify-between items-center">
<h3 class="font-bold text-gray-700">Historial de Servicios</h3> <h3 class="text-xs font-black text-gray-400 uppercase tracking-widest flex items-center gap-2">
<button onclick="createNewServiceForClient()" class="text-xs font-bold text-blue-600 hover:underline">+ Nuevo Servicio</button> <i data-lucide="folder-clock" class="w-4 h-4 text-blue-500"></i> Historial de Expedientes
</h3>
</div> </div>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="w-full text-sm text-left"> <table class="w-full text-sm text-left">
<thead class="text-xs text-gray-500 uppercase bg-gray-50 border-b"> <thead class="text-[10px] font-black text-gray-400 uppercase tracking-widest bg-gray-50 border-b">
<tr> <tr>
<th class="px-4 py-3">Fecha</th> <th class="px-6 py-4">Fecha Entrada</th>
<th class="px-4 py-3">Servicio</th> <th class="px-6 py-4">Referencia</th>
<th class="px-4 py-3">Estado</th> <th class="px-6 py-4">Compañía</th>
<th class="px-4 py-3">Técnico</th> <th class="px-6 py-4">Técnico</th>
<th class="px-4 py-3 text-right">Acción</th> <th class="px-6 py-4">Estado</th>
</tr> </tr>
</thead> </thead>
<tbody id="servicesTableBody" class="text-gray-600"> <tbody id="servicesTableBody" class="text-gray-600 font-medium">
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -130,241 +116,236 @@
</main> </main>
</div> </div>
<div id="newClientModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center backdrop-blur-sm fade-in"> <div id="toast" class="fixed bottom-5 right-5 bg-slate-800 text-white px-6 py-3 rounded-xl shadow-2xl translate-y-20 opacity-0 transition-all duration-300 z-[200] flex items-center gap-3 font-bold text-sm tracking-wide">
<div class="bg-white rounded-xl shadow-2xl w-full max-w-md p-6"> <i data-lucide="info" class="w-4 h-4"></i> <span id="toastMsg"></span>
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-bold text-gray-800">Nuevo Cliente</h3>
<button onclick="document.getElementById('newClientModal').classList.add('hidden')" class="text-gray-400 hover:text-gray-600"><i data-lucide="x" class="w-5 h-5"></i></button>
</div>
<form onsubmit="handleCreateClient(event)" class="space-y-4">
<input type="text" id="ncName" placeholder="Nombre Completo" required class="w-full border p-2 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none">
<input type="tel" id="ncPhone" placeholder="Teléfono" required class="w-full border p-2 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none">
<input type="email" id="ncEmail" placeholder="Email (Opcional)" class="w-full border p-2 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none">
<input type="text" id="ncAddress" placeholder="Dirección Principal" class="w-full border p-2 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none">
<textarea id="ncNotes" placeholder="Notas iniciales..." class="w-full border p-2 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none" rows="3"></textarea>
<button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 rounded-lg shadow transition">Crear Ficha</button>
</form>
</div>
</div> </div>
<div id="toast" class="fixed bottom-5 right-5 bg-slate-800 text-white px-6 py-3 rounded-lg shadow-2xl translate-y-20 opacity-0 transition-all duration-300 z-50 flex items-center gap-3"><span id="toastMsg"></span></div>
<script src="js/layout.js"></script> <script src="js/layout.js"></script>
<script> <script>
let currentClientId = null; const API_URL = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
let currentClientData = null; ? 'http://localhost:3000'
: 'https://integrarepara-api.integrarepara.es';
document.addEventListener("DOMContentLoaded", () => { let systemStatuses = [];
loadClients(); let searchTimeout = null;
document.addEventListener("DOMContentLoaded", async () => {
if (!localStorage.getItem("token")) window.location.href = "index.html";
lucide.createIcons();
await loadStatuses();
// Iniciar búsqueda vacía para traer los últimos clientes
loadClients("");
}); });
// 1. CARGAR LISTA async function loadStatuses() {
try {
const res = await fetch(`${API_URL}/statuses`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if (data.ok) systemStatuses = data.statuses;
} catch (e) { console.error("Error al cargar estados", e); }
}
// DEBOUNCE PARA NO SATURAR EL SERVIDOR AL ESCRIBIR
function debounceSearch() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
searchClients();
}, 300);
}
async function searchClients() {
const val = document.getElementById('searchInput').value.trim();
loadClients(val);
}
async function loadClients(search = "") { async function loadClients(search = "") {
const list = document.getElementById('clientsList'); const list = document.getElementById('clientsList');
list.innerHTML = `<div class="text-center py-10 text-blue-500 flex flex-col items-center gap-2"><i data-lucide="loader-2" class="w-5 h-5 animate-spin"></i><span class="text-xs font-bold uppercase tracking-widest">Buscando...</span></div>`;
lucide.createIcons();
try { try {
const res = await fetch(`${API_URL}/clients?search=${search}`, { // AQUÍ ES DONDE ESTÁ LA MAGIA. Buscamos directamente en scraped_services
const res = await fetch(`${API_URL}/services/active`, {
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
}); });
const data = await res.json(); const data = await res.json();
if (!data.ok) throw new Error("Error en la carga");
// Agrupamos todos los servicios por teléfono para simular "Clientes"
let clientsMap = {};
data.services.forEach(s => {
const raw = s.raw_data || {};
let phone = raw['Teléfono'] || raw['TELEFONO'] || raw['TELEFONOS'] || "";
// Normalizar teléfono
const matchPhone = String(phone).match(/[6789]\d{8}/);
const cleanPhone = matchPhone ? matchPhone[0] : null;
if (cleanPhone) {
if (!clientsMap[cleanPhone]) {
clientsMap[cleanPhone] = {
id: cleanPhone, // Usamos el tfno como ID
full_name: raw['Nombre Cliente'] || raw['CLIENTE'] || "Cliente Sin Nombre",
phone: cleanPhone,
addresses: new Set(),
services: []
};
}
const addr = `${raw['Dirección'] || ''} ${raw['Población'] || ''}`.trim();
if (addr) clientsMap[cleanPhone].addresses.add(addr);
clientsMap[cleanPhone].services.push(s);
}
});
// Convertir mapa a Array y Filtrar si hay texto
let clientsArray = Object.values(clientsMap);
if (search !== "") {
const sLower = search.toLowerCase();
clientsArray = clientsArray.filter(c =>
c.full_name.toLowerCase().includes(sLower) ||
c.phone.includes(sLower)
);
}
// Ordenar por número de expedientes
clientsArray.sort((a, b) => b.services.length - a.services.length);
// Dibujar la lista
list.innerHTML = ""; list.innerHTML = "";
if(data.clients.length === 0) { if(clientsArray.length === 0) {
list.innerHTML = `<div class="text-center py-8 text-gray-400 text-xs">No se encontraron clientes.</div>`; list.innerHTML = `<div class="text-center py-8 text-gray-400 text-xs font-bold uppercase tracking-widest border-2 border-dashed border-gray-200 rounded-xl m-2">Sin resultados</div>`;
return; return;
} }
data.clients.forEach(c => { clientsArray.forEach(c => {
const el = document.createElement('div'); const el = document.createElement('div');
el.className = `p-3 mb-1 rounded-lg cursor-pointer hover:bg-gray-50 transition border border-transparent hover:border-gray-200 flex justify-between items-center group`; el.className = `p-3 mb-1 rounded-xl cursor-pointer hover:bg-blue-50 transition border border-transparent hover:border-blue-100 flex justify-between items-center group`;
el.id = `client-card-${c.id}`; el.id = `client-card-${c.id}`;
el.onclick = () => loadClientDetails(c.id);
// Al hacer click, pintamos el detalle
el.onclick = () => renderClientDetails(c, el.id);
el.innerHTML = ` el.innerHTML = `
<div class="flex items-center gap-3 overflow-hidden"> <div class="flex items-center gap-3 overflow-hidden">
<div class="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center text-slate-500 font-bold text-sm shrink-0"> <div class="w-10 h-10 rounded-full bg-slate-100 text-slate-500 flex items-center justify-center font-black text-sm shrink-0 border border-slate-200 group-hover:bg-blue-600 group-hover:text-white group-hover:border-blue-600 transition-colors uppercase">
${c.full_name.charAt(0).toUpperCase()} ${c.full_name.charAt(0)}
</div> </div>
<div class="min-w-0"> <div class="min-w-0">
<p class="font-bold text-gray-700 text-sm truncate">${c.full_name}</p> <p class="font-black text-slate-700 text-sm truncate uppercase tracking-tight">${c.full_name}</p>
<p class="text-xs text-gray-400 truncate">${c.phone}</p> <p class="text-xs font-bold text-slate-400 truncate mt-0.5">${c.phone}</p>
</div> </div>
</div> </div>
<div class="text-right shrink-0"> <div class="text-right shrink-0">
<span class="bg-blue-50 text-blue-600 text-[10px] font-bold px-2 py-1 rounded-full border border-blue-100"> <span class="bg-slate-100 text-slate-500 group-hover:bg-blue-100 group-hover:text-blue-700 transition-colors text-[10px] font-black px-2 py-1 rounded border border-slate-200 group-hover:border-blue-200">
${c.service_count} Svc ${c.services.length} EXP
</span> </span>
</div> </div>
`; `;
list.appendChild(el); list.appendChild(el);
}); });
} catch(e) { console.error(e); }
} catch(e) {
console.error(e);
list.innerHTML = `<div class="text-center py-8 text-red-400 text-xs font-bold uppercase tracking-widest border-2 border-dashed border-red-200 rounded-xl m-2">Error de carga</div>`;
}
} }
function searchClients() { // 3. RENDERIZAR DETALLES
const val = document.getElementById('searchInput').value; function renderClientDetails(clientData, cardId) {
loadClients(val);
}
// 2. CARGAR DETALLES
async function loadClientDetails(id) {
currentClientId = id;
// UI Toggle // UI Toggle
document.getElementById('emptyState').classList.add('hidden'); document.getElementById('emptyState').classList.add('hidden');
document.getElementById('clientContent').classList.remove('hidden'); document.getElementById('clientContent').classList.remove('hidden');
document.getElementById('clientContent').classList.add('flex'); // Flex para estructura document.getElementById('clientContent').classList.add('flex');
// Resaltar seleccionado // Resaltar seleccionado en la lista
document.querySelectorAll('#clientsList > div').forEach(d => d.classList.remove('selected-card')); document.querySelectorAll('#clientsList > div').forEach(d => d.classList.remove('selected-card'));
const card = document.getElementById(`client-card-${id}`); const card = document.getElementById(cardId);
if(card) card.classList.add('selected-card'); if(card) card.classList.add('selected-card');
try { // Rellenar Cabecera
const res = await fetch(`${API_URL}/clients/${id}/details`, { document.getElementById('detailAvatar').innerText = clientData.full_name.charAt(0).toUpperCase();
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } document.getElementById('detailName').innerText = clientData.full_name;
}); document.getElementById('detailPhone').innerText = clientData.phone;
const data = await res.json();
const c = data.client;
currentClientData = c;
// Rellenar Cabecera // Botones Acción
document.getElementById('detailAvatar').innerText = c.full_name.charAt(0).toUpperCase(); document.getElementById('btnCall').href = `tel:+34${clientData.phone}`;
document.getElementById('detailName').innerText = c.full_name; document.getElementById('btnWhatsapp').href = `https://wa.me/34${clientData.phone}`;
document.getElementById('detailPhone').innerText = c.phone;
document.getElementById('detailEmail').innerText = c.email || "Sin email";
document.getElementById('detailNotes').value = c.notes || "";
// Botones Acción // Direcciones
document.getElementById('btnCall').href = `tel:${c.phone}`; const addrContainer = document.getElementById('addressList');
document.getElementById('btnWhatsapp').href = `https://wa.me/${c.phone.replace('+','')}`; addrContainer.innerHTML = "";
const addresses = Array.from(clientData.addresses);
// Direcciones
const addrContainer = document.getElementById('addressList');
addrContainer.innerHTML = "";
const addresses = c.addresses || [];
if(addresses.length === 0) {
addrContainer.innerHTML = `<p class="text-xs text-gray-400 italic">No hay direcciones guardadas.</p>`;
} else {
addresses.forEach(addr => {
const div = document.createElement('div');
div.className = "flex justify-between items-center text-sm p-2 bg-gray-50 rounded border border-gray-100";
div.innerHTML = `
<span class="truncate pr-2">${addr}</span>
<a href="https://maps.google.com/?q=${encodeURIComponent(addr)}" target="_blank" class="text-blue-500 hover:text-blue-700">
<i data-lucide="map" class="w-4 h-4"></i>
</a>
`;
addrContainer.appendChild(div);
});
}
// Tabla de Servicios
const tbody = document.getElementById('servicesTableBody');
tbody.innerHTML = "";
if(data.services.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" class="px-4 py-8 text-center text-xs text-gray-400">Este cliente aún no tiene servicios.</td></tr>`;
} else {
data.services.forEach(s => {
const tr = document.createElement('tr');
tr.className = "border-b border-gray-50 hover:bg-gray-50 transition";
const date = new Date(s.scheduled_date).toLocaleDateString();
const color = s.status_color || 'gray';
tr.innerHTML = `
<td class="px-4 py-3 font-mono text-xs text-gray-500">${date}</td>
<td class="px-4 py-3 font-medium text-gray-800">${s.title || 'Servicio'}</td>
<td class="px-4 py-3"><span class="bg-${color}-100 text-${color}-700 px-2 py-1 rounded text-xs font-bold">${s.status_name}</span></td>
<td class="px-4 py-3 text-xs text-gray-500">${s.assigned_name || '-'}</td>
<td class="px-4 py-3 text-right">
<a href="servicios.html?id=${s.id}" class="text-blue-600 hover:underline text-xs font-bold">Ver</a>
</td>
`;
tbody.appendChild(tr);
});
}
lucide.createIcons();
} catch(e) { console.error(e); }
}
// 3. GUARDAR NOTAS
async function saveNotes() {
if(!currentClientId || !currentClientData) return;
const newNotes = document.getElementById('detailNotes').value;
try { if(addresses.length === 0) {
// Hacemos PUT actualizando solo las notas, manteniendo el resto addrContainer.innerHTML = `<p class="text-xs font-bold text-gray-400 uppercase tracking-widest">Sin dirección registrada.</p>`;
const body = { } else {
full_name: currentClientData.full_name, addresses.forEach(addr => {
email: currentClientData.email, const div = document.createElement('div');
addresses: currentClientData.addresses, div.className = "flex justify-between items-center text-xs font-bold text-slate-600 p-3 bg-slate-50 rounded-xl border border-slate-100 uppercase";
notes: newNotes div.innerHTML = `
}; <span class="truncate pr-4"><i data-lucide="map-pin" class="w-3 h-3 inline mr-1 text-slate-400"></i> ${addr}</span>
<a href="https://maps.google.com/?q=${encodeURIComponent(addr)}" target="_blank" class="text-blue-500 hover:text-blue-700 bg-blue-50 p-2 rounded-lg border border-blue-100 transition-colors">
const res = await fetch(`${API_URL}/clients/${currentClientId}`, { <i data-lucide="external-link" class="w-4 h-4"></i>
method: 'PUT', </a>
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, `;
body: JSON.stringify(body) addrContainer.appendChild(div);
}); });
}
if(res.ok) { // Tabla de Servicios
currentClientData.notes = newNotes; // Actualizar local const tbody = document.getElementById('servicesTableBody');
showToast("Notas guardadas"); tbody.innerHTML = "";
} else {
showToast("Error al guardar", true); // Ordenar servicios del más nuevo al más antiguo
} clientData.services.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
} catch(e) { showToast("Error conexión", true); }
}
// 4. CREAR CLIENTE clientData.services.forEach(s => {
function openNewClientModal() { const tr = document.createElement('tr');
document.getElementById('newClientModal').classList.remove('hidden'); tr.className = "border-b border-gray-100 hover:bg-blue-50/30 transition-colors";
document.getElementById('ncName').focus();
}
async function handleCreateClient(e) {
e.preventDefault();
const data = {
full_name: document.getElementById('ncName').value,
phone: document.getElementById('ncPhone').value,
email: document.getElementById('ncEmail').value,
address: document.getElementById('ncAddress').value,
notes: document.getElementById('ncNotes').value
};
try {
const res = await fetch(`${API_URL}/clients`, {
method: 'POST',
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify(data)
});
const json = await res.json(); const raw = s.raw_data || {};
if(res.ok) { const date = new Date(s.created_at).toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit', year: '2-digit' });
showToast("Cliente creado");
document.getElementById('newClientModal').classList.add('hidden'); // Buscar estado real
e.target.reset(); const dbStatusId = raw.status_operativo;
loadClients(); // Recargar lista const statusObj = systemStatuses.find(st => String(st.id) === String(dbStatusId));
loadClientDetails(json.id); // Abrir el nuevo cliente const stName = statusObj ? statusObj.name : (s.status_name || 'Pendiente');
} else {
showToast("Error al crear", true); // Color del estado (si es finalizado lo ponemos gris)
} const isFinal = s.is_final || (statusObj && statusObj.is_final) || s.status === 'archived';
} catch(e) { showToast("Error conexión", true); } const badgeClass = isFinal
} ? "bg-slate-100 text-slate-500 border-slate-200"
: "bg-blue-50 text-blue-700 border-blue-200";
function createNewServiceForClient() { tr.innerHTML = `
if(!currentClientData) return; <td class="px-6 py-4 font-mono text-xs font-bold text-slate-500">${date}</td>
// Redirigir a servicios pre-rellenando datos (requiere lógica en servicios.html, o un modal aquí) <td class="px-6 py-4 font-black text-slate-800 text-xs">#${s.service_ref}</td>
// Por simplicidad, mandamos un alert o podríamos abrir el modal de servicios aquí si lo importamos. <td class="px-6 py-4 text-xs font-bold text-slate-500 uppercase">${raw['Compañía'] || 'Particular'}</td>
// Opción Pro: Navegar a calendario con el cliente seleccionado <td class="px-6 py-4 text-[10px] font-black text-slate-400 uppercase tracking-widest flex items-center gap-1.5 mt-1.5">
window.location.href = `calendario.html?client_id=${currentClientId}`; <i data-lucide="hard-hat" class="w-3 h-3"></i> ${s.assigned_name || 'Sin Asignar'}
</td>
<td class="px-6 py-4">
<span class="${badgeClass} px-3 py-1.5 rounded-lg text-[9px] font-black uppercase tracking-widest border shadow-sm">
${stName}
</span>
</td>
`;
tbody.appendChild(tr);
});
lucide.createIcons();
} }
function showToast(msg, isError = false) { function showToast(msg, isError = false) {
const t = document.getElementById('toast'), m = document.getElementById('toastMsg'); const t = document.getElementById('toast');
t.className = `fixed bottom-5 right-5 px-6 py-3 rounded-lg shadow-xl transition-all duration-300 z-50 flex items-center gap-3 ${isError ? 'bg-red-600' : 'bg-slate-800'} text-white font-medium`; const m = document.getElementById('toastMsg');
m.innerText = msg; t.classList.remove('translate-y-20', 'opacity-0'); t.className = `fixed bottom-5 right-5 px-6 py-4 rounded-xl shadow-2xl transition-all duration-300 z-[200] flex items-center gap-3 font-bold text-sm tracking-wide ${isError ? 'bg-red-600' : 'bg-slate-800'} text-white`;
m.innerText = msg;
t.classList.remove('translate-y-20', 'opacity-0');
setTimeout(() => t.classList.add('translate-y-20', 'opacity-0'), 3000); setTimeout(() => t.classList.add('translate-y-20', 'opacity-0'), 3000);
} }
</script> </script>