diff --git a/contabilidad.html b/contabilidad.html
index a8eb9f0..bf12234 100644
--- a/contabilidad.html
+++ b/contabilidad.html
@@ -848,6 +848,72 @@
const name = document.getElementById('bName').value;
if(!name) return showToast("El nombre es obligatorio");
+ const items = [];
+ document.querySelectorAll('#budgetLines > div').forEach(line => {
+ const idEl = line.querySelector('.b-item-id'); // Por si lograste poner el input oculto
+ const conceptEl = line.querySelector('.b-concept');
+ const qtyEl = line.querySelector('.b-qty');
+ const priceEl = line.querySelector('.b-price');
+
+ if (conceptEl && qtyEl && priceEl) {
+ const c = conceptEl.value;
+ // Forzamos que si escribes comas, se pasen a puntos para que JS no falle
+ const qStr = qtyEl.value.toString().replace(',', '.');
+ const pStr = priceEl.value.toString().replace(',', '.');
+
+ const q = parseFloat(qStr) || 0;
+ const p = parseFloat(pStr) || 0;
+
+ if(c && q > 0) {
+ const itemObj = { concept: c, qty: q, price: p };
+ if (idEl && idEl.value) itemObj.id = parseInt(idEl.value);
+ items.push(itemObj);
+ }
+ }
+ });
+
+ if(items.length === 0) return showToast("Añade al menos un concepto");
+
+ // Aseguramos que limpiamos el símbolo de euro y cambiamos comas por puntos en los totales
+ const sub = parseFloat(document.getElementById('bSubtotal').innerText.replace('€', '').replace(',', '.')) || 0;
+ const tax = parseFloat(document.getElementById('bTax').innerText.replace('€', '').replace(',', '.')) || 0;
+ const tot = parseFloat(document.getElementById('bTotal').innerText.replace('€', '').replace(',', '.')) || 0;
+
+ const payload = { client_phone: phone, client_name: name, client_address: document.getElementById('bAddress').value, items, subtotal: sub, tax, total: tot };
+
+ const editId = document.getElementById('editBudgetId').value;
+
+ try {
+ let res;
+ if (editId) {
+ res = await fetch(`${API_URL}/budgets/${editId}`, { method: 'PUT', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify(payload) });
+ } else {
+ res = await fetch(`${API_URL}/budgets`, { method: 'POST', headers: { "Content-Type": "application/json", "Authorization": `Bearer ${localStorage.getItem("token")}` }, body: JSON.stringify(payload) });
+ }
+
+ // Leemos como texto por si el servidor devuelve un string vacío o error HTML
+ const textResponse = await res.text();
+ let data = {};
+ if (textResponse) {
+ try { data = JSON.parse(textResponse); } catch(e) { console.warn("El servidor no devolvió JSON puro:", textResponse); }
+ }
+
+ if (res.ok && data.ok !== false) {
+ document.getElementById('budgetModal').classList.add('hidden');
+ showToast(editId ? "✅ Presupuesto actualizado" : "✅ Presupuesto guardado");
+ loadBudgets();
+ } else {
+ showToast("❌ Error: " + (data.error || "Fallo en el servidor"));
+ console.error("Respuesta del servidor:", data, textResponse);
+ }
+ } catch (error) {
+ showToast("❌ Error de conexión al guardar.");
+ console.error("Error en fetch:", error);
+ }
+ }
+ 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;