diff --git a/src/components/POS/CartItem.vue b/src/components/POS/CartItem.vue
index 89b19fb..07723aa 100644
--- a/src/components/POS/CartItem.vue
+++ b/src/components/POS/CartItem.vue
@@ -129,8 +129,18 @@ const remove = () => {
+
+
+
+
+
+
+ {{ item.unit_name }}
+
+
+
-
+
{{ item.unit_of_measure.name }} ({{ item.unit_of_measure.abbreviation }}) - Permite decimales
diff --git a/src/components/POS/CfdiSelector.vue b/src/components/POS/CfdiSelector.vue
new file mode 100644
index 0000000..5a4e2c7
--- /dev/null
+++ b/src/components/POS/CfdiSelector.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+ USO DE CFDI *
+
+
+ Seleccionar uso de CFDI
+
+ {{ option.label }}
+
+
+
{{ error }}
+
+
\ No newline at end of file
diff --git a/src/components/POS/RegimenSelecto.vue b/src/components/POS/RegimenSelecto.vue
new file mode 100644
index 0000000..350e852
--- /dev/null
+++ b/src/components/POS/RegimenSelecto.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+ RÉGIMEN FISCAL *
+
+
+ Seleccionar régimen fiscal
+
+ {{ option.label }}
+
+
+
{{ error }}
+
+
\ No newline at end of file
diff --git a/src/components/POS/UnitEquivalenceSelector.vue b/src/components/POS/UnitEquivalenceSelector.vue
new file mode 100644
index 0000000..c8b9970
--- /dev/null
+++ b/src/components/POS/UnitEquivalenceSelector.vue
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+ Seleccionar unidad
+
+
+ {{ product?.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ option.label }}
+
+
+ 1 {{ option.unit_name }} = {{ option.conversion_factor }} {{ baseUnit?.abbreviation || '' }}
+
+
+
+
+
+
+ {{ option.priceLabel }}
+
+
+
+
+
+
+
+ Cancelar
+
+
+ Agregar al carrito
+
+
+
+
+
diff --git a/src/lang/es.js b/src/lang/es.js
index d9d60a7..c15bdb8 100644
--- a/src/lang/es.js
+++ b/src/lang/es.js
@@ -462,6 +462,7 @@ export default {
returns: 'Devoluciones',
clients: 'Clientes',
suppliers: 'Proveedores',
+ unitMeasure: 'Unidades de medida',
clientTiers: 'Niveles de Clientes',
billingRequests: 'Solicitudes de Facturación',
warehouses: 'Almacenes',
diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue
index 871f77f..5db9b45 100644
--- a/src/layouts/AppLayout.vue
+++ b/src/layouts/AppLayout.vue
@@ -75,6 +75,11 @@ onMounted(() => {
name="pos.suppliers"
to="pos.suppliers.index"
/>
+
{
});
/** Métodos */
-const calculateTax = () => {
- if (form.retail_price && !form.tax) {
- form.tax = (parseFloat(form.retail_price) * 0.16).toFixed(2);
- }
-};
-
const handleProductSelect = (product) => {
if (selectedProducts.value.find(item => item.product.id === product.id)) {
Notify.warning('Este producto ya está agregado');
@@ -213,7 +207,6 @@ watch(() => props.show, (val) => {
{
});
/** Métodos */
-const calculateTax = () => {
- if (form.retail_price && !form.tax) {
- form.tax = (parseFloat(form.retail_price) * 0.16).toFixed(2);
- }
-};
-
const handleProductSelect = (product) => {
if (selectedProducts.value.find(item => item.product.id === product.id)) {
Notify.warning('Este producto ya está agregado');
@@ -72,7 +66,6 @@ const updateQuantity = (index, quantity) => {
const useSuggestedPrice = () => {
form.retail_price = suggestedPrice.value.toFixed(2);
- calculateTax();
};
const updateBundle = () => {
@@ -229,7 +222,6 @@ watch(() => props.bundle, (bundle) => {
{
-
-
- REGIMEN FISCAL
-
-
-
-
+
@@ -180,18 +174,10 @@ const closeModal = () => {
-
-
- USO DE CFDI
-
-
-
-
+
diff --git a/src/pages/POS/Clients/Edit.vue b/src/pages/POS/Clients/Edit.vue
index b7e1846..5d9769e 100644
--- a/src/pages/POS/Clients/Edit.vue
+++ b/src/pages/POS/Clients/Edit.vue
@@ -5,6 +5,8 @@ import { useForm, apiURL } from '@Services/Api';
import Modal from '@Holos/Modal.vue';
import FormInput from '@Holos/Form/Input.vue';
import FormError from '@Holos/Form/Elements/Error.vue';
+import SelectUsoCfdi from '@Components/POS/CfdiSelector.vue';
+import SelectRegimenFiscal from '@Components/POS/RegimenSelecto.vue';
/** Eventos */
const emit = defineEmits(['close', 'updated']);
@@ -168,21 +170,7 @@ watch(() => props.client, (newClient) => {
/>
-
-
-
-
- REGIMEN FISCAL
-
-
-
-
-
+
@@ -197,19 +185,18 @@ watch(() => props.client, (newClient) => {
+
+
+
+
-
-
- USO CFDI
-
-
-
-
+
diff --git a/src/pages/POS/Clients/Index.vue b/src/pages/POS/Clients/Index.vue
index d55b016..8954331 100644
--- a/src/pages/POS/Clients/Index.vue
+++ b/src/pages/POS/Clients/Index.vue
@@ -2,6 +2,10 @@
import { onMounted, ref } from 'vue';
import { useSearcher, apiURL } from '@Services/Api';
import { can } from './Module.js';
+import { regimenFiscalOptions, usoCfdiOptions } from '@/utils/fiscalData';
+
+const regimenFiscalLabel = (value) => regimenFiscalOptions.find(o => o.value === value)?.label ?? value;
+const usoCfdiLabel = (value) => usoCfdiOptions.find(o => o.value === value)?.label ?? value;
import SearcherHead from '@Holos/Searcher.vue';
import ExcelModal from '@Components/POS/ExcelClient.vue';
@@ -198,13 +202,13 @@ onMounted(() => {
{{ client.razon_social }}
- {{ client.regimen_fiscal }}
+ {{ regimenFiscalLabel(client.regimen_fiscal) }}
{{ client.cp_fiscal }}
- {{ client.uso_cfdi }}
+ {{ usoCfdiLabel(client.uso_cfdi) }}
diff --git a/src/pages/POS/Factura/Index.vue b/src/pages/POS/Factura/Index.vue
index c34b029..523c164 100644
--- a/src/pages/POS/Factura/Index.vue
+++ b/src/pages/POS/Factura/Index.vue
@@ -8,6 +8,8 @@ import GoogleIcon from '@Shared/GoogleIcon.vue';
import Loader from '@Shared/Loader.vue';
import Input from '@Holos/Form/Input.vue';
import PrimaryButton from '@Holos/Button/Primary.vue';
+import SelectUsoCfdi from '@Components/POS/CfdiSelector.vue';
+import SelectRegimenFiscal from '@Components/POS/RegimenSelector.vue';
/** Definidores */
const route = useRoute();
@@ -43,32 +45,6 @@ const paymentMethods = [
{ 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(() => {
const method = paymentMethods.find(m => saleData.value?.payment_method?.includes(m.value));
return method?.label || saleData.value?.payment_method || 'N/A';
@@ -93,16 +69,9 @@ const canRequestInvoice = computed(() => {
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';
-};
+/** Helpers */
+const getRegimenFiscalLabel = (value) => value || 'No registrado';
+const getUsoCfdiLabel = (value) => value || 'No registrado';
/** Métodos */
const fetchSaleData = () => {
@@ -113,8 +82,6 @@ const fetchSaleData = () => {
.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);
@@ -136,9 +103,6 @@ const fetchSaleData = () => {
});
};
-/**
- * Llenar el formulario con los datos del cliente
- */
const fillFormWithClient = (client) => {
form.value = {
name: client.name || '',
@@ -153,9 +117,6 @@ const fillFormWithClient = (client) => {
};
};
-/**
- * Buscar cliente por RFC
- */
const searchClientByRfc = () => {
const rfc = rfcSearch.value?.trim().toUpperCase();
@@ -174,7 +135,6 @@ const searchClientByRfc = () => {
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);
@@ -195,9 +155,6 @@ const searchClientByRfc = () => {
});
};
-/**
- * Manejar Enter en el input de búsqueda
- */
const handleSearchKeypress = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
@@ -205,9 +162,6 @@ const handleSearchKeypress = (event) => {
}
};
-/**
- * Limpiar datos del cliente y volver al formulario limpio
- */
const clearFoundClient = () => {
clientData.value = null;
rfcSearchError.value = '';
@@ -230,7 +184,7 @@ const submitForm = () => {
formErrors.value = {};
window.axios.post(apiURL(`facturacion/${invoiceNumber.value}`), form.value)
- .then(({ data }) => {
+ .then(() => {
submitted.value = true;
})
.catch(({ response }) => {
@@ -247,7 +201,6 @@ const submitForm = () => {
});
};
-/** Ciclos */
onMounted(() => {
fetchSaleData();
});
@@ -649,30 +602,11 @@ onMounted(() => {
:onError="formErrors.razon_social"
/>
-
-
-
- Régimen Fiscal *
-
-
- Seleccionar régimen fiscal
-
- {{ option.label }}
-
-
-
- {{ formErrors.regimen_fiscal[0] }}
-
-
+
{
:onError="formErrors.cp_fiscal"
/>
-
-
-
- Uso de CFDI *
-
-
- Seleccionar uso de CFDI
-
- {{ option.label }}
-
-
-
- {{ formErrors.uso_cfdi[0] }}
-
-
+
{
};
const createProduct = () => {
- form.post(apiURL('inventario'), {
+ form.transform((data) => ({
+ ...data,
+ track_serials: selectedUnit.value ? !selectedUnit.value.allows_decimals && !!data.track_serials : false
+ })).post(apiURL('inventario'), {
onSuccess: () => {
Notify.success('Producto creado exitosamente');
emit('created');
@@ -167,10 +170,9 @@ watch(() => form.track_serials, () => {
@@ -251,25 +253,6 @@ watch(() => form.track_serials, () => {
-
-
-
-
-
- Rastrear números de serie
-
-
-
- No se pueden usar números de serie con esta unidad de medida.
-
-
-
-
diff --git a/src/pages/POS/Inventory/EditModal.vue b/src/pages/POS/Inventory/EditModal.vue
index bd59722..2c0bd93 100644
--- a/src/pages/POS/Inventory/EditModal.vue
+++ b/src/pages/POS/Inventory/EditModal.vue
@@ -1,7 +1,7 @@
@@ -148,7 +297,7 @@ watch(() => form.track_serials, () => {
-
+
Editar Producto
@@ -162,200 +311,420 @@ watch(() => form.track_serials, () => {
-
-
diff --git a/src/pages/POS/Movements/Edit.vue b/src/pages/POS/Movements/Edit.vue
index 6faffe4..7e627f9 100644
--- a/src/pages/POS/Movements/Edit.vue
+++ b/src/pages/POS/Movements/Edit.vue
@@ -218,9 +218,9 @@ watch(() => props.show, (isShown) => {
// Cargar datos del movimiento
form.quantity = props.movement.quantity || 0;
form.unit_cost = props.movement.unit_cost || 0;
- form.supplier_id = props.movement.supplier_id || null;
form.invoice_reference = props.movement.invoice_reference || '';
form.notes = props.movement.notes || '';
+ form.supplier_id = props.movement.supplier_id || null;
// Cargar números de serie si existen
if (props.movement.serials && props.movement.serials.length > 0) {
diff --git a/src/pages/POS/Movements/EntryModal.vue b/src/pages/POS/Movements/EntryModal.vue
index 19d636c..0a39af4 100644
--- a/src/pages/POS/Movements/EntryModal.vue
+++ b/src/pages/POS/Movements/EntryModal.vue
@@ -33,6 +33,9 @@ const productSuggestions = ref([]);
const showProductSuggestions = ref(false);
const currentSearchIndex = ref(null); // Índice del producto que se está buscando
+// Cache de equivalencias por producto
+const equivalencesCache = ref({});
+
const api = useApi();
/** Formulario */
@@ -92,6 +95,34 @@ const loadData = () => {
});
};
+const loadEquivalencesForProduct = async (productId) => {
+ if (equivalencesCache.value[productId]) {
+ return equivalencesCache.value[productId];
+ }
+ try {
+ const response = await fetch(apiURL(`inventario/${productId}/equivalencias`), {
+ headers: {
+ 'Authorization': `Bearer ${sessionStorage.token}`,
+ 'Accept': 'application/json'
+ }
+ });
+ const json = await response.json();
+ const result = {
+ equivalences: (json.data?.equivalences || []).filter(e => e.is_active),
+ base_unit: json.data?.base_unit || null
+ };
+ equivalencesCache.value[productId] = result;
+ return result;
+ } catch {
+ return { equivalences: [], base_unit: null };
+ }
+};
+
+const getSelectedEquivalence = (item) => {
+ if (!item.selected_unit_id || !item.equivalences?.length) return null;
+ return item.equivalences.find(e => e.unit_of_measure_id === item.selected_unit_id) || null;
+};
+
const addProduct = () => {
selectedProducts.value.push({
inventory_id: '',
@@ -102,8 +133,12 @@ const addProduct = () => {
track_serials: false,
unit_of_measure: null,
allows_decimals: false,
- serial_numbers_list: [{ serial_number: '', locked: false }], // Inputs individuales de seriales
- serial_validation_error: ''
+ serial_numbers_list: [{ serial_number: '', locked: false }],
+ serial_validation_error: '',
+ // Equivalencias
+ equivalences: [],
+ base_unit: null,
+ selected_unit_id: null,
});
};
@@ -170,18 +205,27 @@ const searchProduct = () => {
});
};
-const selectProduct = (product) => {
+const selectProduct = async (product) => {
if (currentSearchIndex.value !== null) {
- selectedProducts.value[currentSearchIndex.value].inventory_id = product.id;
- selectedProducts.value[currentSearchIndex.value].product_name = product.name;
- selectedProducts.value[currentSearchIndex.value].product_sku = product.sku;
- selectedProducts.value[currentSearchIndex.value].track_serials = product.track_serials || false;
- selectedProducts.value[currentSearchIndex.value].unit_of_measure = product.unit_of_measure || null;
- selectedProducts.value[currentSearchIndex.value].allows_decimals = product.unit_of_measure?.allows_decimals || false;
+ const item = selectedProducts.value[currentSearchIndex.value];
+ item.inventory_id = product.id;
+ item.product_name = product.name;
+ item.product_sku = product.sku;
+ item.track_serials = product.track_serials || false;
+ item.unit_of_measure = product.unit_of_measure || null;
+ item.allows_decimals = product.unit_of_measure?.allows_decimals || false;
+ item.selected_unit_id = null;
// Limpiar seriales si la unidad permite decimales
if (product.unit_of_measure?.allows_decimals) {
- selectedProducts.value[currentSearchIndex.value].serial_numbers_list = [{ serial_number: '', locked: false }];
+ item.serial_numbers_list = [{ serial_number: '', locked: false }];
+ }
+
+ // Cargar equivalencias (solo si no tiene seriales)
+ if (!product.track_serials) {
+ const { equivalences, base_unit } = await loadEquivalencesForProduct(product.id);
+ item.equivalences = equivalences;
+ item.base_unit = base_unit;
}
}
@@ -223,9 +267,14 @@ const createEntry = () => {
inventory_id: item.inventory_id,
quantity: Number(item.quantity),
unit_cost: Number(item.unit_cost),
- serial_numbers: [] // Inicializar siempre como array vacío
+ serial_numbers: []
};
+ // Incluir unidad de equivalencia si se seleccionó una distinta a la base
+ if (item.selected_unit_id) {
+ productData.unit_of_measure_id = item.selected_unit_id;
+ }
+
// Agregar seriales solo si la unidad lo permite y hay seriales ingresados
if (canUseSerials(item) && item.serial_numbers_list) {
const serials = item.serial_numbers_list
@@ -367,6 +416,24 @@ watch(() => form.warehouse_id, (newWarehouseId, oldWarehouseId) => {
:key="index"
class="p-3 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-800/50"
>
+
+
+ Unidad:
+
+ {{ item.base_unit?.name }} ({{ item.base_unit?.abbreviation }}) — unidad base
+
+ {{ eq.unit_name }} — 1 {{ eq.unit_abbreviation }} = {{ parseFloat(eq.conversion_factor) }} {{ item.base_unit?.abbreviation }}
+
+
+
+
@@ -447,17 +514,24 @@ watch(() => form.warehouse_id, (newWarehouseId, oldWarehouseId) => {
Cantidad
+
+ ({{ getSelectedEquivalence(item).unit_abbreviation }})
+
-
+
+
+ = {{ (item.quantity * parseFloat(getSelectedEquivalence(item).conversion_factor)).toLocaleString('es-MX') }} {{ item.base_unit?.abbreviation }}
+
+
Controlado por seriales
@@ -465,7 +539,9 @@ watch(() => form.warehouse_id, (newWarehouseId, oldWarehouseId) => {
- Costo unit.
+ Costo
+ / {{ getSelectedEquivalence(item).unit_abbreviation }}
+ unit.
{
}
};
+const loadEquivalencesForProduct = async (productId) => {
+ if (equivalencesCache.value[productId]) {
+ return equivalencesCache.value[productId];
+ }
+ try {
+ const response = await fetch(apiURL(`inventario/${productId}/equivalencias`), {
+ headers: {
+ 'Authorization': `Bearer ${sessionStorage.token}`,
+ 'Accept': 'application/json'
+ }
+ });
+ const result = await response.json();
+ const data = {
+ equivalences: (result.data?.equivalences || []).filter(e => e.is_active),
+ baseUnit: result.data?.base_unit || null
+ };
+ equivalencesCache.value[productId] = data;
+ return data;
+ } catch {
+ return { equivalences: [], baseUnit: null };
+ }
+};
+
const addToCart = async (product) => {
try {
const response = await serialService.getAvailableSerials(product.id);
@@ -176,6 +206,27 @@ const addToCart = async (product) => {
return;
}
+ // Si el producto ya está en el carrito (sin seriales), solo incrementar sin abrir selectores
+ const existingItem = cart.items.find(i => i.item_key === 'p:' + product.id);
+ if (existingItem && !existingItem.track_serials) {
+ if (existingItem.quantity < product.stock) {
+ existingItem.quantity++;
+ window.Notify.success(`${product.name} agregado al carrito`);
+ } else {
+ window.Notify.warning('No hay suficiente stock disponible');
+ }
+ return;
+ }
+
+ // Producto nuevo: verificar si tiene equivalencias activas
+ const eqData = await loadEquivalencesForProduct(product.id);
+ if (eqData.equivalences.length > 0) {
+ unitEquivalenceSelectorProduct.value = product;
+ unitEquivalenceSelectorData.value = eqData;
+ showUnitEquivalenceSelector.value = true;
+ return;
+ }
+
cart.addProduct(product);
window.Notify.success(`${product.name} agregado al carrito`);
} catch (error) {
@@ -185,6 +236,18 @@ const addToCart = async (product) => {
}
};
+const closeUnitEquivalenceSelector = () => {
+ showUnitEquivalenceSelector.value = false;
+ unitEquivalenceSelectorProduct.value = null;
+};
+
+const handleUnitEquivalenceConfirm = (unitConfig) => {
+ if (!unitEquivalenceSelectorProduct.value) return;
+ cart.addProduct(unitEquivalenceSelectorProduct.value, unitConfig);
+ window.Notify.success(`${unitEquivalenceSelectorProduct.value.name} agregado al carrito`);
+ closeUnitEquivalenceSelector();
+};
+
const closeSerialSelector = () => {
showSerialSelector.value = false;
serialSelectorProduct.value = null;
@@ -394,7 +457,7 @@ const handleConfirmSale = async (paymentData) => {
}
return bundleItem;
}
- return {
+ const productItem = {
type: 'product',
inventory_id: item.inventory_id,
product_name: item.product_name,
@@ -403,6 +466,10 @@ const handleConfirmSale = async (paymentData) => {
subtotal: parseFloat((item.quantity * item.unit_price).toFixed(2)),
serial_numbers: item.track_serials ? (item.serial_numbers || []) : undefined
};
+ if (item.unit_of_measure_id) {
+ productItem.unit_of_measure_id = item.unit_of_measure_id;
+ }
+ return productItem;
})
};
@@ -871,5 +938,16 @@ watch(activeTab, (newTab) => {
@close="closeBundleSerialSelector"
@confirm="handleBundleSerialConfirm"
/>
+
+
+
diff --git a/src/pages/POS/Suppliers/Create.vue b/src/pages/POS/Suppliers/Create.vue
index acc77af..ba7f9d3 100644
--- a/src/pages/POS/Suppliers/Create.vue
+++ b/src/pages/POS/Suppliers/Create.vue
@@ -161,7 +161,6 @@ const closeModal = () => {
v-model="form.notes"
type="text"
placeholder="Notas"
- required
/>
diff --git a/src/pages/POS/UnitMeasure/Create.vue b/src/pages/POS/UnitMeasure/Create.vue
new file mode 100644
index 0000000..38c58a1
--- /dev/null
+++ b/src/pages/POS/UnitMeasure/Create.vue
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+ Nueva Unidad de Medida
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NOMBRE
+
+
+
+
+
+
+
+
+ ABREVIACIÓN
+
+
+
+
+
+
+
+
+
+
+ Permite cantidades decimales
+
+
+
+ Actívalo para unidades como kg, litros o metros. Desactívalo para piezas, cajas o unidades enteras.
+
+
+
+
+
+
+
+
+
+ Unidad activa
+
+
+
+
+
+
+
+
+
+ Cancelar
+
+
+ Guardando...
+ Guardar
+
+
+
+
+
+
diff --git a/src/pages/POS/UnitMeasure/Edit.vue b/src/pages/POS/UnitMeasure/Edit.vue
new file mode 100644
index 0000000..9379be1
--- /dev/null
+++ b/src/pages/POS/UnitMeasure/Edit.vue
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+ Editar Unidad de Medida
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NOMBRE
+
+
+
+
+
+
+
+
+ ABREVIACIÓN
+
+
+
+
+
+
+
+
+
+
+ Permite cantidades decimales
+
+
+
+ Actívalo para unidades como kg, litros o metros. Desactívalo para piezas, cajas o unidades enteras.
+
+
+
+
+
+
+
+
+
+ Unidad activa
+
+
+
+
+
+
+
+
+
+ Cancelar
+
+
+ Actualizando...
+ Actualizar
+
+
+
+
+
+
diff --git a/src/pages/POS/UnitMeasure/Index.vue b/src/pages/POS/UnitMeasure/Index.vue
new file mode 100644
index 0000000..12eefea
--- /dev/null
+++ b/src/pages/POS/UnitMeasure/Index.vue
@@ -0,0 +1,183 @@
+
+
+
+
+
searcher.search(x)"
+ >
+
+
+ Nueva Unidad
+
+
+
+
+
searcher.pagination(page)"
+ >
+
+ NOMBRE
+ ABREVIACIÓN
+ DECIMALES
+ ESTADO
+ ACCIONES
+
+
+
+
+
+ {{ unit.name }}
+
+
+
+ {{ unit.abbreviation }}
+
+
+
+
+ {{ unit.allows_decimals ? 'Sí' : 'No' }}
+
+
+
+
+ {{ unit.is_active ? 'Activo' : 'Inactivo' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
No hay unidades de medida registradas
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/POS/UnitMeasure/Module.js b/src/pages/POS/UnitMeasure/Module.js
new file mode 100644
index 0000000..41f8a6a
--- /dev/null
+++ b/src/pages/POS/UnitMeasure/Module.js
@@ -0,0 +1,8 @@
+import { hasPermission } from '@Plugins/RolePermission.js';
+
+const can = (permission) => hasPermission(`units.${permission}`)
+
+const viewTo = ({ name = '', params = {}, query = {} }) =>
+ ({ name: `pos.unitMeasure.${name}`, params, query })
+
+export { can, viewTo }
diff --git a/src/router/Index.js b/src/router/Index.js
index 75a0161..db4a6bf 100644
--- a/src/router/Index.js
+++ b/src/router/Index.js
@@ -129,6 +129,12 @@ const router = createRouter({
name: 'pos.suppliers.index',
beforeEnter: (to, from, next) => can(next, 'suppliers.index'),
component: () => import('@Pages/POS/Suppliers/Index.vue')
+ },
+ {
+ path: 'unit-measure',
+ name: 'pos.unitMeasure.index',
+ beforeEnter: (to, from, next) => can(next, 'units.index'),
+ component: () => import('@Pages/POS/UnitMeasure/Index.vue')
}
]
},
diff --git a/src/stores/cart.js b/src/stores/cart.js
index 043b6ae..b66b8fe 100644
--- a/src/stores/cart.js
+++ b/src/stores/cart.js
@@ -39,7 +39,9 @@ const useCart = defineStore('cart', {
actions: {
// Agregar producto al carrito
- addProduct(product, serialConfig = null) {
+ // config puede incluir: serialNumbers, selectionMode (para seriales)
+ // unit_of_measure_id, unit_price, unit_name (para equivalencias)
+ addProduct(product, config = null) {
const key = 'p:' + product.id;
const existingItem = this.items.find(item => item.item_key === key);
@@ -61,6 +63,11 @@ const useCart = defineStore('cart', {
window.Notify.warning('No hay suficiente stock disponible');
}
} else {
+ // Determinar precio: usar el de la equivalencia si se proporcionó, si no el base
+ const unitPrice = config?.unit_price !== undefined
+ ? parseFloat(config.unit_price)
+ : parseFloat(product.price?.retail_price || 0);
+
// Agregar nuevo item
this.items.push({
item_key: key,
@@ -70,16 +77,19 @@ const useCart = defineStore('cart', {
product_name: product.name,
sku: product.sku,
quantity: 1,
- unit_price: parseFloat(product.price?.retail_price || 0),
+ unit_price: unitPrice,
tax_rate: parseFloat(product.price?.tax || 16),
max_stock: product.stock,
// Campos para seriales
track_serials: product.track_serials || false,
- serial_numbers: serialConfig?.serialNumbers || [],
- serial_selection_mode: serialConfig?.selectionMode || null,
- // Campos para unidad de medida
+ serial_numbers: config?.serialNumbers || [],
+ serial_selection_mode: config?.selectionMode || null,
+ // Campos para unidad de medida base
unit_of_measure: product.unit_of_measure || null,
- allows_decimals: product.unit_of_measure?.allows_decimals || false
+ allows_decimals: product.unit_of_measure?.allows_decimals || false,
+ // Campos para equivalencia de unidad seleccionada
+ unit_of_measure_id: config?.unit_of_measure_id || null,
+ unit_name: config?.unit_name || null,
});
}
},
diff --git a/src/utils/fiscalData.js b/src/utils/fiscalData.js
new file mode 100644
index 0000000..1efd316
--- /dev/null
+++ b/src/utils/fiscalData.js
@@ -0,0 +1,25 @@
+export 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' },
+];
+
+export 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' },
+];