203 lines
12 KiB
Plaintext
203 lines
12 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Contabilidad - IntegraReparaPro</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<style>
|
|
.fade-in { animation: fadeIn 0.3s ease-in-out; }
|
|
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
|
|
.no-scrollbar::-webkit-scrollbar { display: none; }
|
|
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 text-gray-800 font-sans antialiased overflow-hidden">
|
|
|
|
<div class="flex h-screen overflow-hidden text-left">
|
|
<div id="sidebar-container" class="h-full shrink-0"></div>
|
|
|
|
<div class="flex-1 flex flex-col overflow-hidden relative">
|
|
<div id="header-container"></div>
|
|
|
|
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-slate-50/50 p-6 relative">
|
|
<div class="max-w-6xl mx-auto fade-in">
|
|
|
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-end gap-4 mb-8">
|
|
<div>
|
|
<h2 class="text-2xl font-black text-slate-800 flex items-center gap-3">
|
|
<span class="bg-emerald-500 p-2.5 rounded-xl text-white shadow-lg shadow-emerald-200"><i data-lucide="wallet"></i></span>
|
|
Finanzas y Cobros
|
|
</h2>
|
|
<p class="text-sm text-slate-500 mt-1 font-medium">Gestiona los pagos de los servicios y presupuestos.</p>
|
|
</div>
|
|
|
|
<button class="bg-white border-2 border-indigo-100 text-indigo-400 px-5 py-2.5 rounded-xl font-black flex items-center gap-2 text-xs uppercase tracking-widest cursor-not-allowed opacity-70" title="Próximamente">
|
|
<i data-lucide="bot" class="w-4 h-4"></i> Robot Contable PDF <span class="bg-indigo-100 text-indigo-600 px-1.5 py-0.5 rounded text-[8px] ml-1">BETA</span>
|
|
</button>
|
|
</div>
|
|
|
|
<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">
|
|
<i data-lucide="coins" class="w-4 h-4"></i> Cobros a Clientes/Cías
|
|
</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">
|
|
<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>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="bg-white border border-slate-200 rounded-[2rem] shadow-sm overflow-hidden">
|
|
<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-3 pl-2">Servicio / Cliente</div>
|
|
<div class="col-span-3">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">
|
|
<i data-lucide="loader-2" class="w-6 h-6 animate-spin mx-auto mb-2"></i> Cargando contabilidad...
|
|
</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>
|
|
|
|
<script src="js/layout.js"></script>
|
|
<script>
|
|
const API_URL = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
|
|
? 'http://localhost:3000'
|
|
: 'https://integrarepara-api.integrarepara.es';
|
|
|
|
let financials = [];
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
if (!localStorage.getItem("token")) window.location.href = "index.html";
|
|
setTimeout(loadFinancials, 200);
|
|
});
|
|
|
|
async function loadFinancials() {
|
|
try {
|
|
const res = await fetch(`${API_URL}/financials`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
|
|
const data = await res.json();
|
|
|
|
if(data.ok) {
|
|
financials = data.financials;
|
|
renderList();
|
|
}
|
|
} catch (error) {
|
|
console.error("Error cargando contabilidad:", error);
|
|
}
|
|
}
|
|
|
|
function renderList() {
|
|
const container = document.getElementById('financialList');
|
|
container.innerHTML = '';
|
|
|
|
if (financials.length === 0) {
|
|
container.innerHTML = '<div class="p-10 text-center font-bold text-slate-400">No hay registros financieros.</div>';
|
|
return;
|
|
}
|
|
|
|
financials.forEach(f => {
|
|
const raw = f.raw_data || {};
|
|
const name = raw['Nombre Cliente'] || raw['CLIENTE'] || "Sin Nombre";
|
|
const company = raw['Compañía'] || raw['COMPAÑIA'] || "Particular";
|
|
|
|
// Selector de estado visual
|
|
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-amber-100 text-amber-700 px-3 py-1 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-1 border border-amber-200"><i data-lucide="clock" class="w-3 h-3"></i> Pendiente</span>`;
|
|
|
|
container.innerHTML += `
|
|
<div class="grid grid-cols-12 gap-4 p-4 items-center hover:bg-slate-50 transition-colors group">
|
|
|
|
<div class="col-span-3 pl-2 min-w-0">
|
|
<p class="text-xs font-black text-slate-800 uppercase truncate" title="${name}">${name}</p>
|
|
<p class="text-[10px] font-bold text-slate-400 mt-0.5">REF: ${f.service_ref}</p>
|
|
</div>
|
|
|
|
<div class="col-span-3 min-w-0">
|
|
<span class="bg-blue-50 text-blue-700 border border-blue-100 px-2 py-0.5 rounded text-[10px] font-black uppercase tracking-widest truncate max-w-full inline-block">
|
|
${company}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="col-span-2">
|
|
<div class="relative">
|
|
<input type="number" step="0.01" id="amt-${f.scraped_id}" value="${f.amount}"
|
|
class="w-full bg-white border border-slate-200 px-3 py-2 rounded-xl text-sm font-black text-center outline-none focus:border-emerald-500 focus:ring-2 focus:ring-emerald-100 transition-all text-slate-700">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-span-2">
|
|
<select id="method-${f.scraped_id}" class="w-full bg-white border border-slate-200 px-2 py-2 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="Cobro Banco" ${f.payment_method === 'Cobro Banco' ? 'selected' : ''}>🏦 Cobro Banco</option>
|
|
<option value="Efectivo" ${f.payment_method === 'Efectivo' ? 'selected' : ''}>💵 Efectivo</option>
|
|
<option value="Tarjeta" ${f.payment_method === 'Tarjeta' ? 'selected' : ''}>💳 Tarjeta</option>
|
|
<option value="Transferencia" ${f.payment_method === 'Transferencia' ? 'selected' : ''}>🔄 Transferencia</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="col-span-2 flex items-center justify-between gap-2">
|
|
<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 hover:bg-emerald-500 hover:text-white p-2 rounded-xl transition-colors shrink-0" title="Guardar Cambios">
|
|
<i data-lucide="save" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
`;
|
|
});
|
|
lucide.createIcons();
|
|
}
|
|
|
|
async function savePayment(scrapedId) {
|
|
const amount = document.getElementById(`amt-${scrapedId}`).value;
|
|
const method = document.getElementById(`method-${scrapedId}`).value;
|
|
|
|
try {
|
|
const res = await fetch(`${API_URL}/financials/${scrapedId}`, {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem("token")}` },
|
|
body: JSON.stringify({ amount, payment_method: method })
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (data.ok) {
|
|
showToast("¡Guardado correctamente!");
|
|
// Actualizamos la pastilla en tiempo real sin recargar la página
|
|
const badgeContainer = document.getElementById(`badge-container-${scrapedId}`);
|
|
if (data.is_paid) {
|
|
badgeContainer.innerHTML = `<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>`;
|
|
} else {
|
|
badgeContainer.innerHTML = `<span class="bg-amber-100 text-amber-700 px-3 py-1 rounded-lg text-[10px] font-black uppercase tracking-widest flex items-center justify-center gap-1 border border-amber-200"><i data-lucide="clock" class="w-3 h-3"></i> Pendiente</span>`;
|
|
}
|
|
lucide.createIcons();
|
|
} else {
|
|
showToast("Error al guardar.");
|
|
}
|
|
} catch (error) {
|
|
showToast("Error de conexión.");
|
|
}
|
|
}
|
|
|
|
function showToast(msg) {
|
|
const t = document.getElementById('toast');
|
|
t.innerHTML = `<i data-lucide="check-circle" class="w-5 h-5 text-emerald-400"></i> ${msg}`;
|
|
lucide.createIcons();
|
|
t.classList.remove('hidden');
|
|
setTimeout(() => t.classList.add('hidden'), 3000);
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |