diff --git a/.env b/.env
old mode 100644
new mode 100755
diff --git a/.gitignore b/.gitignore
old mode 100644
new mode 100755
diff --git a/index.html b/index.html
old mode 100644
new mode 100755
diff --git a/package-lock.json b/package-lock.json
old mode 100644
new mode 100755
diff --git a/package.json b/package.json
old mode 100644
new mode 100755
diff --git a/public/vite.svg b/public/vite.svg
old mode 100644
new mode 100755
diff --git a/src/components/InvoiceItem.js b/src/components/InvoiceItem.js
new file mode 100644
index 0000000..5bc89d2
--- /dev/null
+++ b/src/components/InvoiceItem.js
@@ -0,0 +1,76 @@
+export function InvoiceItem(invoice) {
+ const item = document.createElement('div');
+ item.className = 'invoice-item';
+
+ // Formatear fecha
+ const formatDate = (dateString) => {
+ 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);
+ };
+
+ // Obtener clase de estado
+ const getStatusClass = (status) => {
+ const statusMap = {
+ 'draft': 'status-draft',
+ 'validated': 'status-validated',
+ 'paid': 'status-paid',
+ 'unpaid': 'status-unpaid',
+ 'canceled': 'status-canceled'
+ };
+ return statusMap[status] || 'status-default';
+ };
+
+ // Obtener texto de estado
+ const getStatusText = (status) => {
+ const statusTextMap = {
+ 'draft': 'Borrador',
+ 'validated': 'Validada',
+ 'paid': 'Pagada',
+ 'unpaid': 'Impagada',
+ 'canceled': 'Cancelada'
+ };
+ return statusTextMap[status] || status;
+ };
+
+ item.innerHTML = /*html*/`
+
+
+
+ ${invoice.number}
+
+
+ ${getStatusText(invoice.status)}
+
+
+
+ 👤
+ ${invoice.clientName || 'Sin nombre'}
+
+ ${formatDate(invoice.date)}
+ ${formatCurrency(invoice.total)}
+ ${formatCurrency(invoice.remainToPay)}
+
+
+
+ `;
+
+ // Event listener para el botón de ver
+ const viewBtn = item.querySelector('.btn-view');
+ viewBtn.addEventListener('click', () => {
+ console.log('Ver factura:', invoice.id);
+ // Aquí puedes agregar la lógica para ver los detalles
+ alert(`Ver detalles de factura: ${invoice.number}`);
+ });
+
+ return item;
+}
diff --git a/src/components/Login.js b/src/components/Login.js
old mode 100644
new mode 100755
diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js
old mode 100644
new mode 100755
diff --git a/src/counter.js b/src/counter.js
old mode 100644
new mode 100755
diff --git a/src/javascript.svg b/src/javascript.svg
old mode 100644
new mode 100755
diff --git a/src/main.js b/src/main.js
old mode 100644
new mode 100755
diff --git a/src/pages/CreateInvoicePage.js b/src/pages/CreateInvoicePage.js
new file mode 100644
index 0000000..c043d6f
--- /dev/null
+++ b/src/pages/CreateInvoicePage.js
@@ -0,0 +1,297 @@
+export function renderCreateInvoicePage() {
+ const container = document.createElement('div');
+ container.className = 'create-invoice-page';
+
+ container.innerHTML = /*html*/`
+
+
+
+ `;
+
+ // Establecer fecha de hoy por defecto
+ const today = new Date().toISOString().split('T')[0];
+ container.querySelector('#date').value = today;
+
+ // Establecer fecha de vencimiento (30 días después)
+ const expireDate = new Date();
+ expireDate.setDate(expireDate.getDate() + 30);
+ container.querySelector('#expireDate').value = expireDate.toISOString().split('T')[0];
+
+ // Contador de líneas
+ let lineCounter = 0;
+
+ // Función para crear una nueva línea de borrador
+ function createInvoiceLine() {
+ lineCounter++;
+ const lineDiv = document.createElement('div');
+ lineDiv.className = 'invoice-line';
+ lineDiv.dataset.lineId = lineCounter;
+
+ lineDiv.innerHTML = /*html*/`
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ // Event listeners para calcular subtotal
+ const quantityInput = lineDiv.querySelector('.line-quantity');
+ const priceInput = lineDiv.querySelector('.line-unitPrice');
+ const subtotalInput = lineDiv.querySelector('.line-subtotal');
+
+ function updateSubtotal() {
+ const quantity = parseFloat(quantityInput.value) || 0;
+ const price = parseFloat(priceInput.value) || 0;
+ const subtotal = quantity * price;
+ subtotalInput.value = `${subtotal.toFixed(2)} €`;
+ updateTotal();
+ }
+
+ quantityInput.addEventListener('input', updateSubtotal);
+ priceInput.addEventListener('input', updateSubtotal);
+
+ // Event listener para eliminar línea
+ lineDiv.querySelector('.btn-remove-line').addEventListener('click', () => {
+ lineDiv.remove();
+ updateLineNumbers();
+ updateTotal();
+ });
+
+ return lineDiv;
+ }
+
+ // Función para actualizar números de línea
+ function updateLineNumbers() {
+ const lines = container.querySelectorAll('.invoice-line');
+ lines.forEach((line, index) => {
+ line.querySelector('.line-number').textContent = `Línea ${index + 1}`;
+ });
+ }
+
+ // Función para actualizar el total
+ function updateTotal() {
+ const lines = container.querySelectorAll('.invoice-line');
+ let total = 0;
+ lines.forEach(line => {
+ const quantity = parseFloat(line.querySelector('.line-quantity').value) || 0;
+ const price = parseFloat(line.querySelector('.line-unitPrice').value) || 0;
+ const taxRate = parseFloat(line.querySelector('.line-taxRate').value) || 0;
+ const subtotal = quantity * price;
+ const withTax = subtotal * (1 + taxRate / 100);
+ total += withTax;
+ });
+
+ // Mostrar total en algún lugar si lo deseas
+ console.log('Total borrador:', total.toFixed(2));
+ }
+
+ // Agregar primera línea por defecto
+ const linesContainer = container.querySelector('#invoice-lines-container');
+ linesContainer.appendChild(createInvoiceLine());
+
+ // Event listener para añadir línea
+ container.querySelector('#add-line-btn').addEventListener('click', () => {
+ linesContainer.appendChild(createInvoiceLine());
+ });
+
+ // Event listener para volver
+ container.querySelector('#back-btn').addEventListener('click', () => {
+ window.location.hash = '#invoices';
+ });
+
+ // Event listener para cancelar
+ container.querySelector('#cancel-btn').addEventListener('click', () => {
+ if (confirm('¿Estás seguro de que quieres cancelar? Se perderán los datos no guardados.')) {
+ window.location.hash = '#invoices';
+ }
+ });
+
+ // Event listener para enviar formulario
+ container.querySelector('#invoice-form').addEventListener('submit', async (e) => {
+ e.preventDefault();
+
+ // Recopilar datos del formulario
+ const clientIdValue = parseInt(container.querySelector('#clientId').value);
+
+ // Validar clientId
+ if (!clientIdValue || isNaN(clientIdValue) || clientIdValue < 1) {
+ alert('Por favor ingresa un ID de cliente válido');
+ return;
+ }
+
+ const formData = {
+ clientId: clientIdValue,
+ date: container.querySelector('#date').value + 'T00:00:00',
+ expireDate: container.querySelector('#expireDate').value + 'T00:00:00',
+ reference: container.querySelector('#reference').value || null,
+ notePublic: container.querySelector('#notePublic').value || null,
+ notePrivate: container.querySelector('#notePrivate').value || null,
+ lines: []
+ };
+
+ // Recopilar líneas
+ const lines = container.querySelectorAll('.invoice-line');
+ if (lines.length === 0) {
+ alert('Debes agregar al menos una línea al borrador');
+ return;
+ }
+
+ lines.forEach(line => {
+ const lineData = {
+ description: line.querySelector('.line-description').value,
+ quantity: parseFloat(line.querySelector('.line-quantity').value),
+ unitPrice: parseFloat(line.querySelector('.line-unitPrice').value),
+ taxRate: parseFloat(line.querySelector('.line-taxRate').value)
+ };
+ formData.lines.push(lineData);
+ });
+
+ // Validar que todos los campos requeridos estén completos
+ if (!formData.clientId || formData.lines.some(l => !l.description || !l.quantity || !l.unitPrice)) {
+ alert('Por favor completa todos los campos obligatorios');
+ return;
+ }
+
+ // Enviar a la API
+ try {
+ const submitBtn = container.querySelector('.btn-submit');
+ submitBtn.disabled = true;
+ submitBtn.textContent = 'Creando...';
+
+ const token = localStorage.getItem('token');
+ const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/Invoices`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(formData)
+ });
+
+ if (!response.ok) {
+ const error = await response.text();
+ throw new Error(error || 'Error al crear el borrador');
+ }
+
+ const invoiceId = await response.json();
+ alert(`Borrador creado exitosamente con ID: ${invoiceId}`);
+
+ // Redirigir inmediatamente a la lista de facturas
+ window.location.hash = '#invoices';
+
+ } catch (error) {
+ console.error('Error al crear borrador:', error);
+
+ // Mensaje de error más específico
+ let errorMessage = 'Error al crear el borrador';
+ if (error.message.includes('foreign key constraint')) {
+ errorMessage = 'Error: El cliente con ese ID no existe. Por favor verifica el ID del cliente.';
+ } else if (error.message) {
+ errorMessage = `Error: ${error.message}`;
+ }
+
+ alert(errorMessage);
+
+ const submitBtn = container.querySelector('.btn-submit');
+ if (submitBtn) {
+ submitBtn.disabled = false;
+ submitBtn.textContent = 'Crear Borrador';
+ }
+ }
+ });
+
+ return container;
+}
diff --git a/src/pages/DashboardPage.js b/src/pages/DashboardPage.js
old mode 100644
new mode 100755
diff --git a/src/pages/Facturas.js b/src/pages/Facturas.js
new file mode 100755
index 0000000..e211206
--- /dev/null
+++ b/src/pages/Facturas.js
@@ -0,0 +1,235 @@
+import { InvoiceItem } from '../components/InvoiceItem.js';
+
+export function renderFacturasPage() {
+ const container = document.createElement('div');
+ container.className = 'facturas-page';
+
+ // Estado de paginación
+ let currentPage = 1;
+ let totalPages = 1;
+ const pageSize = 20;
+ let allInvoices = []; // Almacenar todas las facturas
+ let filteredInvoices = []; // Facturas filtradas
+ let currentFilter = ''; // Filtro actual de estado
+ let searchTerm = ''; // Término de búsqueda actual
+
+ container.innerHTML = /*html*/`
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ // Función para aplicar filtros combinados
+ function applyFilters() {
+ // Ya no filtra localmente, hace llamada a la API
+ loadAllInvoices();
+ }
+
+ // Función para renderizar la página actual
+ function renderPage(page = 1) {
+ const invoicesList = container.querySelector('.invoices-list');
+
+ currentPage = page;
+ totalPages = Math.ceil(filteredInvoices.length / pageSize);
+
+ // Calcular índices para la página
+ const startIndex = (page - 1) * pageSize;
+ const endIndex = Math.min(startIndex + pageSize, filteredInvoices.length);
+ const invoices = filteredInvoices.slice(startIndex, endIndex);
+
+ console.log(`Página ${currentPage}/${totalPages}, Facturas en esta página: ${invoices.length}, Total filtrado: ${filteredInvoices.length}`);
+
+ // Actualizar UI de paginación
+ updatePaginationUI();
+
+ // Limpiar lista
+ invoicesList.innerHTML = '';
+
+ // Si no hay facturas
+ if (invoices.length === 0) {
+ invoicesList.innerHTML = 'No hay facturas disponibles
';
+ return;
+ }
+
+ // Renderizar cada factura
+ invoices.forEach(invoice => {
+ const invoiceItem = InvoiceItem(invoice);
+ invoicesList.appendChild(invoiceItem);
+ });
+ }
+
+ // Función para cargar todas las facturas
+ async function loadAllInvoices() {
+ const invoicesList = container.querySelector('.invoices-list');
+
+ try {
+ invoicesList.innerHTML = 'Cargando facturas...
';
+
+ const token = localStorage.getItem('token');
+
+ // Construir URL con parámetros
+ let url = `${import.meta.env.VITE_API_BASE_URL}/api/Invoices?limit=1000`;
+
+ if (currentFilter) {
+ url += `&status=${currentFilter}`;
+ }
+
+ if (searchTerm) {
+ url += `&search=${encodeURIComponent(searchTerm)}`;
+ }
+
+ const response = await fetch(url, {
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error('Error al cargar facturas');
+ }
+
+ const data = await response.json();
+
+ // Manejo de respuesta - obtener todas las facturas
+ allInvoices = Array.isArray(data) ? data : data.data || data.invoices || [];
+ filteredInvoices = [...allInvoices];
+
+ console.log(`Total de facturas cargadas: ${allInvoices.length}`);
+
+ // Renderizar la primera página
+ renderPage(1);
+
+ } catch (error) {
+ console.error('Error al cargar facturas:', error);
+ invoicesList.innerHTML = 'Error al cargar las facturas. Por favor, intenta de nuevo.
';
+ }
+ }
+
+ // Función para actualizar la UI de paginación
+ function updatePaginationUI() {
+ const paginationDiv = container.querySelector('.pagination');
+ const currentPageSpan = container.querySelector('.current-page');
+ const totalPagesSpan = container.querySelector('.total-pages');
+ const btnPrev = container.querySelector('.btn-prev');
+ const btnNext = container.querySelector('.btn-next');
+
+ currentPageSpan.textContent = currentPage;
+ totalPagesSpan.textContent = totalPages;
+
+ // Mostrar u ocultar la paginación según el número de páginas
+ if (totalPages <= 1) {
+ paginationDiv.style.display = 'none';
+ } else {
+ paginationDiv.style.display = 'flex';
+ }
+
+ // Deshabilitar botones según corresponda
+ btnPrev.disabled = currentPage === 1;
+ btnNext.disabled = currentPage === totalPages;
+ }
+
+ // Event listeners
+ const newInvoiceBtn = container.querySelector('.btn-new-invoice');
+ newInvoiceBtn.addEventListener('click', () => {
+ window.location.hash = '#create-invoice';
+ });
+
+ const selectAllCheckbox = container.querySelector('#select-all');
+ selectAllCheckbox.addEventListener('change', (e) => {
+ const checkboxes = container.querySelectorAll('.invoice-item input[type="checkbox"]');
+ checkboxes.forEach(cb => cb.checked = e.target.checked);
+ });
+
+ const searchInput = container.querySelector('.search-input');
+ searchInput.addEventListener('input', (e) => {
+ const inputValue = e.target.value;
+ // Debounce para no hacer muchas llamadas
+ clearTimeout(searchInput.debounceTimer);
+ searchInput.debounceTimer = setTimeout(() => {
+ // Solo aplicar búsqueda si contiene al menos un número
+ const hasNumber = /\d/.test(inputValue);
+ if (hasNumber && inputValue !== 'I' && inputValue !== 'IN' && inputValue !== 'N') {
+ searchTerm = inputValue;
+ } else {
+ searchTerm = ''; // Si no tiene número, mostrar todas
+ }
+ applyFilters();
+ }, 500); // Espera 500ms después de que el usuario deja de escribir
+ });
+
+ const filterStatus = container.querySelector('.filter-status');
+ filterStatus.addEventListener('change', (e) => {
+ currentFilter = e.target.value;
+ console.log(`Filtro aplicado: ${currentFilter || 'Todos'}`);
+ applyFilters();
+ });
+
+ // Event listeners de paginación
+ const btnPrev = container.querySelector('.btn-prev');
+ const btnNext = container.querySelector('.btn-next');
+
+ btnPrev.addEventListener('click', () => {
+ if (currentPage > 1) {
+ renderPage(currentPage - 1);
+ scrollToTop();
+ }
+ });
+
+ btnNext.addEventListener('click', () => {
+ if (currentPage < totalPages) {
+ renderPage(currentPage + 1);
+ scrollToTop();
+ }
+ });
+
+ // Función para scroll al inicio
+ function scrollToTop() {
+ container.scrollIntoView({ behavior: 'smooth', block: 'start' });
+ }
+
+ // Cargar todas las facturas al montar el componente
+ loadAllInvoices();
+
+ return container;
+}
diff --git a/src/pages/LoginPage.js b/src/pages/LoginPage.js
old mode 100644
new mode 100755
diff --git a/src/pages/pagesRegistry.js b/src/pages/pagesRegistry.js
old mode 100644
new mode 100755
index 8a7bf57..0e7814a
--- a/src/pages/pagesRegistry.js
+++ b/src/pages/pagesRegistry.js
@@ -1,5 +1,7 @@
import { renderDashboard } from './DashboardPage.js';
import { renderTestPage } from './test.js';
+import { renderFacturasPage } from './Facturas.js';
+import { renderCreateInvoicePage } from './CreateInvoicePage.js';
// Registry of all pages available in the application
export const pagesRegistry = [
@@ -25,11 +27,15 @@ export const pagesRegistry = [
icon: '📄',
requiresAuth: true,
showInSidebar: true,
- render: () => {
- const div = document.createElement('div');
- div.innerHTML = 'Facturas
Página en construcción...
';
- return div;
- }
+ render: renderFacturasPage
+ },
+ {
+ route: 'create-invoice',
+ name: 'Nueva Factura',
+ icon: '➕',
+ requiresAuth: true,
+ showInSidebar: false, // No mostrar en sidebar
+ render: renderCreateInvoicePage
},
{
route: 'clients',
diff --git a/src/pages/test.js b/src/pages/test.js
old mode 100644
new mode 100755
diff --git a/src/router.js b/src/router.js
old mode 100644
new mode 100755
diff --git a/src/services/auth.js b/src/services/auth.js
old mode 100644
new mode 100755
diff --git a/src/style.css b/src/style.css
old mode 100644
new mode 100755
index 17b2db5..b566d17
--- a/src/style.css
+++ b/src/style.css
@@ -229,22 +229,22 @@ button:focus-visible {
.status-pending {
background-color: #fef3c7;
- color: #d97706;
+ color: #000000;
}
.status-paid {
background-color: #d1fae5;
- color: #059669;
+ color: #000000;
}
.status-overdue {
background-color: #fee2e2;
- color: #dc2626;
+ color: #000000;
}
.status-draft {
background-color: #f1f5f9;
- color: #64748b;
+ color: #000000;
}
.amount-display {
@@ -432,9 +432,350 @@ button:focus-visible {
}
}
-/* Test Page Styles */
-.test-page {
+/* Facturas Page Styles */
+.facturas-page {
padding: 2rem;
+ max-width: 1600px;
+ margin: 0 auto;
+}
+
+.facturas-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+}
+
+.facturas-header h1 {
+ color: var(--text-primary);
+ font-size: 2rem;
+ margin: 0;
+}
+
+.btn-new-invoice {
+ background-color: var(--primary);
+ color: white;
+ padding: 0.75rem 1.5rem;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: 600;
+ transition: background-color 0.2s;
+}
+
+.btn-new-invoice:hover {
+ background-color: var(--primary-hover);
+}
+
+.facturas-filters {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.search-input,
+.filter-status {
+ padding: 0.75rem 1rem;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ font-size: 0.95rem;
+ background: var(--card-bg);
+ color: #000000;
+}
+
+.search-input {
+ flex: 1;
+ max-width: 400px;
+}
+
+.filter-status {
+ min-width: 200px;
+ cursor: pointer;
+}
+
+.invoices-table {
+ background: var(--card-bg);
+ border-radius: 12px;
+ border: 1px solid var(--border-color);
+ overflow: hidden;
+}
+
+.invoices-header,
+.invoice-item {
+ display: grid;
+ grid-template-columns: 40px 150px 120px 120px 120px 120px 120px 120px 80px;
+ gap: 1rem;
+ padding: 1rem 1.5rem;
+ align-items: center;
+}
+
+.invoices-header {
+ background: #f8fafc;
+ font-weight: 600;
+ color: var(--text-secondary);
+ font-size: 0.85rem;
+ text-transform: uppercase;
+ border-bottom: 1px solid var(--border-color);
+}
+
+.invoice-item {
+ border-bottom: 1px solid var(--border-color);
+ transition: background-color 0.2s;
+}
+
+.invoice-item:hover {
+ background-color: #f8fafc;
+}
+
+.invoice-item:last-child {
+ border-bottom: none;
+}
+
+.invoice-checkbox input {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+}
+
+.invoice-number {
+ font-weight: 600;
+ color: var(--text-primary);
+}
+
+.status-badge {
+ display: inline-block;
+ padding: 0.4rem 0.8rem;
+ border-radius: 20px;
+ font-size: 0.8rem;
+ font-weight: 600;
+ text-align: center;
+}
+
+.status-draft {
+ background: #f3f4f6;
+ color: #000000;
+}
+
+.status-validated {
+ background: #dbeafe;
+ color: #000000;
+}
+
+.status-paid {
+ background: #d1fae5;
+ color: #000000;
+}
+
+.status-unpaid {
+ background: #fee2e2;
+ color: #000000;
+}
+
+.status-canceled {
+ background: #f3f4f6;
+ color: #000000;
+ text-decoration: line-through;
+}
+
+.invoice-client {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: var(--text-secondary);
+}
+
+.client-icon {
+ font-size: 1rem;
+}
+
+.invoice-date,
+.invoice-total,
+.invoice-remain {
+ color: var(--text-primary);
+}
+
+.invoice-total,
+.invoice-remain {
+ font-weight: 600;
+}
+
+.invoice-actions {
+ display: flex;
+ gap: 0.5rem;
+ justify-content: center;
+}
+
+.btn-view {
+ background: transparent;
+ border: 1px solid var(--border-color);
+ padding: 0.5rem;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 1.1rem;
+ transition: all 0.2s;
+}
+
+.btn-view:hover {
+ background: var(--primary);
+ border-color: var(--primary);
+ transform: scale(1.05);
+}
+
+.loading,
+.error,
+.no-invoices {
+ padding: 3rem;
+ text-align: center;
+ color: var(--text-secondary);
+ font-size: 1.1rem;
+}
+
+.error {
+ color: var(--danger);
+}
+
+/* Pagination Styles */
+.pagination {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 2rem;
+ margin-top: 2rem;
+ padding: 1.5rem;
+ background: var(--card-bg);
+ border-radius: 12px;
+ border: 1px solid var(--border-color);
+}
+
+.pagination-info {
+ font-weight: 600;
+ color: var(--text-primary);
+ font-size: 1rem;
+ display: flex;
+ gap: 0.5rem;
+ align-items: center;
+}
+
+.pagination-info .current-page,
+.pagination-info .total-pages {
+ font-weight: 700;
+ color: var(--primary);
+ min-width: 30px;
+ text-align: center;
+}
+
+.btn-pagination {
+ padding: 0.75rem 1.5rem;
+ background-color: var(--primary);
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: 600;
+ transition: all 0.2s;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.btn-pagination:hover:not(:disabled) {
+ background-color: var(--primary-hover);
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
+}
+
+.btn-pagination:disabled {
+ background-color: #cbd5e1;
+ cursor: not-allowed;
+ opacity: 0.6;
+}
+
+.btn-pagination:active:not(:disabled) {
+ transform: translateY(0);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .pagination {
+ flex-wrap: wrap;
+ gap: 1rem;
+ }
+
+ .btn-pagination {
+ flex: 1;
+ min-width: 120px;
+ }
+
+ .pagination-info {
+ order: 3;
+ width: 100%;
+ justify-content: center;
+ }
+}
+
+@media (max-width: 1400px) {
+ .invoices-header,
+ .invoice-item {
+ grid-template-columns: 40px 120px 100px 50px 130px 100px 100px 100px 70px;
+ font-size: 0.9rem;
+ }
+}
+
+@media (max-width: 1024px) {
+ .facturas-page {
+ padding: 1rem;
+ }
+
+ .invoices-header,
+ .invoice-item {
+ grid-template-columns: 40px 100px 90px 130px 90px 90px 60px;
+ gap: 0.5rem;
+ padding: 1rem;
+ }
+
+ .invoice-id,
+ .invoice-remain {
+ display: none;
+ }
+}
+
+@media (max-width: 768px) {
+ .facturas-filters {
+ flex-direction: column;
+ }
+
+ .search-input {
+ max-width: 100%;
+ }
+
+ .invoices-header {
+ display: none;
+ }
+
+ .invoice-item {
+ grid-template-columns: 1fr;
+ gap: 0.5rem;
+ padding: 1.5rem;
+ }
+
+ .invoice-item > div {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .invoice-item > div::before {
+ content: attr(class);
+ font-weight: 600;
+ color: var(--text-secondary);
+ text-transform: capitalize;
+ }
+
+ .invoice-checkbox {
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
+ }
}
.test-page h1 {
@@ -474,3 +815,249 @@ button:focus-visible {
min-height: 100vh;
width: 100%;
}
+/* Create Invoice Page Styles */
+.create-invoice-page {
+ padding: 2rem;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.create-invoice-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+}
+
+.create-invoice-header h1 {
+ margin: 0;
+ font-size: 2rem;
+ color: var(--text-primary);
+}
+
+.create-invoice-header .subtitle {
+ margin: 0.5rem 0 0 0;
+ color: var(--text-secondary);
+}
+
+.btn-back {
+ background: var(--card-bg);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+ padding: 0.75rem 1.5rem;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: 600;
+ transition: all 0.2s;
+}
+
+.btn-back:hover {
+ background: #f1f5f9;
+}
+
+.invoice-form {
+ background: var(--card-bg);
+ border-radius: 12px;
+ border: 1px solid var(--border-color);
+ padding: 2rem;
+}
+
+.form-section {
+ margin-bottom: 2rem;
+}
+
+.form-section h2 {
+ font-size: 1.5rem;
+ margin: 0 0 1.5rem 0;
+ color: var(--text-primary);
+ border-bottom: 2px solid var(--primary);
+ padding-bottom: 0.5rem;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+}
+
+.section-header h2 {
+ margin: 0;
+ border: none;
+ padding: 0;
+}
+
+.btn-add-line {
+ background: var(--success);
+ color: white;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ cursor: pointer;
+ font-weight: 600;
+ transition: background 0.2s;
+}
+
+.btn-add-line:hover {
+ background: var(--success-hover);
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.form-row-inline {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr 1fr;
+ gap: 1rem;
+ margin-bottom: 1rem;
+}
+
+.form-group {
+ display: flex;
+ flex-direction: column;
+}
+
+.form-group.full-width {
+ grid-column: 1 / -1;
+}
+
+.form-group label {
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+ color: var(--text-primary);
+}
+
+.form-group input,
+.form-group textarea,
+.form-group select {
+ padding: 0.75rem;
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ font-size: 1rem;
+ color: #000000;
+ background: var(--card-bg);
+ transition: border-color 0.2s;
+}
+
+.form-group input:focus,
+.form-group textarea:focus,
+.form-group select:focus {
+ outline: none;
+ border-color: var(--primary);
+}
+
+.form-group input[readonly] {
+ background: #f1f5f9;
+ cursor: not-allowed;
+}
+
+.invoice-line {
+ background: #f8fafc;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 1.5rem;
+ margin-bottom: 1rem;
+}
+
+.line-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.line-number {
+ font-weight: 700;
+ color: var(--text-primary);
+ font-size: 1.1rem;
+}
+
+.btn-remove-line {
+ background: var(--danger);
+ color: white;
+ border: none;
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ transition: background 0.2s;
+}
+
+.btn-remove-line:hover {
+ background: var(--danger-hover);
+}
+
+.line-content {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.form-actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 1rem;
+ margin-top: 2rem;
+ padding-top: 2rem;
+ border-top: 1px solid var(--border-color);
+}
+
+.btn-cancel {
+ background: var(--card-bg);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+ padding: 0.75rem 2rem;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: 600;
+ transition: all 0.2s;
+}
+
+.btn-cancel:hover {
+ background: #f1f5f9;
+}
+
+.btn-submit {
+ background: var(--primary);
+ color: white;
+ border: none;
+ padding: 0.75rem 2rem;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: 600;
+ transition: background 0.2s;
+}
+
+.btn-submit:hover {
+ background: var(--primary-hover);
+}
+
+.btn-submit:disabled {
+ background: #cbd5e1;
+ cursor: not-allowed;
+}
+
+@media (max-width: 768px) {
+ .create-invoice-page {
+ padding: 1rem;
+ }
+
+ .create-invoice-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1rem;
+ }
+
+ .form-row,
+ .form-row-inline {
+ grid-template-columns: 1fr;
+ }
+
+ .invoice-form {
+ padding: 1rem;
+ }
+}
\ No newline at end of file