diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml
index fbab06966..2b8639ef7 100644
--- a/src/i18n/locale/en.yml
+++ b/src/i18n/locale/en.yml
@@ -448,6 +448,7 @@ ticket:
         futureTickets: Future tickets
         purchaseRequest: Purchase request
         weeklyTickets: Weekly tickets
+        saleTracking: Sale tracking
     list:
         nickname: Nickname
         state: State
diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml
index fec78d5e6..218976e76 100644
--- a/src/i18n/locale/es.yml
+++ b/src/i18n/locale/es.yml
@@ -447,6 +447,7 @@ ticket:
         futureTickets: Tickets a futuro
         purchaseRequest: Petición de compra
         weeklyTickets: Tickets programados
+        saleTracking: Líneas preparadas
     list:
         nickname: Alias
         state: Estado
diff --git a/src/pages/Ticket/Card/TicketSaleTracking.vue b/src/pages/Ticket/Card/TicketSaleTracking.vue
new file mode 100644
index 000000000..a03f75387
--- /dev/null
+++ b/src/pages/Ticket/Card/TicketSaleTracking.vue
@@ -0,0 +1,539 @@
+<script setup>
+import { ref, computed, nextTick, watch } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useRoute } from 'vue-router';
+
+import FetchData from 'components/FetchData.vue';
+import FetchedTags from 'components/ui/FetchedTags.vue';
+import VnInput from 'src/components/common/VnInput.vue';
+import VnSelect from 'src/components/common/VnSelect.vue';
+import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
+import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
+
+import { dashIfEmpty } from 'src/filters';
+import useNotify from 'src/composables/useNotify.js';
+import { toDateTimeFormat } from 'src/filters/date';
+
+import axios from 'axios';
+
+const route = useRoute();
+const { t } = useI18n();
+const { notify } = useNotify();
+const saleTrackingTableDialogRef = ref(null);
+const itemShelvingSaleDialogRef = ref(null);
+const saleTrackingFetchDataRef = ref(null);
+
+const sales = ref([]);
+const saleTrackings = ref([]);
+const itemShelvignsSales = ref([]);
+const shelvingsOptions = ref([]);
+const parkingsOptions = ref([]);
+const saleTrackingUrl = computed(() => `SaleTrackings/${route.params.id}/filter`);
+
+watch(
+    () => route.params.id,
+    async () => nextTick(async () => await saleTrackingFetchDataRef.value.fetch())
+);
+
+const columns = computed(() => [
+    {
+        label: t('ticketSaleTracking.isChecked'),
+        name: 'isChecked',
+        align: 'left',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.item'),
+        name: 'item',
+        align: 'left',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.description'),
+        name: 'description',
+        align: 'left',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.quantity'),
+        name: 'quantity',
+        field: 'quantity',
+        align: 'left',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.parking'),
+        name: 'parking',
+        field: 'parkingCode',
+        align: 'left',
+        sortable: true,
+        format: (value) => dashIfEmpty(value),
+    },
+    {
+        label: '',
+        name: 'actions',
+        align: 'left',
+        sortable: true,
+    },
+]);
+
+const logTableColumns = computed(() => [
+    {
+        label: t('ticketSaleTracking.quantity'),
+        name: 'quantity',
+        field: 'quantity',
+        align: 'left',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.original'),
+        name: 'original',
+        field: 'originalQuantity',
+        align: 'original',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.worker'),
+        name: 'worker',
+        align: 'left',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.state'),
+        name: 'state',
+        field: 'state',
+        align: 'left',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.created'),
+        name: 'created',
+        field: 'created',
+        align: 'left',
+        sortable: true,
+        format: (value) => toDateTimeFormat(value),
+    },
+]);
+
+const shelvingsTableColumns = computed(() => [
+    {
+        label: t('ticketSaleTracking.quantity'),
+        name: 'quantity',
+        align: 'left',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.worker'),
+        name: 'worker',
+        align: 'left',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.shelving'),
+        name: 'shelving',
+        align: 'original',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.parking'),
+        name: 'parking',
+        align: 'left',
+        sortable: true,
+    },
+    {
+        label: t('ticketSaleTracking.created'),
+        name: 'created',
+        field: 'created',
+        align: 'left',
+        sortable: true,
+        format: (value) => toDateTimeFormat(value),
+    },
+]);
+
+const getSaleTrackings = async (sale) => {
+    try {
+        const filter = {
+            where: { saleFk: sale.saleFk },
+            order: ['itemFk DESC'],
+        };
+        const { data } = await axios.get(`SaleTrackings/listSaleTracking`, {
+            params: { filter: JSON.stringify(filter) },
+        });
+        saleTrackings.value = data;
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const showLog = async (sale) => {
+    await getSaleTrackings(sale);
+    saleTrackingTableDialogRef.value.show();
+};
+
+const getItemShelvingSales = async (sale) => {
+    try {
+        const filter = {
+            where: { saleFk: sale.saleFk },
+        };
+        const { data } = await axios.get(`ItemShelvingSales/filter`, {
+            params: { filter: JSON.stringify(filter) },
+        });
+        itemShelvignsSales.value = data;
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const showShelving = async (sale) => {
+    await getItemShelvingSales(sale);
+    itemShelvingSaleDialogRef.value.show();
+};
+
+const updateQuantity = async (sale) => {
+    try {
+        const params = {
+            quantity: sale.quantity,
+        };
+        await axios.patch(`ItemShelvingSales/${sale.id}`, params);
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const updateParking = async (sale) => {
+    try {
+        const filter = {
+            fields: ['id'],
+            where: {
+                code: sale.shelvingFk,
+            },
+        };
+        const { data } = await axios.get(`Shelvings/findOne`, {
+            params: { filter: JSON.stringify(filter) },
+        });
+        const params = {
+            parkingFk: sale.parkingFk,
+        };
+        await axios.patch(`Shelvings/${data.id}`, params);
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const updateShelving = async (sale) => {
+    const params = {
+        shelvingFk: sale.shelvingFk,
+    };
+
+    const { data: patchResponseData } = await axios.patch(
+        `ItemShelvings/${sale.itemShelvingFk}`,
+        params
+    );
+    const filter = {
+        fields: ['parkingFk'],
+        where: {
+            code: patchResponseData.shelvingFk,
+        },
+    };
+    const { data: getResponseData } = await axios.get(`Shelvings/findOne`, { filter });
+    sale.parkingFk = getResponseData.parkingFk;
+};
+
+const saleTrackingNew = async (sale, stateCode, isChecked) => {
+    try {
+        const params = {
+            saleFk: sale.saleFk,
+            isChecked: isChecked,
+            quantity: sale.quantity,
+            stateCode: stateCode,
+        };
+        await axios.post(`SaleTrackings/new`, params);
+        notify(t('globals.dataSaved'), 'positive');
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const saleTrackingDel = async (sale, stateCode) => {
+    try {
+        const params = {
+            saleFk: sale.saleFk,
+            stateCodes: [stateCode],
+        };
+        await axios.post(`SaleTrackings/delete`, params);
+        notify(t('globals.dataSaved'), 'positive');
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const clickSaleGroupDetail = async (sale) => {
+    try {
+        if (!sale.saleGroupDetailFk) return;
+
+        await axios.delete(`SaleGroupDetails/${sale.saleGroupDetailFk}`);
+        sale.hasSaleGroupDetail = false;
+        notify(t('globals.dataSaved'), 'positive');
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const clickPreviousSelected = (sale) => {
+    try {
+        if (!sale.isPreviousSelected) {
+            saleTrackingNew(sale, 'PREVIOUS_PREPARATION', false);
+            sale.isPreviousSelected = true;
+        } else {
+            saleTrackingDel(sale, 'PREVIOUS_PREPARATION');
+            sale.isPreviousSelected = false;
+            sale.isPrevious = false;
+        }
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const clickPrevious = (sale) => {
+    try {
+        if (!sale.isPrevious) {
+            saleTrackingNew(sale, 'PREVIOUS_PREPARATION', true);
+            sale.isPrevious = true;
+            sale.isPreviousSelected = true;
+        } else {
+            saleTrackingNew(sale, 'PREVIOUS_PREPARATION', false);
+            sale.isPrevious = false;
+        }
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const clickPrepared = (sale) => {
+    try {
+        if (!sale.isPrepared) {
+            saleTrackingNew(sale, 'PREPARED', true);
+            sale.isPrepared = true;
+        } else {
+            saleTrackingDel(sale, 'PREPARED');
+            sale.isPrepared = false;
+        }
+    } catch (error) {
+        console.error(error);
+    }
+};
+
+const clickControled = (sale) => {
+    try {
+        if (!sale.isControled) {
+            saleTrackingNew(sale, 'CHECKED', true);
+            sale.isControled = true;
+        } else {
+            saleTrackingDel(sale, 'CHECKED');
+            sale.isControled = false;
+        }
+    } catch (error) {
+        console.error(error);
+    }
+};
+</script>
+
+<template>
+    <FetchData
+        ref="saleTrackingFetchDataRef"
+        :url="saleTrackingUrl"
+        :filter="{ order: ['concept ASC', 'quantity DESC'] }"
+        auto-load
+        @on-fetch="(data) => (sales = data)"
+    />
+    <FetchData
+        url="Shelvings"
+        auto-load
+        @on-fetch="(data) => (shelvingsOptions = data)"
+    />
+    <FetchData url="Parkings" auto-load @on-fetch="(data) => (parkingsOptions = data)" />
+    <QTable
+        :rows="sales"
+        :columns="columns"
+        row-key="id"
+        :pagination="{ rowsPerPage: 0 }"
+        class="full-width q-mt-md"
+        :no-data-label="t('globals.noResults')"
+    >
+        <template #body-cell-isChecked="{ row }">
+            <QTd @click.stop>
+                <QCheckbox
+                    :model-value="!!row.hasSaleGroupDetail"
+                    color="pink"
+                    :toggle-indeterminate="false"
+                    @update:model-value="clickSaleGroupDetail(row)"
+                >
+                    <QTooltip>
+                        {{ t('ticketSaleTracking.saleGroupDetail') }}
+                    </QTooltip>
+                </QCheckbox>
+                <QCheckbox
+                    :model-value="!!row.isPreviousSelected"
+                    color="info"
+                    :toggle-indeterminate="false"
+                    @update:model-value="clickPreviousSelected(row)"
+                >
+                    <QTooltip>
+                        {{ t('ticketSaleTracking.previousSelected') }}
+                    </QTooltip>
+                </QCheckbox>
+                <QCheckbox
+                    :model-value="!!row.isPrevious"
+                    color="cyan"
+                    :toggle-indeterminate="false"
+                    @update:model-value="clickPrevious(row)"
+                >
+                    <QTooltip>
+                        {{ t('ticketSaleTracking.previous') }}
+                    </QTooltip>
+                </QCheckbox>
+                <QCheckbox
+                    :model-value="!!row.isPrepared"
+                    color="warning"
+                    :toggle-indeterminate="false"
+                    @update:model-value="clickPrepared(row)"
+                >
+                    <QTooltip>
+                        {{ t('ticketSaleTracking.prepared') }}
+                    </QTooltip>
+                </QCheckbox>
+                <QCheckbox
+                    :model-value="!!row.isControled"
+                    color="yellow"
+                    :toggle-indeterminate="false"
+                    @update:model-value="clickControled(row)"
+                >
+                    <QTooltip>
+                        {{ t('ticketSaleTracking.checked') }}
+                    </QTooltip>
+                </QCheckbox>
+            </QTd>
+        </template>
+        <template #body-cell-item="{ row }">
+            <QTd @click.stop>
+                <div>
+                    <QBtn flat color="primary">
+                        {{ row.itemFk }}
+                    </QBtn>
+                    <ItemDescriptorProxy :id="row.itemFk" />
+                </div>
+            </QTd>
+        </template>
+        <template #body-cell-description="{ row }">
+            <QTd class="col">
+                <div class="column">
+                    <span>{{ row.concept }}</span>
+                    <span v-if="row.subName" class="color-vn-label">
+                        {{ row.subName }}
+                    </span>
+                    <FetchedTags :item="row" :max-length="6" />
+                </div>
+            </QTd>
+        </template>
+        <template #body-cell-actions="{ row }">
+            <QTd>
+                <QBtn
+                    @click.stop="showLog(row)"
+                    color="primary"
+                    icon="history"
+                    size="md"
+                    flat
+                >
+                    <QTooltip class="text-no-wrap">
+                        {{ t('ticketSaleTracking.historyAction') }}
+                    </QTooltip>
+                </QBtn>
+                <QBtn
+                    @click.stop="showShelving(row)"
+                    color="primary"
+                    icon="vn:inventory"
+                    size="md"
+                    flat
+                >
+                    <QTooltip class="text-no-wrap">
+                        {{ t('ticketSaleTracking.shelvingAction') }}
+                    </QTooltip>
+                </QBtn>
+            </QTd>
+        </template>
+    </QTable>
+    <QDialog
+        ref="saleTrackingTableDialogRef"
+        transition-show="scale"
+        transition-hide="scale"
+    >
+        <QTable
+            data-key="saleTrackingLog"
+            :rows="saleTrackings"
+            :columns="logTableColumns"
+            class="q-pa-sm"
+        >
+            <template #body-cell-worker="{ row }">
+                <QTd auto-width>
+                    <QBtn flat dense color="primary">{{ row.name }}</QBtn>
+                    <WorkerDescriptorProxy :id="row.workerFk" />
+                </QTd>
+            </template>
+        </QTable>
+    </QDialog>
+    <QDialog
+        ref="itemShelvingSaleDialogRef"
+        transition-show="scale"
+        transition-hide="scale"
+    >
+        <QTable
+            data-key="itemShelvingsSales"
+            :rows="itemShelvignsSales"
+            :columns="shelvingsTableColumns"
+            class="q-pa-sm"
+        >
+            <template #body-cell-quantity="{ row }">
+                <QTd auto-width>
+                    <VnInput
+                        v-model.number="row.quantity"
+                        @keyup.enter="updateQuantity(row)"
+                        @blur="updateQuantity(row)"
+                        @focus="edit.oldQuantity = row.quantity"
+                    />
+                </QTd>
+            </template>
+            <template #body-cell-worker="{ row }">
+                <QTd auto-width>
+                    <QBtn flat dense color="primary">{{ row.name }}</QBtn>
+                    <WorkerDescriptorProxy :id="row.userFk" />
+                </QTd>
+            </template>
+            <template #body-cell-shelving="{ row }">
+                <QTd auto-width>
+                    <VnSelect
+                        :options="shelvingsOptions"
+                        hide-selected
+                        option-label="code"
+                        option-value="code"
+                        v-model="row.shelvingFk"
+                        @update:model-value="updateShelving(row)"
+                        style="max-width: 120px"
+                    />
+                </QTd>
+            </template>
+            <template #body-cell-parking="{ row }">
+                <QTd auto-width>
+                    <VnSelect
+                        :options="parkingsOptions"
+                        hide-selected
+                        option-label="code"
+                        option-value="id"
+                        v-model="row.parkingFk"
+                        @update:model-value="updateParking(row)"
+                        style="max-width: 120px"
+                    />
+                </QTd>
+            </template>
+        </QTable>
+    </QDialog>
+</template>
diff --git a/src/pages/Ticket/locale/en.yml b/src/pages/Ticket/locale/en.yml
index 2c648e7f2..2b173eae9 100644
--- a/src/pages/Ticket/locale/en.yml
+++ b/src/pages/Ticket/locale/en.yml
@@ -136,3 +136,21 @@ weeklyTickets:
     salesperson: Salesperson
     search: Search weekly tickets
     searchInfo: Search weekly tickets by id or client id
+ticketSaleTracking:
+    isChecked: Is checked
+    item: Item
+    description: Description
+    quantity: Quantity
+    parking: Parking
+    historyAction: Log states
+    shelvingAction: Shelvings sale
+    original: Original
+    worker: Worker
+    state: State
+    created: Created
+    shelving: Shelving
+    saleGroupDetail: sale group detail
+    previousSelected: previous selected
+    previous: previous
+    prepared: prepared
+    checked: checked
diff --git a/src/pages/Ticket/locale/es.yml b/src/pages/Ticket/locale/es.yml
index 3ce4c0545..f5f4d89f5 100644
--- a/src/pages/Ticket/locale/es.yml
+++ b/src/pages/Ticket/locale/es.yml
@@ -138,3 +138,21 @@ ticketSale:
     shipped: F. Envío
     agency: Agencia
     address: Consignatario
+ticketSaleTracking:
+    isChecked: Comprobado
+    item: Artículo
+    description: Descripción
+    quantity: Cantidad
+    parking: Parking
+    historyAction: Historial estados
+    shelvingAction: Carros línea
+    original: Original
+    worker: Trabajador
+    state: Estado
+    created: Fecha creación
+    shelving: Matrícula
+    saleGroupDetail: detalle grupo líneas
+    previousSelected: previa seleccionado
+    previous: previa
+    prepared: preparado
+    checked: revisado
diff --git a/src/router/modules/ticket.js b/src/router/modules/ticket.js
index 81ca405ee..5850458bb 100644
--- a/src/router/modules/ticket.js
+++ b/src/router/modules/ticket.js
@@ -19,6 +19,7 @@ export default {
             'TicketSale',
             'TicketLog',
             'TicketPurchaseRequest',
+            'TicketSaleTracking',
         ],
     },
     children: [
@@ -147,6 +148,16 @@ export default {
                     },
                     component: () => import('src/pages/Ticket/Card/TicketSms.vue'),
                 },
+                {
+                    path: 'sale-tracking',
+                    name: 'TicketSaleTracking',
+                    meta: {
+                        title: 'saleTracking',
+                        icon: 'vn:buyrequest',
+                    },
+                    component: () =>
+                        import('src/pages/Ticket/Card/TicketSaleTracking.vue'),
+                },
             ],
         },
     ],