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