Actualizar servicios.html
This commit is contained in:
300
servicios.html
300
servicios.html
@@ -27,6 +27,10 @@
|
|||||||
/* Estilos base para formularios mejorados */
|
/* Estilos base para formularios mejorados */
|
||||||
.input-modern { @apply w-full bg-slate-50 border border-slate-200 px-4 py-3 rounded-xl text-sm font-semibold text-slate-700 outline-none transition-all focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-100; }
|
.input-modern { @apply w-full bg-slate-50 border border-slate-200 px-4 py-3 rounded-xl text-sm font-semibold text-slate-700 outline-none transition-all focus:border-blue-500 focus:bg-white focus:ring-2 focus:ring-blue-100; }
|
||||||
.label-modern { @apply block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1.5 ml-1; }
|
.label-modern { @apply block text-[10px] font-black text-slate-500 uppercase tracking-widest mb-1.5 ml-1; }
|
||||||
|
|
||||||
|
/* Estilos para el Modo Edición del Modal */
|
||||||
|
.input-editable { border-bottom: 2px solid #3b82f6 !important; background: #eff6ff !important; border-radius: 0.5rem !important; padding: 4px 8px !important; pointer-events: auto !important; }
|
||||||
|
.input-readonly { border: none !important; background: transparent !important; pointer-events: none; outline: none; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-slate-50 text-slate-800 font-sans antialiased text-left">
|
<body class="bg-slate-50 text-slate-800 font-sans antialiased text-left">
|
||||||
@@ -132,7 +136,6 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-8">
|
<div class="grid grid-cols-1 md:grid-cols-12 gap-8">
|
||||||
|
|
||||||
<div class="md:col-span-7 space-y-6">
|
<div class="md:col-span-7 space-y-6">
|
||||||
|
|
||||||
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-4">
|
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-4">
|
||||||
<h4 class="font-black text-slate-800 uppercase text-xs flex items-center gap-2 border-b border-slate-100 pb-3 mb-4">
|
<h4 class="font-black text-slate-800 uppercase text-xs flex items-center gap-2 border-b border-slate-100 pb-3 mb-4">
|
||||||
<i data-lucide="user" class="w-4 h-4 text-blue-500"></i> Datos del Cliente
|
<i data-lucide="user" class="w-4 h-4 text-blue-500"></i> Datos del Cliente
|
||||||
@@ -141,7 +144,7 @@
|
|||||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="label-modern">Teléfono</label>
|
<label class="label-modern">Teléfono</label>
|
||||||
<input type="tel" id="nPhone" placeholder="Ej: 600123456" class="input-modern" required>
|
<input type="tel" id="nPhone" oninput="searchClientByPhone(this.value)" placeholder="Ej: 600123456" class="input-modern" required>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="label-modern">Nombre Completo</label>
|
<label class="label-modern">Nombre Completo</label>
|
||||||
@@ -151,6 +154,18 @@
|
|||||||
<div>
|
<div>
|
||||||
<label class="label-modern">Dirección Completa</label>
|
<label class="label-modern">Dirección Completa</label>
|
||||||
<input type="text" id="nAddr" placeholder="Calle, número, piso, población..." class="input-modern" required>
|
<input type="text" id="nAddr" placeholder="Calle, número, piso, población..." class="input-modern" required>
|
||||||
|
<div id="addrSuggestions" class="hidden mt-2 p-2 bg-amber-50 border border-amber-200 rounded-lg text-[10px] font-bold text-amber-700">⚠️ Cliente con otras direcciones registradas.</div>
|
||||||
|
</div>
|
||||||
|
<div class="pt-2">
|
||||||
|
<label class="flex items-center gap-3 cursor-pointer">
|
||||||
|
<input type="checkbox" id="isCompanyCheck" onchange="toggleCompanyFields(this.checked)" class="w-5 h-5 rounded text-blue-600">
|
||||||
|
<span class="text-xs font-black text-slate-700 uppercase">¿Es de Compañía de Seguros?</span>
|
||||||
|
</label>
|
||||||
|
<div id="companyFields" class="hidden mt-4 space-y-4 p-4 bg-blue-50/30 rounded-xl border border-blue-100">
|
||||||
|
<select id="nCompanySelect" class="input-modern bg-white"><option value="">Seleccionar compañía...</option></select>
|
||||||
|
<input type="text" id="nExpRef" onblur="checkDuplicateRef(this.value)" placeholder="Nº Expediente" class="input-modern bg-white uppercase">
|
||||||
|
<p id="refAlert" class="hidden text-[10px] text-red-600 font-bold mt-1 uppercase">⚠️ Referencia ya existente</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -159,16 +174,14 @@
|
|||||||
<i data-lucide="align-left" class="w-4 h-4 text-amber-500"></i> Detalles de la Avería
|
<i data-lucide="align-left" class="w-4 h-4 text-amber-500"></i> Detalles de la Avería
|
||||||
</h4>
|
</h4>
|
||||||
<label class="label-modern">Descripción</label>
|
<label class="label-modern">Descripción</label>
|
||||||
<textarea id="nDesc" placeholder="Describe el problema a reparar..." rows="4" class="input-modern resize-none"></textarea>
|
<textarea id="nDesc" placeholder="Describe el problema a reparar..." rows="6" class="input-modern resize-none"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="md:col-span-5 space-y-6">
|
<div class="md:col-span-5 space-y-6">
|
||||||
|
|
||||||
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-4">
|
<div class="bg-white p-6 rounded-2xl border border-slate-200 shadow-sm space-y-4">
|
||||||
<h4 class="font-black text-slate-800 uppercase text-xs flex items-center gap-2 border-b border-slate-100 pb-3 mb-4">
|
<h4 class="font-black text-slate-800 uppercase text-xs flex items-center gap-2 border-b border-slate-100 pb-3 mb-4">
|
||||||
<i data-lucide="hammer" class="w-4 h-4 text-purple-500"></i> Asignación
|
<i data-lucide="hammer" class="w-4 h-4 text-purple-500"></i> Asignación y Tiempo
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -181,7 +194,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="label-modern">Operario</label>
|
<label class="label-modern">Operario</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@@ -192,20 +205,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="label-modern">Duración (Estimada)</label>
|
<label class="label-modern">Duración Estimada</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<select id="nDuration" class="input-modern appearance-none cursor-pointer pr-8">
|
<select id="nDuration" class="input-modern font-black text-blue-600 uppercase appearance-none cursor-pointer pr-8">
|
||||||
<option value="15">15 min</option>
|
<option value="15">15 min</option>
|
||||||
<option value="30">30 min</option>
|
<option value="30">30 min</option>
|
||||||
<option value="45">45 min</option>
|
<option value="45">45 min</option>
|
||||||
<option value="60" selected>1 hora</option>
|
<option value="60" selected>1 hora</option>
|
||||||
<option value="75">1 h 15 min</option>
|
<option value="90">1.5 horas</option>
|
||||||
<option value="90">1 h 30 min</option>
|
|
||||||
<option value="105">1 h 45 min</option>
|
|
||||||
<option value="120">2 horas</option>
|
<option value="120">2 horas</option>
|
||||||
<option value="150">2 h 30 min</option>
|
|
||||||
<option value="180">3 horas</option>
|
<option value="180">3 horas</option>
|
||||||
<option value="210">3 h 30 min</option>
|
|
||||||
<option value="240">4 horas (Max)</option>
|
<option value="240">4 horas (Max)</option>
|
||||||
</select>
|
</select>
|
||||||
<i data-lucide="clock" class="w-4 h-4 absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none"></i>
|
<i data-lucide="clock" class="w-4 h-4 absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none"></i>
|
||||||
@@ -232,7 +241,6 @@
|
|||||||
<i data-lucide="zap" class="w-5 h-5 text-amber-400"></i>
|
<i data-lucide="zap" class="w-5 h-5 text-amber-400"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -242,7 +250,7 @@
|
|||||||
<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-[2rem] shadow-2xl w-full max-w-4xl overflow-hidden flex flex-col fade-in relative">
|
<div class="bg-white rounded-[2rem] shadow-2xl w-full max-w-4xl overflow-hidden flex flex-col fade-in relative">
|
||||||
|
|
||||||
<button onclick="closeDetailModal()" class="absolute top-6 right-6 text-slate-400 hover:text-red-500 bg-white shadow-sm border border-slate-200 p-2.5 rounded-full transition-all hover:bg-red-50 hover:border-red-100 z-10"><i data-lucide="x" class="w-5 h-5"></i></button>
|
<button onclick="closeDetailModal()" class="absolute top-6 right-6 text-slate-400 hover:text-red-500 bg-white shadow-sm border border-slate-200 p-2.5 rounded-full transition-all hover:bg-red-50 hover:border-red-100 z-20"><i data-lucide="x" class="w-5 h-5"></i></button>
|
||||||
|
|
||||||
<div class="p-6 border-b border-slate-100 flex justify-between items-center bg-slate-50/50 pr-24">
|
<div class="p-6 border-b border-slate-100 flex justify-between items-center bg-slate-50/50 pr-24">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
@@ -267,34 +275,31 @@
|
|||||||
<div class="bg-slate-50 p-5 rounded-[1.5rem] border border-slate-100 space-y-4">
|
<div class="bg-slate-50 p-5 rounded-[1.5rem] border border-slate-100 space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest">Asegurado / Cliente</p>
|
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest">Asegurado / Cliente</p>
|
||||||
<input type="text" id="editName" class="input-readonly w-full font-black text-slate-800 text-lg uppercase" readonly>
|
<input type="text" id="editName" class="input-readonly w-full font-black text-slate-800 text-lg uppercase mt-0.5" readonly>
|
||||||
|
|
||||||
<div class="flex items-center gap-4 mt-1.5">
|
<div class="flex items-center gap-4 mt-1.5">
|
||||||
<input type="tel" id="editPhone" class="input-readonly text-sm text-blue-600 font-black" readonly>
|
<div class="flex items-center gap-1.5">
|
||||||
|
<i data-lucide="phone" class="w-3.5 h-3.5 text-blue-600"></i>
|
||||||
|
<input type="tel" id="editPhone" class="input-readonly text-sm text-blue-600 font-black w-full" readonly>
|
||||||
|
</div>
|
||||||
|
<button onclick="copyClientPortalLink()" id="btnPortalLink" class="text-[10px] font-black bg-blue-50 border border-blue-100 hover:bg-blue-600 hover:text-white text-blue-600 px-3 py-1.5 rounded-lg flex items-center gap-1.5 transition-all shadow-sm active:scale-95 shrink-0">
|
||||||
|
<i data-lucide="link" class="w-3 h-3"></i> Copiar Portal
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="border-slate-200">
|
<hr class="border-slate-200">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest">Ubicación y Dirección</p>
|
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest">Ubicación y Dirección</p>
|
||||||
|
<div class="flex items-start gap-1.5 mt-1">
|
||||||
|
<i data-lucide="map-pin" class="w-4 h-4 mt-0.5 text-slate-400 shrink-0"></i>
|
||||||
<textarea id="editAddr" rows="2" class="input-readonly w-full text-sm font-bold text-slate-600 resize-none uppercase" readonly></textarea>
|
<textarea id="editAddr" rows="2" class="input-readonly w-full text-sm font-bold text-slate-600 resize-none uppercase" readonly></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-2 ml-2 block">Descripción de la Avería</p>
|
|
||||||
<textarea id="editDesc" rows="6" class="w-full bg-amber-50/60 border border-amber-100 p-5 rounded-[1.5rem] text-sm font-medium text-slate-700 shadow-inner outline-none" readonly></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-2 ml-2">Descripción de la Avería</p>
|
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-2 ml-2">Descripción de la Avería</p>
|
||||||
<textarea id="editDesc" rows="6" class="w-full bg-amber-50/60 border border-amber-100 p-5 rounded-[1.5rem] text-sm font-medium text-slate-700 shadow-inner outline-none" readonly></textarea>
|
<textarea id="editDesc" rows="6" class="w-full bg-amber-50/60 border border-amber-100 p-5 rounded-[1.5rem] text-sm font-medium text-slate-700 shadow-inner leading-relaxed outline-none" readonly></textarea>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p class="text-[9px] font-black text-slate-400 uppercase tracking-widest mb-2 ml-2">Descripción de la Avería</p>
|
|
||||||
<div class="bg-amber-50/60 border border-amber-100 p-5 rounded-[1.5rem] text-sm font-medium text-slate-700 min-h-[120px] max-h-48 overflow-y-auto no-scrollbar shadow-inner leading-relaxed" id="detDesc"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -375,7 +380,19 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-4 bg-slate-50 border-t border-slate-100 flex justify-end items-center px-8 gap-3">
|
||||||
|
<div id="viewActions">
|
||||||
|
<button onclick="enableEditing()" id="btnEnableEdit" class="text-slate-400 hover:text-blue-600 flex items-center gap-2 transition-all group">
|
||||||
|
<span class="text-[10px] font-black uppercase tracking-widest">Editar Ficha</span>
|
||||||
|
<div class="bg-white border border-slate-200 p-2 rounded-xl shadow-sm group-hover:border-blue-200 group-hover:bg-blue-50 transition-all"><i data-lucide="edit-3" class="w-4 h-4"></i></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="editActions" class="hidden flex gap-3">
|
||||||
|
<button onclick="cancelEditing()" class="px-6 py-2 rounded-xl text-[10px] font-black uppercase tracking-widest text-slate-400 hover:bg-slate-100 transition-all">Cancelar</button>
|
||||||
|
<button onclick="saveFullEdit()" class="px-8 py-3 bg-blue-600 text-white rounded-xl text-[10px] font-black uppercase tracking-widest shadow-lg shadow-blue-100 active:scale-95 transition-all">Guardar Cambios</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -557,29 +574,23 @@
|
|||||||
const filteredData = localData.filter(s => {
|
const filteredData = localData.filter(s => {
|
||||||
const raw = s.raw_data || {};
|
const raw = s.raw_data || {};
|
||||||
const stateInfo = getServiceStateInfo(s);
|
const stateInfo = getServiceStateInfo(s);
|
||||||
s._stateInfo = stateInfo; // Guardamos info para el render
|
s._stateInfo = stateInfo;
|
||||||
|
|
||||||
// LÓGICA DE CONTADORES REVOLUCIONADA:
|
|
||||||
const stName = stateInfo.name.toLowerCase();
|
const stName = stateInfo.name.toLowerCase();
|
||||||
|
|
||||||
// 1. SIN ASIGNAR: No tiene nombre de operario O está en bolsa
|
|
||||||
if (!s.assigned_name || stateInfo.id === 'bolsa' || stName.includes('asignar')) {
|
if (!s.assigned_name || stateInfo.id === 'bolsa' || stName.includes('asignar')) {
|
||||||
kpiUnassigned++;
|
kpiUnassigned++;
|
||||||
}
|
}
|
||||||
// 2. INCIDENCIA: El estado tiene la palabra clave
|
|
||||||
else if (stName.includes('incidencia') || stName.includes('pausa')) {
|
else if (stName.includes('incidencia') || stName.includes('pausa')) {
|
||||||
kpiIncident++;
|
kpiIncident++;
|
||||||
}
|
}
|
||||||
// 3. AGENDADOS: Tiene fecha puesta y no es un estado final
|
|
||||||
else if (raw.scheduled_date && raw.scheduled_date !== "" && !stateInfo.is_final) {
|
else if (raw.scheduled_date && raw.scheduled_date !== "" && !stateInfo.is_final) {
|
||||||
kpiScheduled++;
|
kpiScheduled++;
|
||||||
}
|
}
|
||||||
// 4. ESPERA CLIENTE: No tiene cita pero ya tiene técnico (o estado específico)
|
else if (!stateInfo.is_final && !raw.scheduled_date) {
|
||||||
else if (!stateInfo.is_final && (!raw.scheduled_date || stName.includes('espera'))) {
|
|
||||||
kpiWaiting++;
|
kpiWaiting++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filtros de búsqueda
|
|
||||||
const name = (raw["Nombre Cliente"] || raw["CLIENTE"] || "").toLowerCase();
|
const name = (raw["Nombre Cliente"] || raw["CLIENTE"] || "").toLowerCase();
|
||||||
const ref = (s.service_ref || "").toLowerCase();
|
const ref = (s.service_ref || "").toLowerCase();
|
||||||
const matchesSearch = searchTerm === "" || name.includes(searchTerm) || ref.includes(searchTerm);
|
const matchesSearch = searchTerm === "" || name.includes(searchTerm) || ref.includes(searchTerm);
|
||||||
@@ -597,7 +608,6 @@
|
|||||||
return matchesSearch && matchesOp && matchesWeek && matchesStatus;
|
return matchesSearch && matchesOp && matchesWeek && matchesStatus;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Actualizamos la vista con los números reales
|
|
||||||
document.getElementById('kpi-unassigned').innerText = kpiUnassigned;
|
document.getElementById('kpi-unassigned').innerText = kpiUnassigned;
|
||||||
document.getElementById('kpi-scheduled').innerText = kpiScheduled;
|
document.getElementById('kpi-scheduled').innerText = kpiScheduled;
|
||||||
document.getElementById('kpi-waiting').innerText = kpiWaiting;
|
document.getElementById('kpi-waiting').innerText = kpiWaiting;
|
||||||
@@ -620,7 +630,7 @@
|
|||||||
const addr = raw["Dirección"] || raw["DOMICILIO"] || "---";
|
const addr = raw["Dirección"] || raw["DOMICILIO"] || "---";
|
||||||
const pop = raw["Población"] || raw["POBLACION-PROVINCIA"] || "";
|
const pop = raw["Población"] || raw["POBLACION-PROVINCIA"] || "";
|
||||||
const fullAddr = `${addr} ${pop}`.trim();
|
const fullAddr = `${addr} ${pop}`.trim();
|
||||||
const cita = raw.scheduled_date ? `${raw.scheduled_date} | ${raw.scheduled_time}` : 'Pendiente Cita';
|
const cita = raw.scheduled_date ? `${raw.scheduled_date.split('-').reverse().slice(0,2).join('/')} | ${raw.scheduled_time}` : 'Pte. Cita';
|
||||||
const companyName = raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || (s.provider === 'MANUAL' ? 'PARTICULAR' : 'ASEGURADORA');
|
const companyName = raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || (s.provider === 'MANUAL' ? 'PARTICULAR' : 'ASEGURADORA');
|
||||||
|
|
||||||
const isUrgent = s.is_urgent === true || (raw['Urgente'] && raw['Urgente'].toLowerCase() === 'sí') || (raw['URGENTE'] && raw['URGENTE'].toLowerCase() === 'si');
|
const isUrgent = s.is_urgent === true || (raw['Urgente'] && raw['Urgente'].toLowerCase() === 'sí') || (raw['URGENTE'] && raw['URGENTE'].toLowerCase() === 'si');
|
||||||
@@ -660,16 +670,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center justify-between mt-4 pt-4 border-t border-slate-100">
|
<div class="flex items-center justify-between mt-4 pt-4 border-t border-slate-100 gap-2">
|
||||||
<div class="flex items-center gap-2 min-w-0">
|
<div class="flex items-center gap-1 min-w-0 flex-1">
|
||||||
<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 rounded-lg text-slate-500 shrink-0"><i data-lucide="hard-hat" class="w-3 h-3"></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 flex-1" style="max-width: 70px;" title="${s.assigned_name || 'Sin asignar'}">${s.assigned_name || 'Sin asignar'}</span>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-0.5 shrink-0">
|
||||||
|
<button onclick="event.stopPropagation(); window.location.href='trazabilidad.html?id=${s.id}'" class="p-1.5 text-slate-300 hover:text-blue-600 transition-colors" title="Ver Historial">
|
||||||
|
<i data-lucide="history" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
<button onclick="event.stopPropagation(); openDetail(${s.id}, true)" class="p-1.5 text-slate-300 hover:text-amber-600 transition-colors" title="Editar Ficha">
|
||||||
|
<i data-lucide="edit-3" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-1 text-blue-600 shrink-0 bg-blue-50 px-2 py-1 rounded-md border border-blue-100">
|
||||||
|
<i data-lucide="${iconEstado}" class="w-3 h-3"></i>
|
||||||
|
<span class="text-[9px] font-black uppercase whitespace-nowrap">${raw.scheduled_date ? cita.split('|')[0] : 'Pte. Cita'}</span>
|
||||||
</div>
|
</div>
|
||||||
${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">
|
|
||||||
<i data-lucide="${iconEstado}" class="w-3.5 h-3.5"></i>
|
|
||||||
<span class="text-[9px] font-black uppercase">${cita}</span>
|
|
||||||
</div>` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -698,7 +717,7 @@
|
|||||||
setTimeout(() => { toast.classList.add('hidden'); }, 3500);
|
setTimeout(() => { toast.classList.add('hidden'); }, 3500);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDetail(id) {
|
function openDetail(id, startInEditMode = false) {
|
||||||
const s = localData.find(x => x.id === id);
|
const s = localData.find(x => x.id === id);
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
const raw = s.raw_data;
|
const raw = s.raw_data;
|
||||||
@@ -708,26 +727,21 @@
|
|||||||
|
|
||||||
const companyName = raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || "Particular";
|
const companyName = raw['Compañía'] || raw['COMPAÑIA'] || raw['Procedencia'] || "Particular";
|
||||||
document.getElementById('detCompany').innerText = companyName;
|
document.getElementById('detCompany').innerText = companyName;
|
||||||
document.getElementById('detName').innerText = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado Sin Nombre";
|
|
||||||
|
// RELLENADO DE CAMPOS PARA EL EDITOR Y VISTA
|
||||||
|
const clientName = raw["Nombre Cliente"] || raw["CLIENTE"] || "Asegurado Sin Nombre";
|
||||||
|
if(document.getElementById('editName')) document.getElementById('editName').value = clientName;
|
||||||
|
|
||||||
const rawPhone = raw["Teléfono"] || raw["TELEFONOS"] || raw["TELEFONO"] || "";
|
const rawPhone = raw["Teléfono"] || raw["TELEFONOS"] || raw["TELEFONO"] || "";
|
||||||
const matchPhone = rawPhone.toString().match(/[6789]\d{8}/);
|
const matchPhone = rawPhone.toString().match(/[6789]\d{8}/);
|
||||||
const singlePhone = matchPhone ? matchPhone[0] : "";
|
const singlePhone = matchPhone ? matchPhone[0] : "";
|
||||||
|
if(document.getElementById('editPhone')) document.getElementById('editPhone').value = singlePhone;
|
||||||
|
|
||||||
if (singlePhone) {
|
const fullAddr = `${raw["Dirección"] || ""} ${raw["Población"] || ""}`.trim();
|
||||||
document.getElementById('detPhone').innerText = singlePhone;
|
if(document.getElementById('editAddr')) document.getElementById('editAddr').value = fullAddr;
|
||||||
document.getElementById('detPhoneLink').href = `tel:+34${singlePhone}`;
|
|
||||||
document.getElementById('detPhoneLink').classList.remove('text-slate-400', 'pointer-events-none');
|
|
||||||
document.getElementById('detPhoneLink').classList.add('text-blue-600');
|
|
||||||
} else {
|
|
||||||
document.getElementById('detPhone').innerText = "Sin Teléfono";
|
|
||||||
document.getElementById('detPhoneLink').href = "#";
|
|
||||||
document.getElementById('detPhoneLink').classList.remove('text-blue-600');
|
|
||||||
document.getElementById('detPhoneLink').classList.add('text-slate-400', 'pointer-events-none');
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('detAddrText').innerText = `${raw["Dirección"] || "Dirección no especificada"} ${raw["Población"] || ""}`;
|
const descContent = raw["Descripción"] || raw["DESCRIPCION"] || "Sin notas.";
|
||||||
document.getElementById('detDesc').innerHTML = (raw["Descripción"] || raw["DESCRIPCION"] || "Sin notas.").replace(/\n/g, '<br>');
|
if(document.getElementById('editDesc')) document.getElementById('editDesc').value = descContent;
|
||||||
|
|
||||||
const stateInfo = s._stateInfo;
|
const stateInfo = s._stateInfo;
|
||||||
|
|
||||||
@@ -761,6 +775,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('detailModal').classList.remove('hidden');
|
document.getElementById('detailModal').classList.remove('hidden');
|
||||||
|
|
||||||
|
if (startInEditMode) {
|
||||||
|
enableEditing();
|
||||||
|
} else {
|
||||||
|
cancelEditing();
|
||||||
|
}
|
||||||
|
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,6 +897,8 @@
|
|||||||
async function saveNewService(e) {
|
async function saveNewService(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const action = e.submitter.value;
|
const action = e.submitter.value;
|
||||||
|
const is_company = document.getElementById('isCompanyCheck').checked;
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
phone: document.getElementById('nPhone').value,
|
phone: document.getElementById('nPhone').value,
|
||||||
name: document.getElementById('nName').value,
|
name: document.getElementById('nName').value,
|
||||||
@@ -883,19 +906,70 @@
|
|||||||
description: document.getElementById('nDesc').value,
|
description: document.getElementById('nDesc').value,
|
||||||
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,
|
||||||
duration_minutes: document.getElementById('nDuration').value // <--- CAPTURA DE DURACIÓN
|
duration_minutes: document.getElementById('nDuration').value,
|
||||||
|
is_company: is_company,
|
||||||
|
company_name: is_company ? document.getElementById('nCompanySelect').value : 'Particular',
|
||||||
|
company_ref: is_company ? document.getElementById('nExpRef').value : null,
|
||||||
|
mode: action
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}/services/manual-high`, {
|
const res = await fetch(`${API_URL}/services/manual-high`, {
|
||||||
method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` },
|
||||||
body: JSON.stringify({ ...data, mode: action })
|
body: JSON.stringify(data)
|
||||||
});
|
});
|
||||||
if (res.ok) { closeCreateModal(); refreshPanel(); }
|
if (res.ok) { closeCreateModal(); refreshPanel(); showToast("✅ Servicio guardado correctamente"); }
|
||||||
} catch(e) { alert("Error al guardar"); }
|
} catch(e) { alert("Error al guardar"); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeDetailModal() { document.getElementById('detailModal').classList.add('hidden'); }
|
function closeDetailModal() { document.getElementById('detailModal').classList.add('hidden'); }
|
||||||
function openCreateModal() { document.getElementById('createModal').classList.remove('hidden'); }
|
|
||||||
|
async function openCreateModal() {
|
||||||
|
document.getElementById('createModal').classList.remove('hidden');
|
||||||
|
document.getElementById('isCompanyCheck').checked = false;
|
||||||
|
toggleCompanyFields(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_URL}/companies`, {
|
||||||
|
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
const sel = document.getElementById('nCompanySelect');
|
||||||
|
sel.innerHTML = '<option value="">Seleccionar compañía...</option>';
|
||||||
|
data.companies.forEach(c => {
|
||||||
|
sel.innerHTML += `<option value="${c.name}">${c.name.toUpperCase()}</option>`;
|
||||||
|
});
|
||||||
|
} catch(e) {}
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCompanyFields(show) {
|
||||||
|
const fields = document.getElementById('companyFields');
|
||||||
|
fields.classList.toggle('hidden', !show);
|
||||||
|
if(!show) {
|
||||||
|
document.getElementById('nExpRef').value = "";
|
||||||
|
document.getElementById('refAlert').classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkDuplicateRef(ref) {
|
||||||
|
if(!ref) return;
|
||||||
|
const res = await fetch(`${API_URL}/services/check-ref?ref=${ref}`, {
|
||||||
|
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
const alertEl = document.getElementById('refAlert');
|
||||||
|
const inputEl = document.getElementById('nExpRef');
|
||||||
|
|
||||||
|
if(data.exists) {
|
||||||
|
alertEl.classList.remove('hidden');
|
||||||
|
inputEl.classList.add('border-red-500', 'bg-red-50');
|
||||||
|
} else {
|
||||||
|
alertEl.classList.add('hidden');
|
||||||
|
inputEl.classList.remove('border-red-500', 'bg-red-50');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function closeCreateModal() { document.getElementById('createModal').classList.add('hidden'); }
|
function closeCreateModal() { document.getElementById('createModal').classList.add('hidden'); }
|
||||||
|
|
||||||
async function loadGuilds() {
|
async function loadGuilds() {
|
||||||
@@ -917,9 +991,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function copyClientPortalLink() {
|
async function copyClientPortalLink() {
|
||||||
const phone = document.getElementById('detPhone').innerText;
|
const phone = document.getElementById('editPhone').value;
|
||||||
const name = document.getElementById('detName').innerText;
|
const name = document.getElementById('editName').value;
|
||||||
const addr = document.getElementById('detAddrText').innerText;
|
const addr = document.getElementById('editAddr').value;
|
||||||
|
|
||||||
if (!phone || phone === "Sin Teléfono") {
|
if (!phone || phone === "Sin Teléfono") {
|
||||||
showToast("No hay un teléfono válido para este cliente.", "warning");
|
showToast("No hay un teléfono válido para este cliente.", "warning");
|
||||||
@@ -955,7 +1029,7 @@
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
btn.innerHTML = originalHtml;
|
btn.innerHTML = originalHtml;
|
||||||
btn.className = "text-[10px] font-black bg-blue-50 border border-blue-100 hover:bg-blue-600 hover:text-white text-blue-600 px-3 py-1.5 rounded-lg flex items-center gap-1.5 transition-all shadow-sm active:scale-95";
|
btn.className = "text-[10px] font-black bg-blue-50 border border-blue-100 hover:bg-blue-600 hover:text-white text-blue-600 px-3 py-1.5 rounded-lg flex items-center gap-1.5 transition-all shadow-sm active:scale-95 shrink-0";
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
} else {
|
} else {
|
||||||
@@ -970,6 +1044,88 @@
|
|||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function searchClientByPhone(phone) {
|
||||||
|
if (phone.length < 9) {
|
||||||
|
document.getElementById('addrSuggestions').classList.add('hidden');
|
||||||
|
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 nameInput = document.getElementById('nName');
|
||||||
|
if (!nameInput.value) {
|
||||||
|
nameInput.value = data.client.full_name;
|
||||||
|
nameInput.classList.add('bg-emerald-50');
|
||||||
|
}
|
||||||
|
|
||||||
|
const addrDiv = document.getElementById('addrSuggestions');
|
||||||
|
if (data.client.addresses && data.client.addresses.length > 0) {
|
||||||
|
addrDiv.classList.remove('hidden');
|
||||||
|
if (!document.getElementById('nAddr').value) {
|
||||||
|
document.getElementById('nAddr').value = data.client.addresses[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { console.error("Error en buscador rápido:", e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FUNCIONES DEL EDITOR ---
|
||||||
|
|
||||||
|
function enableEditing() {
|
||||||
|
document.getElementById('viewActions').classList.add('hidden');
|
||||||
|
document.getElementById('editActions').classList.remove('hidden');
|
||||||
|
|
||||||
|
['editName', 'editPhone', 'editAddr', 'editDesc'].forEach(id => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if(el) {
|
||||||
|
el.readOnly = false;
|
||||||
|
el.classList.add('input-editable');
|
||||||
|
el.classList.remove('input-readonly');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelEditing() {
|
||||||
|
document.getElementById('viewActions').classList.remove('hidden');
|
||||||
|
document.getElementById('editActions').classList.add('hidden');
|
||||||
|
|
||||||
|
['editName', 'editPhone', 'editAddr', 'editDesc'].forEach(id => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if(el) {
|
||||||
|
el.readOnly = true;
|
||||||
|
el.classList.add('input-readonly');
|
||||||
|
el.classList.remove('input-editable');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveFullEdit() {
|
||||||
|
const id = document.getElementById('detId').value;
|
||||||
|
const payload = {
|
||||||
|
name: document.getElementById('editName').value,
|
||||||
|
phone: document.getElementById('editPhone').value,
|
||||||
|
address: document.getElementById('editAddr').value,
|
||||||
|
description: document.getElementById('editDesc').value
|
||||||
|
};
|
||||||
|
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(payload)
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
showToast("✅ Ficha actualizada");
|
||||||
|
cancelEditing();
|
||||||
|
refreshPanel();
|
||||||
|
}
|
||||||
|
} catch (e) { showToast("Error al guardar", "warning"); }
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user