Merge branch 'feature/facturas' into development
This commit is contained in:
@@ -1,89 +1,250 @@
|
||||
/**
|
||||
* Definición de columnas para la tabla de facturas
|
||||
*/
|
||||
import type { ColumnDef } from "@tanstack/table-core";
|
||||
import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js";
|
||||
import { createRawSnippet } from "svelte";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns() {
|
||||
/**
|
||||
* Formatea un número como moneda MXN
|
||||
*/
|
||||
function formatCurrencyMXN(value?: number | null): string {
|
||||
if (value === null || value === undefined) return '-';
|
||||
return new Intl.NumberFormat('es-MX', {
|
||||
style: 'currency',
|
||||
currency: 'MXN',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatea un número como moneda USD
|
||||
*/
|
||||
function formatCurrencyUSD(value?: number | null): string {
|
||||
if (value === null || value === undefined) return '-';
|
||||
return new Intl.NumberFormat('es-MX', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatea una fecha
|
||||
*/
|
||||
function formatDate(date?: string | null): string {
|
||||
if (!date) return '-';
|
||||
return new Date(date).toLocaleDateString('es-MX', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el color del badge según el tipo de operación
|
||||
*/
|
||||
function getOperationTypeColor(type?: string | null): string {
|
||||
if (!type) return 'bg-gray-100 text-gray-800';
|
||||
return type === 'imp' ? 'bg-blue-100 text-blue-800' : 'bg-green-100 text-green-800';
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el color del badge según el semáforo fiscal
|
||||
*/
|
||||
function getTrafficLightColor(status?: string | null): string {
|
||||
if (!status) return 'bg-gray-100 text-gray-800';
|
||||
|
||||
const statusLower = status.toLowerCase();
|
||||
if (statusLower.includes('verde') || statusLower === 'green') return 'bg-green-100 text-green-800';
|
||||
if (statusLower.includes('amarillo') || statusLower === 'yellow') return 'bg-yellow-100 text-yellow-800';
|
||||
if (statusLower.includes('rojo') || statusLower === 'red') return 'bg-red-100 text-red-800';
|
||||
|
||||
return 'bg-gray-100 text-gray-800';
|
||||
}
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Invoice>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'id',
|
||||
header: 'ID',
|
||||
cell: (info: any) => info.getValue(),
|
||||
enableSorting: true
|
||||
},
|
||||
{
|
||||
accessorKey: 'operation_type',
|
||||
header: 'Tipo',
|
||||
cell: (info: any) => {
|
||||
const type = info.getValue();
|
||||
return type === 'imp' ? 'Importación' : type === 'exp' ? 'Exportación' : '-';
|
||||
accessorKey: "id",
|
||||
header: "ID",
|
||||
cell: ({ row }) => {
|
||||
const idSnippet = createRawSnippet<[{ id: number }]>((getId) => {
|
||||
const { id } = getId();
|
||||
return {
|
||||
render: () =>
|
||||
`<div class="font-medium">#${id}</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(idSnippet, { id: row.original.id });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'invoice_number',
|
||||
header: 'Número de Factura',
|
||||
cell: (info: any) => info.getValue() || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'invoice_type',
|
||||
header: 'Tipo Factura',
|
||||
cell: (info: any) => info.getValue() || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'project_number',
|
||||
header: 'Proyecto',
|
||||
cell: (info: any) => info.getValue() || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'compliance_mx.pedimento',
|
||||
header: 'Pedimento',
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
return row.compliance_mx?.pedimento || '-';
|
||||
accessorKey: "operation_type",
|
||||
header: "Operación",
|
||||
cell: ({ row }) => {
|
||||
const type = row.original.operation_type;
|
||||
const colorClass = getOperationTypeColor(type);
|
||||
const label = type === 'imp' ? 'IMP' : type === 'exp' ? 'EXP' : 'N/A';
|
||||
|
||||
const typeSnippet = createRawSnippet<[{ label: string; colorClass: string }]>((getType) => {
|
||||
const { label, colorClass } = getType();
|
||||
return {
|
||||
render: () =>
|
||||
`<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">
|
||||
${label}
|
||||
</span>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(typeSnippet, { label, colorClass });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'invoice_date',
|
||||
header: 'Fecha Factura',
|
||||
cell: (info: any) => {
|
||||
const date = info.getValue();
|
||||
if (!date) return '-';
|
||||
return new Date(date).toLocaleDateString('es-MX');
|
||||
accessorKey: "invoice_number",
|
||||
header: "Número de Factura",
|
||||
cell: ({ row }) => {
|
||||
const numberSnippet = createRawSnippet<[{ number?: string | null }]>((getNumber) => {
|
||||
const { number } = getNumber();
|
||||
return {
|
||||
render: () =>
|
||||
`<code class="relative rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold">${number || 'N/A'}</code>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(numberSnippet, { number: row.original.invoice_number });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'financials.value_mn',
|
||||
header: 'Valor MN',
|
||||
cell: (info: any) => {
|
||||
const row = info.row.original;
|
||||
const value = row.financials?.value_mn;
|
||||
if (value === null || value === undefined) return '-';
|
||||
return new Intl.NumberFormat('es-MX', {
|
||||
style: 'currency',
|
||||
currency: 'MXN'
|
||||
}).format(value);
|
||||
accessorKey: "invoice_type",
|
||||
header: "Tipo",
|
||||
cell: ({ row }) => {
|
||||
const typeSnippet = createRawSnippet<[{ type?: string | null }]>((getType) => {
|
||||
const { type } = getType();
|
||||
return {
|
||||
render: () =>
|
||||
`<div class="capitalize">${type || '-'}</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(typeSnippet, { type: row.original.invoice_type });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'traffic_light_status',
|
||||
header: 'Semáforo',
|
||||
cell: (info: any) => info.getValue() || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'capture_date',
|
||||
header: 'Fecha Captura',
|
||||
cell: (info: any) => {
|
||||
const date = info.getValue();
|
||||
if (!date) return '-';
|
||||
return new Date(date).toLocaleDateString('es-MX');
|
||||
accessorKey: "project_number",
|
||||
header: "Proyecto",
|
||||
cell: ({ row }) => {
|
||||
const projectSnippet = createRawSnippet<[{ project?: string | null }]>((getProject) => {
|
||||
const { project } = getProject();
|
||||
return {
|
||||
render: () =>
|
||||
`<div>${project || '-'}</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(projectSnippet, { project: row.original.project_number });
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: (info: any) => DataTableActions,
|
||||
enableSorting: false
|
||||
accessorKey: "compliance_mx.pedimento",
|
||||
header: "Pedimento",
|
||||
cell: ({ row }) => {
|
||||
const pedimento = row.original.compliance_mx?.pedimento;
|
||||
|
||||
const pedimentoSnippet = createRawSnippet<[{ pedimento?: string | null }]>((getPedimento) => {
|
||||
const { pedimento } = getPedimento();
|
||||
return {
|
||||
render: () =>
|
||||
`<div class="text-sm">${pedimento || '-'}</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(pedimentoSnippet, { pedimento });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "financials.value_mn",
|
||||
header: () => {
|
||||
const headerSnippet = createRawSnippet(() => {
|
||||
return {
|
||||
render: () => `<div class="text-right">Valor MN</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(headerSnippet, {});
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const valueMN = row.original.financials?.value_mn;
|
||||
|
||||
const valueSnippet = createRawSnippet<[{ value: string }]>((getValue) => {
|
||||
const { value } = getValue();
|
||||
return {
|
||||
render: () =>
|
||||
`<div class="text-right font-medium">${value}</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(valueSnippet, { value: formatCurrencyMXN(valueMN) });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "financials.value_me",
|
||||
header: () => {
|
||||
const headerSnippet = createRawSnippet(() => {
|
||||
return {
|
||||
render: () => `<div class="text-right">Valor ME</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(headerSnippet, {});
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const valueME = row.original.financials?.value_me;
|
||||
|
||||
const valueSnippet = createRawSnippet<[{ value: string }]>((getValue) => {
|
||||
const { value } = getValue();
|
||||
return {
|
||||
render: () =>
|
||||
`<div class="text-right font-medium">${value}</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(valueSnippet, { value: formatCurrencyUSD(valueME) });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "traffic_light_status",
|
||||
header: "Semáforo",
|
||||
cell: ({ row }) => {
|
||||
const status = row.original.traffic_light_status;
|
||||
const colorClass = getTrafficLightColor(status);
|
||||
|
||||
const statusSnippet = createRawSnippet<[{ status?: string | null; colorClass: string }]>((getStatus) => {
|
||||
const { status, colorClass } = getStatus();
|
||||
return {
|
||||
render: () =>
|
||||
`<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${colorClass}">
|
||||
${status || '-'}
|
||||
</span>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(statusSnippet, { status, colorClass });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "invoice_date",
|
||||
header: "Fecha Factura",
|
||||
cell: ({ row }) => {
|
||||
const dateSnippet = createRawSnippet<[{ date: string }]>((getDate) => {
|
||||
const { date } = getDate();
|
||||
return {
|
||||
render: () =>
|
||||
`<div class="text-sm text-muted-foreground">${date}</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(dateSnippet, { date: formatDate(row.original.invoice_date) });
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, { invoice: row.original, onSuccess });
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Mantener compatibilidad hacia atrás
|
||||
export const columns = createColumns();
|
||||
|
||||
@@ -3,26 +3,24 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { Ellipsis, Eye, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import DetailsDialog from './details-dialog.svelte';
|
||||
import DeleteDialog from './delete-dialog.svelte';
|
||||
|
||||
interface Props {
|
||||
invoice: Invoice;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { invoice }: Props = $props();
|
||||
let { invoice, onSuccess }: Props = $props();
|
||||
|
||||
let showDetails = $state(false);
|
||||
let showDelete = $state(false);
|
||||
|
||||
function dispatchView() {
|
||||
window.dispatchEvent(new CustomEvent('invoiceView', { detail: invoice }));
|
||||
function handleEdit() {
|
||||
// Redirigir a la página de edición
|
||||
window.location.href = `/dashboard/invoices/edit/${invoice.id}`;
|
||||
}
|
||||
|
||||
function dispatchDelete() {
|
||||
window.dispatchEvent(new CustomEvent('invoiceDelete', { detail: invoice }));
|
||||
}
|
||||
|
||||
function dispatchEdit() {
|
||||
window.dispatchEvent(new CustomEvent('invoiceEdit', { detail: invoice }));
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -38,20 +36,36 @@
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
<DropdownMenu.Item onclick={dispatchView}>
|
||||
<DropdownMenu.Item onclick={() => showDetails = true}>
|
||||
<Eye class="mr-2 h-4 w-4" />
|
||||
Ver Detalles
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Item onclick={dispatchEdit}>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={dispatchDelete} class="text-destructive">
|
||||
<DropdownMenu.Item onclick={() => showDelete = true} class="text-destructive">
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Diálogos -->
|
||||
{#if showDetails}
|
||||
<DetailsDialog
|
||||
invoice={invoice}
|
||||
onClose={() => showDetails = false}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if showDelete}
|
||||
<DeleteDialog
|
||||
invoice={invoice}
|
||||
onClose={() => showDelete = false}
|
||||
{onSuccess}
|
||||
/>
|
||||
{/if}
|
||||
@@ -83,18 +83,10 @@
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
{#if cell.column.id === 'actions'}
|
||||
{@const cellDef = cell.column.columnDef.cell}
|
||||
{#if cellDef && typeof cellDef === 'function'}
|
||||
{@const Component = cellDef(cell.getContext())}
|
||||
<Component invoice={cell.row.original} />
|
||||
{/if}
|
||||
{:else}
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
|
||||
@@ -5,27 +5,26 @@
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { LoaderCircle } from 'lucide-svelte';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item: Invoice | null;
|
||||
interface Props {
|
||||
invoice: Invoice;
|
||||
onClose: () => void;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
}
|
||||
|
||||
let { invoice, onClose, onSuccess }: Props = $props();
|
||||
let open = $state(true);
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!item || !companyStore.activeCompany) return;
|
||||
if (!invoice || !companyStore.activeCompany) return;
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await invoicesApi.delete(item.id, companyStore.activeCompany.id);
|
||||
const response = await invoicesApi.delete(invoice.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
@@ -33,7 +32,7 @@
|
||||
}
|
||||
|
||||
// Éxito
|
||||
open = false;
|
||||
onClose();
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
@@ -48,8 +47,10 @@
|
||||
function handleOpenChange(newOpen: boolean) {
|
||||
if (!newOpen) {
|
||||
error = null;
|
||||
onClose();
|
||||
} else {
|
||||
open = newOpen;
|
||||
}
|
||||
open = newOpen;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -59,30 +60,30 @@
|
||||
<AlertDialog.Title>¿Estás seguro?</AlertDialog.Title>
|
||||
<AlertDialog.Description class="space-y-2">
|
||||
<p>Esta acción no se puede deshacer. Se eliminará permanentemente esta factura:</p>
|
||||
{#if item}
|
||||
{#if invoice}
|
||||
<div class="mt-2 rounded-lg bg-muted p-3 space-y-2">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="font-medium">ID:</span>
|
||||
<span class="font-semibold">{item.id}</span>
|
||||
<span class="font-semibold">{invoice.id}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="font-medium">Número de Factura:</span>
|
||||
<code class="font-mono font-semibold">{item.invoice_number || 'N/A'}</code>
|
||||
<code class="font-mono font-semibold">{invoice.invoice_number || 'N/A'}</code>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="font-medium">Tipo:</span>
|
||||
<span class="text-xs">
|
||||
{item.operation_type === 'imp' ? 'Importación' :
|
||||
item.operation_type === 'exp' ? 'Exportación' : 'N/A'}
|
||||
{invoice.operation_type === 'imp' ? 'Importación' :
|
||||
invoice.operation_type === 'exp' ? 'Exportación' : 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="font-medium">Proyecto:</span>
|
||||
<span class="text-xs">{item.project_number || 'N/A'}</span>
|
||||
<span class="text-xs">{invoice.project_number || 'N/A'}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="font-medium">Pedimento:</span>
|
||||
<span class="text-xs">{item.compliance_mx?.pedimento || 'N/A'}</span>
|
||||
<span class="text-xs">{invoice.compliance_mx?.pedimento || 'N/A'}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -5,13 +5,13 @@
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
invoice
|
||||
}: {
|
||||
open: boolean;
|
||||
invoice: Invoice | null;
|
||||
} = $props();
|
||||
interface Props {
|
||||
invoice: Invoice;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
let { invoice, onClose }: Props = $props();
|
||||
let open = $state(true);
|
||||
|
||||
function formatDate(dateString: string | null | undefined): string {
|
||||
if (!dateString) return '-';
|
||||
@@ -32,7 +32,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root {open} onOpenChange={(v) => (open = v)}>
|
||||
<Dialog.Root {open} onOpenChange={(v) => { open = v; if (!v) onClose(); }}>
|
||||
<Dialog.Content class="max-w-5xl max-h-[90vh] overflow-y-auto">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Detalles de Factura #{invoice?.id}</Dialog.Title>
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
<script lang="ts">
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
|
||||
|
||||
let {
|
||||
invoice,
|
||||
formData = $bindable(),
|
||||
exists = $bindable()
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
} = $props();
|
||||
|
||||
if (!formData && invoice?.financials) {
|
||||
formData = {
|
||||
// Currency & Exchange
|
||||
currency: invoice.financials.currency || '',
|
||||
currency_type: invoice.financials.currency_type || '',
|
||||
exchange_rate: invoice.financials.exchange_rate || null,
|
||||
exchange_rate_mm: invoice.financials.exchange_rate_mm || null,
|
||||
// Values
|
||||
value_mn: invoice.financials.value_mn || null,
|
||||
value_me: invoice.financials.value_me || null,
|
||||
value_mc: invoice.financials.value_mc || null,
|
||||
customs_value_mn: invoice.financials.customs_value_mn || null,
|
||||
customs_value_me: invoice.financials.customs_value_me || null,
|
||||
// Raw materials
|
||||
raw_material_value_mn: invoice.financials.raw_material_value_mn || null,
|
||||
raw_material_value_me: invoice.financials.raw_material_value_me || null,
|
||||
// Aggregate values
|
||||
aggregate_value_mn: invoice.financials.aggregate_value_mn || null,
|
||||
aggregate_value_me: invoice.financials.aggregate_value_me || null,
|
||||
aggregate_value_mc: invoice.financials.aggregate_value_mc || null,
|
||||
// Mexican values
|
||||
mexican_value_mn: invoice.financials.mexican_value_mn || null,
|
||||
mexican_value_me: invoice.financials.mexican_value_me || null,
|
||||
mexican_value_mc: invoice.financials.mexican_value_mc || null,
|
||||
// National packaging
|
||||
national_packaging_mn: invoice.financials.national_packaging_mn || null,
|
||||
national_packaging_me: invoice.financials.national_packaging_me || null,
|
||||
national_packaging_mc: invoice.financials.national_packaging_mc || null,
|
||||
// Costs & increments
|
||||
freight: invoice.financials.freight || null,
|
||||
insurance: invoice.financials.insurance || null,
|
||||
insurance_value: invoice.financials.insurance_value || null,
|
||||
packaging: invoice.financials.packaging || null,
|
||||
other_increments: invoice.financials.other_increments || null,
|
||||
total_increments_mn: invoice.financials.total_increments_mn || null,
|
||||
total_increments_me: invoice.financials.total_increments_me || null,
|
||||
// Taxes
|
||||
iva_mn: invoice.financials.iva_mn || null,
|
||||
iva_me: invoice.financials.iva_me || null,
|
||||
iva_mc: invoice.financials.iva_mc || null,
|
||||
iva_factor: invoice.financials.iva_factor || '',
|
||||
tax_value_me: invoice.financials.tax_value_me || null,
|
||||
seal_value_2500: invoice.financials.seal_value_2500 || false,
|
||||
// Weights & quantities
|
||||
total_quantity: invoice.financials.total_quantity || null,
|
||||
gross_weight: invoice.financials.gross_weight || null,
|
||||
net_weight: invoice.financials.net_weight || null,
|
||||
bundle_count: invoice.financials.bundle_count || null,
|
||||
weight_factor: invoice.financials.weight_factor || null,
|
||||
// Additional fields not in backend
|
||||
numero_tipo_transporte: '',
|
||||
es_ferrocarril: 'no',
|
||||
numero_bl: '',
|
||||
cantidad_guias_embarque: null,
|
||||
destino_origen: '',
|
||||
puerto_entrada: '',
|
||||
// Checkboxes
|
||||
fue_revisado_equipo: false,
|
||||
sub_division: false,
|
||||
funge_como_cd: false,
|
||||
llego_pedimento: false,
|
||||
// Errores
|
||||
errores_facturacion: [],
|
||||
// Semáforos
|
||||
semaforo_verde_aduana_mexicana: false,
|
||||
semaforo_verde_aduana_americana: false,
|
||||
semaforo_rojo_aduana_mexicana: false,
|
||||
semaforo_rojo_aduana_americana: false
|
||||
};
|
||||
exists = true;
|
||||
} else if (!formData) {
|
||||
formData = {
|
||||
// Currency & Exchange
|
||||
currency: '',
|
||||
currency_type: '',
|
||||
exchange_rate: null,
|
||||
exchange_rate_mm: null,
|
||||
// Values
|
||||
value_mn: null,
|
||||
value_me: null,
|
||||
value_mc: null,
|
||||
customs_value_mn: null,
|
||||
customs_value_me: null,
|
||||
// Raw materials
|
||||
raw_material_value_mn: null,
|
||||
raw_material_value_me: null,
|
||||
// Aggregate values
|
||||
aggregate_value_mn: null,
|
||||
aggregate_value_me: null,
|
||||
aggregate_value_mc: null,
|
||||
// Mexican values
|
||||
mexican_value_mn: null,
|
||||
mexican_value_me: null,
|
||||
mexican_value_mc: null,
|
||||
// National packaging
|
||||
national_packaging_mn: null,
|
||||
national_packaging_me: null,
|
||||
national_packaging_mc: null,
|
||||
// Costs & increments
|
||||
freight: null,
|
||||
insurance: null,
|
||||
insurance_value: null,
|
||||
packaging: null,
|
||||
other_increments: null,
|
||||
total_increments_mn: null,
|
||||
total_increments_me: null,
|
||||
// Taxes
|
||||
iva_mn: null,
|
||||
iva_me: null,
|
||||
iva_mc: null,
|
||||
iva_factor: '',
|
||||
tax_value_me: null,
|
||||
seal_value_2500: false,
|
||||
// Weights & quantities
|
||||
total_quantity: null,
|
||||
gross_weight: null,
|
||||
net_weight: null,
|
||||
bundle_count: null,
|
||||
weight_factor: null,
|
||||
// Additional fields not in backend
|
||||
numero_tipo_transporte: '',
|
||||
es_ferrocarril: 'no',
|
||||
numero_bl: '',
|
||||
cantidad_guias_embarque: null,
|
||||
destino_origen: '',
|
||||
puerto_entrada: '',
|
||||
// Checkboxes
|
||||
fue_revisado_equipo: false,
|
||||
sub_division: false,
|
||||
funge_como_cd: false,
|
||||
llego_pedimento: false,
|
||||
// Errores
|
||||
errores_facturacion: [],
|
||||
// Semáforos
|
||||
semaforo_verde_aduana_mexicana: false,
|
||||
semaforo_verde_aduana_americana: false,
|
||||
semaforo_rojo_aduana_mexicana: false,
|
||||
semaforo_rojo_aduana_americana: false
|
||||
};
|
||||
exists = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Layout de 2 columnas compacto -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<!-- Columna Izquierda -->
|
||||
<div class="border rounded-md p-3 space-y-3">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Información General</h4>
|
||||
|
||||
<!-- NÚMERO/TIPO DE TRANSPORTE -->
|
||||
<div class="space-y-1.5">
|
||||
<Label for="numero_tipo_transporte" class="text-xs">Número/Tipo de Transporte:</Label>
|
||||
<Input id="numero_tipo_transporte" bind:value={formData.numero_tipo_transporte} class="h-7 text-xs" />
|
||||
</div>
|
||||
|
||||
<!-- DATOS VEHÍCULO -->
|
||||
<div class="border rounded p-2 space-y-2 bg-muted/30">
|
||||
<Label class="text-xs font-semibold">Datos Vehículo:</Label>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">Es Ferrocarril?</Label>
|
||||
<RadioGroup bind:value={formData.es_ferrocarril} class="flex gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<RadioGroupItem value="si" id="ferrocarril_si" />
|
||||
<Label for="ferrocarril_si" class="text-xs">SI</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<RadioGroupItem value="no" id="ferrocarril_no" />
|
||||
<Label for="ferrocarril_no" class="text-xs">NO</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label for="numero_bl" class="text-xs">Número BL:</Label>
|
||||
<Input id="numero_bl" bind:value={formData.numero_bl} class="h-7 text-xs" />
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label for="cantidad_guias_embarque" class="text-xs">Cantidad de Guías de Embarque (BL):</Label>
|
||||
<Input id="cantidad_guias_embarque" type="number" bind:value={formData.cantidad_guias_embarque} class="h-7 text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DESTINO/ORIGEN Y PUERTO ENTRADA -->
|
||||
<div class="space-y-1.5">
|
||||
<Label for="destino_origen" class="text-xs">Destino/Origen:</Label>
|
||||
<Input id="destino_origen" bind:value={formData.destino_origen} placeholder="FRANJA FRONT." class="h-7 text-xs" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="puerto_entrada" class="text-xs">Puerto Entrada:</Label>
|
||||
<Input id="puerto_entrada" bind:value={formData.puerto_entrada} class="h-7 text-xs" />
|
||||
</div>
|
||||
|
||||
<!-- CHECKBOXES -->
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="fue_revisado" bind:checked={formData.fue_revisado_equipo} />
|
||||
<Label for="fue_revisado" class="text-xs">Fue Revisado el Equipo</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="sub_division" bind:checked={formData.sub_division} />
|
||||
<Label for="sub_division" class="text-xs">Sub División</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="funge_como_cd" bind:checked={formData.funge_como_cd} />
|
||||
<Label for="funge_como_cd" class="text-xs">Funge Como CD</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Checkbox id="llego_pedimento" bind:checked={formData.llego_pedimento} />
|
||||
<Label for="llego_pedimento" class="text-xs">Llegó el Pedimento</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Columna Derecha - Errores de Facturación -->
|
||||
<div class="border rounded-md p-3 space-y-3">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Errores de Facturación</h4>
|
||||
|
||||
<div class="border rounded">
|
||||
<table class="w-full text-xs">
|
||||
<thead class="bg-muted">
|
||||
<tr>
|
||||
<th class="border px-2 py-1 text-left">Línea</th>
|
||||
<th class="border px-2 py-1 text-left">Clave</th>
|
||||
<th class="border px-2 py-1 text-left">Descripción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if formData.errores_facturacion?.length}
|
||||
{#each formData.errores_facturacion as error}
|
||||
<tr>
|
||||
<td class="border px-2 py-1">{error.linea}</td>
|
||||
<td class="border px-2 py-1">{error.clave}</td>
|
||||
<td class="border px-2 py-1">{error.descripcion}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan="3" class="border px-2 py-12 text-center text-muted-foreground">
|
||||
Sin errores registrados
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button variant="outline" size="sm" class="h-7 text-xs">Insertar</Button>
|
||||
<Button variant="outline" size="sm" class="h-7 text-xs">Editar</Button>
|
||||
<Button variant="outline" size="sm" class="h-7 text-xs">Borrar</Button>
|
||||
</div>
|
||||
|
||||
<!-- SEMÁFORO -->
|
||||
<div class="border rounded p-2 space-y-2 bg-muted/30">
|
||||
<Label class="text-xs font-semibold">Semáforo</Label>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-green-500"></div>
|
||||
<Checkbox id="verde_mex" bind:checked={formData.semaforo_verde_aduana_mexicana} />
|
||||
<Label for="verde_mex" class="text-xs">Verde MX</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-green-600"></div>
|
||||
<Checkbox id="verde_usa" bind:checked={formData.semaforo_verde_aduana_americana} />
|
||||
<Label for="verde_usa" class="text-xs">Verde USA</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
<Checkbox id="rojo_mex" bind:checked={formData.semaforo_rojo_aduana_mexicana} />
|
||||
<Label for="rojo_mex" class="text-xs">Rojo MX</Label>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-red-600"></div>
|
||||
<Checkbox id="rojo_usa" bind:checked={formData.semaforo_rojo_aduana_americana} />
|
||||
<Label for="rojo_usa" class="text-xs">Rojo USA</Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,574 @@
|
||||
<script lang="ts">
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import * as RadioGroup from '$lib/components/ui/radio-group';
|
||||
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
|
||||
import type { InvoiceType } from '$lib/api/dashboard/refrence_data/invoice_types';
|
||||
import type { CustomsBroker } from '$lib/api/dashboard/a76/customs-brokers';
|
||||
import type { ClientProvider } from '$lib/api/dashboard/a76/clients-providers';
|
||||
|
||||
let {
|
||||
invoice,
|
||||
formData = $bindable(),
|
||||
invoiceTypes = [],
|
||||
customsBrokers = [],
|
||||
clients = [],
|
||||
providers = [],
|
||||
currencyTypes = [],
|
||||
transportTypes = [],
|
||||
defaultOperationType = undefined,
|
||||
defaultInvoiceType = undefined
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
invoiceTypes?: InvoiceType[];
|
||||
customsBrokers?: CustomsBroker[];
|
||||
clients?: ClientProvider[];
|
||||
providers?: any[];
|
||||
currencyTypes?: any[];
|
||||
transportTypes?: any[];
|
||||
defaultOperationType?: number | null;
|
||||
defaultInvoiceType?: string | null;
|
||||
} = $props();
|
||||
|
||||
if (!formData) {
|
||||
if (invoice) {
|
||||
// Editando una factura existente
|
||||
let operationType: number | null = null;
|
||||
if (invoice.operation_type) {
|
||||
operationType = invoice.operation_type === 'exp' ? 1 : 2;
|
||||
}
|
||||
|
||||
formData = {
|
||||
// TOP fields
|
||||
is_pedimento_pending: false,
|
||||
pedimento: invoice.compliance_mx?.pedimento || '',
|
||||
remesa: invoice.compliance_mx?.remesa || '',
|
||||
invoice_number: invoice.invoice_number || '',
|
||||
invoice_date: invoice.invoice_date || '',
|
||||
emission_date: '',
|
||||
|
||||
// Extra fields
|
||||
operation_type: operationType,
|
||||
|
||||
// RANGO DE FECHAS fields
|
||||
fecha_pedimento_del: '',
|
||||
fecha_pedimento_al: '',
|
||||
clave_pedimento: '',
|
||||
regimen_pedimento: '',
|
||||
|
||||
// LEFT fields
|
||||
provider_header: invoice.compliance_mx?.provider_header || '',
|
||||
provider_id: invoice.compliance_mx?.provider_id || null,
|
||||
sold_to_header: invoice.compliance_mx?.sold_to_header || '',
|
||||
sold_to_id: invoice.compliance_mx?.sold_to_id || null,
|
||||
shipped_to_header: invoice.compliance_mx?.shipped_to_header || '',
|
||||
shipped_to_id: invoice.compliance_mx?.shipped_to_id || null,
|
||||
customs_broker_id: invoice.compliance_mx?.customs_broker_id || null,
|
||||
customs_broker_us_id: null,
|
||||
|
||||
// RIGHT fields
|
||||
currency_type: invoice.financials?.currency_type || '',
|
||||
currency_mode: 'extranjera', // extranjera, nacional, captura
|
||||
weight_type: '',
|
||||
iva_factor: invoice.financials?.iva_factor || null,
|
||||
carrier_id: invoice.logistics?.[0]?.carrier_id || null,
|
||||
transport_id: '',
|
||||
driver_name: invoice.logistics?.[0]?.driver_name || '',
|
||||
transport_type: invoice.logistics?.[0]?.transport_type || '',
|
||||
transport_num: invoice.logistics?.[0]?.vehicle_num || '',
|
||||
aduana: invoice.compliance_mx?.aduana || '',
|
||||
invoice_type: invoice.invoice_type || '',
|
||||
clave_regimen_aduanero: '',
|
||||
};
|
||||
} else {
|
||||
// Creando una nueva factura
|
||||
formData = {
|
||||
// TOP fields
|
||||
is_pedimento_pending: false,
|
||||
pedimento: '',
|
||||
remesa: '',
|
||||
invoice_number: '',
|
||||
invoice_date: '',
|
||||
emission_date: '',
|
||||
|
||||
// Extra fields
|
||||
operation_type: defaultOperationType ?? null,
|
||||
|
||||
// RANGO DE FECHAS fields
|
||||
fecha_pedimento_del: '',
|
||||
fecha_pedimento_al: '',
|
||||
clave_pedimento: '',
|
||||
regimen_pedimento: '',
|
||||
|
||||
// LEFT fields
|
||||
provider_header: '',
|
||||
provider_id: null,
|
||||
sold_to_header: '',
|
||||
sold_to_id: null,
|
||||
shipped_to_header: '',
|
||||
shipped_to_id: null,
|
||||
customs_broker_id: null,
|
||||
customs_broker_us_id: null,
|
||||
|
||||
// RIGHT fields
|
||||
currency_type: '',
|
||||
currency_mode: 'extranjera', // extranjera, nacional, captura
|
||||
weight_type: '',
|
||||
iva_factor: null,
|
||||
carrier_id: null,
|
||||
transport_id: '',
|
||||
driver_name: '',
|
||||
transport_type: '',
|
||||
transport_num: '',
|
||||
aduana: '',
|
||||
invoice_type: defaultInvoiceType ?? '',
|
||||
clave_regimen_aduanero: '',
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Si formData ya existe, asegurar que tiene currency_mode
|
||||
if (formData.currency_mode === undefined) {
|
||||
formData.currency_mode = 'extranjera';
|
||||
}
|
||||
}
|
||||
|
||||
// Opciones de tipo de peso
|
||||
const weightTypeOptions = [
|
||||
{ value: 'kg', label: 'Kilogramos (kg)' },
|
||||
{ value: 'lb', label: 'Libras (lb)' }
|
||||
];
|
||||
|
||||
// Opciones de encabezados
|
||||
const providerHeaderOptions = [
|
||||
{ value: 'proveedor', label: 'Proveedor' },
|
||||
{ value: 'exportador', label: 'Exportador' }
|
||||
];
|
||||
|
||||
const soldToHeaderOptions = $derived([
|
||||
{ value: 'consignado_a', label: 'Consignado a' },
|
||||
{ value: 'vendido_a', label: 'Vendido a' },
|
||||
{ value: formData.operation_type === 1 ? 'exportado_a' : 'importador', label: formData.operation_type === 1 ? 'Exportado a' : 'Importador' }
|
||||
]);
|
||||
|
||||
const shippedToHeaderOptions = $derived(
|
||||
formData.operation_type === 1
|
||||
? [
|
||||
{ value: 'enviado_por', label: 'Enviado Por' },
|
||||
{ value: 'destinatario', label: 'Destinatario' },
|
||||
{ value: 'vendido_por', label: 'Vendido Por' },
|
||||
{ value: 'consignado_a', label: 'Consignado a' },
|
||||
{ value: 'vendido_a', label: 'Vendido a' },
|
||||
{ value: 'exportado_a', label: 'Exportado a' },
|
||||
{ value: 'enviado_a', label: 'Enviado a' },
|
||||
{ value: 'transferido_a', label: 'Transferido a' },
|
||||
{ value: 'donado_a', label: 'Donado a' },
|
||||
{ value: 'notificar_a', label: 'Notificar a' }
|
||||
]
|
||||
: [
|
||||
{ value: 'enviado_a', label: 'Enviado a' },
|
||||
{ value: 'transferido_a', label: 'Transferido a' }
|
||||
]
|
||||
);
|
||||
|
||||
// Combinar clientes y proveedores para shipped_to
|
||||
const allClientsProviders = [...clients, ...providers];
|
||||
</script>
|
||||
|
||||
<!-- Layout de 2 columnas compacto -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- Columna Izquierda: Clientes - Proveedores - Agente Aduanal -->
|
||||
<div class="border rounded-md p-3 space-y-3">
|
||||
<div class="space-y-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Datos del pedimento</h4>
|
||||
<div class="grid grid-cols-4 gap-3 text-xs">
|
||||
<div>
|
||||
<span class="text-muted-foreground">Fecha del:</span>
|
||||
<p class="font-medium">{formData.fecha_pedimento_del || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">Fecha al:</span>
|
||||
<p class="font-medium">{formData.fecha_pedimento_al || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">Clave:</span>
|
||||
<p class="font-medium">{formData.clave_pedimento || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-muted-foreground">Régimen:</span>
|
||||
<p class="font-medium">{formData.regimen_pedimento || '-'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Clientes - Proveedores - Agente Aduanal</h4>
|
||||
<div class="space-y-1.5">
|
||||
<Label for="provider_id" class="text-xs">Proveedor:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.provider_header || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.provider_header = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="provider_header" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.provider_header
|
||||
? providerHeaderOptions.find(o => o.value === formData.provider_header)?.label || formData.provider_header
|
||||
: 'Selecciona encabezado...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each providerHeaderOptions as option}
|
||||
<Select.Item value={option.value}>
|
||||
{option.label}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.provider_id ? String(formData.provider_id) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.provider_id = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="provider_id" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.provider_id
|
||||
? providers.find(p => p.id === formData.provider_id)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each providers as provider}
|
||||
<Select.Item value={String(provider.id)}>
|
||||
{provider.name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="sold_to_id" class="text-xs">Consignado a:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.sold_to_header || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.sold_to_header = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="sold_to_header" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.sold_to_header
|
||||
? soldToHeaderOptions.find(o => o.value === formData.sold_to_header)?.label || formData.sold_to_header
|
||||
: 'Selecciona encabezado...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each soldToHeaderOptions as option}
|
||||
<Select.Item value={option.value}>
|
||||
{option.label}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.sold_to_id ? String(formData.sold_to_id) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.sold_to_id = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="sold_to_id" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.sold_to_id
|
||||
? clients.find(c => c.id === formData.sold_to_id)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each clients as client}
|
||||
<Select.Item value={String(client.id)}>
|
||||
{client.name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="shipped_to_id" class="text-xs">Enviado a:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.shipped_to_header || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.shipped_to_header = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="shipped_to_header" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.shipped_to_header
|
||||
? shippedToHeaderOptions.find(o => o.value === formData.shipped_to_header)?.label || formData.shipped_to_header
|
||||
: 'Selecciona encabezado...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each shippedToHeaderOptions as option}
|
||||
<Select.Item value={option.value}>
|
||||
{option.label}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.shipped_to_id ? String(formData.shipped_to_id) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.shipped_to_id = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="shipped_to_id" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.shipped_to_id
|
||||
? allClientsProviders.find(cp => cp.id === formData.shipped_to_id)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each allClientsProviders as cp}
|
||||
<Select.Item value={String(cp.id)}>
|
||||
{cp.name} ({cp.type === 'client' ? 'C' : 'P'})
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="space-y-1.5">
|
||||
<Label for="customs_broker_id" class="text-xs">Agente Aduanal Mex:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.customs_broker_id || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.customs_broker_id = v || null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="customs_broker_id" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.customs_broker_id
|
||||
? customsBrokers.find(cb => cb.broker_key === formData.customs_broker_id)?.name || '...'
|
||||
: '...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each customsBrokers as broker}
|
||||
<Select.Item value={broker.broker_key}>
|
||||
{broker.name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="customs_broker_us_id" class="text-xs">Agente Aduanal Ame:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.customs_broker_us_id || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.customs_broker_us_id = v || null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="customs_broker_us_id" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.customs_broker_us_id
|
||||
? customsBrokers.find(cb => cb.broker_key === formData.customs_broker_us_id)?.name || '...'
|
||||
: '...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each customsBrokers as broker}
|
||||
<Select.Item value={broker.broker_key}>
|
||||
{broker.name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Columna Derecha: Tipo de Moneda y Transportista -->
|
||||
<div class="space-y-3">
|
||||
<!-- Tipo de Moneda - Pesos Netos y Brutos -->
|
||||
<div class="border rounded-md p-3 space-y-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Tipo de Moneda - Pesos Netos y Brutos</h4>
|
||||
|
||||
<!-- Radio buttons para tipo de moneda -->
|
||||
<div class="space-y-1.5">
|
||||
<RadioGroup.Root bind:value={formData.currency_mode} class="flex gap-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="extranjera" id="currency-extranjera" class="h-4 w-4" />
|
||||
<Label for="currency-extranjera" class="text-xs font-normal cursor-pointer">Extranjera (Dlls)</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="nacional" id="currency-nacional" class="h-4 w-4" />
|
||||
<Label for="currency-nacional" class="text-xs font-normal cursor-pointer">Nacional (Pesos)</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroup.Item value="captura" id="currency-captura" class="h-4 w-4" />
|
||||
<Label for="currency-captura" class="text-xs font-normal cursor-pointer">De Captura</Label>
|
||||
</div>
|
||||
</RadioGroup.Root>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="space-y-1.5">
|
||||
<Label for="currency_type" class="text-xs">Moneda:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.currency_type || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.currency_type = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="currency_type" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.currency_type || '...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each currencyTypes as currencyType}
|
||||
<Select.Item value={currencyType.code}>
|
||||
{currencyType.code}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="weight_type" class="text-xs">Tipo Peso:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.weight_type || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.weight_type = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="weight_type" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.weight_type || '...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each weightTypeOptions as weightType}
|
||||
<Select.Item value={weightType.value}>
|
||||
{weightType.value}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="iva_factor" class="text-xs">IVA:</Label>
|
||||
<Input id="iva_factor" type="number" step="0.0001" bind:value={formData.iva_factor} placeholder="0.16" class="h-7 text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="invoice_type" class="text-xs">Tipo de Cambio:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.invoice_type || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.invoice_type = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="invoice_type" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.invoice_type
|
||||
? `${formData.invoice_type}`
|
||||
: '...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each invoiceTypes as type}
|
||||
<Select.Item value={type.key}>
|
||||
{type.key} - {type.description}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transportista -->
|
||||
<div class="border rounded-md p-3 space-y-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Transportista</h4>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="space-y-1.5">
|
||||
<Label for="carrier_id" class="text-xs">Clave:</Label>
|
||||
<Input id="carrier_id" type="number" bind:value={formData.carrier_id} class="h-7 text-xs" />
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 space-y-1.5">
|
||||
<Label for="transport_type" class="text-xs">Clave Transporte:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.transport_type || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.transport_type = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="transport_type" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.transport_type || '...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each transportTypes as transportType}
|
||||
<Select.Item value={transportType.transport_code}>
|
||||
{transportType.transport_code}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="driver_name" class="text-xs">Conductor:</Label>
|
||||
<Input id="driver_name" bind:value={formData.driver_name} class="h-7 text-xs" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="space-y-1.5">
|
||||
<Label for="transport_type" class="text-xs">Tipo Transporte:</Label>
|
||||
<Input id="transport_id" bind:value={formData.transport_id} class="h-7 text-xs" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="transport_num" class="text-xs">Placas:</Label>
|
||||
<Input id="transport_num" bind:value={formData.transport_num} class="h-7 text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="aduana" class="text-xs">Aduana y Sección de Despacho:</Label>
|
||||
<Input id="aduana" bind:value={formData.aduana} class="h-7 text-xs" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-1.5">
|
||||
<Label for="clave_regimen_aduanero" class="text-xs">Clave de Régimen Aduanero:</Label>
|
||||
<Input id="clave_regimen_aduanero" bind:value={formData.clave_regimen_aduanero} class="h-7 text-xs" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,176 @@
|
||||
<script lang="ts">
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
|
||||
import type { InvoiceType } from '$lib/api/dashboard/refrence_data/invoice_types';
|
||||
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
|
||||
|
||||
let {
|
||||
invoice,
|
||||
formData = $bindable(),
|
||||
invoiceTypes = [],
|
||||
pedimentos = [],
|
||||
defaultOperationType = undefined,
|
||||
defaultInvoiceType = undefined,
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
invoiceTypes?: InvoiceType[];
|
||||
pedimentos?: Pedimento[];
|
||||
defaultOperationType?: number | null;
|
||||
defaultInvoiceType?: string | null;
|
||||
} = $props();
|
||||
|
||||
function handlePedimentoChange(pedimentoId: string) {
|
||||
if (!pedimentoId) return;
|
||||
|
||||
const selectedPedimento = pedimentos.find(p => p.id === parseInt(pedimentoId));
|
||||
if (!selectedPedimento) return;
|
||||
|
||||
// Actualizar los campos del pedimento en formData
|
||||
formData.fecha_pedimento_del = selectedPedimento.pedimento_dates?.start_date || '';
|
||||
formData.fecha_pedimento_al = selectedPedimento.pedimento_dates?.end_date || '';
|
||||
formData.clave_pedimento = selectedPedimento.pedimento_code || '';
|
||||
formData.regimen_pedimento = selectedPedimento.regime || '';
|
||||
|
||||
// Construir el número de pedimento completo
|
||||
const pedimentoNumber = `${selectedPedimento.year || ''}-${selectedPedimento.customs_office || ''}-${selectedPedimento.license || ''}-${selectedPedimento.pedimento_number || ''}`.replace(/^-+|-+$/g, '');
|
||||
formData.pedimento = pedimentoNumber;
|
||||
}
|
||||
|
||||
if (!formData) {
|
||||
let operationType: number | null = null;
|
||||
if (invoice?.operation_type) {
|
||||
operationType = invoice.operation_type === 'exp' ? 1 : 2;
|
||||
} else if (defaultOperationType !== undefined) {
|
||||
operationType = defaultOperationType ?? null;
|
||||
}
|
||||
|
||||
formData = {
|
||||
is_pedimento_pending: false,
|
||||
pedimento_id: null,
|
||||
pedimento: invoice?.compliance_mx?.pedimento || '',
|
||||
remesa: invoice?.compliance_mx?.remesa || '',
|
||||
invoice_number: invoice?.invoice_number || '',
|
||||
invoice_date: invoice?.invoice_date || '',
|
||||
emission_date: '',
|
||||
operation_type: operationType,
|
||||
invoice_type: invoice?.invoice_type || (defaultInvoiceType ?? ''),
|
||||
// Campos del pedimento (se llenarán al seleccionar un pedimento)
|
||||
fecha_pedimento_del: '',
|
||||
fecha_pedimento_al: '',
|
||||
clave_pedimento: '',
|
||||
regimen_pedimento: '',
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Datos Principales en una fila compacta (reusable across tabs) -->
|
||||
<div class="grid grid-cols-12 gap-3 items-end pb-3">
|
||||
<div class="col-span-1 space-y-1">
|
||||
<Label for="operation_type" class="text-xs">Tipo de Operación *</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.operation_type !== null ? String(formData.operation_type) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.operation_type = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="operation_type" class="h-8 text-sm">
|
||||
<span class="truncate">
|
||||
{formData.operation_type !== null
|
||||
? (formData.operation_type === 1 ? 'Exp' : 'Imp')
|
||||
: '...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="1">Exportación</Select.Item>
|
||||
<Select.Item value="2">Importación</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="col-span-1 space-y-1">
|
||||
<Label for="operation_type" class="text-xs">Tipo de Operación *</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.invoice_type || ''}
|
||||
onValueChange={(v) => {
|
||||
formData.invoice_type = v ?? '';
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="invoice_type" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.invoice_type
|
||||
? `${formData.invoice_type}`
|
||||
: '...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each invoiceTypes as type}
|
||||
<Select.Item value={type.key}>
|
||||
{type.key} - {type.description}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<div class="col-span-1 space-y-1 pb-1 items-center flex flex-col">
|
||||
<Label for="is_pedimento_pending" class="text-xs">Pedimento Pendiente?</Label>
|
||||
<Switch
|
||||
id="is_pedimento_pending"
|
||||
checked={formData.is_pedimento_pending}
|
||||
onCheckedChange={(checked) => {
|
||||
formData.is_pedimento_pending = checked;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="col-span-2 space-y-1">
|
||||
<Label for="pedimento" class="text-xs">Pedimento</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.pedimento_id ? String(formData.pedimento_id) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.pedimento_id = v ? parseInt(v) : null;
|
||||
if (v) {
|
||||
handlePedimentoChange(v);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="pedimento" class="h-8 text-sm">
|
||||
<span class="truncate">
|
||||
{formData.pedimento || 'Selecciona pedimento...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each pedimentos as pedimento}
|
||||
<Select.Item value={String(pedimento.id)}>
|
||||
{pedimento.year}-{pedimento.customs_office}-{pedimento.license}-{pedimento.pedimento_number}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<div class="col-span-1 space-y-1">
|
||||
<Label for="remesa" class="text-xs">Remesa</Label>
|
||||
<Input id="remesa" bind:value={formData.remesa} class="h-8 text-sm" />
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 space-y-1">
|
||||
<Label for="invoice_number" class="text-xs">Núm. Factura *</Label>
|
||||
<Input id="invoice_number" bind:value={formData.invoice_number} class="h-8 text-sm font-medium" required />
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 space-y-1">
|
||||
<Label for="invoice_date" class="text-xs">Fecha Factura</Label>
|
||||
<Input id="invoice_date" type="date" bind:value={formData.invoice_date} class="h-8 text-sm" />
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 space-y-1">
|
||||
<Label for="emission_date" class="text-xs">Fecha Emisión</Label>
|
||||
<Input id="emission_date" type="date" bind:value={formData.emission_date} class="h-8 text-sm" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,404 @@
|
||||
<script lang="ts">
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Textarea } from "$lib/components/ui/textarea/index.js";
|
||||
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
|
||||
|
||||
let {
|
||||
invoice,
|
||||
formData = $bindable(),
|
||||
exists = $bindable(),
|
||||
seals = [],
|
||||
incoterms = [],
|
||||
enclosure = [],
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
seals?: any[];
|
||||
incoterms?: any[];
|
||||
enclosure?: any[];
|
||||
} = $props();
|
||||
|
||||
if (!formData && invoice) {
|
||||
formData = {
|
||||
// Invoice header fields
|
||||
observation_es: invoice.observation_es || '',
|
||||
observation_en: invoice.observation_en || '',
|
||||
alternate_invoice: invoice.alternate_invoice || '',
|
||||
// Compliance MX fields
|
||||
pedimento: invoice.compliance_mx?.pedimento || '',
|
||||
pedimento_code: invoice.compliance_mx?.pedimento_code || '',
|
||||
pedimento_k1: invoice.compliance_mx?.pedimento_k1 || '',
|
||||
remesa: invoice.compliance_mx?.remesa || null,
|
||||
aduana: invoice.compliance_mx?.aduana || '',
|
||||
port_of_entry: invoice.compliance_mx?.port_of_entry || '',
|
||||
destination: invoice.compliance_mx?.destination || '',
|
||||
manifest_number: invoice.compliance_mx?.manifest_number || '',
|
||||
provider_header: invoice.compliance_mx?.provider_header || '',
|
||||
provider_id: invoice.compliance_mx?.provider_id || null,
|
||||
sold_to_header: invoice.compliance_mx?.sold_to_header || '',
|
||||
sold_to_id: invoice.compliance_mx?.sold_to_id || null,
|
||||
shipped_to_header: invoice.compliance_mx?.shipped_to_header || '',
|
||||
shipped_to_id: invoice.compliance_mx?.shipped_to_id || null,
|
||||
shipped_by_header: invoice.compliance_mx?.shipped_by_header || '',
|
||||
shipped_by_id: invoice.compliance_mx?.shipped_by_id || null,
|
||||
customs_broker_id: invoice.compliance_mx?.customs_broker_id || null,
|
||||
broker_invoice_num: invoice.compliance_mx?.broker_invoice_num || '',
|
||||
broker_invoice_date: invoice.compliance_mx?.broker_invoice_date || '',
|
||||
is_mixed: invoice.compliance_mx?.is_mixed || null,
|
||||
waste_type: invoice.compliance_mx?.waste_type || '',
|
||||
scrap_type: invoice.compliance_mx?.scrap_type || '',
|
||||
appendix_17: invoice.compliance_mx?.appendix_17 || null,
|
||||
is_regime_change: invoice.compliance_mx?.is_regime_change || '',
|
||||
which_exchange_rate: invoice.compliance_mx?.which_exchange_rate || '',
|
||||
value_method: invoice.compliance_mx?.value_method || '',
|
||||
act_value: invoice.compliance_mx?.act_value || '',
|
||||
is_pedimento_pending: invoice.compliance_mx?.is_pedimento_pending || false,
|
||||
is_owner_of_goods: invoice.compliance_mx?.is_owner_of_goods || '',
|
||||
generate_balances: invoice.compliance_mx?.generate_balances || '',
|
||||
was_reviewed_by_company: invoice.compliance_mx?.was_reviewed_by_company || false,
|
||||
edocument: invoice.compliance_mx?.edocument || '',
|
||||
electronic_signature: invoice.compliance_mx?.electronic_signature || '',
|
||||
certificate_number: invoice.compliance_mx?.certificate_number || '',
|
||||
niu_number: invoice.compliance_mx?.niu_number || '',
|
||||
bill_of_lading_count: invoice.compliance_mx?.bill_of_lading_count || '',
|
||||
addendum_vu: invoice.compliance_mx?.addendum_vu || '',
|
||||
origin_destination_cove: invoice.compliance_mx?.origin_destination_cove || '',
|
||||
vucem_operation_num: invoice.compliance_mx?.vucem_operation_num || '',
|
||||
customs_person_line: invoice.compliance_mx?.customs_person_line || null,
|
||||
contingency_mode: invoice.compliance_mx?.contingency_mode || false,
|
||||
enclosure: invoice.compliance_mx?.enclosure || '',
|
||||
guide_type_to_identify: invoice.compliance_mx?.guide_type_to_identify || '',
|
||||
location: invoice.compliance_mx?.location || '',
|
||||
dot_code: invoice.compliance_mx?.dot_code || '',
|
||||
subdivision: invoice.compliance_mx?.subdivision || '',
|
||||
acts_as: invoice.compliance_mx?.acts_as || '',
|
||||
movement_type: invoice.compliance_mx?.movement_type || '',
|
||||
office_document: invoice.compliance_mx?.office_document || '',
|
||||
reason_export: invoice.compliance_mx?.reason_export || '',
|
||||
signature_key: invoice.compliance_mx?.signature_key || '',
|
||||
sem_id: invoice.compliance_mx?.sem_id || null,
|
||||
// Financials fields (incrementables)
|
||||
freight: invoice.financials?.freight || null,
|
||||
insurance_value: invoice.financials?.insurance_value || null,
|
||||
insurance: invoice.financials?.insurance || null,
|
||||
packaging: invoice.financials?.packaging || null,
|
||||
other_increments: invoice.financials?.other_increments || null,
|
||||
total_increments_mn: invoice.financials?.total_increments_mn || null,
|
||||
total_increments_me: invoice.financials?.total_increments_me || null,
|
||||
// Logistics fields
|
||||
incoterm: invoice.logistics?.[0]?.incoterm || ''
|
||||
};
|
||||
exists = true;
|
||||
} else if (!formData) {
|
||||
formData = {
|
||||
// Invoice header fields
|
||||
observation_es: '',
|
||||
observation_en: '',
|
||||
alternate_invoice: '',
|
||||
// Compliance MX fields
|
||||
pedimento: '',
|
||||
pedimento_code: '',
|
||||
pedimento_k1: '',
|
||||
remesa: null,
|
||||
aduana: '',
|
||||
port_of_entry: '',
|
||||
destination: '',
|
||||
manifest_number: '',
|
||||
provider_header: '',
|
||||
provider_id: null,
|
||||
sold_to_header: '',
|
||||
sold_to_id: null,
|
||||
shipped_to_header: '',
|
||||
shipped_to_id: null,
|
||||
shipped_by_header: '',
|
||||
shipped_by_id: null,
|
||||
customs_broker_id: null,
|
||||
broker_invoice_num: '',
|
||||
broker_invoice_date: '',
|
||||
is_mixed: null,
|
||||
waste_type: '',
|
||||
scrap_type: '',
|
||||
appendix_17: null,
|
||||
is_regime_change: '',
|
||||
which_exchange_rate: '',
|
||||
value_method: '',
|
||||
act_value: '',
|
||||
is_pedimento_pending: false,
|
||||
is_owner_of_goods: '',
|
||||
generate_balances: '',
|
||||
was_reviewed_by_company: false,
|
||||
edocument: '',
|
||||
electronic_signature: '',
|
||||
certificate_number: '',
|
||||
niu_number: '',
|
||||
bill_of_lading_count: '',
|
||||
addendum_vu: '',
|
||||
origin_destination_cove: '',
|
||||
vucem_operation_num: '',
|
||||
customs_person_line: null,
|
||||
contingency_mode: false,
|
||||
enclosure: '',
|
||||
guide_type_to_identify: '',
|
||||
location: '',
|
||||
dot_code: '',
|
||||
subdivision: '',
|
||||
acts_as: '',
|
||||
movement_type: '',
|
||||
office_document: '',
|
||||
reason_export: '',
|
||||
signature_key: '',
|
||||
sem_id: null,
|
||||
// Financials fields
|
||||
freight: null,
|
||||
insurance_value: null,
|
||||
insurance: null,
|
||||
packaging: null,
|
||||
other_increments: null,
|
||||
total_increments_mn: null,
|
||||
total_increments_me: null,
|
||||
// Logistics fields
|
||||
incoterm: ''
|
||||
};
|
||||
exists = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<div class="grid grid-cols-2 grid-rows-5 gap-4">
|
||||
<div class="border rounded-md p-3 space-y-3 row-span-2">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Observacion de la factura mexicana y bilingue:</h4>
|
||||
<div class="space-y-1">
|
||||
<Textarea
|
||||
id="observation_es"
|
||||
bind:value={formData.observation_es}
|
||||
class="min-h-[150px] max-h-[150px] text-sm font-medium"
|
||||
placeholder="Escribe tus observaciones aqui."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md p-3 space-y-3 row-span-2 col-start-1 row-start-3">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Observacion de la factura americana:</h4>
|
||||
<Textarea
|
||||
id="observation_en"
|
||||
bind:value={formData.observation_en}
|
||||
class="min-h-[150px] max-h-[150px] text-sm font-medium"
|
||||
placeholder="Escribe tus observaciones aqui."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md p-3 space-y-3 col-span-2 col-start-1 row-start-5">
|
||||
<div class="grid grid-cols-4 gap-3">
|
||||
<div class="space-y-1">
|
||||
<Label for="num_seals" class="text-xs">Num Precintos:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.num_seals ? String(formData.num_seals) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.num_seals = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="num_seals" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.num_seals
|
||||
? seals.find(p => p.id === formData.num_seals)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each seals as seal}
|
||||
<Select.Item value={String(seal.id)}>
|
||||
{seal.name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="movement_type" class="text-xs">Tipo Movimiento:</Label>
|
||||
<Input
|
||||
id="movement_type"
|
||||
bind:value={formData.movement_type}
|
||||
class="h-8 text-sm font-medium"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="alternate_invoice" class="text-xs">Factura Alterna:</Label>
|
||||
<Input
|
||||
id="alternate_invoice"
|
||||
bind:value={formData.alternate_invoice}
|
||||
class="h-8 text-sm font-medium"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="valuation_method" class="text-xs">Met. Valoracion:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.valuation_method ? String(formData.valuation_method) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.valuation_method = v;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="valuation_method" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.valuation_method || 'Selecciona...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
<Select.Item value="general">General</Select.Item>
|
||||
<Select.Item value="devalued">Devaluado</Select.Item>
|
||||
<Select.Item value="special">Especial</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md p-3 space-y-3 row-span-3 col-start-2 row-start-1">
|
||||
<h4 class="text-xs font-semibold text-muted-foreground uppercase">Incrementables:</h4>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="space-y-1">
|
||||
<Label for="freight" class="text-xs">Flete:</Label>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Input
|
||||
id="freight"
|
||||
type="number"
|
||||
bind:value={formData.freight}
|
||||
class="h-8 text-sm font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label for="insurance_value" class="text-xs">Val. Seguros:</Label>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Input
|
||||
id="insurance_value"
|
||||
type="number"
|
||||
bind:value={formData.insurance_value}
|
||||
class="h-8 text-sm font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label for="insurance" class="text-xs">Seguros:</Label>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Input
|
||||
id="insurance"
|
||||
type="number"
|
||||
bind:value={formData.insurance}
|
||||
class="h-8 text-sm font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label for="packaging" class="text-xs">Embalajes:</Label>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Input
|
||||
id="packaging"
|
||||
type="number"
|
||||
bind:value={formData.packaging}
|
||||
class="h-8 text-sm font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label for="other_increments" class="text-xs">Otros incrementables:</Label>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Input
|
||||
id="other_increments"
|
||||
type="number"
|
||||
bind:value={formData.other_increments}
|
||||
class="h-8 text-sm font-medium"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<Label for="total_increments_mn" class="text-xs">Total Incrementables:</Label>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Input
|
||||
id="total_increments_mn"
|
||||
type="number"
|
||||
bind:value={formData.total_increments_mn}
|
||||
class="h-8 text-sm font-medium"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Input
|
||||
id="total_increments_me"
|
||||
type="number"
|
||||
bind:value={formData.total_increments_me}
|
||||
class="h-8 text-sm font-medium"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md p-3 space-y-3 col-start-2 row-start-4">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="space-y-1">
|
||||
<Label for="incoterm" class="text-xs">Incoterm:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.incoterm ? String(formData.incoterm) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.incoterm = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="incoterm" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.incoterm
|
||||
? incoterms.find(p => p.id === formData.incoterm)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each incoterms as inco}
|
||||
<Select.Item value={String(inco.id)}>
|
||||
{inco.name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<Label for="enclosure" class="text-xs">Recinto:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={formData.enclosure ? String(formData.enclosure) : ''}
|
||||
onValueChange={(v) => {
|
||||
formData.enclosure = v ? parseInt(v) : null;
|
||||
}}
|
||||
>
|
||||
<Select.Trigger id="enclosure" class="h-7 text-xs">
|
||||
<span class="truncate">
|
||||
{formData.enclosure
|
||||
? enclosure.find(p => p.id === formData.enclosure)?.name || 'Selecciona...'
|
||||
: 'Selecciona...'}
|
||||
</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each enclosure as rec}
|
||||
<Select.Item value={String(rec.id)}>
|
||||
{rec.name}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,285 @@
|
||||
<script lang="ts">
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import { RadioGroup, RadioGroupItem } from '$lib/components/ui/radio-group';
|
||||
import * as Select from '$lib/components/ui/select';
|
||||
import { Textarea } from '$lib/components/ui/textarea';
|
||||
import { Plus, Upload } from 'lucide-svelte';
|
||||
import type { Invoice } from '$lib/api/dashboard/a76/invoices';
|
||||
|
||||
let {
|
||||
invoice,
|
||||
formData = $bindable(),
|
||||
exists = $bindable()
|
||||
}: {
|
||||
invoice: Invoice | null;
|
||||
formData?: any;
|
||||
exists?: boolean;
|
||||
} = $props();
|
||||
|
||||
if (!formData && invoice?.logistics && invoice.logistics.length > 0) {
|
||||
formData = invoice.logistics.map(l => ({
|
||||
// Carrier info
|
||||
carrier_id: l.carrier_id || '',
|
||||
transport_id: l.transport_id || '',
|
||||
transport_us_id: l.transport_us_id || '',
|
||||
transport_type: l.transport_type || null,
|
||||
transport_num: l.transport_num || '',
|
||||
transport_mode: l.transport_mode || '',
|
||||
driver_name: l.driver_name || '',
|
||||
is_rail: l.is_rail || '',
|
||||
rail_id: l.rail_id || '',
|
||||
// Vehicle & tracking
|
||||
vehicle_num: l.vehicle_num || '',
|
||||
license_plate: l.license_plate || '',
|
||||
license_plate_complete: l.license_plate_complete || '',
|
||||
trailer_num: l.trailer_num || '',
|
||||
seal_number: l.seal_number || '',
|
||||
guide_number: l.guide_number || '',
|
||||
bill_number: l.bill_number || '',
|
||||
reference_number: l.reference_number || '',
|
||||
shipment_number: l.shipment_number || '',
|
||||
// Incoterms
|
||||
incoterm: l.incoterm || '',
|
||||
// Identifiers
|
||||
identifier_1: l.identifier_1 || '',
|
||||
complement_1: l.complement_1 || '',
|
||||
identifier_2: l.identifier_2 || '',
|
||||
complement_2: l.complement_2 || '',
|
||||
// Weight & container
|
||||
weight_type: l.weight_type || '',
|
||||
container_types: l.container_types || '',
|
||||
vehicle_data: l.vehicle_data || '',
|
||||
// Locations
|
||||
origin_location: l.origin_location || '',
|
||||
destination_location: l.destination_location || '',
|
||||
transport_itinerary: l.transport_itinerary || '',
|
||||
destination_goods: l.destination_goods || '',
|
||||
// Dates
|
||||
entry_exit_date: l.entry_exit_date || '',
|
||||
delivery_date: l.delivery_date || '',
|
||||
// Delivery control
|
||||
delivered_status: l.delivered_status || '',
|
||||
received_by: l.received_by || '',
|
||||
// Payment
|
||||
payment_date: l.payment_date || '',
|
||||
payment_receipt_num: l.payment_receipt_num || '',
|
||||
// CTM
|
||||
is_ctm_process: l.is_ctm_process || ''
|
||||
}));
|
||||
exists = true;
|
||||
} else if (!formData) {
|
||||
formData = [];
|
||||
exists = false;
|
||||
}
|
||||
|
||||
// Campos adicionales que van en otros recursos
|
||||
let transportMode = $state('TRUCK');
|
||||
// is_mixed va en compliance_mx
|
||||
let isMixed = $state(invoice?.compliance_mx?.is_mixed ? 'yes' : 'no');
|
||||
// related_doc_id va en invoice header
|
||||
let relationDocsId = $state(invoice?.related_doc_id?.toString() || '0');
|
||||
// electronic_signature va en compliance_mx
|
||||
let code_signature = $state(invoice?.compliance_mx?.code_signature || '');
|
||||
let electronicSignature = $state(invoice?.compliance_mx?.electronic_signature || '');
|
||||
// Estos campos no existen en el schema del backend
|
||||
let mandatoryPerson = $state('0');
|
||||
let rfc = $state('');
|
||||
let contingencyMode = $state(invoice?.compliance_mx?.contingency_mode || false);
|
||||
let curp = $state('');
|
||||
let rule3121PartiesII = $state<boolean>(false);
|
||||
// origin_destination_cove va en compliance_mx
|
||||
let cove = $state(invoice?.compliance_mx?.origin_destination_cove || '');
|
||||
// vucem_operation_num va en compliance_mx
|
||||
let operationNum = $state(invoice?.compliance_mx?.vucem_operation_num || '');
|
||||
// addendum_vu va en compliance_mx
|
||||
let adendas = $state(invoice?.compliance_mx?.addendum_vu || '');
|
||||
// vu_observations va en invoice header
|
||||
let observationsVU = $state(invoice?.vu_observations || '');
|
||||
// certificate_number va en compliance_mx
|
||||
let certifiedNumber = $state(invoice?.compliance_mx?.certificate_number || '');
|
||||
// seal_value_2500 va en financials
|
||||
let printStamp = $state(invoice?.financials?.seal_value_2500 || false);
|
||||
// comments_status va en invoice header
|
||||
let commentsStatus = $state(invoice?.comments_status || '');
|
||||
|
||||
const transportModes = [
|
||||
{ value: 'TRUCK', label: 'Camión' },
|
||||
{ value: 'TRAIN', label: 'Tren' },
|
||||
{ value: 'SHIP', label: 'Marítimo' },
|
||||
{ value: 'AIR', label: 'Aéreo' },
|
||||
{ value: 'OTHER', label: 'Otro' }
|
||||
];
|
||||
|
||||
function addLogistic() {
|
||||
formData = [...formData, {
|
||||
carrier_id: '',
|
||||
transport_type: null,
|
||||
driver_name: '',
|
||||
vehicle_num: '',
|
||||
license_plate: ''
|
||||
}];
|
||||
}
|
||||
|
||||
function loadInfo() {
|
||||
// Función para cargar información
|
||||
console.log('Cargar información');
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-3 grid-rows-1 gap-3">
|
||||
<div class="border rounded-md p-3 space-y-3">
|
||||
|
||||
<!-- Modo de Transporte -->
|
||||
<div class="space-y-2">
|
||||
<Label for="transport-mode">Modo de Transporte:</Label>
|
||||
<Select.Root type="single" value={transportMode} onValueChange={(value: string | undefined) => transportMode = value || 'TRUCK'}>
|
||||
<Select.Trigger id="transport-mode">
|
||||
{transportModes.find(m => m.value === transportMode)?.label || 'Seleccionar modo'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each transportModes as mode}
|
||||
<Select.Item value={mode.value}>
|
||||
{mode.label}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
<!-- Imprimir Sello -->
|
||||
<div class="space-y-2">
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="print-stamp" bind:checked={printStamp} />
|
||||
<Label for="print-stamp" class="font-normal">
|
||||
Imprimir el Sello por Valor menor a 2500 dlls
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Es Mixto -->
|
||||
<div class="space-y-2">
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
<Label>Es Mixto?</Label>
|
||||
<RadioGroup bind:value={isMixed} class="flex gap-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem value="yes" id="mixed-yes" />
|
||||
<Label for="mixed-yes" class="font-normal">Sí</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<RadioGroupItem value="no" id="mixed-no" />
|
||||
<Label for="mixed-no" class="font-normal">No</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="rule-3121" bind:checked={rule3121PartiesII} />
|
||||
<Label for="rule-3121" class="font-normal">Regla 3.1.21 Partes II</Label>
|
||||
</div>
|
||||
|
||||
<!-- Comentario estatus -->
|
||||
<div class="space-y-2">
|
||||
<Label class="opacity-0">Spacer</Label>
|
||||
<Label>Comentario Estatus:</Label>
|
||||
<Textarea
|
||||
id="description_es"
|
||||
bind:value={formData.description_es}
|
||||
placeholder="Comentario estatus"
|
||||
rows={3}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md p-3 space-y-3 col-span-2">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- ID Relación Docs -->
|
||||
<div class="space-y-2">
|
||||
<Label for="relation-docs-id">ID Relación Docs:</Label>
|
||||
<Input id="relation-docs-id" bind:value={relationDocsId} />
|
||||
</div>
|
||||
|
||||
<!-- Firma Electrónica -->
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="electronic-sig-1">Firma Electrónica:</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input id="electronic-sig-1" bind:value={code_signature} class="flex-1" />
|
||||
<Button variant="outline" size="icon">
|
||||
<Upload class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mandatario/Persona Autorizada -->
|
||||
<div class="space-y-2">
|
||||
<Label for="mandatory-person">Mandatario/Persona Autorizada:</Label>
|
||||
<Input id="mandatory-person" bind:value={mandatoryPerson} />
|
||||
</div>
|
||||
|
||||
<!-- RFC -->
|
||||
<div class="space-y-2">
|
||||
<Label id="rfc" for="rfc">RFC: {rfc}</Label>
|
||||
</div>
|
||||
|
||||
<!-- CURP -->
|
||||
<div class="space-y-2">
|
||||
<Label id="curp" for="curp">CURP: {curp}</Label>
|
||||
</div>
|
||||
|
||||
<!-- Modo Contingencia -->
|
||||
<div class="space-y-2 col-span-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox id="contingency-mode" bind:checked={contingencyMode} />
|
||||
<Label for="contingency-mode" class="font-normal">Modo Contingencia</Label>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- COVE -->
|
||||
<div class="space-y-2">
|
||||
<Label for="cove">COVE:</Label>
|
||||
<Input id="cove" bind:value={cove} placeholder="COVE" />
|
||||
</div>
|
||||
|
||||
<!-- Número de Operación -->
|
||||
<div class="space-y-2">
|
||||
<Label for="operation-num">Núm Operación:</Label>
|
||||
<Input id="operation-num" bind:value={operationNum} />
|
||||
</div>
|
||||
|
||||
<!-- Adenda(s) -->
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="adendas">Adenda(s):</Label>
|
||||
<Input id="adendas" bind:value={adendas} />
|
||||
</div>
|
||||
|
||||
<!-- Observaciones VU -->
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="observations-vu">Observaciones VU:</Label>
|
||||
<div class="flex gap-2">
|
||||
<Textarea id="observations-vu" bind:value={observationsVU} class="flex-1 min-h-[60px]" />
|
||||
<Button variant="outline" onclick={loadInfo}>
|
||||
Cargar Info.
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Número Certificado -->
|
||||
<div class="space-y-2">
|
||||
<Label for="certified-num">Número Certificado:</Label>
|
||||
<Input id="certified-num" bind:value={certifiedNumber} />
|
||||
</div>
|
||||
|
||||
<!-- Firma Electrónica 2 -->
|
||||
<div class="space-y-2 md:col-span-2">
|
||||
<Label for="electronic-sig-2">Firma Electrónica:</Label>
|
||||
<Input id="electronic-sig-2" bind:value={electronicSignature} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -323,23 +323,19 @@ export function getSidebarData(): SidebarData {
|
||||
items: [
|
||||
{
|
||||
title: m["sidebar.import_invoices.temporary"](),
|
||||
//url: "/dashboard/invoices?operation_type=imp&invoice_type=TEM"
|
||||
url: "/dashboard/invoices/importacion/temporal",
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=TEM"
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.definitive"](),
|
||||
//url: "/dashboard/invoices?operation_type=imp&invoice_type=DEF",
|
||||
url: "/dashboard/invoices/importacion/definitiva",
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=DEF",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.mexican_purchases"](),
|
||||
//url: "/dashboard/invoices?operation_type=imp&invoice_type=MEX",
|
||||
url: "/dashboard/invoices/importacion/compras_mexicanas",
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=MEX",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.import_invoices.regime_change"](),
|
||||
//url: "/dashboard/invoices?operation_type=imp&invoice_type=CR",
|
||||
url: "/dashboard/invoices/importacion/cambio_regimen",
|
||||
url: "/dashboard/invoices?operation_type=imp&invoice_type=CR",
|
||||
}
|
||||
],
|
||||
},
|
||||
@@ -350,13 +346,11 @@ export function getSidebarData(): SidebarData {
|
||||
items: [
|
||||
{
|
||||
title: m["sidebar.export_invoices.exportation"](),
|
||||
//url: "/dashboard/invoices?operation_type=exp",
|
||||
url: "/dashboard/invoices/exportacion/exportacion",
|
||||
url: "/dashboard/invoices?operation_type=exp",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.export_invoices.repair"](),
|
||||
//url: "/dashboard/invoices?operation_type=exp&invoice_type=REPAR",
|
||||
url: "/dashboard/invoices/exportacion/reparacion",
|
||||
url: "/dashboard/invoices?operation_type=exp&invoice_type=REPAR",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
10
frontend/src/lib/components/ui/radio-group/index.ts
Normal file
10
frontend/src/lib/components/ui/radio-group/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import Root from "./radio-group.svelte";
|
||||
import Item from "./radio-group-item.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Item,
|
||||
//
|
||||
Root as RadioGroup,
|
||||
Item as RadioGroupItem,
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<RadioGroupPrimitive.ItemProps> = $props();
|
||||
</script>
|
||||
|
||||
<RadioGroupPrimitive.Item
|
||||
bind:ref
|
||||
data-slot="radio-group-item"
|
||||
class={cn(
|
||||
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked })}
|
||||
<div data-slot="radio-group-indicator" class="relative flex items-center justify-center">
|
||||
{#if checked}
|
||||
<CircleIcon
|
||||
class="fill-primary absolute start-1/2 top-1/2 size-2 -translate-x-1/2 -translate-y-1/2"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</RadioGroupPrimitive.Item>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { RadioGroup as RadioGroupPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
value = $bindable(""),
|
||||
...restProps
|
||||
}: RadioGroupPrimitive.RootProps = $props();
|
||||
</script>
|
||||
|
||||
<RadioGroupPrimitive.Root
|
||||
bind:ref
|
||||
bind:value
|
||||
data-slot="radio-group"
|
||||
class={cn("grid gap-3", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
Reference in New Issue
Block a user