diff --git a/src/pages/POS/CashRegister/Detail.vue b/src/pages/POS/CashRegister/Detail.vue index e85bc22..12eebf0 100644 --- a/src/pages/POS/CashRegister/Detail.vue +++ b/src/pages/POS/CashRegister/Detail.vue @@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router'; import cashRegisterService from '@Services/cashRegisterService'; import salesService from '@Services/salesService'; import ticketService from '@Services/ticketService'; +import { formatCurrency, formatDate, safeParseFloat, PAYMENT_METHODS } from '@/utils/formatters'; import GoogleIcon from '@Shared/GoogleIcon.vue'; import SaleDetailModal from '@Pages/POS/Sales/DetailModal.vue'; @@ -21,8 +22,8 @@ const selectedSale = ref(null); /** Computed */ const getSalesByMethod = (method) => { return sales.value - .filter(sale => sale.payment_method === method) - .reduce((sum, sale) => sum + parseFloat(sale.total || 0), 0); + .filter(sale => sale.status === 'completed' && sale.payment_method === method) + .reduce((sum, sale) => sum + safeParseFloat(sale.total), 0); }; const totalCashSales = computed(() => getSalesByMethod('cash')); @@ -32,8 +33,13 @@ const totalCardSales = computed(() => totalCreditCard.value + totalDebitCard.val const difference = computed(() => { if (!cashRegister.value) return 0; - const finalCash = parseFloat(cashRegister.value.final_cash || 0); - const initialCash = parseFloat(cashRegister.value.initial_cash || 0); + // Si ya se calculó en loadData, usar ese valor + if (cashRegister.value.difference !== undefined) { + return safeParseFloat(cashRegister.value.difference); + } + // Fallback: calcular aquí + const finalCash = safeParseFloat(cashRegister.value.final_cash); + const initialCash = safeParseFloat(cashRegister.value.initial_cash); const expectedCash = initialCash + totalCashSales.value; return finalCash - expectedCash; }); @@ -46,11 +52,27 @@ const loadData = async () => { const registerData = await cashRegisterService.getCashRegisterDetail(route.params.id); cashRegister.value = registerData; - // Cargar ventas del período + // Cargar ventas del período (filtradas por cash_register_id en el backend) const salesData = await salesService.getSales({ cash_register_id: route.params.id }); - sales.value = salesData.data || []; + + const completedSales = salesData.data.filter(sale => sale.status === 'completed'); + + sales.value = salesData.data || []; // Muestra todas las ventas + + // Calcula solo con ventas completadas + const cashSales = completedSales + .filter(s => s.payment_method === 'cash') + .reduce((sum, s) => sum + safeParseFloat(s.total), 0); + + const expectedCash = safeParseFloat(cashRegister.value.initial_cash) + cashSales; + const calculatedDifference = safeParseFloat(cashRegister.value.final_cash) - expectedCash; + + // Actualiza los valores calculados + cashRegister.value.expected_cash = expectedCash; + cashRegister.value.difference = calculatedDifference; + } catch (error) { window.Notify.error('Error al cargar el detalle del corte'); } finally { @@ -89,24 +111,6 @@ const downloadTicket = () => { } }; -const formatCurrency = (amount) => { - return new Intl.NumberFormat('es-MX', { - style: 'currency', - currency: 'MXN' - }).format(amount || 0); -}; - -const formatDate = (dateString) => { - if (!dateString) return '-'; - return new Date(dateString).toLocaleString('es-MX', { - year: 'numeric', - month: 'short', - day: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); -}; - const getDifferenceColor = () => { const diff = difference.value; if (Math.abs(diff) < 0.01) return 'text-gray-600 dark:text-gray-400'; @@ -120,12 +124,7 @@ const getDifferenceIcon = () => { }; const getPaymentMethodLabel = (method) => { - const methods = { - cash: 'Efectivo', - credit_card: 'Tarjeta de Crédito', - debit_card: 'Tarjeta de Débito' - }; - return methods[method] || method; + return PAYMENT_METHODS[method] || method; }; const getPaymentMethodIcon = (method) => { @@ -210,7 +209,7 @@ onMounted(() => {
Apertura
- {{ formatDate(cashRegister.opened_at) }} + {{ formatDate(cashRegister.opened_at, { includeTime: true }) }}
Cierre
- {{ formatDate(cashRegister.closed_at) }} + {{ formatDate(cashRegister.closed_at, { includeTime: true }) }}
| Folio | -Hora | -Método | -Total | +Folio | +Hora | +Método | +Estado | +Total | Acciones | + | {{ sale.invoice_number || `#${String(sale.id).padStart(6, '0')}` }} | -+ | {{ new Date(sale.created_at).toLocaleTimeString('es-MX', { hour: '2-digit', @@ -347,7 +346,7 @@ onMounted(() => { }) }} | -+ |
|
- + | + + {{ sale.status === 'completed' ? 'Completada' : 'Cancelada' }} + + | +
{{ formatCurrency(sale.total) }}
diff --git a/src/pages/POS/CashRegister/History.vue b/src/pages/POS/CashRegister/History.vue
index 7900ec9..0540173 100644
--- a/src/pages/POS/CashRegister/History.vue
+++ b/src/pages/POS/CashRegister/History.vue
@@ -178,9 +178,6 @@ onMounted(() => {
${{ parseFloat(register.total_sales || 0).toLocaleString('es-MX', { minimumFractionDigits: 2 }) }} -- {{ register.transaction_count || 0 }} ventas - |
+import { ref } from 'vue';
+import { apiURL } from '@Services/Api';
+import GoogleIcon from '@Shared/GoogleIcon.vue';
+
+/** Props */
+const props = defineProps({
+ show: Boolean
+});
+
+/** Eventos */
+const emit = defineEmits(['close', 'imported']);
+
+/** Estado */
+const selectedFile = ref(null);
+const uploading = ref(false);
+const importResults = ref(null);
+const fileInput = ref(null);
+
+/** Métodos */
+const handleFileSelect = (event) => {
+ const file = event.target.files[0];
+
+ if (!file) {
+ selectedFile.value = null;
+ return;
+ }
+
+ // Validar extensión
+ const validExtensions = ['.xlsx', '.xls'];
+ const fileExtension = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
+
+ if (!validExtensions.includes(fileExtension)) {
+ window.Notify.error('Por favor selecciona un archivo Excel (.xlsx o .xls)');
+ selectedFile.value = null;
+ event.target.value = '';
+ return;
+ }
+
+ selectedFile.value = file;
+ importResults.value = null;
+};
+
+const downloadTemplate = async () => {
+ try {
+ window.Notify.info('Descargando plantilla...');
+
+ const response = await fetch(apiURL('inventario/template/download'), {
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${sessionStorage.token}`,
+ }
+ });
+
+ if (!response.ok) {
+ throw new Error('Error al descargar la plantilla');
+ }
+
+ const blob = await response.blob();
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = 'plantilla_productos.xlsx';
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(url);
+
+ window.Notify.success('Plantilla descargada exitosamente');
+ } catch (error) {
+ console.error('Error:', error);
+ window.Notify.error('Error al descargar la plantilla');
+ }
+};
+
+const importProducts = async () => {
+ if (!selectedFile.value) {
+ window.Notify.warning('Por favor selecciona un archivo');
+ return;
+ }
+
+ uploading.value = true;
+ importResults.value = null;
+
+ try {
+ const formData = new FormData();
+ formData.append('file', selectedFile.value);
+
+ const response = await fetch(apiURL('inventario/import'), {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${sessionStorage.token}`,
+ 'Accept': 'application/json',
+ },
+ body: formData
+ });
+
+ const data = await response.json();
+
+ if (response.ok && data.status === 'success') {
+ importResults.value = data.data;
+
+ const { imported, skipped, errors } = data.data;
+
+ if (imported > 0) {
+ window.Notify.success(`${imported} producto(s) importado(s) exitosamente`);
+ }
+
+ if (skipped > 0) {
+ window.Notify.warning(`${skipped} producto(s) omitido(s)`);
+ }
+
+ if (errors && errors.length > 0) {
+ console.error('Errores de importación:', errors);
+ }
+
+ // Resetear formulario
+ selectedFile.value = null;
+ if (fileInput.value) {
+ fileInput.value.value = '';
+ }
+
+ // Notificar al componente padre
+ emit('imported');
+ } else {
+ // Manejar errores de validación
+ if (data.data?.errors) {
+ const errorMessages = data.data.errors.map(err =>
+ `Fila ${err.row}: ${err.errors.join(', ')}`
+ ).join('\n');
+
+ console.error('Errores de validación:', errorMessages);
+ window.Notify.error('Error de validación en el archivo. Revisa la consola.');
+ } else {
+ window.Notify.error(data.data?.message || 'Error al importar productos');
+ }
+ }
+ } catch (error) {
+ console.error('Error:', error);
+ window.Notify.error('Error al importar productos');
+ } finally {
+ uploading.value = false;
+ }
+};
+
+const closeModal = () => {
+ selectedFile.value = null;
+ importResults.value = null;
+ if (fileInput.value) {
+ fileInput.value.value = '';
+ }
+ emit('close');
+};
+
+
+
+ Instrucciones: Errores: |
|---|