Actualizar contabilidad.html
This commit is contained in:
@@ -39,14 +39,77 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-2 border-b border-slate-200 mb-6">
|
<div class="flex gap-2 border-b border-slate-200 mb-6">
|
||||||
<button class="px-6 py-3 font-black text-sm text-emerald-600 border-b-2 border-emerald-500 flex items-center gap-2">
|
<button onclick="toggleTab('cobros')" id="tab-cobros" class="px-6 py-3 font-black text-sm text-emerald-600 border-b-2 border-emerald-500 flex items-center gap-2 transition-colors">
|
||||||
<i data-lucide="coins" class="w-4 h-4"></i> Cobros a Clientes/Cías
|
<i data-lucide="coins" class="w-4 h-4"></i> Cobros a Clientes/Cías
|
||||||
</button>
|
</button>
|
||||||
<button class="px-6 py-3 font-bold text-sm text-slate-400 hover:text-slate-600 transition-colors flex items-center gap-2 cursor-not-allowed">
|
<button onclick="toggleTab('presupuestos')" id="tab-presupuestos" class="px-6 py-3 font-bold text-sm text-slate-400 border-b-2 border-transparent hover:text-slate-600 transition-colors flex items-center gap-2">
|
||||||
<i data-lucide="file-spreadsheet" class="w-4 h-4"></i> Presupuestos <span class="text-[9px] bg-slate-100 px-2 py-0.5 rounded uppercase tracking-widest ml-1">Pendiente</span>
|
<i data-lucide="file-spreadsheet" class="w-4 h-4"></i> Presupuestos
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="view-cobros">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||||
|
<div class="bg-white p-5 rounded-[1.5rem] border border-slate-200 shadow-sm flex items-center gap-4">
|
||||||
|
<div class="w-12 h-12 rounded-full bg-blue-50 text-blue-500 flex items-center justify-center shrink-0"><i data-lucide="sigma"></i></div>
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Total Facturado</p>
|
||||||
|
<p class="text-2xl font-black text-slate-800" id="kpi-total">0.00 €</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white p-5 rounded-[1.5rem] border border-slate-200 shadow-sm flex items-center gap-4">
|
||||||
|
<div class="w-12 h-12 rounded-full bg-emerald-50 text-emerald-500 flex items-center justify-center shrink-0"><i data-lucide="check-circle-2"></i></div>
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Total Cobrado</p>
|
||||||
|
<p class="text-2xl font-black text-emerald-600" id="kpi-paid">0.00 €</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white p-5 rounded-[1.5rem] border border-red-100 shadow-sm flex items-center gap-4 relative overflow-hidden">
|
||||||
|
<div class="absolute right-0 top-0 bottom-0 w-2 bg-red-400"></div>
|
||||||
|
<div class="w-12 h-12 rounded-full bg-red-50 text-red-500 flex items-center justify-center shrink-0"><i data-lucide="clock"></i></div>
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-red-400 uppercase tracking-widest">Pendiente de Cobro</p>
|
||||||
|
<p class="text-2xl font-black text-red-600" id="kpi-pending">0.00 €</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white p-4 rounded-2xl border border-slate-200 shadow-sm mb-6 flex flex-wrap gap-3 items-center">
|
||||||
|
<div class="flex-1 min-w-[200px] relative">
|
||||||
|
<i data-lucide="search" class="w-4 h-4 absolute left-4 top-1/2 -translate-y-1/2 text-slate-400"></i>
|
||||||
|
<input type="text" id="searchBox" oninput="renderList()" placeholder="Buscar por cliente, referencia..." class="w-full pl-11 pr-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-emerald-500 outline-none transition-all">
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<div class="flex items-center bg-slate-50 border border-slate-200 rounded-xl overflow-hidden">
|
||||||
|
<div class="px-3 text-slate-400 border-r border-slate-200"><i data-lucide="calendar-days" class="w-4 h-4"></i></div>
|
||||||
|
<input type="month" id="filterMonth" onchange="renderList()" class="bg-transparent text-xs font-black px-3 py-2.5 outline-none text-slate-600 uppercase tracking-widest cursor-pointer">
|
||||||
|
</div>
|
||||||
|
<select id="filterCompany" onchange="renderList()" class="bg-slate-50 border border-slate-200 text-[10px] font-black px-4 py-2.5 rounded-xl outline-none focus:ring-2 focus:ring-emerald-500 uppercase tracking-widest max-w-[200px]">
|
||||||
|
<option value="ALL">TODAS LAS COMPAÑÍAS</option>
|
||||||
|
</select>
|
||||||
|
<select id="filterStatus" onchange="renderList()" class="bg-slate-50 border border-slate-200 text-[10px] font-black px-4 py-2.5 rounded-xl outline-none focus:ring-2 focus:ring-emerald-500 uppercase tracking-widest">
|
||||||
|
<option value="ALL">ESTADO DE COBRO</option>
|
||||||
|
<option value="paid">PAGADOS</option>
|
||||||
|
<option value="pending">PENDIENTES</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white border border-slate-200 rounded-[2rem] shadow-sm overflow-hidden mb-10">
|
||||||
|
<div class="grid grid-cols-12 gap-4 p-4 bg-slate-50 border-b border-slate-100 text-[10px] font-black uppercase tracking-widest text-slate-400">
|
||||||
|
<div class="col-span-4 pl-2">Servicio / Cliente / Fecha</div>
|
||||||
|
<div class="col-span-2">Compañía</div>
|
||||||
|
<div class="col-span-2 text-center">Importe (€)</div>
|
||||||
|
<div class="col-span-2">Método de Pago</div>
|
||||||
|
<div class="col-span-2 text-center">Estado</div>
|
||||||
|
</div>
|
||||||
|
<div id="financialList" class="divide-y divide-slate-100">
|
||||||
|
<div class="p-10 text-center text-slate-400 flex flex-col items-center">
|
||||||
|
<i data-lucide="loader-2" class="w-8 h-8 animate-spin mb-3"></i>
|
||||||
|
<span class="font-bold text-sm tracking-widest uppercase">Cargando contabilidad...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="view-presupuestos" class="hidden">
|
<div id="view-presupuestos" class="hidden">
|
||||||
<div class="flex justify-between items-center mb-4">
|
<div class="flex justify-between items-center mb-4">
|
||||||
@@ -73,6 +136,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="budgetModal" class="fixed inset-0 bg-slate-900/75 hidden z-[100] flex items-center justify-center backdrop-blur-sm p-4">
|
<div id="budgetModal" class="fixed inset-0 bg-slate-900/75 hidden z-[100] flex items-center justify-center backdrop-blur-sm p-4">
|
||||||
<div class="bg-white rounded-[2rem] w-full max-w-3xl flex flex-col max-h-[90vh] shadow-2xl">
|
<div class="bg-white rounded-[2rem] w-full max-w-3xl flex flex-col max-h-[90vh] shadow-2xl">
|
||||||
<div class="p-6 border-b border-slate-100 flex justify-between items-center">
|
<div class="p-6 border-b border-slate-100 flex justify-between items-center">
|
||||||
@@ -152,87 +220,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
||||||
<div class="bg-white p-5 rounded-[1.5rem] border border-slate-200 shadow-sm flex items-center gap-4">
|
|
||||||
<div class="w-12 h-12 rounded-full bg-blue-50 text-blue-500 flex items-center justify-center shrink-0"><i data-lucide="sigma"></i></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Total Facturado</p>
|
|
||||||
<p class="text-2xl font-black text-slate-800" id="kpi-total">0.00 €</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white p-5 rounded-[1.5rem] border border-slate-200 shadow-sm flex items-center gap-4">
|
|
||||||
<div class="w-12 h-12 rounded-full bg-emerald-50 text-emerald-500 flex items-center justify-center shrink-0"><i data-lucide="check-circle-2"></i></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">Total Cobrado</p>
|
|
||||||
<p class="text-2xl font-black text-emerald-600" id="kpi-paid">0.00 €</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-white p-5 rounded-[1.5rem] border border-red-100 shadow-sm flex items-center gap-4 relative overflow-hidden">
|
|
||||||
<div class="absolute right-0 top-0 bottom-0 w-2 bg-red-400"></div>
|
|
||||||
<div class="w-12 h-12 rounded-full bg-red-50 text-red-500 flex items-center justify-center shrink-0"><i data-lucide="clock"></i></div>
|
|
||||||
<div>
|
|
||||||
<p class="text-[10px] font-black text-red-400 uppercase tracking-widest">Pendiente de Cobro</p>
|
|
||||||
<p class="text-2xl font-black text-red-600" id="kpi-pending">0.00 €</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white p-4 rounded-2xl border border-slate-200 shadow-sm mb-6 flex flex-wrap gap-3 items-center">
|
|
||||||
<div class="flex-1 min-w-[200px] relative">
|
|
||||||
<i data-lucide="search" class="w-4 h-4 absolute left-4 top-1/2 -translate-y-1/2 text-slate-400"></i>
|
|
||||||
<input type="text" id="searchBox" oninput="renderList()" placeholder="Buscar por cliente, referencia..." class="w-full pl-11 pr-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl text-sm focus:ring-2 focus:ring-emerald-500 outline-none transition-all">
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<div class="flex items-center bg-slate-50 border border-slate-200 rounded-xl overflow-hidden">
|
|
||||||
<div class="px-3 text-slate-400 border-r border-slate-200"><i data-lucide="calendar-days" class="w-4 h-4"></i></div>
|
|
||||||
<input type="month" id="filterMonth" onchange="renderList()" class="bg-transparent text-xs font-black px-3 py-2.5 outline-none text-slate-600 uppercase tracking-widest cursor-pointer">
|
|
||||||
</div>
|
|
||||||
<select id="filterCompany" onchange="renderList()" class="bg-slate-50 border border-slate-200 text-[10px] font-black px-4 py-2.5 rounded-xl outline-none focus:ring-2 focus:ring-emerald-500 uppercase tracking-widest max-w-[200px]">
|
|
||||||
<option value="ALL">TODAS LAS COMPAÑÍAS</option>
|
|
||||||
</select>
|
|
||||||
<select id="filterStatus" onchange="renderList()" class="bg-slate-50 border border-slate-200 text-[10px] font-black px-4 py-2.5 rounded-xl outline-none focus:ring-2 focus:ring-emerald-500 uppercase tracking-widest">
|
|
||||||
<option value="ALL">ESTADO DE COBRO</option>
|
|
||||||
<option value="paid">PAGADOS</option>
|
|
||||||
<option value="pending">PENDIENTES</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="bg-white border border-slate-200 rounded-[2rem] shadow-sm overflow-hidden mb-10">
|
|
||||||
<div class="grid grid-cols-12 gap-4 p-4 bg-slate-50 border-b border-slate-100 text-[10px] font-black uppercase tracking-widest text-slate-400">
|
|
||||||
<div class="col-span-4 pl-2">Servicio / Cliente / Fecha</div>
|
|
||||||
<div class="col-span-2">Compañía</div>
|
|
||||||
<div class="col-span-2 text-center">Importe (€)</div>
|
|
||||||
<div class="col-span-2">Método de Pago</div>
|
|
||||||
<div class="col-span-2 text-center">Estado</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="financialList" class="divide-y divide-slate-100">
|
|
||||||
<div class="p-10 text-center text-slate-400 flex flex-col items-center">
|
|
||||||
<i data-lucide="loader-2" class="w-8 h-8 animate-spin mb-3"></i>
|
|
||||||
<span class="font-bold text-sm tracking-widest uppercase">Cargando contabilidad...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="toast" class="fixed bottom-8 right-8 bg-slate-900 text-white px-6 py-3 rounded-2xl shadow-2xl hidden z-[200] font-bold text-sm flex items-center gap-2 transition-all"></div>
|
<div id="toast" class="fixed bottom-8 right-8 bg-slate-900 text-white px-6 py-3 rounded-2xl shadow-2xl hidden z-[200] font-bold text-sm flex items-center gap-2 transition-all"></div>
|
||||||
|
|
||||||
<script src="js/layout.js"></script>
|
<script src="js/layout.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
// ==========================================
|
||||||
|
// LÓGICA DE COBROS (CONTABILIDAD)
|
||||||
|
// ==========================================
|
||||||
let allFinancials = [];
|
let allFinancials = [];
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
if (!localStorage.getItem("token")) window.location.href = "index.html";
|
if (!localStorage.getItem("token")) window.location.href = "index.html";
|
||||||
|
|
||||||
// Establecer el mes actual por defecto en el filtro
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const currentMonthStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
const currentMonthStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||||
document.getElementById('filterMonth').value = currentMonthStr;
|
document.getElementById('filterMonth').value = currentMonthStr;
|
||||||
@@ -246,10 +245,8 @@
|
|||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if(data.ok) {
|
if(data.ok) {
|
||||||
// Filtrar los bloqueos de agenda por si acaso vienen cruzados
|
|
||||||
allFinancials = data.financials.filter(f => f.service_ref && !f.service_ref.includes('BLOCK-'));
|
allFinancials = data.financials.filter(f => f.service_ref && !f.service_ref.includes('BLOCK-'));
|
||||||
|
|
||||||
// Extraer compañías únicas para el filtro
|
|
||||||
const compSelect = document.getElementById('filterCompany');
|
const compSelect = document.getElementById('filterCompany');
|
||||||
const currentVal = compSelect.value;
|
const currentVal = compSelect.value;
|
||||||
const uniqueCompanies = [...new Set(allFinancials.map(f => {
|
const uniqueCompanies = [...new Set(allFinancials.map(f => {
|
||||||
@@ -259,13 +256,11 @@
|
|||||||
|
|
||||||
compSelect.innerHTML = '<option value="ALL">TODAS LAS COMPAÑÍAS</option>';
|
compSelect.innerHTML = '<option value="ALL">TODAS LAS COMPAÑÍAS</option>';
|
||||||
uniqueCompanies.forEach(c => compSelect.innerHTML += `<option value="${c}">${c}</option>`);
|
uniqueCompanies.forEach(c => compSelect.innerHTML += `<option value="${c}">${c}</option>`);
|
||||||
compSelect.value = currentVal; // Mantener selección si se recarga
|
compSelect.value = currentVal;
|
||||||
|
|
||||||
renderList();
|
renderList();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) { console.error("Error cargando contabilidad:", error); }
|
||||||
console.error("Error cargando contabilidad:", error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderList() {
|
function renderList() {
|
||||||
@@ -273,34 +268,26 @@
|
|||||||
const searchTerm = document.getElementById('searchBox').value.toLowerCase();
|
const searchTerm = document.getElementById('searchBox').value.toLowerCase();
|
||||||
const filterCompany = document.getElementById('filterCompany').value;
|
const filterCompany = document.getElementById('filterCompany').value;
|
||||||
const filterStatus = document.getElementById('filterStatus').value;
|
const filterStatus = document.getElementById('filterStatus').value;
|
||||||
const filterMonth = document.getElementById('filterMonth').value; // Formato "YYYY-MM"
|
const filterMonth = document.getElementById('filterMonth').value;
|
||||||
|
|
||||||
// Filtrado del array
|
|
||||||
const filtered = allFinancials.filter(f => {
|
const filtered = allFinancials.filter(f => {
|
||||||
const raw = f.raw_data || {};
|
const raw = f.raw_data || {};
|
||||||
const name = (raw['Nombre Cliente'] || raw['CLIENTE'] || "").toLowerCase();
|
const name = (raw['Nombre Cliente'] || raw['CLIENTE'] || "").toLowerCase();
|
||||||
const ref = (f.service_ref || "").toLowerCase();
|
const ref = (f.service_ref || "").toLowerCase();
|
||||||
const company = (raw['Compañía'] || raw['COMPAÑIA'] || "Particular").toUpperCase().trim();
|
const company = (raw['Compañía'] || raw['COMPAÑIA'] || "Particular").toUpperCase().trim();
|
||||||
|
|
||||||
// Extraer el mes del servicio para el filtro
|
|
||||||
let serviceMonth = "";
|
let serviceMonth = "";
|
||||||
if (raw.scheduled_date) {
|
if (raw.scheduled_date) serviceMonth = raw.scheduled_date.substring(0, 7);
|
||||||
// formato asumido YYYY-MM-DD
|
else if (f.created_at) serviceMonth = f.created_at.substring(0, 7);
|
||||||
serviceMonth = raw.scheduled_date.substring(0, 7);
|
|
||||||
} else if (f.created_at) {
|
|
||||||
serviceMonth = f.created_at.substring(0, 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Condiciones
|
|
||||||
const matchSearch = name.includes(searchTerm) || ref.includes(searchTerm);
|
const matchSearch = name.includes(searchTerm) || ref.includes(searchTerm);
|
||||||
const matchCompany = filterCompany === 'ALL' || company === filterCompany;
|
const matchCompany = filterCompany === 'ALL' || company === filterCompany;
|
||||||
const matchStatus = filterStatus === 'ALL' || (filterStatus === 'paid' ? f.is_paid : !f.is_paid);
|
const matchStatus = filterStatus === 'ALL' || (filterStatus === 'paid' ? f.is_paid : !f.is_paid);
|
||||||
const matchMonth = !filterMonth || serviceMonth === filterMonth; // Si no hay mes seleccionado, muestra todo
|
const matchMonth = !filterMonth || serviceMonth === filterMonth;
|
||||||
|
|
||||||
return matchSearch && matchCompany && matchStatus && matchMonth;
|
return matchSearch && matchCompany && matchStatus && matchMonth;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Variables para KPIs
|
|
||||||
let totalFacturado = 0;
|
let totalFacturado = 0;
|
||||||
let totalCobrado = 0;
|
let totalCobrado = 0;
|
||||||
|
|
||||||
@@ -309,9 +296,7 @@
|
|||||||
if (filtered.length === 0) {
|
if (filtered.length === 0) {
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="p-16 text-center flex flex-col items-center">
|
<div class="p-16 text-center flex flex-col items-center">
|
||||||
<div class="w-16 h-16 bg-slate-100 text-slate-300 rounded-full flex items-center justify-center mb-4">
|
<div class="w-16 h-16 bg-slate-100 text-slate-300 rounded-full flex items-center justify-center mb-4"><i data-lucide="inbox" class="w-8 h-8"></i></div>
|
||||||
<i data-lucide="inbox" class="w-8 h-8"></i>
|
|
||||||
</div>
|
|
||||||
<p class="font-bold text-slate-500 mb-1">No se encontraron cobros</p>
|
<p class="font-bold text-slate-500 mb-1">No se encontraron cobros</p>
|
||||||
<p class="text-xs text-slate-400">Prueba a cambiar los filtros o el mes de búsqueda.</p>
|
<p class="text-xs text-slate-400">Prueba a cambiar los filtros o el mes de búsqueda.</p>
|
||||||
</div>`;
|
</div>`;
|
||||||
@@ -325,26 +310,22 @@
|
|||||||
const name = raw['Nombre Cliente'] || raw['CLIENTE'] || "Sin Nombre";
|
const name = raw['Nombre Cliente'] || raw['CLIENTE'] || "Sin Nombre";
|
||||||
const company = raw['Compañía'] || raw['COMPAÑIA'] || "Particular";
|
const company = raw['Compañía'] || raw['COMPAÑIA'] || "Particular";
|
||||||
|
|
||||||
// Formatear Fecha
|
|
||||||
let fechaVis = "Sin fecha";
|
let fechaVis = "Sin fecha";
|
||||||
if(raw.scheduled_date) {
|
if(raw.scheduled_date) {
|
||||||
const [y, m, d] = raw.scheduled_date.split('-');
|
const [y, m, d] = raw.scheduled_date.split('-');
|
||||||
fechaVis = `${d}/${m}/${y}`;
|
fechaVis = `${d}/${m}/${y}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cálculos matemáticos
|
|
||||||
const amt = parseFloat(f.amount || 0);
|
const amt = parseFloat(f.amount || 0);
|
||||||
totalFacturado += amt;
|
totalFacturado += amt;
|
||||||
if (f.is_paid) totalCobrado += amt;
|
if (f.is_paid) totalCobrado += amt;
|
||||||
|
|
||||||
// Selector de estado visual
|
|
||||||
const statusBadge = f.is_paid
|
const statusBadge = f.is_paid
|
||||||
? `<span class="bg-emerald-100 text-emerald-700 px-3 py-1 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-1 border border-emerald-200"><i data-lucide="check-circle-2" class="w-3 h-3"></i> Pagado</span>`
|
? `<span class="bg-emerald-100 text-emerald-700 px-3 py-1 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-1 border border-emerald-200"><i data-lucide="check-circle-2" class="w-3 h-3"></i> Pagado</span>`
|
||||||
: `<span class="bg-red-50 text-red-600 px-3 py-1 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-1 border border-red-200"><i data-lucide="clock" class="w-3 h-3"></i> Pendiente</span>`;
|
: `<span class="bg-red-50 text-red-600 px-3 py-1 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-1 border border-red-200"><i data-lucide="clock" class="w-3 h-3"></i> Pendiente</span>`;
|
||||||
|
|
||||||
container.innerHTML += `
|
container.innerHTML += `
|
||||||
<div class="grid grid-cols-12 gap-4 p-4 items-center hover:bg-slate-50 transition-colors group">
|
<div class="grid grid-cols-12 gap-4 p-4 items-center hover:bg-slate-50 transition-colors group">
|
||||||
|
|
||||||
<div class="col-span-4 pl-2 min-w-0">
|
<div class="col-span-4 pl-2 min-w-0">
|
||||||
<p class="text-xs font-black text-slate-800 uppercase truncate" title="${name}">${name}</p>
|
<p class="text-xs font-black text-slate-800 uppercase truncate" title="${name}">${name}</p>
|
||||||
<div class="flex items-center gap-2 mt-1 text-[10px] font-bold text-slate-400">
|
<div class="flex items-center gap-2 mt-1 text-[10px] font-bold text-slate-400">
|
||||||
@@ -352,19 +333,14 @@
|
|||||||
<span><i data-lucide="calendar" class="w-3 h-3 inline mr-0.5"></i> ${fechaVis}</span>
|
<span><i data-lucide="calendar" class="w-3 h-3 inline mr-0.5"></i> ${fechaVis}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-2 min-w-0">
|
<div class="col-span-2 min-w-0">
|
||||||
<span class="bg-blue-50 text-blue-700 border border-blue-100 px-2 py-1 rounded text-[9px] font-black uppercase tracking-widest truncate max-w-full inline-block">
|
<span class="bg-blue-50 text-blue-700 border border-blue-100 px-2 py-1 rounded text-[9px] font-black uppercase tracking-widest truncate max-w-full inline-block">${company}</span>
|
||||||
${company}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-2 relative">
|
<div class="col-span-2 relative">
|
||||||
<span class="absolute right-4 top-1/2 -translate-y-1/2 text-sm font-black text-slate-400 pointer-events-none">€</span>
|
<span class="absolute right-4 top-1/2 -translate-y-1/2 text-sm font-black text-slate-400 pointer-events-none">€</span>
|
||||||
<input type="number" step="0.01" id="amt-${f.scraped_id}" value="${f.amount}"
|
<input type="number" step="0.01" id="amt-${f.scraped_id}" value="${f.amount}"
|
||||||
class="w-full bg-white border border-slate-200 pl-3 pr-8 py-2.5 rounded-xl text-sm font-black text-right outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-100 transition-all ${f.is_paid ? 'text-emerald-700 bg-emerald-50/30' : 'text-slate-700'}">
|
class="w-full bg-white border border-slate-200 pl-3 pr-8 py-2.5 rounded-xl text-sm font-black text-right outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-100 transition-all ${f.is_paid ? 'text-emerald-700 bg-emerald-50/30' : 'text-slate-700'}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-2">
|
<div class="col-span-2">
|
||||||
<select id="method-${f.scraped_id}" class="w-full bg-white border border-slate-200 px-2 py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest text-slate-600 outline-none focus:border-emerald-500 cursor-pointer">
|
<select id="method-${f.scraped_id}" class="w-full bg-white border border-slate-200 px-2 py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest text-slate-600 outline-none focus:border-emerald-500 cursor-pointer">
|
||||||
<option value="Pendiente" ${f.payment_method === 'Pendiente' ? 'selected' : ''}>⏳ Pendiente</option>
|
<option value="Pendiente" ${f.payment_method === 'Pendiente' ? 'selected' : ''}>⏳ Pendiente</option>
|
||||||
@@ -374,14 +350,10 @@
|
|||||||
<option value="Transferencia" ${f.payment_method === 'Transferencia' ? 'selected' : ''}>🔄 Transfer.</option>
|
<option value="Transferencia" ${f.payment_method === 'Transferencia' ? 'selected' : ''}>🔄 Transfer.</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-span-2 flex items-center justify-between gap-2">
|
<div class="col-span-2 flex items-center justify-between gap-2">
|
||||||
<div id="badge-container-${f.scraped_id}" class="flex-1">${statusBadge}</div>
|
<div id="badge-container-${f.scraped_id}" class="flex-1">${statusBadge}</div>
|
||||||
<button onclick="savePayment(${f.scraped_id})" class="bg-slate-100 text-slate-500 border border-slate-200 hover:bg-emerald-500 hover:border-emerald-600 hover:text-white p-2 rounded-xl transition-all shrink-0 active:scale-95 shadow-sm" title="Guardar Cambios">
|
<button onclick="savePayment(${f.scraped_id})" class="bg-slate-100 text-slate-500 border border-slate-200 hover:bg-emerald-500 hover:border-emerald-600 hover:text-white p-2 rounded-xl transition-all shrink-0 active:scale-95 shadow-sm" title="Guardar Cambios"><i data-lucide="save" class="w-4 h-4"></i></button>
|
||||||
<i data-lucide="save" class="w-4 h-4"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
@@ -392,7 +364,6 @@
|
|||||||
|
|
||||||
function updateKPIs(facturado, cobrado) {
|
function updateKPIs(facturado, cobrado) {
|
||||||
const pendiente = facturado - cobrado;
|
const pendiente = facturado - cobrado;
|
||||||
|
|
||||||
document.getElementById('kpi-total').innerText = facturado.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
|
document.getElementById('kpi-total').innerText = facturado.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
|
||||||
document.getElementById('kpi-paid').innerText = cobrado.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
|
document.getElementById('kpi-paid').innerText = cobrado.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
|
||||||
document.getElementById('kpi-pending').innerText = pendiente.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
|
document.getElementById('kpi-pending').innerText = pendiente.toLocaleString('es-ES', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
|
||||||
@@ -405,80 +376,55 @@
|
|||||||
const amount = amountInput.value;
|
const amount = amountInput.value;
|
||||||
const method = methodInput.value;
|
const method = methodInput.value;
|
||||||
|
|
||||||
// Bloquear inputs mientras guarda
|
amountInput.disabled = true; methodInput.disabled = true;
|
||||||
amountInput.disabled = true;
|
|
||||||
methodInput.disabled = true;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API_URL}/financials/${scrapedId}`, {
|
const res = await fetch(`${API_URL}/financials/${scrapedId}`, {
|
||||||
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({ amount, payment_method: method })
|
body: JSON.stringify({ amount, payment_method: method })
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
showToast("Cobro actualizado");
|
showToast("Cobro actualizado");
|
||||||
|
|
||||||
// Actualizar el array local usando la nueva regla de negocio
|
|
||||||
const fIndex = allFinancials.findIndex(f => f.scraped_id === scrapedId);
|
const fIndex = allFinancials.findIndex(f => f.scraped_id === scrapedId);
|
||||||
if (fIndex > -1) {
|
if (fIndex > -1) {
|
||||||
allFinancials[fIndex].amount = parseFloat(amount || 0);
|
allFinancials[fIndex].amount = parseFloat(amount || 0);
|
||||||
allFinancials[fIndex].payment_method = method;
|
allFinancials[fIndex].payment_method = method;
|
||||||
// Si nos manda el backend que está pagado o si no es pendiente, es TRUE
|
|
||||||
allFinancials[fIndex].is_paid = method !== 'Pendiente';
|
allFinancials[fIndex].is_paid = method !== 'Pendiente';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refrescar lista y KPIs visualmente
|
|
||||||
renderList();
|
renderList();
|
||||||
} else {
|
} else { showToast("Error al guardar."); }
|
||||||
showToast("Error al guardar.");
|
} catch (error) { showToast("Error de conexión."); }
|
||||||
|
finally { amountInput.disabled = false; methodInput.disabled = false; }
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
showToast("Error de conexión.");
|
|
||||||
} finally {
|
|
||||||
amountInput.disabled = false;
|
|
||||||
methodInput.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// LÓGICA DE PESTAÑAS
|
// LÓGICA DE PESTAÑAS Y PRESUPUESTOS
|
||||||
// ==========================================
|
// ==========================================
|
||||||
function toggleTab(tab) {
|
function toggleTab(tab) {
|
||||||
const vFin = document.querySelector('.max-w-6xl > div:nth-child(3)'); // Los KPIs
|
const vCobros = document.getElementById('view-cobros');
|
||||||
const vSearch = document.querySelector('.max-w-6xl > div:nth-child(4)'); // Barra busqueda
|
|
||||||
const vTable = document.querySelector('.max-w-6xl > div:nth-child(5)'); // Tabla
|
|
||||||
const vPres = document.getElementById('view-presupuestos');
|
const vPres = document.getElementById('view-presupuestos');
|
||||||
|
|
||||||
const tCobros = document.getElementById('tab-cobros');
|
const tCobros = document.getElementById('tab-cobros');
|
||||||
const tPres = document.getElementById('tab-presupuestos');
|
const tPres = document.getElementById('tab-presupuestos');
|
||||||
|
|
||||||
if (tab === 'cobros') {
|
if (tab === 'cobros') {
|
||||||
vFin.classList.remove('hidden'); vSearch.classList.remove('hidden'); vTable.classList.remove('hidden');
|
vCobros.classList.remove('hidden'); vPres.classList.add('hidden');
|
||||||
vPres.classList.add('hidden');
|
|
||||||
tCobros.className = "px-6 py-3 font-black text-sm text-emerald-600 border-b-2 border-emerald-500 flex items-center gap-2 transition-colors";
|
tCobros.className = "px-6 py-3 font-black text-sm text-emerald-600 border-b-2 border-emerald-500 flex items-center gap-2 transition-colors";
|
||||||
tPres.className = "px-6 py-3 font-bold text-sm text-slate-400 border-b-2 border-transparent hover:text-slate-600 transition-colors flex items-center gap-2";
|
tPres.className = "px-6 py-3 font-bold text-sm text-slate-400 border-b-2 border-transparent hover:text-slate-600 transition-colors flex items-center gap-2";
|
||||||
} else {
|
} else {
|
||||||
vFin.classList.add('hidden'); vSearch.classList.add('hidden'); vTable.classList.add('hidden');
|
vCobros.classList.add('hidden'); vPres.classList.remove('hidden');
|
||||||
vPres.classList.remove('hidden');
|
|
||||||
tPres.className = "px-6 py-3 font-black text-sm text-blue-600 border-b-2 border-blue-500 flex items-center gap-2 transition-colors";
|
tPres.className = "px-6 py-3 font-black text-sm text-blue-600 border-b-2 border-blue-500 flex items-center gap-2 transition-colors";
|
||||||
tCobros.className = "px-6 py-3 font-bold text-sm text-slate-400 border-b-2 border-transparent hover:text-slate-600 transition-colors flex items-center gap-2";
|
tCobros.className = "px-6 py-3 font-bold text-sm text-slate-400 border-b-2 border-transparent hover:text-slate-600 transition-colors flex items-center gap-2";
|
||||||
loadBudgets();
|
loadBudgets();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// LÓGICA DE PRESUPUESTOS Y ARTÍCULOS
|
|
||||||
// ==========================================
|
|
||||||
let myArticles = [];
|
let myArticles = [];
|
||||||
let myBudgets = [];
|
let myBudgets = [];
|
||||||
|
|
||||||
async function loadBudgets() {
|
async function loadBudgets() {
|
||||||
try {
|
try {
|
||||||
// Cargamos presupuestos y artículos a la vez
|
|
||||||
const [rB, rA] = await Promise.all([
|
const [rB, rA] = await Promise.all([
|
||||||
fetch(`${API_URL}/budgets`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }),
|
fetch(`${API_URL}/budgets`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }),
|
||||||
fetch(`${API_URL}/articles`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } })
|
fetch(`${API_URL}/articles`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } })
|
||||||
@@ -555,11 +501,10 @@
|
|||||||
loadBudgets();
|
loadBudgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- FORMULARIO NUEVO PRESUPUESTO ---
|
|
||||||
function openBudgetModal() {
|
function openBudgetModal() {
|
||||||
document.getElementById('bPhone').value = ""; document.getElementById('bName').value = ""; document.getElementById('bAddress').value = "";
|
document.getElementById('bPhone').value = ""; document.getElementById('bName').value = ""; document.getElementById('bAddress').value = "";
|
||||||
document.getElementById('budgetLines').innerHTML = "";
|
document.getElementById('budgetLines').innerHTML = "";
|
||||||
addBudgetLine(); // Crea una línea vacía
|
addBudgetLine();
|
||||||
calcBudget();
|
calcBudget();
|
||||||
document.getElementById('budgetModal').classList.remove('hidden');
|
document.getElementById('budgetModal').classList.remove('hidden');
|
||||||
}
|
}
|
||||||
@@ -652,7 +597,6 @@
|
|||||||
loadBudgets();
|
loadBudgets();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CATÁLOGO DE ARTÍCULOS ---
|
|
||||||
function openArticlesModal() {
|
function openArticlesModal() {
|
||||||
renderArticles();
|
renderArticles();
|
||||||
document.getElementById('articlesModal').classList.remove('hidden');
|
document.getElementById('articlesModal').classList.remove('hidden');
|
||||||
@@ -678,7 +622,7 @@
|
|||||||
if(!name || !price) return;
|
if(!name || !price) return;
|
||||||
await fetch(`${API_URL}/articles`, { method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({name, price}) });
|
await fetch(`${API_URL}/articles`, { method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({name, price}) });
|
||||||
document.getElementById('newArtName').value = ""; document.getElementById('newArtPrice').value = "";
|
document.getElementById('newArtName').value = ""; document.getElementById('newArtPrice').value = "";
|
||||||
await loadBudgets(); // Recarga y pinta
|
await loadBudgets();
|
||||||
renderArticles();
|
renderArticles();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -691,9 +635,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function showToast(msg) {
|
function showToast(msg) {
|
||||||
const t = document.getElementById('toast');
|
const t = document.getElementById('toast');
|
||||||
t.innerHTML = `<i data-lucide="check-circle" class="w-5 h-5 text-emerald-400"></i> ${msg}`;
|
t.innerHTML = `<i data-lucide="check-circle" class="w-5 h-5 text-emerald-400"></i> ${msg}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user