feature/reportes

This commit is contained in:
2026-04-16 15:59:18 -06:00
parent 11db840a11
commit de8f944a35
16 changed files with 2321 additions and 45 deletions

View File

@@ -3,13 +3,15 @@
import * as Dialog from '$lib/components/ui/dialog';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import { FolderSearch, Scale } from 'lucide-svelte';
import UnitMeasureSelectorDialog from '$lib/components/dashboard/goods/modales/unit-measure-dialog.svelte';
import { companyStore } from '$lib/stores/company.svelte';
import { Scale } from 'lucide-svelte';
import {
createEquivalencyItem,
updateEquivalencyItem,
type EquivalencyItem
} from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
import type { UnitOfMeasure } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
let {
open = $bindable(false),
@@ -37,25 +39,33 @@
let loading = $state(false);
let error = $state<string | null>(null);
let showOriginalModal = $state(false);
let showExternalModal = $state(false);
$effect(() => {
if (!open) return;
if (item) {
formData = {
original_field: item.original_field || '',
external_field: item.external_field || ''
};
formData.original_field = item.original_field || '';
formData.external_field = item.external_field || '';
} else {
formData = {
original_field: defaultOriginalField ?? '',
external_field: ''
};
formData.original_field = defaultOriginalField ?? '';
formData.external_field = '';
}
error = null;
});
function handleSelectOriginal(unit: UnitOfMeasure) {
formData.original_field = unit.code;
showOriginalModal = false;
}
function handleSelectExternal(unit: UnitOfMeasure) {
formData.external_field = unit.code;
showExternalModal = false;
}
async function handleSubmit() {
const companyId = companyStore.activeCompany?.id;
if (!companyId) {
@@ -113,40 +123,64 @@
<div class="grid gap-4 py-4">
<div class="grid gap-2">
<Label for="from_unit_code">
<Label for="original_field">
Campo Original <span class="text-destructive">*</span>
</Label>
<div class="flex gap-2">
<div class="relative w-full">
<Scale class="absolute top-2.5 left-3 h-4 w-4 text-muted-foreground" />
<Input
id="from_unit_code"
bind:value={formData.original_field}
class="pl-9 font-mono"
placeholder="Ej: PZA, KGM..."
disabled={loading}
required
/>
<Input
id="original_field"
bind:value={formData.original_field}
readonly
onclick={() => (showOriginalModal = true)}
class="cursor-pointer pl-9 font-mono"
placeholder="Seleccione..."
disabled={loading}
required
/>
</div>
<Button
variant="outline"
size="icon"
type="button"
onclick={() => (showOriginalModal = true)}
disabled={loading}
class="shrink-0"
>
<FolderSearch class="h-4 w-4" />
</Button>
</div>
</div>
<div class="grid gap-2">
<Label for="to_unit_code">
<Label for="external_field">
Campo Exterior <span class="text-destructive">*</span>
</Label>
<div class="flex gap-2">
<div class="relative w-full">
<Scale class="absolute top-2.5 left-3 h-4 w-4 text-muted-foreground" />
<Input
id="to_unit_code"
bind:value={formData.external_field}
class="pl-9 font-mono"
placeholder="Ej: PIEZAS, KGS..."
disabled={loading}
required
/>
<Input
id="external_field"
bind:value={formData.external_field}
readonly
onclick={() => (showExternalModal = true)}
class="cursor-pointer pl-9 font-mono"
placeholder="Seleccione..."
disabled={loading}
required
/>
</div>
<Button
variant="outline"
size="icon"
type="button"
onclick={() => (showExternalModal = true)}
disabled={loading}
class="shrink-0"
>
<FolderSearch class="h-4 w-4" />
</Button>
</div>
</div>
</div>
@@ -160,3 +194,6 @@
</form>
</Dialog.Content>
</Dialog.Root>
<UnitMeasureSelectorDialog bind:open={showOriginalModal} onSelect={handleSelectOriginal} />
<UnitMeasureSelectorDialog bind:open={showExternalModal} onSelect={handleSelectExternal} />

View File

@@ -33,13 +33,9 @@
operation_type: operationType,
invoice_number: searchTerm || undefined
};
if (operationType === 'imp' && regimen) {
if (regimen === 'Temporal' || regimen === 'TEMPORAL SCAF') {
filters.invoice_type = 'TEM';
} else if (regimen === 'Definitiva' || regimen === 'DEFINITIVO SCAF') {
filters.invoice_type = 'DEF';
}
}
// Do NOT filter by invoice_type here: restricting to TEM or DEF based on
// the current movement_type_import value would hide valid invoices of the
// other type. Let the user search freely and pick the right one.
const res = await invoicesApi.list(companyStore.activeCompany.id, 1, 50, filters);
if (res.data) {
@@ -134,7 +130,8 @@
{invoice.invoice_number}
</td>
<td class="max-w-[400px] truncate p-3 text-muted-foreground italic">
{invoice.compliance_mx?.pedimento_r1 ||
{invoice.compliance_mx?.pedimento?.pedimento_number ||
invoice.compliance_mx?.pedimento_r1 ||
invoice.compliance_mx?.pedimento_id ||
'-'}
</td>

View File

@@ -164,10 +164,12 @@
if (showLinkToImportBlock && num && !selectedImportInvoiceId && !loadingImportLines) {
(async () => {
try {
const res = await invoicesApi.list(companyStore.activeCompany!.id, 1, 5, {
// Do NOT filter by invoice_type: if movement_type_import is null or
// mismatched the invoice won't be found, leaving the line picker
// permanently disabled. Exact match is enforced by .find() below.
const res = await invoicesApi.list(companyStore.activeCompany!.id, 1, 50, {
operation_type: 'imp',
invoice_number: num,
invoice_type: movementType === 'DEF' ? 'DEF' : 'TEM'
invoice_number: num
});
const items = res.data?.items ?? [];
const inv = items.find((i: Invoice) => i.invoice_number === num);
@@ -183,7 +185,7 @@
if (showRepairBlock && num && !selectedExportInvoiceId && !loadingExportLines) {
(async () => {
try {
const res = await invoicesApi.list(companyStore.activeCompany!.id, 1, 5, {
const res = await invoicesApi.list(companyStore.activeCompany!.id, 1, 50, {
operation_type: 'exp',
invoice_number: num
});
@@ -735,7 +737,7 @@
<Dialog.Header>
<Dialog.Title class="text-sm">Seleccionar línea de importación</Dialog.Title>
<p class="text-xs text-muted-foreground mt-0.5">
Solo se muestran líneas con saldo disponible
Líneas sin saldo disponible se muestran en gris.
</p>
</Dialog.Header>
<!-- overflow-x on a wrapper that does NOT also do overflow-y.
@@ -743,9 +745,9 @@
independently from the horizontal scrollbar. -->
<div class="overflow-x-auto">
<div class="overflow-y-auto max-h-[500px]">
{#if importInvoiceLines.every(l => !l.has_balance)}
{#if importInvoiceLines.length === 0}
<p class="px-3 py-8 text-xs text-muted-foreground text-center">
No hay líneas con saldo disponible en esta factura.
No hay líneas en esta factura.
</p>
{:else}
<table class="text-xs border-collapse" style="min-width: max-content; width: 100%;">
@@ -768,9 +770,8 @@
</thead>
<tbody class="divide-y divide-border">
{#each importInvoiceLines as lineItem}
{#if lineItem.has_balance}
<tr
class="hover:bg-muted/50 cursor-pointer transition-colors group"
class="{lineItem.has_balance ? 'hover:bg-muted/50 cursor-pointer' : 'opacity-50 cursor-default'} transition-colors group"
onclick={() => {
editingItem.fa_data = editingItem.fa_data || {};
editingItem.fa_data.search_line = lineItem.line_number;
@@ -830,7 +831,6 @@
{/if}
</td>
</tr>
{/if}
{/each}
</tbody>
</table>

View File

@@ -507,6 +507,10 @@ export function getSidebarData(): SidebarData {
title: "Facturas Impo/Expo",
url: "/dashboard/reports/invoices",
},
{
title: "Partes descargadas",
url: "/dashboard/reports/partes-descargadas",
},
],
},
{