Actualizar presupuestos.html

This commit is contained in:
2026-03-29 21:13:54 +00:00
parent 0f881ae7c4
commit 67df90f698

View File

@@ -364,14 +364,13 @@
}
} catch (e) { showToast("Error de conexión"); }
}
async function downloadPDF(id) {
// Buscamos el presupuesto en el array de la app móvil
const budget = allBudgets.find(b => b.id === id);
if(!budget) return showToast("❌ Error: Presupuesto no encontrado");
showToast("⏳ Generando PDF, espera unos segundos...");
// Valores por defecto
let empNombre = "IntegraRepara";
let empDni = "CIF/NIF no configurado";
let empAddress = "Dirección no configurada";
@@ -383,13 +382,11 @@ async function downloadPDF(id) {
let empObs = null;
try {
// Descargamos la configuración de tu empresa desde la API
const confRes = await fetch(`${API_URL}/config/company`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const confData = await confRes.json();
if (confData.ok && confData.config) {
empLogo = confData.config.company_logo || null;
if (confData.config.billing_settings) {
const bSet = confData.config.billing_settings;
empNombre = bSet.name || confData.config.full_name || empNombre;
@@ -401,19 +398,15 @@ async function downloadPDF(id) {
empIban = bSet.iban || null;
empObs = bSet.obs || null;
} else {
// Fallback por si no hay billing config guardada
empNombre = confData.config.full_name || empNombre;
}
}
} catch(e) { console.error("Error al cargar facturación", e); }
// Inyectamos los datos en el diseño
document.getElementById('pdf-company-name').innerText = empNombre;
document.getElementById('pdf-company-dni').innerText = empDni;
document.getElementById('pdf-company-address').innerText = empAddress;
const locText = [empZip, empCity, empState ? `(${empState})` : ''].filter(Boolean).join(' ');
document.getElementById('pdf-company-location').innerText = locText;
document.getElementById('pdf-company-location').innerText = [empZip, empCity, empState ? `(${empState})` : ''].filter(Boolean).join(' ');
const logoImg = document.getElementById('pdf-company-logo');
const logoTxt = document.getElementById('pdf-company-name-fallback');
@@ -464,23 +457,22 @@ async function downloadPDF(id) {
obsContainer.style.display = 'none';
}
// Generar PDF con html2pdf
const wrapper = document.getElementById('pdf-wrapper');
wrapper.classList.remove('hidden');
wrapper.style.position = 'absolute';
wrapper.style.left = '-9999px'; // Lo escondemos fuera de la pantalla del móvil
wrapper.style.left = '-9999px';
const element = document.getElementById('pdf-content');
const opt = {
margin: 0,
filename: `Presupuesto_PRE${budget.id}_${budget.client_name.replace(/\s+/g, '_')}.pdf`,
filename: `Presupuesto_PRE${budget.id}_${(budget.client_name||'').replace(/\s+/g, '_')}.pdf`,
image: { type: 'jpeg', quality: 1 },
html2canvas: { scale: 2, useCORS: true, logging: false },
jsPDF: { unit: 'in', format: 'a4', orientation: 'portrait' }
};
try {
await new Promise(r => setTimeout(r, 500)); // Esperamos medio segundo para cargar logo
await new Promise(r => setTimeout(r, 500));
await html2pdf().set(opt).from(element).save();
showToast("✅ PDF Descargado con éxito");
} catch (error) {
@@ -528,7 +520,6 @@ async function downloadPDF(id) {
const d = bDate.toLocaleDateString('es-ES', { day: '2-digit', month: 'short', year: '2-digit' });
// LÓGICA DE BOTONES PARA EL TÉCNICO
let actionBtns = '';
if (b.status === 'pending' && !isExpired) {
actionBtns = `
@@ -573,62 +564,45 @@ async function downloadPDF(id) {
</div>
`;
}).join('');
lucide.createIcons();
}
// ------------------ BUSCADOR DE CLIENTES AUTOMÁTICO ------------------
async function searchClientByPhone() {
const phoneInput = document.getElementById('c_phone');
const phone = phoneInput.value.trim();
const loading = document.getElementById('phoneLoading');
// Si el teléfono tiene menos de 9 dígitos, no hacemos nada
if (!phone || phone.length < 9) return;
loading.classList.remove('hidden'); // Mostramos el iconito de pensar
loading.classList.remove('hidden');
try {
const res = await fetch(`${API_URL}/clients/search?phone=${encodeURIComponent(phone)}`, {
headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` }
});
const data = await res.json();
if (data.ok && data.client) {
// Si encuentra al cliente, rellenamos los datos
document.getElementById('c_name').value = data.client.full_name || '';
if (data.client.addresses && data.client.addresses.length > 0) {
// Cogemos la primera dirección que tenga guardada
document.getElementById('c_address').value = data.client.addresses[0] || '';
}
showToast("✅ Cliente encontrado y cargado");
}
} catch (e) {
console.error("Error buscando cliente", e);
} finally {
loading.classList.add('hidden'); // Ocultamos el iconito
}
} catch (e) { console.error("Error buscando cliente", e); }
finally { loading.classList.add('hidden'); }
}
// ------------------ CREACIÓN DE PRESUPUESTO (ITEMS) ------------------
function addItem() {
const conceptInput = document.getElementById('item_concept');
const qtyInput = document.getElementById('item_qty');
const priceInput = document.getElementById('item_price');
const concept = conceptInput.value.trim();
const qty = parseFloat(qtyInput.value);
const price = parseFloat(priceInput.value);
if(!concept || isNaN(qty) || isNaN(price)) {
showToast("Rellena concepto y precio.");
return;
}
if(!concept || isNaN(qty) || isNaN(price)) { showToast("Rellena concepto y precio."); return; }
currentItems.push({ concept, qty, price, total: qty * price });
conceptInput.value = '';
qtyInput.value = '1';
priceInput.value = '';
conceptInput.value = ''; qtyInput.value = '1'; priceInput.value = '';
conceptInput.focus();
renderItems();
}
@@ -658,19 +632,17 @@ async function downloadPDF(id) {
function calcTotals() {
let sub = 0;
currentItems.forEach(i => sub += i.total);
let tax = sub * 0.21; // IVA 21%
let tax = sub * 0.21;
let tot = sub + tax;
document.getElementById('lbl_subtotal').innerText = `${sub.toFixed(2)}`;
document.getElementById('lbl_tax').innerText = `${tax.toFixed(2)}`;
document.getElementById('lbl_total').innerText = `${tot.toFixed(2)}`;
return { sub, tax, tot };
}
async function saveBudget() {
if(currentItems.length === 0) { showToast("Añade al menos 1 artículo."); return; }
const cName = document.getElementById('c_name').value.trim();
const cPhone = document.getElementById('c_phone').value.trim();
const cAddress = document.getElementById('c_address').value.trim();
@@ -683,16 +655,7 @@ async function downloadPDF(id) {
lucide.createIcons();
const totals = calcTotals();
const payload = {
client_name: cName,
client_phone: cPhone,
client_address: cAddress,
items: currentItems,
subtotal: totals.sub,
tax: totals.tax,
total: totals.tot
};
const payload = { client_name: cName, client_phone: cPhone, client_address: cAddress, items: currentItems, subtotal: totals.sub, tax: totals.tax, total: totals.tot };
try {
const res = await fetch(`${API_URL}/budgets`, {
@@ -700,25 +663,20 @@ async function downloadPDF(id) {
headers: { 'Content-Type': 'application/json', "Authorization": `Bearer ${localStorage.getItem("token")}` },
body: JSON.stringify(payload)
});
const data = await res.json();
if(data.ok) {
showToast("¡Presupuesto Creado!");
hideCreateView();
fetchBudgets();
} else {
showToast("Error al guardar.");
}
} catch(e) {
showToast("Fallo de conexión.");
} finally {
} else { showToast("Error al guardar."); }
} catch(e) { showToast("Fallo de conexión."); }
finally {
btn.disabled = false;
btn.innerHTML = '<i data-lucide="save" class="w-4 h-4"></i> Generar Presupuesto';
lucide.createIcons();
}
}
// ------------------ SISTEMA DE CITA Y AGENDA EN TIEMPO REAL ------------------
function openAppointmentModal(id, clientName) {
document.getElementById('apptBudgetId').value = id;
document.getElementById('apptClientName').innerText = clientName || "Cliente";
@@ -729,7 +687,6 @@ async function downloadPDF(id) {
document.getElementById('appointmentModal').classList.remove('hidden');
document.getElementById('appointmentModal').classList.add('flex');
setTimeout(() => document.getElementById('apptModalSheet').classList.remove('translate-y-full'), 10);
loadGuilds();
}
@@ -757,7 +714,6 @@ async function downloadPDF(id) {
async function checkAgendaForDate(dateStr) {
const preview = document.getElementById('agendaPreview');
const list = document.getElementById('agendaList');
if(!dateStr) { preview.classList.add('hidden'); return; }
preview.classList.remove('hidden');
@@ -767,10 +723,8 @@ async function downloadPDF(id) {
try {
const res = await fetch(`${API_URL}/services`, { headers: { "Authorization": `Bearer ${localStorage.getItem("token")}` } });
const data = await res.json();
if(data.ok && data.services) {
const dayServices = data.services.filter(s => s.scheduled_date === dateStr && s.status !== 'archived');
if(dayServices.length === 0) {
list.innerHTML = '<p class="text-emerald-600 font-bold text-center py-2 bg-white rounded-lg border border-emerald-100 shadow-sm">Libre: No tienes nada agendado este día.</p>';
return;
@@ -812,7 +766,6 @@ async function downloadPDF(id) {
body: JSON.stringify(payload)
});
const data = await res.json();
if(data.ok) {
showToast("✅ ¡Cita agendada con éxito!");
closeAppointmentModal();