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