diff --git a/src/components/BlockedNavigationToast.js b/src/components/BlockedNavigationToast.js index b8d5bb4..c33ff4c 100644 --- a/src/components/BlockedNavigationToast.js +++ b/src/components/BlockedNavigationToast.js @@ -35,7 +35,7 @@ export function showBlockedNavigationToast(message = 'Guarda los cambios antes d setTimeout(() => { toast.classList.remove('visible'); toast.classList.add('hiding'); - + setTimeout(() => { toast.remove(); }, 300); diff --git a/src/components/ConfirmExitModal.js b/src/components/ConfirmExitModal.js index ccc6c9a..db0b33a 100644 --- a/src/components/ConfirmExitModal.js +++ b/src/components/ConfirmExitModal.js @@ -15,8 +15,8 @@ export function ConfirmExitModal(options = {}) { message = '¿Estás seguro de que quieres salir? Los cambios no guardados se perderán.', confirmText = 'Salir sin guardar', cancelText = 'Quedarse', - onConfirm = () => {}, - onCancel = () => {} + onConfirm = () => { }, + onCancel = () => { } } = options; const modal = document.createElement('div'); @@ -123,7 +123,7 @@ export class FormChangeTracker { */ getFormState(container) { const state = {}; - + // Inputs de texto, date, number, etc. container.querySelectorAll('input:not([type="button"]):not([type="submit"])').forEach(input => { const key = input.name || input.id || input.className; @@ -162,9 +162,9 @@ export class FormChangeTracker { */ checkForChanges(container) { if (!this.initialState) return false; - + const currentState = this.getFormState(container); - + // Comparar estados const initialKeys = Object.keys(this.initialState); const currentKeys = Object.keys(currentState); diff --git a/src/components/InvoiceItem.js b/src/components/InvoiceItem.js index 26fde95..6c0c272 100644 --- a/src/components/InvoiceItem.js +++ b/src/components/InvoiceItem.js @@ -20,7 +20,7 @@ export function InvoiceItem(invoice, onView) { const getStatusClass = (status) => { const statusMap = { 'draft': 'status-draft', - 'validated': 'status-validated', + 'validated': 'status-unpaid', 'paid': 'status-paid', 'unpaid': 'status-unpaid', 'canceled': 'status-canceled' @@ -32,9 +32,9 @@ export function InvoiceItem(invoice, onView) { const getStatusText = (status) => { const statusTextMap = { 'draft': 'Borrador', - 'validated': 'Validada', + 'validated': 'Pte. Pago', 'paid': 'Pagada', - 'unpaid': 'Impagada', + 'unpaid': 'Pte. Pago', 'canceled': 'Cancelada' }; return statusTextMap[status] || status; diff --git a/src/components/InvoiceModal.js b/src/components/InvoiceModal.js index ccc02b7..6a34e63 100644 --- a/src/components/InvoiceModal.js +++ b/src/components/InvoiceModal.js @@ -2,57 +2,57 @@ import { getInvoiceById, updateInvoice, validateInvoice, addInvoiceLine, deleteI import { showConfirmExitModal, FormChangeTracker } from './ConfirmExitModal.js'; export function InvoiceModal(invoiceId, onClose, onUpdate) { - const modal = document.createElement('div'); - modal.className = 'modal-overlay'; + const modal = document.createElement('div'); + modal.className = 'modal-overlay'; - let invoice = null; - let payments = []; - let isLoading = true; - - // Tracker de cambios - const changeTracker = new FormChangeTracker(); - let savedSuccessfully = false; + let invoice = null; + let payments = []; + let isLoading = true; - // Formatear fecha para input type="date" - const formatDateForInput = (dateString) => { - if (!dateString) return ''; - const date = new Date(dateString); - return date.toISOString().split('T')[0]; + // Tracker de cambios + const changeTracker = new FormChangeTracker(); + let savedSuccessfully = false; + + // Formatear fecha para input type="date" + const formatDateForInput = (dateString) => { + if (!dateString) return ''; + const date = new Date(dateString); + return date.toISOString().split('T')[0]; + }; + + // Formatear fecha para mostrar + const formatDate = (dateString) => { + if (!dateString) return '-'; + const date = new Date(dateString); + return date.toLocaleDateString('es-ES'); + }; + + // Formatear moneda + const formatCurrency = (amount) => { + return new Intl.NumberFormat('es-ES', { + style: 'currency', + currency: 'EUR' + }).format(amount || 0); + }; + + // Obtener texto de estado + const getStatusText = (status) => { + const statusTextMap = { + 'draft': 'Borrador', + 'validated': 'Pte. Pago', + 'paid': 'Pagada', + 'unpaid': 'Pte. Pago', + 'canceled': 'Cancelada' }; + return statusTextMap[status] || status; + }; - // Formatear fecha para mostrar - const formatDate = (dateString) => { - if (!dateString) return '-'; - const date = new Date(dateString); - return date.toLocaleDateString('es-ES'); - }; + // Renderizar contenido del modal + const renderContent = () => { + const modalContent = modal.querySelector('.modal-content'); - // Formatear moneda - const formatCurrency = (amount) => { - return new Intl.NumberFormat('es-ES', { - style: 'currency', - currency: 'EUR' - }).format(amount || 0); - }; - - // Obtener texto de estado - const getStatusText = (status) => { - const statusTextMap = { - 'draft': 'Borrador', - 'validated': 'Validada', - 'paid': 'Pagada', - 'unpaid': 'Impagada', - 'canceled': 'Cancelada' - }; - return statusTextMap[status] || status; - }; - - // Renderizar contenido del modal - const renderContent = () => { - const modalContent = modal.querySelector('.modal-content'); - - if (isLoading) { - modalContent.innerHTML = ` + if (isLoading) { + modalContent.innerHTML = ` `; - return; - } + return; + } - if (!invoice) { - modalContent.innerHTML = ` + if (!invoice) { + modalContent.innerHTML = ` `; - return; - } + return; + } - const isDraft = invoice.status === 'draft'; - const canEdit = isDraft || invoice.status === 'validated' || invoice.status === 'unpaid'; + const isDraft = invoice.status === 'draft'; + const canEdit = isDraft || invoice.status === 'validated' || invoice.status === 'unpaid'; - modalContent.innerHTML = ` + modalContent.innerHTML = ` @@ -328,389 +328,389 @@ export function InvoiceModal(invoiceId, onClose, onUpdate) { `; - attachEventListeners(); - - // Capturar estado inicial después de renderizar - setTimeout(() => { - const modalContent = modal.querySelector('.modal-content'); - if (modalContent && !isLoading) { - changeTracker.captureInitialState(modalContent); - changeTracker.setupAutoTracking(modalContent); - } - }, 100); + attachEventListeners(); + + // Capturar estado inicial después de renderizar + setTimeout(() => { + const modalContent = modal.querySelector('.modal-content'); + if (modalContent && !isLoading) { + changeTracker.captureInitialState(modalContent); + changeTracker.setupAutoTracking(modalContent); + } + }, 100); + }; + + // Adjuntar event listeners + const attachEventListeners = () => { + // Botón cerrar (X) - muestra modal si hay cambios + const closeBtn = modal.querySelector('.btn-close'); + closeBtn?.addEventListener('click', handleClose); + + // Botón cancelar modal - muestra modal si hay cambios + const cancelBtn = modal.querySelector('.btn-cancel-modal'); + cancelBtn?.addEventListener('click', handleClose); + + // Botón guardar + const saveBtn = modal.querySelector('.btn-save'); + saveBtn?.addEventListener('click', handleSave); + + // Botón validar + const validateBtn = modal.querySelector('.btn-validate'); + validateBtn?.addEventListener('click', handleValidate); + + // Botón añadir línea + const addLineBtn = modal.querySelector('.btn-add-line'); + addLineBtn?.addEventListener('click', toggleAddLineForm); + + // Botón guardar línea + const saveLineBtn = modal.querySelector('.btn-save-line'); + saveLineBtn?.addEventListener('click', handleSaveLine); + + // Botón cancelar línea + const cancelLineBtn = modal.querySelector('.btn-cancel-line'); + cancelLineBtn?.addEventListener('click', () => { + const form = modal.querySelector('.add-line-form'); + if (form) form.style.display = 'none'; + }); + + // Botones eliminar línea + const deleteLineBtns = modal.querySelectorAll('.btn-delete-line'); + deleteLineBtns.forEach(btn => { + btn.addEventListener('click', () => { + const lineId = parseInt(btn.dataset.lineId); + handleDeleteLine(lineId); + }); + }); + + // Botón registrar pago + const payBtn = modal.querySelector('#btn-pay'); + payBtn?.addEventListener('click', handlePayment); + + // Click fuera del modal - muestra modal si hay cambios + modal.addEventListener('click', (e) => { + if (e.target === modal) { + handleClose(); + } + }); + }; + + // Manejar cierre + const handleClose = async () => { + // Si ya se guardó exitosamente, cerrar directamente + if (savedSuccessfully) { + changeTracker.cleanup(); + modal.remove(); + if (onClose) onClose(); + return; + } + + // Verificar si hay cambios sin guardar + const modalContent = modal.querySelector('.modal-content'); + if (modalContent) { + changeTracker.checkForChanges(modalContent); + } + + if (changeTracker.hasChanges) { + // Mostrar modal de confirmación + const confirmed = await showConfirmExitModal({ + title: 'Cambios sin guardar', + message: 'Tienes cambios sin guardar en la factura. ¿Seguro que quieres cerrar?', + confirmText: 'Cerrar sin guardar', + cancelText: 'Seguir editando' + }); + + if (!confirmed) { + return; // No cerrar el modal + } + } + + changeTracker.cleanup(); + modal.remove(); + if (onClose) onClose(); + }; + + // Manejar guardado + const handleSave = async () => { + const form = modal.querySelector('#invoice-form'); + const formData = new FormData(form); + + // La API espera camelCase en el PUT (notePublic/notePrivate) + const data = { + number: formData.get('number') || undefined, + expireDate: formData.get('expireDate') || undefined, + notePublic: formData.get('note_public') || undefined, + notePrivate: formData.get('note_private') || undefined }; - // Adjuntar event listeners - const attachEventListeners = () => { - // Botón cerrar (X) - muestra modal si hay cambios - const closeBtn = modal.querySelector('.btn-close'); - closeBtn?.addEventListener('click', handleClose); + // Filtrar valores undefined + Object.keys(data).forEach(key => { + if (data[key] === undefined || data[key] === '') { + delete data[key]; + } + }); - // Botón cancelar modal - muestra modal si hay cambios - const cancelBtn = modal.querySelector('.btn-cancel-modal'); - cancelBtn?.addEventListener('click', handleClose); + try { + const saveBtn = modal.querySelector('.btn-save'); + saveBtn.disabled = true; + saveBtn.textContent = 'Guardando...'; - // Botón guardar - const saveBtn = modal.querySelector('.btn-save'); - saveBtn?.addEventListener('click', handleSave); + await updateInvoice(invoiceId, data); - // Botón validar - const validateBtn = modal.querySelector('.btn-validate'); - validateBtn?.addEventListener('click', handleValidate); + // Marcar como guardado exitosamente + savedSuccessfully = true; + changeTracker.markAsSaved(); - // Botón añadir línea - const addLineBtn = modal.querySelector('.btn-add-line'); - addLineBtn?.addEventListener('click', toggleAddLineForm); + alert('Factura actualizada correctamente'); + if (onUpdate) onUpdate(); + handleClose(); + } catch (error) { + console.error('Error al guardar:', error); + alert('Error al guardar la factura: ' + error.message); + } finally { + const saveBtn = modal.querySelector('.btn-save'); + if (saveBtn) { + saveBtn.disabled = false; + saveBtn.textContent = 'Guardar Cambios'; + } + } + }; - // Botón guardar línea - const saveLineBtn = modal.querySelector('.btn-save-line'); - saveLineBtn?.addEventListener('click', handleSaveLine); + // Manejar validación + const handleValidate = async () => { + if (!confirm('¿Estás seguro de que quieres validar esta factura? No podrás editarla completamente después.')) { + return; + } - // Botón cancelar línea - const cancelLineBtn = modal.querySelector('.btn-cancel-line'); - cancelLineBtn?.addEventListener('click', () => { - const form = modal.querySelector('.add-line-form'); - if (form) form.style.display = 'none'; - }); + try { + const validateBtn = modal.querySelector('.btn-validate'); + validateBtn.disabled = true; + validateBtn.textContent = 'Validando...'; - // Botones eliminar línea - const deleteLineBtns = modal.querySelectorAll('.btn-delete-line'); - deleteLineBtns.forEach(btn => { - btn.addEventListener('click', () => { - const lineId = parseInt(btn.dataset.lineId); - handleDeleteLine(lineId); - }); - }); + await validateInvoice(invoiceId); - // Botón registrar pago - const payBtn = modal.querySelector('#btn-pay'); - payBtn?.addEventListener('click', handlePayment); + // Marcar como guardado para no mostrar el modal de confirmación + savedSuccessfully = true; + changeTracker.markAsSaved(); - // Click fuera del modal - muestra modal si hay cambios - modal.addEventListener('click', (e) => { - if (e.target === modal) { - handleClose(); - } - }); - }; + alert('Factura validada correctamente'); + if (onUpdate) onUpdate(); - // Manejar cierre - const handleClose = async () => { - // Si ya se guardó exitosamente, cerrar directamente - if (savedSuccessfully) { - changeTracker.cleanup(); - modal.remove(); - if (onClose) onClose(); - return; - } + // Recargar la factura para mostrar el nuevo estado + await loadInvoice(); - // Verificar si hay cambios sin guardar - const modalContent = modal.querySelector('.modal-content'); - if (modalContent) { - changeTracker.checkForChanges(modalContent); - } + // Recapturar estado inicial después de recargar + savedSuccessfully = false; + } catch (error) { + console.error('Error al validar:', error); + alert('Error al validar la factura: ' + error.message); - if (changeTracker.hasChanges) { - // Mostrar modal de confirmación - const confirmed = await showConfirmExitModal({ - title: 'Cambios sin guardar', - message: 'Tienes cambios sin guardar en la factura. ¿Seguro que quieres cerrar?', - confirmText: 'Cerrar sin guardar', - cancelText: 'Seguir editando' - }); + const validateBtn = modal.querySelector('.btn-validate'); + if (validateBtn) { + validateBtn.disabled = false; + validateBtn.innerHTML = 'Validar Factura'; + } + } + }; - if (!confirmed) { - return; // No cerrar el modal - } - } + // Mostrar/ocultar formulario de añadir línea + const toggleAddLineForm = () => { + const form = modal.querySelector('.add-line-form'); + if (form) { + const isVisible = form.style.display !== 'none'; + form.style.display = isVisible ? 'none' : 'block'; - changeTracker.cleanup(); - modal.remove(); - if (onClose) onClose(); - }; + if (!isVisible) { + // Limpiar campos + modal.querySelector('#line-description').value = ''; + modal.querySelector('#line-quantity').value = '1'; + modal.querySelector('#line-price').value = '0'; + modal.querySelector('#line-tax').value = '21'; + } + } + }; - // Manejar guardado - const handleSave = async () => { - const form = modal.querySelector('#invoice-form'); - const formData = new FormData(form); + // Guardar nueva línea + const handleSaveLine = async () => { + const description = modal.querySelector('#line-description').value.trim(); + const quantity = parseFloat(modal.querySelector('#line-quantity').value); + const unitPrice = parseFloat(modal.querySelector('#line-price').value); + const taxRate = parseFloat(modal.querySelector('#line-tax').value); - // La API espera camelCase en el PUT (notePublic/notePrivate) - const data = { - number: formData.get('number') || undefined, - expireDate: formData.get('expireDate') || undefined, - notePublic: formData.get('note_public') || undefined, - notePrivate: formData.get('note_private') || undefined - }; + if (!description) { + alert('La descripción es obligatoria'); + return; + } - // Filtrar valores undefined - Object.keys(data).forEach(key => { - if (data[key] === undefined || data[key] === '') { - delete data[key]; - } - }); + if (!quantity || quantity <= 0) { + alert('La cantidad debe ser mayor que 0'); + return; + } - try { - const saveBtn = modal.querySelector('.btn-save'); - saveBtn.disabled = true; - saveBtn.textContent = 'Guardando...'; + if (unitPrice < 0) { + alert('El precio no puede ser negativo'); + return; + } - await updateInvoice(invoiceId, data); + if (taxRate < 0 || taxRate > 100) { + alert('El IVA debe estar entre 0 y 100'); + return; + } - // Marcar como guardado exitosamente - savedSuccessfully = true; - changeTracker.markAsSaved(); + try { + const saveBtn = modal.querySelector('.btn-save-line'); + saveBtn.disabled = true; + saveBtn.textContent = 'Guardando...'; - alert('Factura actualizada correctamente'); - if (onUpdate) onUpdate(); - handleClose(); - } catch (error) { - console.error('Error al guardar:', error); - alert('Error al guardar la factura: ' + error.message); - } finally { - const saveBtn = modal.querySelector('.btn-save'); - if (saveBtn) { - saveBtn.disabled = false; - saveBtn.textContent = 'Guardar Cambios'; - } - } - }; + await addInvoiceLine(invoiceId, { + description, + quantity, + unitPrice, + taxRate + }); - // Manejar validación - const handleValidate = async () => { - if (!confirm('¿Estás seguro de que quieres validar esta factura? No podrás editarla completamente después.')) { - return; - } + alert('Línea añadida correctamente'); + toggleAddLineForm(); - try { - const validateBtn = modal.querySelector('.btn-validate'); - validateBtn.disabled = true; - validateBtn.textContent = 'Validando...'; + // Recargar y recapturar estado inicial + await loadInvoice(); + changeTracker.markAsSaved(); + } catch (error) { + console.error('Error al añadir línea:', error); + alert('Error al añadir línea: ' + error.message); + } finally { + const saveBtn = modal.querySelector('.btn-save-line'); + if (saveBtn) { + saveBtn.disabled = false; + saveBtn.textContent = 'Guardar Línea'; + } + } + }; - await validateInvoice(invoiceId); + // Eliminar línea de factura + const handleDeleteLine = async (lineId) => { + if (!confirm('¿Estás seguro de que quieres eliminar esta línea?')) { + return; + } - // Marcar como guardado para no mostrar el modal de confirmación - savedSuccessfully = true; - changeTracker.markAsSaved(); + try { + // Deshabilitar el botón mientras se elimina + const btn = modal.querySelector(`.btn-delete-line[data-line-id="${lineId}"]`); + if (btn) { + btn.disabled = true; + btn.innerHTML = ''; + } - alert('Factura validada correctamente'); - if (onUpdate) onUpdate(); + await deleteInvoiceLine(invoiceId, lineId); - // Recargar la factura para mostrar el nuevo estado - await loadInvoice(); - - // Recapturar estado inicial después de recargar - savedSuccessfully = false; - } catch (error) { - console.error('Error al validar:', error); - alert('Error al validar la factura: ' + error.message); + if (onUpdate) onUpdate(); + await loadInvoice(); + changeTracker.markAsSaved(); + } catch (error) { + console.error('Error al eliminar línea:', error); + alert('Error al eliminar línea: ' + error.message); - const validateBtn = modal.querySelector('.btn-validate'); - if (validateBtn) { - validateBtn.disabled = false; - validateBtn.innerHTML = 'Validar Factura'; - } - } - }; + const btn = modal.querySelector(`.btn-delete-line[data-line-id="${lineId}"]`); + if (btn) { + btn.disabled = false; + btn.innerHTML = ''; + } + } + }; - // Mostrar/ocultar formulario de añadir línea - const toggleAddLineForm = () => { - const form = modal.querySelector('.add-line-form'); - if (form) { - const isVisible = form.style.display !== 'none'; - form.style.display = isVisible ? 'none' : 'block'; + // Manejar pago de factura + const handlePayment = async () => { + const amountInput = modal.querySelector('#payment-amount'); + const dateInput = modal.querySelector('#payment-date'); + const refInput = modal.querySelector('#payment-ref'); - if (!isVisible) { - // Limpiar campos - modal.querySelector('#line-description').value = ''; - modal.querySelector('#line-quantity').value = '1'; - modal.querySelector('#line-price').value = '0'; - modal.querySelector('#line-tax').value = '21'; - } - } - }; + if (!amountInput || !dateInput) return; - // Guardar nueva línea - const handleSaveLine = async () => { - const description = modal.querySelector('#line-description').value.trim(); - const quantity = parseFloat(modal.querySelector('#line-quantity').value); - const unitPrice = parseFloat(modal.querySelector('#line-price').value); - const taxRate = parseFloat(modal.querySelector('#line-tax').value); + const rawAmount = parseFloat(amountInput.value); + const remainToPay = invoice.remainToPay; - if (!description) { - alert('La descripción es obligatoria'); - return; - } + // Si vacío o 0, pago total (enviar null) + let amount = null; + if (!isNaN(rawAmount) && rawAmount > 0) { + if (rawAmount > remainToPay) { + alert(`La cantidad no puede superar el pendiente de pago (${remainToPay.toFixed(2)} €)`); + return; + } + amount = rawAmount; + } - if (!quantity || quantity <= 0) { - alert('La cantidad debe ser mayor que 0'); - return; - } + const paymentDate = dateInput.value; + if (!paymentDate) { + alert('La fecha de pago es obligatoria'); + return; + } - if (unitPrice < 0) { - alert('El precio no puede ser negativo'); - return; - } + const paymentRef = refInput?.value?.trim() || undefined; - if (taxRate < 0 || taxRate > 100) { - alert('El IVA debe estar entre 0 y 100'); - return; - } + try { + const payBtn = modal.querySelector('#btn-pay'); + payBtn.disabled = true; + payBtn.textContent = 'Procesando...'; - try { - const saveBtn = modal.querySelector('.btn-save-line'); - saveBtn.disabled = true; - saveBtn.textContent = 'Guardando...'; + await addPayment(invoiceId, { + amount: amount, + paymentDate: paymentDate, + paymentModeId: 4, + closePaidInvoices: "yes", + accountId: 1, + numPayment: paymentRef + }); - await addInvoiceLine(invoiceId, { - description, - quantity, - unitPrice, - taxRate - }); + savedSuccessfully = true; + changeTracker.markAsSaved(); - alert('Línea añadida correctamente'); - toggleAddLineForm(); - - // Recargar y recapturar estado inicial - await loadInvoice(); - changeTracker.markAsSaved(); - } catch (error) { - console.error('Error al añadir línea:', error); - alert('Error al añadir línea: ' + error.message); - } finally { - const saveBtn = modal.querySelector('.btn-save-line'); - if (saveBtn) { - saveBtn.disabled = false; - saveBtn.textContent = 'Guardar Línea'; - } - } - }; + const displayAmount = amount ? `${amount.toFixed(2)} €` : `${remainToPay.toFixed(2)} € (total)`; + alert(`Pago de ${displayAmount} registrado correctamente`); - // Eliminar línea de factura - const handleDeleteLine = async (lineId) => { - if (!confirm('¿Estás seguro de que quieres eliminar esta línea?')) { - return; - } + if (onUpdate) onUpdate(); + await loadInvoice(); + savedSuccessfully = false; + } catch (error) { + console.error('Error al registrar pago:', error); + alert('Error al registrar el pago: ' + error.message); + } finally { + const payBtn = modal.querySelector('#btn-pay'); + if (payBtn) { + payBtn.disabled = false; + payBtn.innerHTML = 'Registrar Pago'; + } + } + }; - try { - // Deshabilitar el botón mientras se elimina - const btn = modal.querySelector(`.btn-delete-line[data-line-id="${lineId}"]`); - if (btn) { - btn.disabled = true; - btn.innerHTML = ''; - } + // Cargar factura + const loadInvoice = async () => { + try { + isLoading = true; + renderContent(); - await deleteInvoiceLine(invoiceId, lineId); + invoice = await getInvoiceById(invoiceId); + try { + payments = await getPayments(invoiceId); + } catch (e) { + payments = []; + } + isLoading = false; + renderContent(); + } catch (error) { + console.error('Error al cargar factura:', error); + isLoading = false; + invoice = null; + renderContent(); + } + }; - if (onUpdate) onUpdate(); - await loadInvoice(); - changeTracker.markAsSaved(); - } catch (error) { - console.error('Error al eliminar línea:', error); - alert('Error al eliminar línea: ' + error.message); - - const btn = modal.querySelector(`.btn-delete-line[data-line-id="${lineId}"]`); - if (btn) { - btn.disabled = false; - btn.innerHTML = ''; - } - } - }; - - // Manejar pago de factura - const handlePayment = async () => { - const amountInput = modal.querySelector('#payment-amount'); - const dateInput = modal.querySelector('#payment-date'); - const refInput = modal.querySelector('#payment-ref'); - - if (!amountInput || !dateInput) return; - - const rawAmount = parseFloat(amountInput.value); - const remainToPay = invoice.remainToPay; - - // Si vacío o 0, pago total (enviar null) - let amount = null; - if (!isNaN(rawAmount) && rawAmount > 0) { - if (rawAmount > remainToPay) { - alert(`La cantidad no puede superar el pendiente de pago (${remainToPay.toFixed(2)} €)`); - return; - } - amount = rawAmount; - } - - const paymentDate = dateInput.value; - if (!paymentDate) { - alert('La fecha de pago es obligatoria'); - return; - } - - const paymentRef = refInput?.value?.trim() || undefined; - - try { - const payBtn = modal.querySelector('#btn-pay'); - payBtn.disabled = true; - payBtn.textContent = 'Procesando...'; - - await addPayment(invoiceId, { - amount: amount, - paymentDate: paymentDate, - paymentModeId: 4, - closePaidInvoices: "yes", - accountId: 1, - numPayment: paymentRef - }); - - savedSuccessfully = true; - changeTracker.markAsSaved(); - - const displayAmount = amount ? `${amount.toFixed(2)} €` : `${remainToPay.toFixed(2)} € (total)`; - alert(`Pago de ${displayAmount} registrado correctamente`); - - if (onUpdate) onUpdate(); - await loadInvoice(); - savedSuccessfully = false; - } catch (error) { - console.error('Error al registrar pago:', error); - alert('Error al registrar el pago: ' + error.message); - } finally { - const payBtn = modal.querySelector('#btn-pay'); - if (payBtn) { - payBtn.disabled = false; - payBtn.innerHTML = 'Registrar Pago'; - } - } - }; - - // Cargar factura - const loadInvoice = async () => { - try { - isLoading = true; - renderContent(); - - invoice = await getInvoiceById(invoiceId); - try { - payments = await getPayments(invoiceId); - } catch (e) { - payments = []; - } - isLoading = false; - renderContent(); - } catch (error) { - console.error('Error al cargar factura:', error); - isLoading = false; - invoice = null; - renderContent(); - } - }; - - // Inicializar modal - modal.innerHTML = ` + // Inicializar modal + modal.innerHTML = ` `; - // Cargar factura - loadInvoice(); + // Cargar factura + loadInvoice(); - return modal; + return modal; } diff --git a/src/pages/Facturas.js b/src/pages/Facturas.js index dd75110..ddacb2c 100755 --- a/src/pages/Facturas.js +++ b/src/pages/Facturas.js @@ -24,9 +24,8 @@ export function renderFacturasPage() { diff --git a/src/style.css b/src/style.css index bb7ae24..9b68df8 100755 --- a/src/style.css +++ b/src/style.css @@ -95,7 +95,9 @@ /* ============================================ Reset & Base ============================================ */ -*, *::before, *::after { +*, +*::before, +*::after { box-sizing: border-box; } @@ -115,7 +117,12 @@ a:hover { color: var(--primary-hover); } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { margin: 0; font-weight: 600; line-height: 1.3; @@ -123,9 +130,17 @@ h1, h2, h3, h4, h5, h6 { letter-spacing: -0.01em; } -h1 { font-size: 1.5rem; } -h2 { font-size: 1.25rem; } -h3 { font-size: 1rem; } +h1 { + font-size: 1.5rem; +} + +h2 { + font-size: 1.25rem; +} + +h3 { + font-size: 1rem; +} #app { width: 100%; @@ -406,7 +421,7 @@ button:disabled { min-width: 0; } -.main-content > div { +.main-content>div { max-width: 1280px; margin: 0 auto; padding: var(--space-6); @@ -1622,6 +1637,7 @@ button:disabled { } @media (max-width: 1024px) { + .clientes-page, .facturas-page { padding: var(--space-4); @@ -1693,4 +1709,4 @@ button:disabled { .dashboard-card-desc { font-size: 13px; color: var(--text-secondary); -} +} \ No newline at end of file