diff --git a/contabilidad.html b/contabilidad.html
index 630960f..c2e241d 100644
--- a/contabilidad.html
+++ b/contabilidad.html
@@ -443,6 +443,257 @@
}
}
+
+ // ==========================================
+ // LÓGICA DE PESTAÑAS
+ // ==========================================
+ function toggleTab(tab) {
+ const vFin = document.querySelector('.max-w-6xl > div:nth-child(3)'); // Los KPIs
+ 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 tCobros = document.getElementById('tab-cobros');
+ const tPres = document.getElementById('tab-presupuestos');
+
+ if (tab === 'cobros') {
+ vFin.classList.remove('hidden'); vSearch.classList.remove('hidden'); vTable.classList.remove('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";
+ 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 {
+ vFin.classList.add('hidden'); vSearch.classList.add('hidden'); vTable.classList.add('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";
+ 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();
+ }
+ }
+
+ // ==========================================
+ // LÓGICA DE PRESUPUESTOS Y ARTÍCULOS
+ // ==========================================
+ let myArticles = [];
+ let myBudgets = [];
+
+ async function loadBudgets() {
+ try {
+ // Cargamos presupuestos y artículos a la vez
+ const [rB, rA] = await Promise.all([
+ fetch(`${API_URL}/budgets`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } }),
+ fetch(`${API_URL}/articles`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } })
+ ]);
+ const dataB = await rB.json();
+ const dataA = await rA.json();
+
+ if (dataB.ok) myBudgets = dataB.budgets;
+ if (dataA.ok) myArticles = dataA.articles;
+
+ renderBudgets();
+ } catch(e) {}
+ }
+
+ function renderBudgets() {
+ const list = document.getElementById('budgetsList');
+ list.innerHTML = "";
+ if(myBudgets.length === 0) { list.innerHTML = `
Sin presupuestos
`; return; }
+
+ myBudgets.forEach(b => {
+ const date = new Date(b.created_at).toLocaleDateString('es-ES');
+
+ let bStatus = '';
+ if(b.status === 'pending') bStatus = ` Pte. Resolver`;
+ if(b.status === 'rejected') bStatus = ` Rechazado`;
+ if(b.status === 'accepted') bStatus = ` Aceptado`;
+ if(b.status === 'converted') bStatus = ` Es Servicio`;
+
+ let actions = '';
+ if(b.status === 'pending') {
+ actions = `
+
+
+ `;
+ } else if(b.status === 'accepted') {
+ actions = ``;
+ }
+
+ list.innerHTML += `
+
+
+
${b.client_name}
+
${date} - 📞 ${b.client_phone}
+
+
+
+
${bStatus} ${actions}
+
+ `;
+ });
+ lucide.createIcons();
+ }
+
+ async function updateBudgetStatus(id, status) {
+ if(!confirm("¿Actualizar estado?")) return;
+ await fetch(`${API_URL}/budgets/${id}/status`, { method: 'PATCH', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({status}) });
+ loadBudgets();
+ }
+
+ function openConvertModal(id) {
+ document.getElementById('convBudgetId').value = id;
+ document.getElementById('convertModal').classList.remove('hidden');
+ }
+
+ async function confirmConversion() {
+ const id = document.getElementById('convBudgetId').value;
+ const date = document.getElementById('convDate').value;
+ const time = document.getElementById('convTime').value;
+ if(!date || !time) return showToast("Debes elegir fecha y hora.");
+
+ await fetch(`${API_URL}/budgets/${id}/convert`, { method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({date, time}) });
+ document.getElementById('convertModal').classList.add('hidden');
+ showToast("¡Convertido a Servicio con éxito!");
+ loadBudgets();
+ }
+
+ // --- FORMULARIO NUEVO PRESUPUESTO ---
+ function openBudgetModal() {
+ document.getElementById('bPhone').value = ""; document.getElementById('bName').value = ""; document.getElementById('bAddress').value = "";
+ document.getElementById('budgetLines').innerHTML = "";
+ addBudgetLine(); // Crea una línea vacía
+ calcBudget();
+ document.getElementById('budgetModal').classList.remove('hidden');
+ }
+
+ async function searchClientByPhone() {
+ const phone = document.getElementById('bPhone').value;
+ if(!phone || phone.length < 9) return;
+ const res = await fetch(`${API_URL}/clients/search?phone=${encodeURIComponent(phone)}`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
+ const data = await res.json();
+ if (data.client) {
+ document.getElementById('bName').value = data.client.full_name;
+ if(data.client.addresses && data.client.addresses.length > 0) document.getElementById('bAddress').value = data.client.addresses[0];
+ showToast("Datos de cliente cargados");
+ }
+ }
+
+ function addBudgetLine() {
+ const lineId = Date.now();
+ const artOptions = myArticles.map(a => ``).join("");
+
+ const html = `
+
+
+
+
+
+ `;
+ document.getElementById('budgetLines').insertAdjacentHTML('beforeend', html);
+ lucide.createIcons();
+ }
+
+ function applyArticleToLine(lineId, val) {
+ if(!val) return;
+ const [name, price] = val.split('|');
+ const line = document.getElementById(`bl-${lineId}`);
+ line.querySelector('.b-concept').value = name;
+ line.querySelector('.b-price').value = price;
+ calcBudget();
+ }
+
+ function calcBudget() {
+ let sub = 0;
+ document.querySelectorAll('#budgetLines > div').forEach(line => {
+ const q = parseFloat(line.querySelector('.b-qty').value) || 0;
+ const p = parseFloat(line.querySelector('.b-price').value) || 0;
+ sub += (q * p);
+ });
+ const tax = sub * 0.21;
+ const tot = sub + tax;
+
+ document.getElementById('bSubtotal').innerText = sub.toFixed(2) + "€";
+ document.getElementById('bTax').innerText = tax.toFixed(2) + "€";
+ document.getElementById('bTotal').innerText = tot.toFixed(2) + "€";
+ }
+
+ async function saveBudget() {
+ const phone = document.getElementById('bPhone').value;
+ const name = document.getElementById('bName').value;
+ if(!name) return showToast("El nombre es obligatorio");
+
+ const items = [];
+ document.querySelectorAll('#budgetLines > div').forEach(line => {
+ const c = line.querySelector('.b-concept').value;
+ const q = parseFloat(line.querySelector('.b-qty').value) || 0;
+ const p = parseFloat(line.querySelector('.b-price').value) || 0;
+ if(c && q > 0) items.push({concept: c, qty: q, price: p});
+ });
+
+ if(items.length === 0) return showToast("Añade al menos un concepto");
+
+ const sub = parseFloat(document.getElementById('bSubtotal').innerText);
+ const tax = parseFloat(document.getElementById('bTax').innerText);
+ const tot = parseFloat(document.getElementById('bTotal').innerText);
+
+ const payload = { client_phone: phone, client_name: name, client_address: document.getElementById('bAddress').value, items, subtotal: sub, tax, total: tot };
+
+ await fetch(`${API_URL}/budgets`, { method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify(payload) });
+
+ document.getElementById('budgetModal').classList.add('hidden');
+ showToast("Presupuesto guardado");
+ loadBudgets();
+ }
+
+ // --- CATÁLOGO DE ARTÍCULOS ---
+ function openArticlesModal() {
+ renderArticles();
+ document.getElementById('articlesModal').classList.remove('hidden');
+ }
+
+ function renderArticles() {
+ const list = document.getElementById('articlesList');
+ list.innerHTML = myArticles.map(a => `
+
+
${a.name}
+
+ ${a.price}€
+
+
+
+ `).join("");
+ lucide.createIcons();
+ }
+
+ async function saveArticle() {
+ const name = document.getElementById('newArtName').value;
+ const price = document.getElementById('newArtPrice').value;
+ 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}) });
+ document.getElementById('newArtName').value = ""; document.getElementById('newArtPrice').value = "";
+ await loadBudgets(); // Recarga y pinta
+ renderArticles();
+ }
+
+ async function editArticle(id, oldName, oldPrice) {
+ const n = prompt("Nuevo nombre:", oldName);
+ const p = prompt("Nuevo precio:", oldPrice);
+ if(n && p) {
+ await fetch(`${API_URL}/articles/${id}`, { method: 'PUT', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify({name: n, price: p}) });
+ await loadBudgets(); renderArticles();
+ }
+ }
+
+
+
+
function showToast(msg) {
const t = document.getElementById('toast');
t.innerHTML = ` ${msg}`;