+
+ No se puede enviar por WhatsApp: el cliente no tiene teléfono registrado.
+
diff --git a/src/pages/POS/Factura/Index.vue b/src/pages/POS/Factura/Index.vue
index bc4a92c..c34b029 100644
--- a/src/pages/POS/Factura/Index.vue
+++ b/src/pages/POS/Factura/Index.vue
@@ -21,6 +21,9 @@ const saleData = ref(null);
const clientData = ref(null);
const existingInvoiceRequest = ref(null);
const formErrors = ref({});
+const searchingRfc = ref(false);
+const rfcSearch = ref('');
+const rfcSearchError = ref('');
const form = ref({
name: '',
@@ -35,18 +38,35 @@ const form = ref({
});
const paymentMethods = [
- {
- value: 'cash',
- label: 'Efectivo',
- },
- {
- value: 'credit_card',
- label: 'Tarjeta de Crédito',
- },
- {
- value: 'debit_card',
- label: 'Tarjeta de Débito',
- }
+ { value: 'cash', label: 'Efectivo' },
+ { value: 'credit_card', label: 'Tarjeta de Crédito' },
+ { value: 'debit_card', label: 'Tarjeta de Débito' }
+];
+
+const usoCfdiOptions = [
+ { value: 'G01', label: 'G01 - Adquisición de mercancías' },
+ { value: 'G02', label: 'G02 - Devoluciones, descuentos o bonificaciones' },
+ { value: 'G03', label: 'G03 - Gastos en general' },
+ { value: 'I01', label: 'I01 - Construcciones' },
+ { value: 'I02', label: 'I02 - Mobiliario y equipo de oficina por inversiones' },
+ { value: 'I03', label: 'I03 - Equipo de transporte' },
+ { value: 'I04', label: 'I04 - Equipo de computo y accesorios' },
+ { value: 'I05', label: 'I05 - Dados, troqueles, moldes, matrices y herramental' },
+ { value: 'I06', label: 'I06 - Comunicaciones telefónicas' },
+ { value: 'I07', label: 'I07 - Comunicaciones satelitales' },
+ { value: 'I08', label: 'I08 - Otra maquinaria y equipo' },
+ { value: 'S01', label: 'S01 - Sin efectos fiscales' }
+];
+
+const regimenFiscalOptions = [
+ { value: '601', label: '601 - General de Ley Personas Morales' },
+ { value: '603', label: '603 - Personas Morales con Fines no Lucrativos' },
+ { value: '610', label: '610 - Residentes en el Extranjero sin Establecimiento Permanente en México' },
+ { value: '620', label: '620 - Sociedades Cooperativas de Producción que optan por diferir sus ingresos' },
+ { value: '622', label: '622 - Actividades Agrícolas, Ganaderas, Silvícolas y Pesqueras' },
+ { value: '623', label: '623 - Opcional para Grupos de Sociedades' },
+ { value: '624', label: '624 - Coordinados' },
+ { value: '626', label: '626 - Régimen Simplificado de Confianza' }
];
const paymentMethodLabel = computed(() => {
@@ -70,101 +90,161 @@ const latestRequest = computed(() => {
const canRequestInvoice = computed(() => {
if (!latestRequest.value) return true;
- // Solo permitir nueva solicitud si la última fue rechazada
return latestRequest.value.status === 'rejected';
});
+/** Helpers para mostrar labels legibles */
+const getRegimenFiscalLabel = (value) => {
+ const option = regimenFiscalOptions.find(o => o.value === value);
+ return option ? option.label : value || 'No registrado';
+};
+
+const getUsoCfdiLabel = (value) => {
+ const option = usoCfdiOptions.find(o => o.value === value);
+ return option ? option.label : value || 'No registrado';
+};
+
/** Métodos */
-const fetchSaleData = async () => {
+const fetchSaleData = () => {
loading.value = true;
error.value = null;
- try {
- const response = await fetch(apiURL(`facturacion/${invoiceNumber.value}`), {
- method: 'GET',
- headers: {
- 'Accept': 'application/json'
+ window.axios.get(apiURL(`facturacion/${invoiceNumber.value}`))
+ .then(({ data }) => {
+ if (data.status === 'success') {
+ saleData.value = data.data.sale;
+
+ // Si la venta ya tiene un cliente asociado, cargar sus datos
+ if (data.data.client) {
+ clientData.value = data.data.client;
+ fillFormWithClient(data.data.client);
+ }
}
- });
-
- const result = await response.json();
-
- if (!response.ok) {
- if (response.status === 404) {
+ })
+ .catch(({ response }) => {
+ if (response?.status === 404) {
error.value = 'No se encontró la venta con el folio proporcionado.';
- } else if (response.status === 400) {
- // Venta ya tiene solicitud de facturación pendiente/procesada
- error.value = result.message || 'Esta venta ya tiene una solicitud de facturación.';
- existingInvoiceRequest.value = result.data?.invoice_request || null;
+ } else if (response?.status === 400 || response?.status === 422) {
+ error.value = response.data?.data?.message || response.data?.message || 'Esta venta ya tiene una solicitud de facturación.';
+ existingInvoiceRequest.value = response.data?.data?.invoice_request || null;
} else {
- error.value = result.message || 'Error al obtener los datos de la venta.';
+ error.value = response?.data?.message || 'Error al obtener los datos de la venta.';
}
- return;
- }
+ })
+ .finally(() => {
+ loading.value = false;
+ });
+};
- saleData.value = result.data.sale;
- clientData.value = result.data.client || null;
+/**
+ * Llenar el formulario con los datos del cliente
+ */
+const fillFormWithClient = (client) => {
+ form.value = {
+ name: client.name || '',
+ email: client.email || '',
+ phone: client.phone || '',
+ address: client.address || '',
+ rfc: client.rfc || '',
+ razon_social: client.razon_social || '',
+ regimen_fiscal: client.regimen_fiscal || '',
+ cp_fiscal: client.cp_fiscal || '',
+ uso_cfdi: client.uso_cfdi || ''
+ };
+};
- } catch (err) {
- console.error('Error:', err);
- error.value = 'Error de conexión. Por favor intente más tarde.';
- } finally {
- loading.value = false;
+/**
+ * Buscar cliente por RFC
+ */
+const searchClientByRfc = () => {
+ const rfc = rfcSearch.value?.trim().toUpperCase();
+
+ if (!rfc) {
+ rfcSearchError.value = 'Por favor ingrese un RFC';
+ return;
+ }
+
+ if (rfc.length < 12 || rfc.length > 13) {
+ rfcSearchError.value = 'El RFC debe tener entre 12 y 13 caracteres';
+ return;
+ }
+
+ searchingRfc.value = true;
+ rfcSearchError.value = '';
+
+ window.axios.get(apiURL(`facturacion/check-rfc?rfc=${rfc}`))
+ .then(({ data }) => {
+ // La respuesta viene: { status: 'success', data: { exists: true, client: {...} } }
+ if (data.status === 'success' && data.data?.exists && data.data?.client) {
+ clientData.value = data.data.client;
+ fillFormWithClient(data.data.client);
+ rfcSearch.value = '';
+ window.Notify.success('Cliente encontrado. Verifica los datos antes de continuar.');
+ } else if (data.status === 'success' && !data.data?.exists) {
+ rfcSearchError.value = 'No se encontró ningún cliente con este RFC';
+ window.Notify.warning('RFC no encontrado. Complete el formulario manualmente.');
+ } else {
+ rfcSearchError.value = 'No se encontró ningún cliente con este RFC';
+ }
+ })
+ .catch(() => {
+ rfcSearchError.value = 'Error al buscar el RFC. Intente nuevamente.';
+ })
+ .finally(() => {
+ searchingRfc.value = false;
+ });
+};
+
+/**
+ * Manejar Enter en el input de búsqueda
+ */
+const handleSearchKeypress = (event) => {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ searchClientByRfc();
}
};
-const submitForm = async () => {
+/**
+ * Limpiar datos del cliente y volver al formulario limpio
+ */
+const clearFoundClient = () => {
+ clientData.value = null;
+ rfcSearchError.value = '';
+ form.value = {
+ name: '',
+ email: '',
+ phone: '',
+ address: '',
+ rfc: '',
+ razon_social: '',
+ regimen_fiscal: '',
+ cp_fiscal: '',
+ uso_cfdi: ''
+ };
+ window.Notify.info('Formulario limpio. Puede ingresar nuevos datos.');
+};
+
+const submitForm = () => {
submitting.value = true;
formErrors.value = {};
- const payload = hasClient.value
- ? {
- name: clientData.value.name,
- email: clientData.value.email,
- phone: clientData.value.phone,
- address: clientData.value.address,
- rfc: clientData.value.rfc,
- razon_social: clientData.value.razon_social,
- regimen_fiscal: clientData.value.regimen_fiscal,
- cp_fiscal: clientData.value.cp_fiscal,
- uso_cfdi: clientData.value.uso_cfdi
- }
- : form.value;
-
- try {
- const response = await fetch(apiURL(`facturacion/${invoiceNumber.value}`), {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Accept': 'application/json'
- },
- body: JSON.stringify(payload)
- });
-
- if (response.status === 204 || response.headers.get('content-length') === '0') {
+ window.axios.post(apiURL(`facturacion/${invoiceNumber.value}`), form.value)
+ .then(({ data }) => {
submitted.value = true;
- return;
- }
-
- const data = await response.json();
-
- if (!response.ok) {
- if (data.errors) {
- formErrors.value = data.errors;
+ })
+ .catch(({ response }) => {
+ if (response?.status === 422 && response.data?.errors) {
+ formErrors.value = response.data.errors;
+ } else if (response?.data?.data?.errors) {
+ formErrors.value = response.data.data.errors;
} else {
- error.value = data.message || 'Error al enviar los datos.';
+ error.value = response?.data?.message || response?.data?.data?.message || 'Error al enviar los datos.';
}
- return;
- }
-
- submitted.value = true;
-
- } catch (err) {
- console.error('Error:', err);
- error.value = 'Error de conexión. Por favor intente más tarde.';
- } finally {
- submitting.value = false;
- }
+ })
+ .finally(() => {
+ submitting.value = false;
+ });
};
/** Ciclos */
@@ -185,7 +265,7 @@ onMounted(() => {
Solicitud de Factura
- {{ hasClient ? 'Confirme los datos para generar su factura' : 'Complete sus datos fiscales para generar su factura' }}
+ Complete sus datos fiscales para generar su factura
@@ -206,7 +286,6 @@ onMounted(() => {
{{ error }}
-
Detalles de la solicitud:
@@ -233,7 +312,7 @@ onMounted(() => {
-
+
@@ -325,13 +404,12 @@ onMounted(() => {
-
+
Historial de Solicitudes
-
@@ -361,7 +439,6 @@ onMounted(() => {
-
@@ -376,7 +453,7 @@ onMounted(() => {
-
+
@@ -388,65 +465,130 @@ onMounted(() => {
-
-
-
-
- Datos del Cliente
-
+
+
+
+
+
+ ¿Ya eres cliente?
+
+
+
+ Busca tu RFC para autocompletar tus datos fiscales
+
+
+
+
+
+
+
+
+
+ {{ rfcSearchError }}
+
+
+
+
+ Si no encuentras tu RFC, no te preocupes. Podrás llenar el formulario manualmente más abajo.
+
+
+
-
-
- Nombre:
- {{ clientData.name }}
+
+
+
+
+
+ Cliente Identificado
+
+
+
+
+
+
+
+ Datos fiscales encontrados. Verifica que sean correctos antes de continuar.
+
- La factura se generará con los datos mostrados y se enviará al correo del cliente.
+ La factura se generará con los datos mostrados y se enviará a tu correo electrónico.
-
-
+
+
+
+
+
+
+ Esta unidad permite cantidades decimales (ej: 25.750)
+ Esta unidad solo permite cantidades enteras
+
+
+
+
+
+
+
+ No se pueden usar números de serie con esta unidad de medida.
+
+
+
+
+
+
+
+
+
+
+ Esta unidad permite cantidades decimales (ej: 25.750)
+ Esta unidad solo permite cantidades enteras
+
+
+
+
+
+
+
+ No se pueden usar números de serie con esta unidad de medida.
+