From 8210d7dd2fb0d76d72fbd6cc38723e5012617821 Mon Sep 17 00:00:00 2001 From: Juan Felipe Zapata Moreno Date: Fri, 30 Jan 2026 14:17:55 -0600 Subject: [PATCH] feat: agregar campos fiscales y funcionalidad de descarga de reportes en Excel --- src/components/POS/CheckoutModal.vue | 96 +++++++++-- src/components/POS/ExcelSale.vue | 229 +++++++++++++++++++++++++++ src/pages/POS/Clients/Create.vue | 60 +++++++ src/pages/POS/Clients/Edit.vue | 64 ++++++++ src/pages/POS/Clients/Index.vue | 18 ++- src/pages/POS/Factura/Index.vue | 133 +++++++++++++--- src/pages/POS/Sales/Index.vue | 42 +++-- src/services/ticketService.js | 8 +- 8 files changed, 600 insertions(+), 50 deletions(-) create mode 100644 src/components/POS/ExcelSale.vue diff --git a/src/components/POS/CheckoutModal.vue b/src/components/POS/CheckoutModal.vue index 7e6755e..7921bc5 100644 --- a/src/components/POS/CheckoutModal.vue +++ b/src/components/POS/CheckoutModal.vue @@ -25,12 +25,15 @@ const props = defineProps({ const emit = defineEmits(['close', 'confirm']); /** Estado */ +let debounceTimer = null; const selectedMethod = ref('cash'); const cashReceived = ref(0); const clientNumber = ref(''); const selectedClient = ref(null); const searchingClient = ref(false); const clientNotFound = ref(false); +const clientSuggestions = ref([]); +const showClientSuggestions = ref(false); /** Computados */ const formattedSubtotal = computed(() => { @@ -116,10 +119,25 @@ const paymentMethods = [ ]; /** Métodos de búsqueda de cliente */ +const onClientInput = () =>{ + clientNotFound.value = false; + + if(!clientNumber.value || clientNumber.value.trim().length < 2) { + clientSuggestions.value = []; + showClientSuggestions.value = false; + return; + } + + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + showClientSuggestions.value = true; + searchClient(); + }, 300); +} const searchClient = () => { if (!clientNumber.value || clientNumber.value.trim() === '') { - selectedClient.value = null; - clientNotFound.value = false; + clientSuggestions.value = []; + showClientSuggestions.value = false; return; } @@ -131,22 +149,23 @@ const searchClient = () => { api.get(apiURL(`clients?${urlParams}`), { onSuccess: (data) => { if (data.clients && data.clients.data.length > 0) { - const client = data.clients.data[0]; - selectedClient.value = client; - clientNotFound.value = false; - window.Notify.success(`Cliente ${client.name} encontrado`); + clientSuggestions.value = data.clients.data; + showClientSuggestions.value = true; } else { - selectedClient.value = null; + clientSuggestions.value = []; + showClientSuggestions.value = false; clientNotFound.value = true; } }, onFail: (data) => { - selectedClient.value = null; + clientSuggestions.value = []; + showClientSuggestions.value = false; clientNotFound.value = true; window.Notify.error(data.message || 'Error al buscar cliente'); }, onError: () => { - selectedClient.value = null; + clientSuggestions.value = []; + showClientSuggestions.value = false; clientNotFound.value = true; }, onFinish: () => { @@ -155,10 +174,22 @@ const searchClient = () => { }); }; +const selectClient = (client) => { + selectedClient.value = client; + clientNumber.value = ''; + clientSuggestions.value = []; + showClientSuggestions.value = false; + clientNotFound.value = false; + window.Notify.success(`Cliente ${client.name} seleccionado`); +}; + + const clearClient = () => { clientNumber.value = ''; selectedClient.value = null; clientNotFound.value = false; + clientSuggestions.value = []; + showClientSuggestions.value = false; }; @@ -178,6 +209,8 @@ watch(() => props.show, (isShown) => { clientNumber.value = ''; selectedClient.value = null; clientNotFound.value = false; + clientSuggestions.value = []; + showClientSuggestions.value = false; } }); @@ -358,22 +391,51 @@ const formattedEstimatedTotal = computed(() => {
-
+
+ +
+ +
- {{ searchingClient ? 'Buscando...' : 'Buscar' }} - + +
diff --git a/src/components/POS/ExcelSale.vue b/src/components/POS/ExcelSale.vue new file mode 100644 index 0000000..021af3d --- /dev/null +++ b/src/components/POS/ExcelSale.vue @@ -0,0 +1,229 @@ + + + diff --git a/src/pages/POS/Clients/Create.vue b/src/pages/POS/Clients/Create.vue index aa91512..4fd1578 100644 --- a/src/pages/POS/Clients/Create.vue +++ b/src/pages/POS/Clients/Create.vue @@ -20,6 +20,10 @@ const form = useForm({ phone: '', address: '', rfc: '', + razon_social: '', + regimen_fiscal: '', + cp_fiscal: '', + uso_cfdi: '' }); /** Métodos */ @@ -132,6 +136,62 @@ const closeModal = () => { /> + + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
diff --git a/src/pages/POS/Clients/Edit.vue b/src/pages/POS/Clients/Edit.vue index 6b7f38e..b7e1846 100644 --- a/src/pages/POS/Clients/Edit.vue +++ b/src/pages/POS/Clients/Edit.vue @@ -22,6 +22,10 @@ const form = useForm({ phone: '', address: '', rfc: '', + razon_social: '', + regimen_fiscal: '', + cp_fiscal: '', + uso_cfdi: '' }); /** Métodos */ @@ -51,6 +55,10 @@ watch(() => props.client, (newClient) => { form.phone = newClient.phone || ''; form.address = newClient.address || ''; form.rfc = newClient.rfc || ''; + form.razon_social = newClient.razon_social || ''; + form.regimen_fiscal = newClient.regimen_fiscal || ''; + form.cp_fiscal = newClient.cp_fiscal || ''; + form.uso_cfdi = newClient.uso_cfdi || ''; } }, { immediate: true }); @@ -146,6 +154,62 @@ watch(() => props.client, (newClient) => { /> + + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
diff --git a/src/pages/POS/Clients/Index.vue b/src/pages/POS/Clients/Index.vue index 6ab5cbd..d55b016 100644 --- a/src/pages/POS/Clients/Index.vue +++ b/src/pages/POS/Clients/Index.vue @@ -164,6 +164,10 @@ onMounted(() => { TELEFONO DIRECCIÓN RFC + RAZÓN SOCIAL + REGIMEN FISCAL + CP FISCAL + USO CFDI ACCIONES + diff --git a/src/pages/POS/Sales/Index.vue b/src/pages/POS/Sales/Index.vue index be012fd..63e1af9 100644 --- a/src/pages/POS/Sales/Index.vue +++ b/src/pages/POS/Sales/Index.vue @@ -8,11 +8,13 @@ import SearcherHead from '@Holos/Searcher.vue'; import Table from '@Holos/Table.vue'; import GoogleIcon from '@Shared/GoogleIcon.vue'; import SaleDetailModal from './DetailModal.vue'; +import ExcelModal from '@Components/POS/ExcelSale.vue'; /** Estado */ const models = ref([]); const showDetailModal = ref(false); const selectedSale = ref(null); +const showExcelModal = ref(false); /** Buscador de ventas */ const searcher = useSearcher({ @@ -38,6 +40,14 @@ const closeDetailModal = () => { selectedSale.value = null; }; +const openExcelModal = () => { + showExcelModal.value = true; +}; + +const closeExcelModal = () => { + showExcelModal.value = false; +}; + const handleCancelSale = async (saleId) => { if (!confirm('¿Estás seguro de cancelar esta venta? Se restaurará el stock.')) { return; @@ -108,17 +118,14 @@ onMounted(() => { placeholder="Buscar por folio o cajero..." @search="(x) => searcher.search(x)" > - + - - - -
{
+ + + + + + + diff --git a/src/services/ticketService.js b/src/services/ticketService.js index 048f203..910512c 100644 --- a/src/services/ticketService.js +++ b/src/services/ticketService.js @@ -12,9 +12,9 @@ const ticketService = { */ async getUserLocation() { return { - city: import.meta.env.VITE_BUSINESS_CITY || 'Villahermosa', - state: import.meta.env.VITE_BUSINESS_STATE || 'Tabasco', - country: import.meta.env.VITE_BUSINESS_COUNTRY || 'México' + city: import.meta.env.VITE_BUSINESS_CITY, + state: import.meta.env.VITE_BUSINESS_STATE, + country: import.meta.env.VITE_BUSINESS_COUNTRY }; }, @@ -31,7 +31,7 @@ const ticketService = { // Detectar ubicación del usuario const location = await this.getUserLocation(); - const businessAddress = `${location.city}, ${location.state}`; + const businessAddress = `${location.city}, ${location.state}, ${location.country}`; const businessPhone = 'Tel: (52) 0000-0000'; // Crear documento PDF - Ticket térmico 80mm de ancho