feat: actualizar terminología de categoría a clasificación en toda la aplicación

This commit is contained in:
Juan Felipe Zapata Moreno 2026-03-06 10:08:09 -06:00
parent fe79f843f6
commit b7154af381
18 changed files with 77 additions and 75 deletions

View File

@ -71,7 +71,7 @@ const handleAddToCart = () => {
<div class="flex items-center gap-1 mb-3"> <div class="flex items-center gap-1 mb-3">
<GoogleIcon name="category" class="text-sm text-gray-400" /> <GoogleIcon name="category" class="text-sm text-gray-400" />
<span class="text-xs text-gray-500 dark:text-gray-400"> <span class="text-xs text-gray-500 dark:text-gray-400">
{{ product.category?.name || 'Sin categoría' }} {{ product.category?.name || 'Sin clasificación' }}
</span> </span>
</div> </div>

View File

@ -502,7 +502,7 @@ export default {
}, },
sku: 'SKU / Código', sku: 'SKU / Código',
product: 'Producto', product: 'Producto',
category: 'Categoría', category: 'Clasificación',
stock: 'Stock', stock: 'Stock',
state: 'Estado', state: 'Estado',
cost: 'Costo', cost: 'Costo',

View File

@ -24,12 +24,12 @@ const form = useForm({
const createCategory = () => { const createCategory = () => {
form.post(apiURL('categorias'), { form.post(apiURL('categorias'), {
onSuccess: (data) => { onSuccess: (data) => {
Notify.success('Categoría creada exitosamente'); Notify.success('Clasificación creada exitosamente');
emit('created', data?.model); emit('created', data?.model);
closeModal(); closeModal();
}, },
onError: () => { onError: () => {
Notify.error('Error al crear la categoría'); Notify.error('Error al crear la clasificación');
} }
}); });
}; };
@ -46,7 +46,7 @@ const closeModal = () => {
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100"> <h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">
Crear Categoría Crear Clasificación
</h3> </h3>
<button <button
@click="closeModal" @click="closeModal"
@ -68,7 +68,7 @@ const closeModal = () => {
<FormInput <FormInput
v-model="form.name" v-model="form.name"
type="text" type="text"
placeholder="Nombre de la categoría" placeholder="Nombre de la clasificación"
required required
/> />
<FormError :message="form.errors?.name" /> <FormError :message="form.errors?.name" />
@ -83,7 +83,7 @@ const closeModal = () => {
v-model="form.description" v-model="form.description"
rows="3" rows="3"
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100"
placeholder="Descripción de la categoría" placeholder="Descripción de la clasificación"
></textarea> ></textarea>
<FormError :message="form.errors?.description" /> <FormError :message="form.errors?.description" />
</div> </div>

View File

@ -37,7 +37,7 @@ const handleClose = () => {
<GoogleIcon name="delete_forever" class="text-2xl text-red-600 dark:text-red-400" /> <GoogleIcon name="delete_forever" class="text-2xl text-red-600 dark:text-red-400" />
</div> </div>
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100"> <h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">
Eliminar Categoría Eliminar Clasificación
</h3> </h3>
</div> </div>
<button <button
@ -53,7 +53,7 @@ const handleClose = () => {
<!-- Content --> <!-- Content -->
<div class="space-y-5"> <div class="space-y-5">
<p class="text-gray-700 dark:text-gray-300 text-base"> <p class="text-gray-700 dark:text-gray-300 text-base">
¿Estás seguro de que deseas eliminar esta categoría? ¿Estás seguro de que deseas eliminar esta clasificación?
</p> </p>
<div v-if="category" class="bg-linear-to-br from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl p-5 space-y-2"> <div v-if="category" class="bg-linear-to-br from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl p-5 space-y-2">
@ -89,7 +89,7 @@ const handleClose = () => {
<div class="flex items-start gap-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4"> <div class="flex items-start gap-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
<GoogleIcon name="info" class="text-red-600 dark:text-red-400 text-xl shrink-0 mt-0.5" /> <GoogleIcon name="info" class="text-red-600 dark:text-red-400 text-xl shrink-0 mt-0.5" />
<p class="text-sm text-red-800 dark:text-red-300 font-medium"> <p class="text-sm text-red-800 dark:text-red-300 font-medium">
Esta acción es permanente y no se puede deshacer. Los productos asociados a esta categoría perderán su categorización. Esta acción es permanente y no se puede deshacer. Los productos asociados a esta clasificación perderán su clasificación.
</p> </p>
</div> </div>
</div> </div>
@ -109,7 +109,7 @@ const handleClose = () => {
class="flex items-center gap-2 px-5 py-2.5 bg-red-600 hover:bg-red-700 text-white text-sm font-semibold rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-600 shadow-lg shadow-red-600/30 transition-all" class="flex items-center gap-2 px-5 py-2.5 bg-red-600 hover:bg-red-700 text-white text-sm font-semibold rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-600 shadow-lg shadow-red-600/30 transition-all"
> >
<GoogleIcon name="delete" class="text-xl" /> <GoogleIcon name="delete" class="text-xl" />
Eliminar Categoría Eliminar Clasificación
</button> </button>
</div> </div>
</div> </div>

View File

@ -26,12 +26,12 @@ const form = useForm({
const updateCategory = () => { const updateCategory = () => {
form.put(apiURL(`categorias/${props.category.id}`), { form.put(apiURL(`categorias/${props.category.id}`), {
onSuccess: () => { onSuccess: () => {
Notify.success('Categoría actualizada exitosamente'); Notify.success('Clasificación actualizada exitosamente');
emit('updated'); emit('updated');
closeModal(); closeModal();
}, },
onError: () => { onError: () => {
Notify.error('Error al actualizar la categoría'); Notify.error('Error al actualizar la clasificación');
} }
}); });
}; };
@ -57,7 +57,7 @@ watch(() => props.category, (newCategory) => {
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100"> <h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">
Editar Categoría Editar Clasificación
</h3> </h3>
<button <button
@click="closeModal" @click="closeModal"
@ -79,7 +79,7 @@ watch(() => props.category, (newCategory) => {
<FormInput <FormInput
v-model="form.name" v-model="form.name"
type="text" type="text"
placeholder="Nombre de la categoría" placeholder="Nombre de la clasificación"
required required
/> />
<FormError :message="form.errors?.name" /> <FormError :message="form.errors?.name" />
@ -94,7 +94,7 @@ watch(() => props.category, (newCategory) => {
v-model="form.description" v-model="form.description"
rows="3" rows="3"
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100"
placeholder="Descripción de la categoría" placeholder="Descripción de la clasificación"
></textarea> ></textarea>
<FormError :message="form.errors?.description" /> <FormError :message="form.errors?.description" />
</div> </div>

View File

@ -72,15 +72,15 @@ const confirmDelete = async (id) => {
}); });
if (response.ok) { if (response.ok) {
Notify.success('Categoría eliminada exitosamente'); Notify.success('Clasificación eliminada exitosamente');
closeDeleteModal(); closeDeleteModal();
searcher.search(); searcher.search();
} else { } else {
Notify.error('Error al eliminar la categoría'); Notify.error('Error al eliminar la clasificación');
} }
} catch (error) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
Notify.error('Error al eliminar la categoría'); Notify.error('Error al eliminar la clasificación');
} }
}; };
@ -102,7 +102,7 @@ onMounted(() => {
@click="openCreateModal" @click="openCreateModal"
> >
<GoogleIcon name="add" class="text-xl" /> <GoogleIcon name="add" class="text-xl" />
Nueva Categoría Nueva Clasificación
</button> </button>
</SearcherHead> </SearcherHead>
@ -169,7 +169,7 @@ onMounted(() => {
<button <button
@click="router.push({ name: 'pos.category.subcategories', params: { id: model.id } })" @click="router.push({ name: 'pos.category.subcategories', params: { id: model.id } })"
class="text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200 transition-colors" class="text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200 transition-colors"
title="Gestionar subcategorías" title="Gestionar subclasificaciones"
> >
<GoogleIcon name="account_tree" class="text-xl" /> <GoogleIcon name="account_tree" class="text-xl" />
</button> </button>
@ -182,7 +182,7 @@ onMounted(() => {
<button <button
@click="openDeleteModal(model)" @click="openDeleteModal(model)"
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors"
title="Eliminar categoría" title="Eliminar clasificación"
> >
<GoogleIcon name="delete" class="text-xl" /> <GoogleIcon name="delete" class="text-xl" />
</button> </button>

View File

@ -43,14 +43,14 @@ const createSubcategory = () => {
onSuccess: (data) => { onSuccess: (data) => {
Notify.success( Notify.success(
entries.value.length === 1 entries.value.length === 1
? 'Subcategoría creada exitosamente' ? 'Subclasificación creada exitosamente'
: 'Subcategorías creadas exitosamente' : 'Subclasificaciones creadas exitosamente'
); );
emit('created', data?.models ?? data?.model); emit('created', data?.models ?? data?.model);
closeModal(); closeModal();
}, },
onError: () => { onError: () => {
Notify.error('Error al crear la subcategoría'); Notify.error('Error al crear la subclasificación');
} }
}); });
}; };
@ -68,7 +68,7 @@ const closeModal = () => {
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100"> <h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">
Crear Subcategoría Crear Subclasificación
</h3> </h3>
<button <button
@click="closeModal" @click="closeModal"
@ -92,7 +92,7 @@ const closeModal = () => {
> >
<div v-if="entries.length > 1" class="flex items-center justify-between"> <div v-if="entries.length > 1" class="flex items-center justify-between">
<span class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase"> <span class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase">
Subcategoría {{ index + 1 }} Subclasificación {{ index + 1 }}
</span> </span>
<button <button
type="button" type="button"
@ -114,7 +114,7 @@ const closeModal = () => {
<FormInput <FormInput
v-model="entry.name" v-model="entry.name"
type="text" type="text"
placeholder="Nombre de la subcategoría" placeholder="Nombre de la subclasificación"
required required
/> />
<FormError :message="form.errors?.[`${index}.name`] ?? form.errors?.name" /> <FormError :message="form.errors?.[`${index}.name`] ?? form.errors?.name" />
@ -129,7 +129,7 @@ const closeModal = () => {
v-model="entry.description" v-model="entry.description"
rows="2" rows="2"
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100"
placeholder="Descripción de la subcategoría" placeholder="Descripción de la subclasificación"
></textarea> ></textarea>
<FormError :message="form.errors?.[`${index}.description`] ?? form.errors?.description" /> <FormError :message="form.errors?.[`${index}.description`] ?? form.errors?.description" />
</div> </div>
@ -159,7 +159,7 @@ const closeModal = () => {
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
</svg> </svg>
Agregar otra subcategoría Agregar otra subclasificación
</button> </button>
<!-- Botones --> <!-- Botones -->

View File

@ -28,13 +28,13 @@ const form = useForm({
const createSubcategory = () => { const createSubcategory = () => {
form.post(apiURL(`categorias/${props.categoryId}/subcategorias`), { form.post(apiURL(`categorias/${props.categoryId}/subcategorias`), {
onSuccess: (data) => { onSuccess: (data) => {
Notify.success('Subcategoría creada exitosamente'); Notify.success('Subclasificación creada exitosamente');
const created = data?.model ?? (data?.models ? data.models[0] : []); const created = data?.model ?? (data?.models ? data.models[0] : []);
emit('created', created); emit('created', created);
closeModal(); closeModal();
}, },
onError: () => { onError: () => {
Notify.error('Error al crear la subcategoría'); Notify.error('Error al crear la subclasificación');
} }
}); });
}; };
@ -51,7 +51,7 @@ const closeModal = () => {
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100"> <h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">
Crear Subcategoría Crear Subclasificación
</h3> </h3>
<button <button
@click="closeModal" @click="closeModal"
@ -73,7 +73,7 @@ const closeModal = () => {
<FormInput <FormInput
v-model="form.name" v-model="form.name"
type="text" type="text"
placeholder="Nombre de la subcategoría" placeholder="Nombre de la subclasificación"
required required
/> />
<FormError :message="form.errors?.name" /> <FormError :message="form.errors?.name" />
@ -88,7 +88,7 @@ const closeModal = () => {
v-model="form.description" v-model="form.description"
rows="3" rows="3"
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100"
placeholder="Descripción de la subcategoría" placeholder="Descripción de la subclasificación"
></textarea> ></textarea>
<FormError :message="form.errors?.description" /> <FormError :message="form.errors?.description" />
</div> </div>

View File

@ -37,7 +37,7 @@ const handleClose = () => {
<GoogleIcon name="delete_forever" class="text-2xl text-red-600 dark:text-red-400" /> <GoogleIcon name="delete_forever" class="text-2xl text-red-600 dark:text-red-400" />
</div> </div>
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100"> <h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">
Eliminar Subcategoría Eliminar Subclasificación
</h3> </h3>
</div> </div>
<button <button
@ -53,7 +53,7 @@ const handleClose = () => {
<!-- Content --> <!-- Content -->
<div class="space-y-5"> <div class="space-y-5">
<p class="text-gray-700 dark:text-gray-300 text-base"> <p class="text-gray-700 dark:text-gray-300 text-base">
¿Estás seguro de que deseas eliminar esta subcategoría? ¿Estás seguro de que deseas eliminar esta subclasificación?
</p> </p>
<div v-if="subcategory" class="bg-linear-to-br from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl p-5 space-y-2"> <div v-if="subcategory" class="bg-linear-to-br from-gray-50 to-gray-100 dark:from-gray-800 dark:to-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl p-5 space-y-2">
@ -89,7 +89,7 @@ const handleClose = () => {
<div class="flex items-start gap-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4"> <div class="flex items-start gap-2 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
<GoogleIcon name="info" class="text-red-600 dark:text-red-400 text-xl shrink-0 mt-0.5" /> <GoogleIcon name="info" class="text-red-600 dark:text-red-400 text-xl shrink-0 mt-0.5" />
<p class="text-sm text-red-800 dark:text-red-300 font-medium"> <p class="text-sm text-red-800 dark:text-red-300 font-medium">
Los productos que tenían asignada esta subcategoría quedarán sin subcategoría automáticamente. Los productos que tenían asignada esta subclasificación quedarán sin subclasificación automáticamente.
</p> </p>
</div> </div>
</div> </div>
@ -109,7 +109,7 @@ const handleClose = () => {
class="flex items-center gap-2 px-5 py-2.5 bg-red-600 hover:bg-red-700 text-white text-sm font-semibold rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-600 shadow-lg shadow-red-600/30 transition-all" class="flex items-center gap-2 px-5 py-2.5 bg-red-600 hover:bg-red-700 text-white text-sm font-semibold rounded-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-600 shadow-lg shadow-red-600/30 transition-all"
> >
<GoogleIcon name="delete" class="text-xl" /> <GoogleIcon name="delete" class="text-xl" />
Eliminar Subcategoría Eliminar Subclasificación
</button> </button>
</div> </div>
</div> </div>

View File

@ -30,12 +30,12 @@ const form = useForm({
const updateSubcategory = () => { const updateSubcategory = () => {
form.put(apiURL(`categorias/${props.categoryId}/subcategorias/${props.subcategory.id}`), { form.put(apiURL(`categorias/${props.categoryId}/subcategorias/${props.subcategory.id}`), {
onSuccess: () => { onSuccess: () => {
Notify.success('Subcategoría actualizada exitosamente'); Notify.success('Subclasificación actualizada exitosamente');
emit('updated'); emit('updated');
closeModal(); closeModal();
}, },
onError: () => { onError: () => {
Notify.error('Error al actualizar la subcategoría'); Notify.error('Error al actualizar la subclasificación');
} }
}); });
}; };
@ -61,7 +61,7 @@ watch(() => props.subcategory, (newSubcategory) => {
<!-- Header --> <!-- Header -->
<div class="flex items-center justify-between mb-6"> <div class="flex items-center justify-between mb-6">
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100"> <h3 class="text-lg font-bold text-gray-900 dark:text-gray-100">
Editar Subcategoría Editar Subclasificación
</h3> </h3>
<button <button
@click="closeModal" @click="closeModal"
@ -83,7 +83,7 @@ watch(() => props.subcategory, (newSubcategory) => {
<FormInput <FormInput
v-model="form.name" v-model="form.name"
type="text" type="text"
placeholder="Nombre de la subcategoría" placeholder="Nombre de la subclasificación"
required required
/> />
<FormError :message="form.errors?.name" /> <FormError :message="form.errors?.name" />
@ -98,7 +98,7 @@ watch(() => props.subcategory, (newSubcategory) => {
v-model="form.description" v-model="form.description"
rows="3" rows="3"
class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-800 dark:border-gray-600 dark:text-gray-100"
placeholder="Descripción de la subcategoría" placeholder="Descripción de la subclasificación"
></textarea> ></textarea>
<FormError :message="form.errors?.description" /> <FormError :message="form.errors?.description" />
</div> </div>

View File

@ -93,15 +93,15 @@ const confirmDelete = async (id) => {
}); });
if (response.ok) { if (response.ok) {
Notify.success('Subcategoría eliminada exitosamente'); Notify.success('Subclasificación eliminada exitosamente');
closeDeleteModal(); closeDeleteModal();
searcher.search(); searcher.search();
} else { } else {
Notify.error('Error al eliminar la subcategoría'); Notify.error('Error al eliminar la subclasificación');
} }
} catch (error) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
Notify.error('Error al eliminar la subcategoría'); Notify.error('Error al eliminar la subclasificación');
} }
}; };
@ -120,14 +120,14 @@ onMounted(() => {
<button <button
@click="router.push({ name: 'pos.category.index' })" @click="router.push({ name: 'pos.category.index' })"
class="flex items-center justify-center w-8 h-8 rounded-lg text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-800 transition-colors" class="flex items-center justify-center w-8 h-8 rounded-lg text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-gray-800 transition-colors"
title="Volver a categorías" title="Volver a clasificaciones"
> >
<GoogleIcon name="arrow_back" class="text-xl" /> <GoogleIcon name="arrow_back" class="text-xl" />
</button> </button>
<div> <div>
<p class="text-xs text-gray-500 dark:text-gray-400">Categoría</p> <p class="text-xs text-gray-500 dark:text-gray-400">Clasificación</p>
<h1 class="text-xl font-bold text-gray-900 dark:text-gray-100"> <h1 class="text-xl font-bold text-gray-900 dark:text-gray-100">
{{ categoryName || 'Subcategorías' }} {{ categoryName || 'Subclasificaciones' }}
</h1> </h1>
</div> </div>
</div> </div>
@ -136,7 +136,7 @@ onMounted(() => {
@click="openCreateModal" @click="openCreateModal"
> >
<GoogleIcon name="add" class="text-xl" /> <GoogleIcon name="add" class="text-xl" />
Nueva Subcategoría Nueva Subclasificación
</button> </button>
</div> </div>
@ -204,14 +204,14 @@ onMounted(() => {
<button <button
@click="openEditModal(model)" @click="openEditModal(model)"
class="text-indigo-600 hover:text-indigo-900 transition-colors" class="text-indigo-600 hover:text-indigo-900 transition-colors"
title="Editar subcategoría" title="Editar subclasificación"
> >
<GoogleIcon name="edit" class="text-xl" /> <GoogleIcon name="edit" class="text-xl" />
</button> </button>
<button <button
@click="openDeleteModal(model)" @click="openDeleteModal(model)"
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors" class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300 transition-colors"
title="Eliminar subcategoría" title="Eliminar subclasificación"
> >
<GoogleIcon name="delete" class="text-xl" /> <GoogleIcon name="delete" class="text-xl" />
</button> </button>
@ -226,7 +226,7 @@ onMounted(() => {
name="category" name="category"
class="text-6xl mb-2 opacity-50" class="text-6xl mb-2 opacity-50"
/> />
<p class="font-semibold">No hay subcategorías registradas</p> <p class="font-semibold">No hay subclasificaciones registradas</p>
</div> </div>
</td> </td>
</template> </template>

View File

@ -157,10 +157,12 @@ const sendInvoiceByWhatsapp = async () => {
sendingWhatsapp.value = true; sendingWhatsapp.value = true;
try { try {
await whatsappService.sendInvoice({ const ticket = request.sale?.invoice_number || `SOL-${request.id}`;
await whatsappService.sendDocument({
phone_number: request.client.phone, phone_number: request.client.phone,
invoice_number: request.sale?.invoice_number || `SOL-${request.id}`, document_url: request.invoice_pdf_url,
pdf_url: request.invoice_pdf_url, filename: `${ticket}.pdf`,
ticket,
customer_name: request.client.name customer_name: request.client.name
}); });
@ -419,7 +421,7 @@ const sendInvoiceByWhatsapp = async () => {
{{ item.product_name }} {{ item.product_name }}
</p> </p>
<p class="text-xs text-green-700 dark:text-green-300"> <p class="text-xs text-green-700 dark:text-green-300">
SKU: {{ item.inventory?.sku || 'N/A' }} {{ item.inventory?.category?.name || 'Sin categoría' }} SKU: {{ item.inventory?.sku || 'N/A' }} {{ item.inventory?.category?.name || 'Sin clasificación' }}
</p> </p>
<div v-if="item.serials && item.serials.length > 0" class="mt-1"> <div v-if="item.serials && item.serials.length > 0" class="mt-1">
<p class="text-xs text-green-600 dark:text-green-400"> <p class="text-xs text-green-600 dark:text-green-400">

View File

@ -65,7 +65,7 @@ const handleClose = () => {
<div class="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400"> <div class="flex items-center gap-3 text-sm text-gray-600 dark:text-gray-400">
<span class="font-mono font-medium">SKU: {{ product.sku }}</span> <span class="font-mono font-medium">SKU: {{ product.sku }}</span>
<span class="text-gray-400"></span> <span class="text-gray-400"></span>
<span>{{ product.category?.name || 'Sin categoría' }}</span> <span>{{ product.category?.name || 'Sin clasificación' }}</span>
</div> </div>
</div> </div>
<div class="text-right"> <div class="text-right">

View File

@ -58,7 +58,7 @@ const loadCategories = async () => {
categories.value = result.data.categories.data; categories.value = result.data.categories.data;
} }
} catch (error) { } catch (error) {
console.error('Error al cargar categorías:', error); console.error('Error al cargar clasificaciones:', error);
} }
}; };
@ -75,7 +75,7 @@ const loadSubcategories = async () => {
categories.value = result.data.subcategories.data; categories.value = result.data.subcategories.data;
} }
} catch (error) { } catch (error) {
console.error('Error al cargar subcategorías:', error); console.error('Error al cargar subclasificaciones:', error);
} }
}; };
@ -266,13 +266,13 @@ onMounted(() => {
<div class="pt-4 pb-2"> <div class="pt-4 pb-2">
<div class="flex items-end gap-4"> <div class="flex items-end gap-4">
<div> <div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Filtrar por categoría:</label> <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Filtrar por clasificación:</label>
<select <select
v-model="selectedCategory" v-model="selectedCategory"
@change="handleCategoryChange" @change="handleCategoryChange"
class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100" class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
> >
<option value="">Todas las categorías</option> <option value="">Todas las clasificaciones</option>
<option v-for="category in categories" :key="category.id" :value="category.id"> <option v-for="category in categories" :key="category.id" :value="category.id">
{{ category.name }} {{ category.name }}
</option> </option>

View File

@ -103,7 +103,7 @@ onMounted(() => {
<p class="text-sm text-gray-500 dark:text-gray-400"> <p class="text-sm text-gray-500 dark:text-gray-400">
SKU: {{ inventory.sku }} SKU: {{ inventory.sku }}
<span v-if="inventory.category"> <span v-if="inventory.category">
| Categoría: {{ inventory.category.name }} | Clasificación: {{ inventory.category.name }}
</span> </span>
</p> </p>
</div> </div>

View File

@ -64,7 +64,7 @@ const loadCategories = async () => {
categories.value = result.data.categories.data; categories.value = result.data.categories.data;
} }
} catch (error) { } catch (error) {
console.error('Error al cargar categorías:', error); console.error('Error al cargar clasificaciones:', error);
} }
}; };
@ -175,13 +175,13 @@ onMounted(async () => {
<div class="pt-4 pb-2"> <div class="pt-4 pb-2">
<div class="flex items-end gap-4"> <div class="flex items-end gap-4">
<div> <div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">Filtrar por categoría:</label> <label class="text-sm font-medium text-gray-700 dark:text-gray-300">Filtrar por clasificación:</label>
<select <select
v-model="selectedCategory" v-model="selectedCategory"
@change="handleCategoryChange" @change="handleCategoryChange"
class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100" class="px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100"
> >
<option value="">Todas las categorías</option> <option value="">Todas las clasificaciones</option>
<option v-for="category in categories" :key="category.id" :value="category.id"> <option v-for="category in categories" :key="category.id" :value="category.id">
{{ category.name }} {{ category.name }}
</option> </option>
@ -207,7 +207,7 @@ onMounted(async () => {
<template #head> <template #head>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">SKU / CÓDIGO</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">SKU / CÓDIGO</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">PRODUCTO</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">PRODUCTO</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">CATEGORÍA</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">CLASIFICACIÓN</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">PRECIO</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">PRECIO</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">STOCK</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">STOCK</th>
<th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">TOTAL</th> <th class="px-6 py-3 text-center text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">TOTAL</th>

View File

@ -6,19 +6,19 @@ import axios from 'axios';
*/ */
const whatsappService = { const whatsappService = {
/** /**
* Enviar factura por WhatsApp * Enviar documento por WhatsApp
* @param {Object} data - Datos de la factura * @param {Object} data - Datos del documento
* @returns {Promise} * @returns {Promise}
*/ */
async sendInvoice({ phone_number, invoice_number, pdf_url, xml_url = null, customer_name }) { async sendDocument({ phone_number, document_url, filename, ticket, customer_name }) {
try { try {
const { data } = await axios.post( const { data } = await axios.post(
apiURL('whatsapp/send-invoice'), apiURL('whatsapp/send-document'),
{ {
phone_number, phone_number,
invoice_number, document_url,
pdf_url, filename,
xml_url, ticket,
customer_name customer_name
}, },
{ {