feat: add partidas tab form component with CRUD functionality

- Implemented the partidas tab form in Svelte for managing partidas.
- Added functionality to create, edit, delete, and list partidas.
- Integrated a dialog for adding and editing partidas with validation.
- Included a checkbox component for selecting partidas.
- Added total calculations for partidas in the UI.
- Enhanced UI with buttons for various actions and a responsive table layout.
This commit is contained in:
Galindo97
2025-12-24 09:10:10 -06:00
parent 6a06f19b1c
commit 7585cedd3e
16 changed files with 6971 additions and 290 deletions

View File

@@ -24,16 +24,20 @@ export interface ExchangeRateListResponse {
/**
* Get exchange rate by date
*/
export async function getExchangeRateByDate(date: string): Promise<ExchangeRate | null> {
export async function getExchangeRateByDate(date: string, companyId: number): Promise<ExchangeRate | null> {
try {
// Convert date to ISO format with time for backend datetime field
const dateWithTime = `${date}T00:00:00`;
const response = await api.get<ExchangeRateListResponse>(`/v1/a76/exchange-rate?date=${dateWithTime}`);
// Get all exchange rates and filter by date on client side
const response = await api.get<ExchangeRateListResponse>(`/v1/a76/exchange-rate/?company_id=${companyId}`);
if (response.data && response.data.items && response.data.items.length > 0) {
// Find USD exchange rate
const usdRate = response.data.items.find(rate => rate.foreign_currency === 'USD');
return usdRate || null;
// Filter by date and find USD exchange rate
const dateOnly = date.split('T')[0]; // Get YYYY-MM-DD part
const matchingRates = response.data.items.filter(rate => {
const rateDate = rate.date.split('T')[0];
return rateDate === dateOnly && rate.foreign_currency === 'USD';
});
return matchingRates.length > 0 ? matchingRates[0] : null;
}
return null;

View File

@@ -50,6 +50,41 @@ export interface PedimentoValidation {
responsible_id?: number | null;
}
export interface PedimentoIncrementables {
insured_value?: number | null;
packaging?: number | null;
freight?: number | null;
deductibles?: number | null;
currency?: string | null;
not_affect_usd_value?: number | null;
not_affect_customs_value?: number | null;
}
export interface PedimentoDecrementables {
freight?: number | null;
insurance?: number | null;
loading?: number | null;
unloading?: number | null;
others?: number | null;
currency?: string | null;
not_affect_usd_value?: number | null;
}
export interface PedimentoIndexes {
update_factor_type?: number | null;
update_factor?: number | null;
manual_update_factor?: number | null;
}
export interface PedimentoConfigAdditional {
manual_pedimento_year?: string | null;
add_po_identifier?: number | null;
do_not_exempt_norms_complement_x?: number | null;
enable_import_invoice_recipient?: number | null;
send_502_validation_file_for_consolidated?: number | null;
add_remove_norms?: number | null;
}
export interface Pedimento {
id: number;
tenant_id: number;
@@ -67,12 +102,17 @@ export interface Pedimento {
paid_price?: number | null;
gross_weight?: number | null;
exchange_rate?: number | null;
observaciones?: string | null;
created_at: string;
// Sub-resources
pedimento_dates?: PedimentoDates | null;
pedimento_payments?: PedimentoPayments | null;
pedimento_transport_means?: PedimentoTransportMeans | null;
pedimento_validation?: PedimentoValidation | null;
pedimento_validation?: PedimentoValidation | null;
pedimento_incrementables?: PedimentoIncrementables | null;
pedimento_decrementables?: PedimentoDecrementables | null;
pedimento_indexes?: PedimentoIndexes | null;
pedimento_config_additional?: PedimentoConfigAdditional | null;
}
export interface PedimentoListResponse {
@@ -97,11 +137,16 @@ export interface CreatePedimentoData {
paid_price?: number | null;
gross_weight?: number | null;
exchange_rate?: number | null;
observaciones?: string | null;
// Sub-resources
pedimento_dates?: PedimentoDates | null;
pedimento_payments?: PedimentoPayments | null;
pedimento_transport_means?: PedimentoTransportMeans | null;
pedimento_validation?: PedimentoValidation | null;
pedimento_incrementables?: PedimentoIncrementables | null;
pedimento_decrementables?: PedimentoDecrementables | null;
pedimento_indexes?: PedimentoIndexes | null;
pedimento_config_additional?: PedimentoConfigAdditional | null;
}
export interface UpdatePedimentoData {
@@ -119,11 +164,16 @@ export interface UpdatePedimentoData {
paid_price?: number | null;
gross_weight?: number | null;
exchange_rate?: number | null;
observaciones?: string | null;
// Sub-resources
pedimento_dates?: PedimentoDates | null;
pedimento_payments?: PedimentoPayments | null;
pedimento_transport_means?: PedimentoTransportMeans | null;
pedimento_validation?: PedimentoValidation | null;
pedimento_incrementables?: PedimentoIncrementables | null;
pedimento_decrementables?: PedimentoDecrementables | null;
pedimento_indexes?: PedimentoIndexes | null;
pedimento_config_additional?: PedimentoConfigAdditional | null;
}
export interface PedimentoFilters {

View File

@@ -0,0 +1,547 @@
<script lang="ts">
import { Card, CardContent } from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/ui/table';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter
} from '$lib/components/ui/dialog';
import Checkbox from '$lib/components/ui/checkbox/checkbox.svelte';
import {
Plus,
Pencil,
Trash2
} from 'lucide-svelte';
interface Contribucion {
id?: number;
contribucion: string;
tipo_tasa: string;
tasa: number;
forma_pago: string;
importe: number;
gravamen: string;
abreviacion: string;
forma_pago_2: string;
importe_2: number;
}
let {
formData = $bindable({
// Campos por pestaña
dta: {
forma_pago_recargo: 0,
tasa_recargo: 0.0,
importe_recargo: 0
},
prev: {
forma_pago_prevalidacion: 0
},
eci: {
forma_pago_eci: 0,
importe_eci: 0
},
mult: {
forma_pago_multa: 0,
importe_multa: 0
},
rec: {
forma_pago_uia: 0,
importe_compensar: 0
},
// Configuración
calculo_manual: false,
operaciones_regla_31_40: false,
// Contribuciones (tabla compartida)
contribuciones: [] as Contribucion[]
})
}: {
formData: {
dta: {
forma_pago_recargo: number;
tasa_recargo: number;
importe_recargo: number;
};
prev: {
forma_pago_prevalidacion: number;
};
eci: {
forma_pago_eci: number;
importe_eci: number;
};
mult: {
forma_pago_multa: number;
importe_multa: number;
};
rec: {
forma_pago_uia: number;
importe_compensar: number;
};
calculo_manual: boolean;
operaciones_regla_31_40: boolean;
contribuciones: Contribucion[];
};
} = $props();
// Pestaña activa
let activeTab = $state('DTA');
// Estados para diálogo
let isDialogOpen = $state(false);
let editingIndex = $state<number | null>(null);
let currentContribucion = $state<Contribucion>({
contribucion: '',
tipo_tasa: '',
tasa: 0,
forma_pago: '',
importe: 0,
gravamen: '',
abreviacion: '',
forma_pago_2: '',
importe_2: 0
});
function openNewContribucion() {
editingIndex = null;
currentContribucion = {
contribucion: '',
tipo_tasa: '',
tasa: 0,
forma_pago: '',
importe: 0,
gravamen: '',
abreviacion: '',
forma_pago_2: '',
importe_2: 0
};
isDialogOpen = true;
}
function openEditContribucion(index: number) {
if (!formData?.contribuciones) return;
editingIndex = index;
currentContribucion = { ...formData.contribuciones[index] };
isDialogOpen = true;
}
function saveContribucion() {
if (!formData?.contribuciones) return;
if (editingIndex !== null) {
formData.contribuciones[editingIndex] = { ...currentContribucion };
} else {
formData.contribuciones = [...formData.contribuciones, { ...currentContribucion }];
}
isDialogOpen = false;
}
function deleteContribucion(index: number) {
if (!formData?.contribuciones) return;
if (confirm('¿Está seguro de eliminar esta contribución?')) {
formData.contribuciones = formData.contribuciones.filter((_, i) => i !== index);
}
}
</script>
<Card>
<CardContent class="p-6 space-y-4">
<!-- Pestañas de navegación -->
<div class="flex gap-1 border-b">
<button
class="px-4 py-2 text-sm font-medium border-b-2 transition-colors {activeTab === 'DTA'
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:text-foreground'}"
onclick={() => (activeTab = 'DTA')}
>
DTA
</button>
<button
class="px-4 py-2 text-sm font-medium border-b-2 transition-colors {activeTab === 'PREV'
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:text-foreground'}"
onclick={() => (activeTab = 'PREV')}
>
PREV
</button>
<button
class="px-4 py-2 text-sm font-medium border-b-2 transition-colors {activeTab === 'ECI'
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:text-foreground'}"
onclick={() => (activeTab = 'ECI')}
>
ECI
</button>
<button
class="px-4 py-2 text-sm font-medium border-b-2 transition-colors {activeTab === 'MULT'
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:text-foreground'}"
onclick={() => (activeTab = 'MULT')}
>
MULT
</button>
<button
class="px-4 py-2 text-sm font-medium border-b-2 transition-colors {activeTab === 'REC'
? 'border-primary text-primary'
: 'border-transparent text-muted-foreground hover:text-foreground'}"
onclick={() => (activeTab = 'REC')}
>
REC
</button>
</div>
<!-- Contenido dinámico según pestaña -->
<div class="border rounded-lg p-4 bg-muted/30">
{#if activeTab === 'DTA'}
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2">
<Label for="forma_pago_recargo">Forma de pago Recargo:</Label>
<Input
id="forma_pago_recargo"
type="number"
bind:value={formData.dta.forma_pago_recargo}
placeholder="0"
/>
</div>
<div class="space-y-2">
<Label for="tasa_recargo">Tasa Recargo:</Label>
<Input
id="tasa_recargo"
type="number"
step="0.00001"
bind:value={formData.dta.tasa_recargo}
placeholder="0.00000"
/>
<p class="text-xs text-muted-foreground">(dejar en cero para calcular automático)</p>
</div>
<div class="space-y-2">
<Label for="importe_recargo">Importe Recargo:</Label>
<Input
id="importe_recargo"
type="number"
bind:value={formData.dta.importe_recargo}
placeholder="0"
/>
<p class="text-xs text-muted-foreground">(dejar en cero para calcular automático)</p>
</div>
</div>
{:else if activeTab === 'PREV'}
<div class="grid grid-cols-1 gap-4 max-w-md">
<div class="space-y-2">
<Label for="forma_pago_prevalidacion">Forma de pago Prevalidación:</Label>
<Input
id="forma_pago_prevalidacion"
type="number"
bind:value={formData.prev.forma_pago_prevalidacion}
placeholder="0"
/>
</div>
</div>
{:else if activeTab === 'ECI'}
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="forma_pago_eci">Forma de pago ECI:</Label>
<Input
id="forma_pago_eci"
type="number"
bind:value={formData.eci.forma_pago_eci}
placeholder="0"
/>
</div>
<div class="space-y-2">
<Label for="importe_eci">Importe ECI:</Label>
<Input
id="importe_eci"
type="number"
bind:value={formData.eci.importe_eci}
placeholder="0"
/>
<p class="text-xs text-muted-foreground">
(dejar en cero para que el sistema calcule el importe)
</p>
</div>
</div>
{:else if activeTab === 'MULT'}
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="forma_pago_multa">Forma de pago Multa:</Label>
<Input
id="forma_pago_multa"
type="number"
bind:value={formData.mult.forma_pago_multa}
placeholder="0"
/>
</div>
<div class="space-y-2">
<Label for="importe_multa">Importe Multa:</Label>
<Input
id="importe_multa"
type="number"
bind:value={formData.mult.importe_multa}
placeholder="0"
/>
</div>
</div>
{:else if activeTab === 'REC'}
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="forma_pago_uia">Forma de pago U I A:</Label>
<Input
id="forma_pago_uia"
type="number"
bind:value={formData.rec.forma_pago_uia}
placeholder="0"
/>
</div>
<div class="space-y-2">
<Label for="importe_compensar">Importe a Compensar:</Label>
<Input
id="importe_compensar"
type="number"
bind:value={formData.rec.importe_compensar}
placeholder="0"
/>
</div>
</div>
{/if}
</div>
<!-- Tabla de contribuciones -->
<div class="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead>Contribución</TableHead>
<TableHead>T.T.</TableHead>
<TableHead>Tasa</TableHead>
<TableHead>F.P.</TableHead>
<TableHead class="text-right">Importe</TableHead>
<TableHead>Gravamen</TableHead>
<TableHead>Abreviación</TableHead>
<TableHead>F.P.</TableHead>
<TableHead class="text-right">Importe</TableHead>
<TableHead class="w-[100px]">Acciones</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#if !formData?.contribuciones || formData.contribuciones.length === 0}
<TableRow>
<TableCell colspan={10} class="text-center text-muted-foreground py-8">
No hay contribuciones registradas
</TableCell>
</TableRow>
{:else}
{#each formData.contribuciones as contribucion, index}
<TableRow>
<TableCell>{contribucion.contribucion}</TableCell>
<TableCell>{contribucion.tipo_tasa}</TableCell>
<TableCell class="text-right"
>{contribucion.tasa.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 5
})}</TableCell
>
<TableCell>{contribucion.forma_pago}</TableCell>
<TableCell class="text-right"
>{contribucion.importe.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}</TableCell
>
<TableCell>{contribucion.gravamen}</TableCell>
<TableCell>{contribucion.abreviacion}</TableCell>
<TableCell>{contribucion.forma_pago_2}</TableCell>
<TableCell class="text-right"
>{contribucion.importe_2.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}</TableCell
>
<TableCell>
<div class="flex gap-1">
<Button size="sm" variant="ghost" onclick={() => openEditContribucion(index)}>
<Pencil size={14} />
</Button>
<Button size="sm" variant="ghost" onclick={() => deleteContribucion(index)}>
<Trash2 size={14} />
</Button>
</div>
</TableCell>
</TableRow>
{/each}
{/if}
</TableBody>
</Table>
</div>
<!-- Controles inferiores -->
<div class="flex items-center justify-between border-t pt-4">
<div class="flex items-center gap-6">
<div class="flex items-center space-x-2">
<Checkbox
id="calculo_manual"
checked={formData?.calculo_manual ?? false}
onCheckedChange={(checked: boolean | 'indeterminate') =>
formData && (formData.calculo_manual = checked === true)}
/>
<Label for="calculo_manual" class="font-normal cursor-pointer text-sm"
>Cálculo Manual</Label
>
</div>
<div class="flex items-center space-x-2">
<Checkbox
id="operaciones_regla"
checked={formData?.operaciones_regla_31_40 ?? false}
onCheckedChange={(checked: boolean | 'indeterminate') =>
formData && (formData.operaciones_regla_31_40 = checked === true)}
/>
<Label for="operaciones_regla" class="font-normal cursor-pointer text-sm"
>Operaciones al amparo de la regla 31.40</Label
>
</div>
</div>
<div class="flex gap-2">
<Button size="sm" onclick={openNewContribucion}>
<Plus class="mr-1.5" size={14} />
Nuevo
</Button>
<Button
size="sm"
variant="outline"
disabled={formData?.contribuciones?.length === 0}
onclick={() => {
const index = 0;
if (formData?.contribuciones?.[index]) openEditContribucion(index);
}}
>
<Pencil class="mr-1.5" size={14} />
Editar
</Button>
<Button
size="sm"
variant="destructive"
disabled={formData?.contribuciones?.length === 0}
onclick={() => {
const index = 0;
if (formData?.contribuciones?.[index]) deleteContribucion(index);
}}
>
<Trash2 class="mr-1.5" size={14} />
Borrar
</Button>
</div>
</div>
</CardContent>
</Card>
<!-- Dialog para Contribuciones -->
<Dialog bind:open={isDialogOpen}>
<DialogContent class="max-w-3xl">
<DialogHeader>
<DialogTitle>
{editingIndex !== null ? 'Editar Contribución' : 'Nueva Contribución'}
</DialogTitle>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="contribucion">Contribución *</Label>
<Input
id="contribucion"
bind:value={currentContribucion.contribucion}
placeholder="Nombre de la contribución"
/>
</div>
<div class="space-y-2">
<Label for="tipo_tasa">T.T. (Tipo de Tasa) *</Label>
<Input id="tipo_tasa" bind:value={currentContribucion.tipo_tasa} placeholder="Tipo" />
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2">
<Label for="tasa">Tasa *</Label>
<Input
id="tasa"
type="number"
step="0.00001"
bind:value={currentContribucion.tasa}
placeholder="0.00000"
/>
</div>
<div class="space-y-2">
<Label for="forma_pago">F.P. (Forma de Pago) *</Label>
<Input id="forma_pago" bind:value={currentContribucion.forma_pago} placeholder="FP" />
</div>
<div class="space-y-2">
<Label for="importe">Importe *</Label>
<Input
id="importe"
type="number"
step="0.01"
bind:value={currentContribucion.importe}
placeholder="0.00"
/>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="gravamen">Gravamen</Label>
<Input id="gravamen" bind:value={currentContribucion.gravamen} placeholder="Gravamen" />
</div>
<div class="space-y-2">
<Label for="abreviacion">Abreviación</Label>
<Input
id="abreviacion"
bind:value={currentContribucion.abreviacion}
placeholder="Abreviación"
/>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="forma_pago_2">F.P. 2</Label>
<Input id="forma_pago_2" bind:value={currentContribucion.forma_pago_2} placeholder="FP" />
</div>
<div class="space-y-2">
<Label for="importe_2">Importe 2</Label>
<Input
id="importe_2"
type="number"
step="0.01"
bind:value={currentContribucion.importe_2}
placeholder="0.00"
/>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isDialogOpen = false)}>Cancelar</Button>
<Button
onclick={saveContribucion}
disabled={!currentContribucion.contribucion ||
!currentContribucion.tipo_tasa ||
!currentContribucion.forma_pago}
>
{editingIndex !== null ? 'Actualizar' : 'Guardar'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -0,0 +1,632 @@
<script lang="ts">
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/ui/table';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter
} from '$lib/components/ui/dialog';
import {
Plus,
Pencil,
Trash2
} from 'lucide-svelte';
interface CuentaGarantia {
id?: number;
institucion_emisora: string;
numero_contrato: string;
total_garantia: number;
folio_constancia: string;
fecha_constancia: string;
um: string;
valor_unitario: number;
}
interface Compensacion {
id?: number;
patente_original: string;
pedimento_original: string;
}
interface DocumentoPago {
id?: number;
forma_pago: string;
dependencia: string;
}
let {
formData = $bindable({
cuentas_garantia: [] as CuentaGarantia[],
compensaciones: [] as Compensacion[],
documentos_pago: [] as DocumentoPago[]
})
}: {
formData: {
cuentas_garantia: CuentaGarantia[];
compensaciones: Compensacion[];
documentos_pago: DocumentoPago[];
};
} = $props();
// Estados para diálogos
let isCuentaDialogOpen = $state(false);
let isCompensacionDialogOpen = $state(false);
let isDocumentoDialogOpen = $state(false);
// Estados para edición
let editingCuentaIndex = $state<number | null>(null);
let editingCompensacionIndex = $state<number | null>(null);
let editingDocumentoIndex = $state<number | null>(null);
// Estados actuales
let currentCuenta = $state<CuentaGarantia>({
institucion_emisora: '',
numero_contrato: '',
total_garantia: 0,
folio_constancia: '',
fecha_constancia: '',
um: '',
valor_unitario: 0
});
let currentCompensacion = $state<Compensacion>({
patente_original: '',
pedimento_original: ''
});
let currentDocumento = $state<DocumentoPago>({
forma_pago: '',
dependencia: ''
});
const unidadesMedida = ['KG', 'LT', 'PZ', 'MT', 'M2', 'M3', 'TON', 'CAJ', 'PAR'];
// Funciones para Cuentas Garantía
function openNewCuenta() {
editingCuentaIndex = null;
currentCuenta = {
institucion_emisora: '',
numero_contrato: '',
total_garantia: 0,
folio_constancia: '',
fecha_constancia: '',
um: '',
valor_unitario: 0
};
isCuentaDialogOpen = true;
}
function openEditCuenta(index: number) {
if (!formData?.cuentas_garantia) return;
editingCuentaIndex = index;
currentCuenta = { ...formData.cuentas_garantia[index] };
isCuentaDialogOpen = true;
}
function saveCuenta() {
if (!formData?.cuentas_garantia) return;
if (editingCuentaIndex !== null) {
formData.cuentas_garantia[editingCuentaIndex] = { ...currentCuenta };
} else {
formData.cuentas_garantia = [...formData.cuentas_garantia, { ...currentCuenta }];
}
isCuentaDialogOpen = false;
}
function deleteCuenta(index: number) {
if (!formData?.cuentas_garantia) return;
if (confirm('¿Está seguro de eliminar esta cuenta?')) {
formData.cuentas_garantia = formData.cuentas_garantia.filter((_, i) => i !== index);
}
}
// Funciones para Compensaciones
function openNewCompensacion() {
editingCompensacionIndex = null;
currentCompensacion = {
patente_original: '',
pedimento_original: ''
};
isCompensacionDialogOpen = true;
}
function openEditCompensacion(index: number) {
if (!formData?.compensaciones) return;
editingCompensacionIndex = index;
currentCompensacion = { ...formData.compensaciones[index] };
isCompensacionDialogOpen = true;
}
function saveCompensacion() {
if (!formData?.compensaciones) return;
if (editingCompensacionIndex !== null) {
formData.compensaciones[editingCompensacionIndex] = { ...currentCompensacion };
} else {
formData.compensaciones = [...formData.compensaciones, { ...currentCompensacion }];
}
isCompensacionDialogOpen = false;
}
function deleteCompensacion(index: number) {
if (!formData?.compensaciones) return;
if (confirm('¿Está seguro de eliminar esta compensación?')) {
formData.compensaciones = formData.compensaciones.filter((_, i) => i !== index);
}
}
// Funciones para Documentos de Pago
function openNewDocumento() {
editingDocumentoIndex = null;
currentDocumento = {
forma_pago: '',
dependencia: ''
};
isDocumentoDialogOpen = true;
}
function openEditDocumento(index: number) {
if (!formData?.documentos_pago) return;
editingDocumentoIndex = index;
currentDocumento = { ...formData.documentos_pago[index] };
isDocumentoDialogOpen = true;
}
function saveDocumento() {
if (!formData?.documentos_pago) return;
if (editingDocumentoIndex !== null) {
formData.documentos_pago[editingDocumentoIndex] = { ...currentDocumento };
} else {
formData.documentos_pago = [...formData.documentos_pago, { ...currentDocumento }];
}
isDocumentoDialogOpen = false;
}
function deleteDocumento(index: number) {
if (!formData?.documentos_pago) return;
if (confirm('¿Está seguro de eliminar este documento?')) {
formData.documentos_pago = formData.documentos_pago.filter((_, i) => i !== index);
}
}
</script>
<div class="space-y-4">
<!-- Sección de Cuentas Garantía -->
<Card>
<CardContent class="p-6 space-y-4">
<div class="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead>Institución Emisora</TableHead>
<TableHead>Número Contrato</TableHead>
<TableHead class="text-right">Total Garantía</TableHead>
<TableHead>Folio Constancia</TableHead>
<TableHead>Fecha Constancia</TableHead>
<TableHead>UM</TableHead>
<TableHead class="text-right">Valor Unitario</TableHead>
<TableHead class="w-[100px]">Acciones</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#if !formData?.cuentas_garantia || formData.cuentas_garantia.length === 0}
<TableRow>
<TableCell colspan={8} class="text-center text-muted-foreground py-8">
No hay cuentas de garantía registradas
</TableCell>
</TableRow>
{:else}
{#each formData.cuentas_garantia as cuenta, index}
<TableRow>
<TableCell>{cuenta.institucion_emisora}</TableCell>
<TableCell>{cuenta.numero_contrato}</TableCell>
<TableCell class="text-right"
>{cuenta.total_garantia.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}</TableCell
>
<TableCell>{cuenta.folio_constancia}</TableCell>
<TableCell>{cuenta.fecha_constancia}</TableCell>
<TableCell>{cuenta.um}</TableCell>
<TableCell class="text-right"
>{cuenta.valor_unitario.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}</TableCell
>
<TableCell>
<div class="flex gap-1">
<Button size="sm" variant="ghost" onclick={() => openEditCuenta(index)}>
<Pencil size={14} />
</Button>
<Button size="sm" variant="ghost" onclick={() => deleteCuenta(index)}>
<Trash2 size={14} />
</Button>
</div>
</TableCell>
</TableRow>
{/each}
{/if}
</TableBody>
</Table>
</div>
<div class="flex gap-2">
<Button size="sm" onclick={openNewCuenta}>
<Plus class="mr-1.5" size={14} />
Nuevo
</Button>
<Button
size="sm"
variant="outline"
disabled={formData?.cuentas_garantia?.length === 0}
onclick={() => {
const index = 0;
if (formData?.cuentas_garantia?.[index]) openEditCuenta(index);
}}
>
<Pencil class="mr-1.5" size={14} />
Editar
</Button>
<Button
size="sm"
variant="destructive"
disabled={formData?.cuentas_garantia?.length === 0}
onclick={() => {
const index = 0;
if (formData?.cuentas_garantia?.[index]) deleteCuenta(index);
}}
>
<Trash2 class="mr-1.5" size={14} />
Borrar
</Button>
</div>
</CardContent>
</Card>
<!-- Secciones en grid -->
<div class="grid grid-cols-2 gap-4">
<!-- Compensaciones -->
<Card>
<CardHeader class="pb-3">
<CardTitle class="text-base">Compensaciones</CardTitle>
</CardHeader>
<CardContent class="space-y-4">
<div class="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead>Patente Original</TableHead>
<TableHead>Pedimento Original</TableHead>
<TableHead class="w-[100px]">Acciones</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#if !formData?.compensaciones || formData.compensaciones.length === 0}
<TableRow>
<TableCell colspan={3} class="text-center text-muted-foreground py-8">
No hay compensaciones registradas
</TableCell>
</TableRow>
{:else}
{#each formData.compensaciones as compensacion, index}
<TableRow>
<TableCell>{compensacion.patente_original}</TableCell>
<TableCell>{compensacion.pedimento_original}</TableCell>
<TableCell>
<div class="flex gap-1">
<Button
size="sm"
variant="ghost"
onclick={() => openEditCompensacion(index)}
>
<Pencil size={14} />
</Button>
<Button
size="sm"
variant="ghost"
onclick={() => deleteCompensacion(index)}
>
<Trash2 size={14} />
</Button>
</div>
</TableCell>
</TableRow>
{/each}
{/if}
</TableBody>
</Table>
</div>
<div class="flex gap-2">
<Button size="sm" onclick={openNewCompensacion}>
<Plus class="mr-1.5" size={14} />
Nuevo
</Button>
<Button
size="sm"
variant="outline"
disabled={formData?.compensaciones?.length === 0}
onclick={() => {
const index = 0;
if (formData?.compensaciones?.[index]) openEditCompensacion(index);
}}
>
<Pencil class="mr-1.5" size={14} />
Editar
</Button>
<Button
size="sm"
variant="destructive"
disabled={formData?.compensaciones?.length === 0}
onclick={() => {
const index = 0;
if (formData?.compensaciones?.[index]) deleteCompensacion(index);
}}
>
<Trash2 class="mr-1.5" size={14} />
Borrar
</Button>
</div>
</CardContent>
</Card>
<!-- Documentos que amparan las Formas de Pago -->
<Card>
<CardHeader class="pb-3">
<CardTitle class="text-base">Documentos que amparan las Formas de Pago</CardTitle>
</CardHeader>
<CardContent class="space-y-4">
<div class="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead>Forma de Pago</TableHead>
<TableHead>Dependencia</TableHead>
<TableHead class="w-[100px]">Acciones</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#if !formData?.documentos_pago || formData.documentos_pago.length === 0}
<TableRow>
<TableCell colspan={3} class="text-center text-muted-foreground py-8">
No hay documentos registrados
</TableCell>
</TableRow>
{:else}
{#each formData.documentos_pago as documento, index}
<TableRow>
<TableCell>{documento.forma_pago}</TableCell>
<TableCell>{documento.dependencia}</TableCell>
<TableCell>
<div class="flex gap-1">
<Button size="sm" variant="ghost" onclick={() => openEditDocumento(index)}>
<Pencil size={14} />
</Button>
<Button size="sm" variant="ghost" onclick={() => deleteDocumento(index)}>
<Trash2 size={14} />
</Button>
</div>
</TableCell>
</TableRow>
{/each}
{/if}
</TableBody>
</Table>
</div>
<div class="flex gap-2">
<Button size="sm" onclick={openNewDocumento}>
<Plus class="mr-1.5" size={14} />
Nuevo
</Button>
<Button
size="sm"
variant="outline"
disabled={formData?.documentos_pago?.length === 0}
onclick={() => {
const index = 0;
if (formData?.documentos_pago?.[index]) openEditDocumento(index);
}}
>
<Pencil class="mr-1.5" size={14} />
Editar
</Button>
<Button
size="sm"
variant="destructive"
disabled={formData?.documentos_pago?.length === 0}
onclick={() => {
const index = 0;
if (formData?.documentos_pago?.[index]) deleteDocumento(index);
}}
>
<Trash2 class="mr-1.5" size={14} />
Borrar
</Button>
</div>
</CardContent>
</Card>
</div>
</div>
<!-- Dialog para Cuentas Garantía -->
<Dialog bind:open={isCuentaDialogOpen}>
<DialogContent class="max-w-3xl">
<DialogHeader>
<DialogTitle>
{editingCuentaIndex !== null ? 'Editar Cuenta Garantía' : 'Nueva Cuenta Garantía'}
</DialogTitle>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="institucion_emisora">Institución Emisora *</Label>
<Input
id="institucion_emisora"
bind:value={currentCuenta.institucion_emisora}
placeholder="Nombre de la institución"
/>
</div>
<div class="space-y-2">
<Label for="numero_contrato">Número Contrato *</Label>
<Input
id="numero_contrato"
bind:value={currentCuenta.numero_contrato}
placeholder="Número de contrato"
/>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="total_garantia">Total Garantía *</Label>
<Input
id="total_garantia"
type="number"
step="0.01"
bind:value={currentCuenta.total_garantia}
placeholder="0.00"
/>
</div>
<div class="space-y-2">
<Label for="folio_constancia">Folio Constancia *</Label>
<Input
id="folio_constancia"
bind:value={currentCuenta.folio_constancia}
placeholder="Folio"
/>
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2">
<Label for="fecha_constancia">Fecha Constancia *</Label>
<Input id="fecha_constancia" type="date" bind:value={currentCuenta.fecha_constancia} />
</div>
<div class="space-y-2">
<Label for="um">UM *</Label>
<Input id="um" bind:value={currentCuenta.um} placeholder="Unidad de medida" />
</div>
<div class="space-y-2">
<Label for="valor_unitario">Valor Unitario *</Label>
<Input
id="valor_unitario"
type="number"
step="0.01"
bind:value={currentCuenta.valor_unitario}
placeholder="0.00"
/>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isCuentaDialogOpen = false)}>Cancelar</Button>
<Button
onclick={saveCuenta}
disabled={!currentCuenta.institucion_emisora ||
!currentCuenta.numero_contrato ||
!currentCuenta.folio_constancia ||
!currentCuenta.fecha_constancia ||
!currentCuenta.um}
>
{editingCuentaIndex !== null ? 'Actualizar' : 'Guardar'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<!-- Dialog para Compensaciones -->
<Dialog bind:open={isCompensacionDialogOpen}>
<DialogContent class="max-w-lg">
<DialogHeader>
<DialogTitle>
{editingCompensacionIndex !== null ? 'Editar Compensación' : 'Nueva Compensación'}
</DialogTitle>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="space-y-2">
<Label for="patente_original">Patente Original *</Label>
<Input
id="patente_original"
bind:value={currentCompensacion.patente_original}
placeholder="0000"
/>
</div>
<div class="space-y-2">
<Label for="pedimento_original">Pedimento Original *</Label>
<Input
id="pedimento_original"
bind:value={currentCompensacion.pedimento_original}
placeholder="00 00 0000 0000000"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isCompensacionDialogOpen = false)}
>Cancelar</Button
>
<Button
onclick={saveCompensacion}
disabled={!currentCompensacion.patente_original || !currentCompensacion.pedimento_original}
>
{editingCompensacionIndex !== null ? 'Actualizar' : 'Guardar'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<!-- Dialog para Documentos de Pago -->
<Dialog bind:open={isDocumentoDialogOpen}>
<DialogContent class="max-w-lg">
<DialogHeader>
<DialogTitle>
{editingDocumentoIndex !== null ? 'Editar Documento' : 'Nuevo Documento'}
</DialogTitle>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="space-y-2">
<Label for="forma_pago">Forma de Pago *</Label>
<Input id="forma_pago" bind:value={currentDocumento.forma_pago} placeholder="Forma de pago" />
</div>
<div class="space-y-2">
<Label for="dependencia">Dependencia *</Label>
<Input
id="dependencia"
bind:value={currentDocumento.dependencia}
placeholder="Dependencia"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isDocumentoDialogOpen = false)}>Cancelar</Button>
<Button
onclick={saveDocumento}
disabled={!currentDocumento.forma_pago || !currentDocumento.dependencia}
>
{editingDocumentoIndex !== null ? 'Actualizar' : 'Guardar'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -1,107 +1,83 @@
<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 type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
let {
pedimento,
formData = $bindable(),
exists = $bindable()
exists = $bindable(),
pedimentoType = '',
pedimentoNumber = ''
}: {
pedimento: Pedimento | null;
formData?: any;
exists?: boolean;
pedimentoType?: string;
pedimentoNumber?: string;
} = $props();
// Inicializar formData inmediatamente
const datesData = pedimento?.pedimento_dates;
if (datesData) {
exists = true;
if (!formData) {
formData = {
entry_date: datesData.entry_date ? datesData.entry_date.substring(0, 10) : '',
pedimento_date: datesData.pedimento_date ? datesData.pedimento_date.substring(0, 10) : '',
payment_date: datesData.payment_date ? datesData.payment_date.substring(0, 10) : '',
rectification_payment_date: datesData.rectification_payment_date ? datesData.rectification_payment_date.substring(0, 10) : '',
extraction_date: datesData.extraction_date ? datesData.extraction_date.substring(0, 10) : '',
submission_date: datesData.submission_date ? datesData.submission_date.substring(0, 10) : '',
eucan_date: datesData.eucan_date ? datesData.eucan_date.substring(0, 10) : '',
original_date: datesData.original_date ? datesData.original_date.substring(0, 10) : '',
start_date: datesData.start_date ? datesData.start_date.substring(0, 10) : '',
end_date: datesData.end_date ? datesData.end_date.substring(0, 10) : '',
};
}
} else {
exists = false;
if (!formData) {
formData = {
entry_date: '',
pedimento_date: '',
payment_date: '',
rectification_payment_date: '',
extraction_date: '',
submission_date: '',
eucan_date: '',
original_date: '',
start_date: '',
end_date: '',
capture_time: ''
};
}
// Inicializar formData
if (!formData) {
formData = {
observaciones: pedimento?.observaciones || ''
};
}
exists = !!pedimento?.observaciones;
// Mapear el tipo de pedimento a un nombre legible
const tipoPedimentoNombre = $derived.by(() => {
switch(pedimentoType?.toLowerCase()) {
case 'automovil':
return 'Automóvil';
case 'complementario':
return 'Complementario';
case 'consolidado':
return 'Consolidado';
case 'normal':
return 'Normal';
default:
return pedimentoType || 'No especificado';
}
});
</script>
<Card.Root>
<Card.Header>
<Card.Title>Fechas del Pedimento</Card.Title>
<Card.Title>Observaciones</Card.Title>
<Card.Description>
Gestiona las fechas importantes del pedimento
Agrega notas y observaciones sobre el pedimento
</Card.Description>
</Card.Header>
<Card.Content>
<div class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Fecha de Envío -->
<div class="space-y-2">
<Label for="submission_date">Fecha de Envío</Label>
<Input
id="submission_date"
type="date"
bind:value={formData.submission_date}
/>
<div class="space-y-4">
<!-- Mostrar el tipo de pedimento y número -->
<div class="rounded-lg border bg-card p-4">
<div class="space-y-2">
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-muted-foreground">Tipo de Pedimento:</span>
<span class="text-sm font-semibold">{tipoPedimentoNombre}</span>
</div>
<!-- Fecha EUCAN -->
<div class="space-y-2">
<Label for="eucan_date">Fecha EUCAN</Label>
<Input
id="eucan_date"
type="date"
bind:value={formData.eucan_date}
/>
</div>
<!-- Fecha de Inicio -->
<div class="space-y-2">
<Label for="start_date">Fecha de Inicio</Label>
<Input
id="start_date"
type="date"
bind:value={formData.start_date}
/>
</div>
<!-- Fecha de Fin -->
<div class="space-y-2">
<Label for="end_date">Fecha de Fin</Label>
<Input
id="end_date"
type="date"
bind:value={formData.end_date}
/>
</div>
{#if pedimentoNumber}
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-muted-foreground">Número de Pedimento:</span>
<span class="text-sm font-semibold">{pedimentoNumber}</span>
</div>
{/if}
</div>
</div>
</Card.Content>
</Card.Root>
<div class="space-y-2">
<Label for="observaciones">Observaciones</Label>
<textarea
id="observaciones"
bind:value={formData.observaciones}
placeholder="Escribe tus observaciones aquí..."
rows={10}
class="flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-y"
></textarea>
</div>
</div>
</Card.Content>
</Card.Root>

View File

@@ -0,0 +1,495 @@
<script lang="ts">
import { Card, CardContent, CardHeader, CardTitle } from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/ui/table';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter
} from '$lib/components/ui/dialog';
import {
Plus,
Pencil,
Trash2,
Upload,
Trash
} from 'lucide-svelte';
interface Descargo {
id?: number;
patente: string;
pedimento: string;
aduana: string;
clave: string;
fecha_pago: string;
fraccion: string;
umt: string;
cantidad_tarifa: number;
}
interface Destinatario {
id?: number;
clave: string;
identificacion_fiscal: string;
nombre: string;
}
let {
formData = $bindable({
descargos: [] as Descargo[],
destinatarios: [] as Destinatario[]
})
}: {
formData: {
descargos: Descargo[];
destinatarios: Destinatario[];
};
} = $props();
// Estados para diálogos
let isDescargoDialogOpen = $state(false);
let isDestinatarioDialogOpen = $state(false);
// Estados para edición
let editingDescargoIndex = $state<number | null>(null);
let editingDestinatarioIndex = $state<number | null>(null);
// Estados actuales
let currentDescargo = $state<Descargo>({
patente: '',
pedimento: '',
aduana: '',
clave: '',
fecha_pago: '',
fraccion: '',
umt: '',
cantidad_tarifa: 0
});
let currentDestinatario = $state<Destinatario>({
clave: '',
identificacion_fiscal: '',
nombre: ''
});
const unidadesMedida = ['KG', 'LT', 'PZ', 'MT', 'M2', 'M3', 'TON', 'CAJ', 'PAR'];
// Funciones para Descargos
function openNewDescargo() {
editingDescargoIndex = null;
currentDescargo = {
patente: '',
pedimento: '',
aduana: '',
clave: '',
fecha_pago: '',
fraccion: '',
umt: '',
cantidad_tarifa: 0
};
isDescargoDialogOpen = true;
}
function openEditDescargo(index: number) {
if (!formData?.descargos) return;
editingDescargoIndex = index;
currentDescargo = { ...formData.descargos[index] };
isDescargoDialogOpen = true;
}
function saveDescargo() {
if (!formData?.descargos) return;
if (editingDescargoIndex !== null) {
formData.descargos[editingDescargoIndex] = { ...currentDescargo };
} else {
formData.descargos = [...formData.descargos, { ...currentDescargo }];
}
isDescargoDialogOpen = false;
}
function deleteDescargo(index: number) {
if (!formData?.descargos) return;
if (confirm('¿Está seguro de eliminar este descargo?')) {
formData.descargos = formData.descargos.filter((_, i) => i !== index);
}
}
function deleteAllDescargos() {
if (!formData?.descargos) return;
if (formData.descargos.length === 0) {
alert('No hay descargos para eliminar');
return;
}
if (confirm(`¿Está seguro de eliminar todos los descargos (${formData.descargos.length})?`)) {
formData.descargos = [];
}
}
function importarDescargos() {
alert('Importar Descargos (Formato CSV) no implementado aún');
}
// Funciones para Destinatarios
function openNewDestinatario() {
editingDestinatarioIndex = null;
currentDestinatario = {
clave: '',
identificacion_fiscal: '',
nombre: ''
};
isDestinatarioDialogOpen = true;
}
function openEditDestinatario(index: number) {
if (!formData?.destinatarios) return;
editingDestinatarioIndex = index;
currentDestinatario = { ...formData.destinatarios[index] };
isDestinatarioDialogOpen = true;
}
function saveDestinatario() {
if (!formData?.destinatarios) return;
if (editingDestinatarioIndex !== null) {
formData.destinatarios[editingDestinatarioIndex] = { ...currentDestinatario };
} else {
formData.destinatarios = [...formData.destinatarios, { ...currentDestinatario }];
}
isDestinatarioDialogOpen = false;
}
function deleteDestinatario(index: number) {
if (!formData?.destinatarios) return;
if (confirm('¿Está seguro de eliminar este destinatario?')) {
formData.destinatarios = formData.destinatarios.filter((_, i) => i !== index);
}
}
</script>
<div class="space-y-4">
<!-- Sección de Descargos -->
<Card>
<CardHeader class="pb-3">
<CardTitle class="text-base">Descargos</CardTitle>
</CardHeader>
<CardContent class="space-y-4">
<div class="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead>Patente</TableHead>
<TableHead>Pedimento</TableHead>
<TableHead>Aduana</TableHead>
<TableHead>Clave</TableHead>
<TableHead>Fecha Pago</TableHead>
<TableHead>Fracción</TableHead>
<TableHead>UMT</TableHead>
<TableHead class="text-right">Cantidad Tarifa</TableHead>
<TableHead class="w-[100px]">Acciones</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#if !formData?.descargos || formData.descargos.length === 0}
<TableRow>
<TableCell colspan={9} class="text-center text-muted-foreground py-8">
No hay descargos registrados
</TableCell>
</TableRow>
{:else}
{#each formData.descargos as descargo, index}
<TableRow>
<TableCell>{descargo.patente}</TableCell>
<TableCell>{descargo.pedimento}</TableCell>
<TableCell>{descargo.aduana}</TableCell>
<TableCell>{descargo.clave}</TableCell>
<TableCell>{descargo.fecha_pago}</TableCell>
<TableCell>{descargo.fraccion}</TableCell>
<TableCell>{descargo.umt}</TableCell>
<TableCell class="text-right"
>{descargo.cantidad_tarifa.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}</TableCell
>
<TableCell>
<div class="flex gap-1">
<Button size="sm" variant="ghost" onclick={() => openEditDescargo(index)}>
<Pencil size={14} />
</Button>
<Button size="sm" variant="ghost" onclick={() => deleteDescargo(index)}>
<Trash2 size={14} />
</Button>
</div>
</TableCell>
</TableRow>
{/each}
{/if}
</TableBody>
</Table>
</div>
<div class="flex gap-2">
<Button size="sm" variant="outline" onclick={importarDescargos}>
<Upload class="mr-1.5" size={14} />
Importar Descargos (Formato CSV)
</Button>
<Button size="sm" onclick={openNewDescargo}>
<Plus class="mr-1.5" size={14} />
Nuevo
</Button>
<Button
size="sm"
variant="outline"
disabled={formData?.descargos?.length === 0}
onclick={() => {
const index = 0;
if (formData?.descargos?.[index]) openEditDescargo(index);
}}
>
<Pencil class="mr-1.5" size={14} />
Editar
</Button>
<Button
size="sm"
variant="destructive"
disabled={formData?.descargos?.length === 0}
onclick={() => {
const index = 0;
if (formData?.descargos?.[index]) deleteDescargo(index);
}}
>
<Trash2 class="mr-1.5" size={14} />
Borrar
</Button>
<Button
size="sm"
variant="destructive"
disabled={formData?.descargos?.length === 0}
onclick={deleteAllDescargos}
>
<Trash class="mr-1.5" size={14} />
Borrar Todo
</Button>
</div>
</CardContent>
</Card>
<!-- Sección de Destinatarios -->
<Card>
<CardHeader class="pb-3">
<CardTitle class="text-base">Destinatarios</CardTitle>
</CardHeader>
<CardContent class="space-y-4">
<div class="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead>Clave</TableHead>
<TableHead>Identificación Fiscal</TableHead>
<TableHead>Nombre</TableHead>
<TableHead class="w-[100px]">Acciones</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#if !formData?.destinatarios || formData.destinatarios.length === 0}
<TableRow>
<TableCell colspan={4} class="text-center text-muted-foreground py-8">
No hay destinatarios registrados
</TableCell>
</TableRow>
{:else}
{#each formData.destinatarios as destinatario, index}
<TableRow>
<TableCell>{destinatario.clave}</TableCell>
<TableCell>{destinatario.identificacion_fiscal}</TableCell>
<TableCell>{destinatario.nombre}</TableCell>
<TableCell>
<div class="flex gap-1">
<Button size="sm" variant="ghost" onclick={() => openEditDestinatario(index)}>
<Pencil size={14} />
</Button>
<Button size="sm" variant="ghost" onclick={() => deleteDestinatario(index)}>
<Trash2 size={14} />
</Button>
</div>
</TableCell>
</TableRow>
{/each}
{/if}
</TableBody>
</Table>
</div>
<div class="flex gap-2">
<Button size="sm" onclick={openNewDestinatario}>
<Plus class="mr-1.5" size={14} />
Nuevo
</Button>
<Button
size="sm"
variant="outline"
disabled={formData?.destinatarios?.length === 0}
onclick={() => {
const index = 0;
if (formData?.destinatarios?.[index]) openEditDestinatario(index);
}}
>
<Pencil class="mr-1.5" size={14} />
Editar
</Button>
<Button
size="sm"
variant="destructive"
disabled={formData?.destinatarios?.length === 0}
onclick={() => {
const index = 0;
if (formData?.destinatarios?.[index]) deleteDestinatario(index);
}}
>
<Trash2 class="mr-1.5" size={14} />
Borrar
</Button>
</div>
</CardContent>
</Card>
</div>
<!-- Dialog para Descargos -->
<Dialog bind:open={isDescargoDialogOpen}>
<DialogContent class="max-w-3xl">
<DialogHeader>
<DialogTitle>
{editingDescargoIndex !== null ? 'Editar Descargo' : 'Nuevo Descargo'}
</DialogTitle>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2">
<Label for="d_patente">Patente *</Label>
<Input id="d_patente" bind:value={currentDescargo.patente} placeholder="0000" />
</div>
<div class="space-y-2">
<Label for="d_pedimento">Pedimento *</Label>
<Input
id="d_pedimento"
bind:value={currentDescargo.pedimento}
placeholder="00 00 0000 0000000"
/>
</div>
<div class="space-y-2">
<Label for="d_aduana">Aduana *</Label>
<Input id="d_aduana" bind:value={currentDescargo.aduana} placeholder="00" />
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="d_clave">Clave *</Label>
<Input id="d_clave" bind:value={currentDescargo.clave} placeholder="Clave" />
</div>
<div class="space-y-2">
<Label for="d_fecha_pago">Fecha Pago *</Label>
<Input id="d_fecha_pago" type="date" bind:value={currentDescargo.fecha_pago} />
</div>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2">
<Label for="d_fraccion">Fracción *</Label>
<Input id="d_fraccion" bind:value={currentDescargo.fraccion} placeholder="0000.00.00" />
</div>
<div class="space-y-2">
<Label for="d_umt">UMT *</Label>
<Input id="d_umt" bind:value={currentDescargo.umt} placeholder="Ej: KG" />
</div>
<div class="space-y-2">
<Label for="d_cantidad_tarifa">Cantidad Tarifa *</Label>
<Input
id="d_cantidad_tarifa"
type="number"
step="0.01"
bind:value={currentDescargo.cantidad_tarifa}
placeholder="0.00"
/>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isDescargoDialogOpen = false)}>Cancelar</Button>
<Button
onclick={saveDescargo}
disabled={!currentDescargo.patente ||
!currentDescargo.pedimento ||
!currentDescargo.aduana ||
!currentDescargo.clave ||
!currentDescargo.fecha_pago ||
!currentDescargo.fraccion ||
!currentDescargo.umt}
>
{editingDescargoIndex !== null ? 'Actualizar' : 'Guardar'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<!-- Dialog para Destinatarios -->
<Dialog bind:open={isDestinatarioDialogOpen}>
<DialogContent class="max-w-2xl">
<DialogHeader>
<DialogTitle>
{editingDestinatarioIndex !== null ? 'Editar Destinatario' : 'Nuevo Destinatario'}
</DialogTitle>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="space-y-2">
<Label for="dest_clave">Clave *</Label>
<Input id="dest_clave" bind:value={currentDestinatario.clave} placeholder="Clave" />
</div>
<div class="space-y-2">
<Label for="dest_identificacion">Identificación Fiscal *</Label>
<Input
id="dest_identificacion"
bind:value={currentDestinatario.identificacion_fiscal}
placeholder="RFC o identificación fiscal"
/>
</div>
<div class="space-y-2">
<Label for="dest_nombre">Nombre *</Label>
<Input
id="dest_nombre"
bind:value={currentDestinatario.nombre}
placeholder="Nombre del destinatario"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isDestinatarioDialogOpen = false)}
>Cancelar</Button
>
<Button
onclick={saveDestinatario}
disabled={!currentDestinatario.clave ||
!currentDestinatario.identificacion_fiscal ||
!currentDestinatario.nombre}
>
{editingDestinatarioIndex !== null ? 'Actualizar' : 'Guardar'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -0,0 +1,414 @@
<script lang="ts">
import { Card, CardContent } from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/ui/table';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter
} from '$lib/components/ui/dialog';
import { Textarea } from '$lib/components/ui/textarea';
import {
Plus,
Pencil,
Trash2,
Upload,
FileText,
Search,
FolderOpen,
CheckCircle,
ChevronsLeft,
ChevronLeft,
ChevronRight,
ChevronsRight
} from 'lucide-svelte';
interface Digitalizacion {
id?: number;
linea: number;
clave: string;
documento: string;
e_document: string;
operacion: string;
observaciones: string;
}
let {
formData = $bindable({
digitalizaciones: [] as Digitalizacion[]
})
}: {
formData: {
digitalizaciones: Digitalizacion[];
};
} = $props();
// Estados para diálogo
let isDialogOpen = $state(false);
let editingIndex = $state<number | null>(null);
// Paginación
let currentPage = $state(0);
let pageSize = 10;
let currentDigitalizacion = $state<Digitalizacion>({
linea: 0,
clave: '',
documento: '',
e_document: '',
operacion: '',
observaciones: ''
});
const totalPages = $derived(
Math.ceil((formData?.digitalizaciones?.length ?? 0) / pageSize)
);
const paginatedDigitalizaciones = $derived(
formData?.digitalizaciones?.slice(
currentPage * pageSize,
(currentPage + 1) * pageSize
) ?? []
);
function openNewDigitalizacion() {
editingIndex = null;
const nextLinea = formData?.digitalizaciones?.length
? Math.max(...formData.digitalizaciones.map((d) => d.linea)) + 1
: 1;
currentDigitalizacion = {
linea: nextLinea,
clave: '',
documento: '',
e_document: '',
operacion: '',
observaciones: ''
};
isDialogOpen = true;
}
function openEditDigitalizacion(index: number) {
if (!formData?.digitalizaciones) return;
const realIndex = currentPage * pageSize + index;
editingIndex = realIndex;
currentDigitalizacion = { ...formData.digitalizaciones[realIndex] };
isDialogOpen = true;
}
function saveDigitalizacion() {
if (!formData?.digitalizaciones) return;
if (editingIndex !== null) {
formData.digitalizaciones[editingIndex] = { ...currentDigitalizacion };
} else {
formData.digitalizaciones = [...formData.digitalizaciones, { ...currentDigitalizacion }];
}
isDialogOpen = false;
}
function deleteDigitalizacion(index: number) {
if (!formData?.digitalizaciones) return;
const realIndex = currentPage * pageSize + index;
if (confirm('¿Está seguro de eliminar esta digitalización?')) {
formData.digitalizaciones = formData.digitalizaciones.filter((_, i) => i !== realIndex);
// Reordenar líneas
formData.digitalizaciones = formData.digitalizaciones.map((d, idx) => ({
...d,
linea: idx + 1
}));
// Ajustar página si es necesario
if (paginatedDigitalizaciones.length === 0 && currentPage > 0) {
currentPage--;
}
}
}
function digitalizarVU() {
alert('Digitalizar en VU no implementado aún');
}
function acuseDigitalizacion() {
alert('Acuse de Digitalización no implementado aún');
}
function procesarDigitalizacion() {
alert('Procesar Digitalización no implementado aún');
}
function consultarOperacion() {
alert('Consultar Operación no implementado aún');
}
function abrirDocumento() {
alert('Abrir Documento no implementado aún');
}
function cargarEDocument() {
alert('Cargar E-Document no implementado aún');
}
function goToFirstPage() {
currentPage = 0;
}
function goToPreviousPage() {
if (currentPage > 0) currentPage--;
}
function goToNextPage() {
if (currentPage < totalPages - 1) currentPage++;
}
function goToLastPage() {
currentPage = totalPages - 1;
}
</script>
<Card>
<CardContent class="p-6 space-y-4">
<!-- Tabla de digitalizaciones -->
<div class="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead class="w-[80px]">Línea</TableHead>
<TableHead>Clave</TableHead>
<TableHead>Documento</TableHead>
<TableHead>E-Document</TableHead>
<TableHead>Operación</TableHead>
<TableHead>Observaciones</TableHead>
<TableHead class="w-[100px]">Acciones</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#if !formData?.digitalizaciones || formData.digitalizaciones.length === 0}
<TableRow>
<TableCell colspan={7} class="text-center text-muted-foreground py-8">
No hay digitalizaciones registradas
</TableCell>
</TableRow>
{:else if paginatedDigitalizaciones.length === 0}
<TableRow>
<TableCell colspan={7} class="text-center text-muted-foreground py-8">
No hay datos en esta página
</TableCell>
</TableRow>
{:else}
{#each paginatedDigitalizaciones as digitalizacion, index}
<TableRow>
<TableCell class="font-medium">{digitalizacion.linea}</TableCell>
<TableCell>{digitalizacion.clave}</TableCell>
<TableCell>{digitalizacion.documento}</TableCell>
<TableCell>{digitalizacion.e_document}</TableCell>
<TableCell>{digitalizacion.operacion}</TableCell>
<TableCell class="max-w-[200px] truncate" title={digitalizacion.observaciones}>
{digitalizacion.observaciones}
</TableCell>
<TableCell>
<div class="flex gap-1">
<Button
size="sm"
variant="ghost"
onclick={() => openEditDigitalizacion(index)}
>
<Pencil size={14} />
</Button>
<Button size="sm" variant="ghost" onclick={() => deleteDigitalizacion(index)}>
<Trash2 size={14} />
</Button>
</div>
</TableCell>
</TableRow>
{/each}
{/if}
</TableBody>
</Table>
</div>
<!-- Controles de paginación -->
{#if formData?.digitalizaciones && formData.digitalizaciones.length > 0}
<div class="flex items-center justify-center gap-2 border-t pt-3">
<Button
size="sm"
variant="outline"
onclick={goToFirstPage}
disabled={currentPage === 0}
class="w-8 h-8 p-0"
>
<ChevronsLeft size={16} />
</Button>
<Button
size="sm"
variant="outline"
onclick={goToPreviousPage}
disabled={currentPage === 0}
class="w-8 h-8 p-0"
>
<ChevronLeft size={16} />
</Button>
<span class="text-sm text-muted-foreground px-2">
Página {currentPage + 1} de {totalPages || 1}
</span>
<Button
size="sm"
variant="outline"
onclick={goToNextPage}
disabled={currentPage >= totalPages - 1}
class="w-8 h-8 p-0"
>
<ChevronRight size={16} />
</Button>
<Button
size="sm"
variant="outline"
onclick={goToLastPage}
disabled={currentPage >= totalPages - 1}
class="w-8 h-8 p-0"
>
<ChevronsRight size={16} />
</Button>
</div>
{/if}
<!-- Botones de acción -->
<div class="flex flex-wrap gap-2 border-t pt-4">
<Button size="sm" variant="outline" onclick={digitalizarVU}>
<Upload class="mr-1.5" size={14} />
Digitalizar en VU
</Button>
<Button size="sm" variant="outline" onclick={acuseDigitalizacion}>
<CheckCircle class="mr-1.5" size={14} />
Acuse de Digitalización
</Button>
<Button size="sm" variant="outline" onclick={procesarDigitalizacion}>
<FileText class="mr-1.5" size={14} />
Procesar Digitalización
</Button>
<Button size="sm" onclick={openNewDigitalizacion}>
<Plus class="mr-1.5" size={14} />
Nuevo
</Button>
<Button
size="sm"
variant="outline"
disabled={formData?.digitalizaciones?.length === 0}
onclick={() => {
if (paginatedDigitalizaciones[0]) openEditDigitalizacion(0);
}}
>
<Pencil class="mr-1.5" size={14} />
Editar
</Button>
<Button
size="sm"
variant="destructive"
disabled={formData?.digitalizaciones?.length === 0}
onclick={() => {
if (paginatedDigitalizaciones[0]) deleteDigitalizacion(0);
}}
>
<Trash2 class="mr-1.5" size={14} />
Borrar
</Button>
<Button size="sm" variant="outline" onclick={consultarOperacion}>
<Search class="mr-1.5" size={14} />
Consultar Operación
</Button>
<Button size="sm" variant="outline" onclick={abrirDocumento}>
<FolderOpen class="mr-1.5" size={14} />
Abrir Documento
</Button>
<Button size="sm" variant="outline" onclick={cargarEDocument}>
<Upload class="mr-1.5" size={14} />
Cargar E-Document
</Button>
</div>
</CardContent>
</Card>
<!-- Dialog para Digitalización -->
<Dialog bind:open={isDialogOpen}>
<DialogContent class="max-w-2xl">
<DialogHeader>
<DialogTitle>
{editingIndex !== null ? 'Editar Digitalización' : 'Nueva Digitalización'}
</DialogTitle>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="linea">Línea *</Label>
<Input
id="linea"
type="number"
bind:value={currentDigitalizacion.linea}
placeholder="1"
readonly
/>
</div>
<div class="space-y-2">
<Label for="clave">Clave *</Label>
<Input id="clave" bind:value={currentDigitalizacion.clave} placeholder="Clave" />
</div>
</div>
<div class="space-y-2">
<Label for="documento">Documento *</Label>
<Input
id="documento"
bind:value={currentDigitalizacion.documento}
placeholder="Nombre o tipo del documento"
/>
</div>
<div class="space-y-2">
<Label for="e_document">E-Document</Label>
<Input
id="e_document"
bind:value={currentDigitalizacion.e_document}
placeholder="Referencia del documento electrónico"
/>
</div>
<div class="space-y-2">
<Label for="operacion">Operación</Label>
<Input
id="operacion"
bind:value={currentDigitalizacion.operacion}
placeholder="Tipo de operación"
/>
</div>
<div class="space-y-2">
<Label for="observaciones">Observaciones</Label>
<Textarea
id="observaciones"
bind:value={currentDigitalizacion.observaciones}
placeholder="Observaciones adicionales"
rows={3}
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isDialogOpen = false)}>Cancelar</Button>
<Button
onclick={saveDigitalizacion}
disabled={!currentDigitalizacion.clave || !currentDigitalizacion.documento}
>
{editingIndex !== null ? 'Actualizar' : 'Guardar'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -0,0 +1,530 @@
<script lang="ts">
import { Card, CardContent } from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/ui/table';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter
} from '$lib/components/ui/dialog';
import * as Select from '$lib/components/ui/select';
import Checkbox from '$lib/components/ui/checkbox/checkbox.svelte';
import { Textarea } from '$lib/components/ui/textarea';
import {
Plus,
Pencil,
Trash2,
FileSpreadsheet,
FileText,
Check,
RotateCw,
ArrowUpDown
} from 'lucide-svelte';
interface Factura {
id?: number;
numero_factura: string;
fecha: string;
proveedor: string;
pais: string;
moneda: string;
valor_factura: number;
valor_usd: number;
es_consolidado: boolean;
arribo_consolidado: boolean;
inicio?: string;
fin?: string;
detalle_e_document?: string;
}
let {
formData = $bindable({
facturas: [] as Factura[]
})
}: {
formData: {
facturas: Factura[];
};
} = $props();
let isDialogOpen = $state(false);
let editingIndex = $state<number | null>(null);
let selectedFacturas = $state<Set<number>>(new Set());
let currentFactura = $state<Factura>({
numero_factura: '',
fecha: '',
proveedor: '',
pais: '',
moneda: 'USD',
valor_factura: 0,
valor_usd: 0,
es_consolidado: false,
arribo_consolidado: false,
inicio: '',
fin: '',
detalle_e_document: ''
});
const monedas = ['USD', 'MXN', 'EUR', 'CAD', 'JPY', 'GBP', 'CHF'];
const paises = [
'Estados Unidos',
'México',
'Canadá',
'China',
'Alemania',
'Japón',
'Reino Unido',
'Francia',
'Italia',
'España',
'Corea del Sur',
'India'
];
const totalFacturas = $derived(formData?.facturas?.length ?? 0);
const sumaPesos = $derived(
formData?.facturas?.reduce((sum, f) => sum + (f.valor_factura || 0), 0) ?? 0
);
const sumaDolares = $derived(formData?.facturas?.reduce((sum, f) => sum + (f.valor_usd || 0), 0) ?? 0);
function openNewFactura() {
editingIndex = null;
currentFactura = {
numero_factura: '',
fecha: '',
proveedor: '',
pais: '',
moneda: 'USD',
valor_factura: 0,
valor_usd: 0,
es_consolidado: false,
arribo_consolidado: false,
inicio: '',
fin: '',
detalle_e_document: ''
};
isDialogOpen = true;
}
function openEditFactura(index: number) {
if (!formData?.facturas) return;
editingIndex = index;
currentFactura = { ...formData.facturas[index] };
isDialogOpen = true;
}
function saveFactura() {
if (!formData?.facturas) return;
if (editingIndex !== null) {
formData.facturas[editingIndex] = { ...currentFactura };
} else {
formData.facturas = [...formData.facturas, { ...currentFactura }];
}
isDialogOpen = false;
}
function deleteFactura(index: number) {
if (!formData?.facturas) return;
if (confirm('¿Está seguro de eliminar esta factura?')) {
formData.facturas = formData.facturas.filter((_, i) => i !== index);
}
}
function clearForm() {
currentFactura = {
numero_factura: '',
fecha: '',
proveedor: '',
pais: '',
moneda: 'USD',
valor_factura: 0,
valor_usd: 0,
es_consolidado: false,
arribo_consolidado: false,
inicio: '',
fin: '',
detalle_e_document: ''
};
}
function toggleSelectFactura(index: number) {
if (selectedFacturas.has(index)) {
selectedFacturas.delete(index);
} else {
selectedFacturas.add(index);
}
selectedFacturas = new Set(selectedFacturas);
}
function selectAllWithoutCove() {
// Esta función seleccionaría facturas sin COVE
// Por ahora seleccionamos todas
if (!formData?.facturas) return;
selectedFacturas = new Set(formData.facturas.map((_, i) => i));
}
function deleteSelectedFacturas() {
if (!formData?.facturas) return;
if (selectedFacturas.size === 0) {
alert('No hay facturas seleccionadas');
return;
}
if (confirm(`¿Está seguro de eliminar ${selectedFacturas.size} factura(s)?`)) {
formData.facturas = formData.facturas.filter((_, i) => !selectedFacturas.has(i));
selectedFacturas = new Set();
}
}
function validateVentanillaUnica() {
alert('Validación en Ventanilla Única no implementada aún');
}
function importFacturas() {
alert('Importación de facturas no implementada aún');
}
function overwriteFacturas() {
alert('Sobre escribir facturas no implementado aún');
}
function reorderFacturas() {
alert('Reordenar facturas no implementado aún');
}
function datosSonana() {
alert('Datos Sonana no implementado aún');
}
</script>
<Card>
<CardContent class="p-6 space-y-4">
<!-- Barra de herramientas superior -->
<div class="flex flex-wrap gap-2 border-b pb-4">
<Button size="sm" variant="outline" onclick={clearForm}>
<RotateCw class="mr-1.5" size={14} />
Limpiar
</Button>
<Button size="sm" variant="outline" onclick={selectAllWithoutCove}>
<Check class="mr-1.5" size={14} />
Selec. sin cove
</Button>
<Button size="sm" onclick={openNewFactura}>
<Plus class="mr-1.5" size={14} />
Nuevo
</Button>
<Button
size="sm"
variant="outline"
disabled={selectedFacturas.size !== 1}
onclick={() => {
const index = Array.from(selectedFacturas)[0];
if (index !== undefined) openEditFactura(index);
}}
>
<Pencil class="mr-1.5" size={14} />
Editar
</Button>
<Button
size="sm"
variant="destructive"
disabled={selectedFacturas.size !== 1}
onclick={() => {
const index = Array.from(selectedFacturas)[0];
if (index !== undefined) deleteFactura(index);
}}
>
<Trash2 class="mr-1.5" size={14} />
Borrar
</Button>
<Button
size="sm"
variant="destructive"
disabled={selectedFacturas.size === 0}
onclick={deleteSelectedFacturas}
>
<Trash2 class="mr-1.5" size={14} />
Borrar Facturas Selec.
</Button>
<Button size="sm" variant="outline" onclick={validateVentanillaUnica}>
<Check class="mr-1.5" size={14} />
Validar en Ventanilla Única
</Button>
<Button size="sm" variant="outline" onclick={importFacturas}>
<FileSpreadsheet class="mr-1.5" size={14} />
Imp Facturas
</Button>
<Button size="sm" variant="outline" onclick={overwriteFacturas}>
<FileText class="mr-1.5" size={14} />
Sobre escribir
</Button>
<Button size="sm" variant="outline" onclick={reorderFacturas}>
<ArrowUpDown class="mr-1.5" size={14} />
Reordenar
</Button>
<Button size="sm" variant="outline" onclick={datosSonana}>
<FileText class="mr-1.5" size={14} />
Datos Sonana
</Button>
</div>
<!-- Tabla de facturas -->
<div class="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead class="w-[50px]">Sel.</TableHead>
<TableHead>Núm. Factura</TableHead>
<TableHead>Fecha</TableHead>
<TableHead>Proveedor</TableHead>
<TableHead>País</TableHead>
<TableHead>Moneda</TableHead>
<TableHead class="text-right">Valor Factura</TableHead>
<TableHead class="text-right">Valor USD</TableHead>
<TableHead class="text-center">Consolidado</TableHead>
<TableHead class="text-center">Arribo Consol.</TableHead>
<TableHead class="w-[100px]">Acciones</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#if !formData?.facturas || formData.facturas.length === 0}
<TableRow>
<TableCell colspan={11} class="text-center text-muted-foreground py-8">
No hay facturas registradas. Haga clic en "Nuevo" para agregar una.
</TableCell>
</TableRow>
{:else}
{#each formData.facturas as factura, index}
<TableRow>
<TableCell>
<Checkbox
checked={selectedFacturas.has(index)}
onCheckedChange={() => toggleSelectFactura(index)}
/>
</TableCell>
<TableCell class="font-medium">{factura.numero_factura}</TableCell>
<TableCell>{factura.fecha}</TableCell>
<TableCell>{factura.proveedor}</TableCell>
<TableCell>{factura.pais}</TableCell>
<TableCell>{factura.moneda}</TableCell>
<TableCell class="text-right"
>{factura.valor_factura.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}</TableCell
>
<TableCell class="text-right"
>{factura.valor_usd.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}</TableCell
>
<TableCell class="text-center">
{#if factura.es_consolidado}
<Check class="inline" size={16} />
{/if}
</TableCell>
<TableCell class="text-center">
{#if factura.arribo_consolidado}
<Check class="inline" size={16} />
{/if}
</TableCell>
<TableCell>
<div class="flex gap-1">
<Button size="sm" variant="ghost" onclick={() => openEditFactura(index)}>
<Pencil size={14} />
</Button>
<Button size="sm" variant="ghost" onclick={() => deleteFactura(index)}>
<Trash2 size={14} />
</Button>
</div>
</TableCell>
</TableRow>
{/each}
{/if}
</TableBody>
</Table>
</div>
<!-- Totales -->
<div class="flex gap-6 text-sm font-medium border-t pt-4">
<div>Facturas: <span class="text-primary">{totalFacturas}</span></div>
<div>
Suma Pesos: <span class="text-primary"
>{sumaPesos.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
MXN</span
>
</div>
<div>
Suma USD: <span class="text-primary"
>{sumaDolares.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})} USD</span
>
</div>
</div>
</CardContent>
</Card>
<!-- Dialog para agregar/editar factura -->
<Dialog bind:open={isDialogOpen}>
<DialogContent class="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingIndex !== null ? 'Editar Factura' : 'Nueva Factura'}</DialogTitle>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="numero_factura">Núm. de Factura *</Label>
<Input
id="numero_factura"
bind:value={currentFactura.numero_factura}
placeholder="Ej: INV-2024-001"
/>
</div>
<div class="space-y-2">
<Label for="fecha">Fecha *</Label>
<Input id="fecha" type="date" bind:value={currentFactura.fecha} />
</div>
</div>
<div class="space-y-2">
<Label for="proveedor">Proveedor *</Label>
<Input
id="proveedor"
bind:value={currentFactura.proveedor}
placeholder="Nombre del proveedor"
/>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="pais">País *</Label>
<Select.Root
type="single"
value={currentFactura.pais}
onValueChange={(v: string | undefined) => v && (currentFactura.pais = v)}
>
<Select.Trigger id="pais">
{currentFactura.pais || 'Seleccionar país'}
</Select.Trigger>
<Select.Content>
{#each paises as pais}
<Select.Item value={pais}>{pais}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<div class="space-y-2">
<Label for="moneda">Moneda *</Label>
<Select.Root
type="single"
value={currentFactura.moneda}
onValueChange={(v: string | undefined) => v && (currentFactura.moneda = v)}
>
<Select.Trigger id="moneda">
{currentFactura.moneda || 'Seleccionar moneda'}
</Select.Trigger>
<Select.Content>
{#each monedas as moneda}
<Select.Item value={moneda}>{moneda}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="valor_factura">Valor Factura *</Label>
<Input
id="valor_factura"
type="number"
step="0.01"
bind:value={currentFactura.valor_factura}
placeholder="0.00"
/>
</div>
<div class="space-y-2">
<Label for="valor_usd">Valor USD *</Label>
<Input
id="valor_usd"
type="number"
step="0.01"
bind:value={currentFactura.valor_usd}
placeholder="0.00"
/>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="flex items-center space-x-2">
<Checkbox
id="es_consolidado"
checked={currentFactura.es_consolidado}
onCheckedChange={(checked: boolean | 'indeterminate') => (currentFactura.es_consolidado = checked === true)}
/>
<Label for="es_consolidado" class="font-normal cursor-pointer">Es Consolidado</Label>
</div>
<div class="flex items-center space-x-2">
<Checkbox
id="arribo_consolidado"
checked={currentFactura.arribo_consolidado}
onCheckedChange={(checked: boolean | 'indeterminate') => (currentFactura.arribo_consolidado = checked === true)}
/>
<Label for="arribo_consolidado" class="font-normal cursor-pointer"
>Arribo Consolidado</Label
>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="inicio">Inicio</Label>
<Input id="inicio" type="date" bind:value={currentFactura.inicio} />
</div>
<div class="space-y-2">
<Label for="fin">Fin</Label>
<Input id="fin" type="date" bind:value={currentFactura.fin} />
</div>
</div>
<div class="space-y-2">
<Label for="detalle_e_document">Detalle de E_Document</Label>
<Textarea
id="detalle_e_document"
bind:value={currentFactura.detalle_e_document}
placeholder="Detalles adicionales del documento electrónico"
rows={3}
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isDialogOpen = false)}>Cancelar</Button>
<Button
onclick={saveFactura}
disabled={!currentFactura.numero_factura ||
!currentFactura.fecha ||
!currentFactura.proveedor ||
!currentFactura.pais ||
!currentFactura.moneda}
>
{editingIndex !== null ? 'Actualizar' : 'Guardar'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -4,6 +4,7 @@
import { Label } from '$lib/components/ui/label';
import * as Select from '$lib/components/ui/select';
import { getExchangeRateByDate } from '$lib/api/dashboard/a76/exchange-rate';
import { companyStore } from '$lib/stores/company.svelte';
import type { Pedimento } from '$lib/api/dashboard/a76/pedimentos';
import type { PedimentoCode } from '$lib/api/dashboard/refrence_data/pedimento_codes';
import type { CustomsSection } from '$lib/api/dashboard/refrence_data/customs_sections';
@@ -29,6 +30,9 @@
codePedimentoRegimens?: CodePedimentoRegimen[];
} = $props();
// Estado para controlar la sección activa de la navegación
let activeSection = $state('fechas');
// Extraer regímenes únicos de codePedimentoRegimens
const uniqueRegimens = $derived(
Array.from(new Set(codePedimentoRegimens.map(r => r.regimen_code).filter((code): code is string => code !== null)))
@@ -198,6 +202,11 @@
// Obtener el año actual (últimos 2 dígitos)
const currentYear = String(new Date().getFullYear()).slice(-2);
// Obtener fecha y hora actual para captura
const now = new Date();
const currentDate = now.toISOString().substring(0, 10); // YYYY-MM-DD
const currentTime = now.toTimeString().substring(0, 5); // HH:MM
// Inicializar formData con los valores del pedimento (o vacío si es null)
if (!formData) {
@@ -223,7 +232,10 @@
extraction_date: datesData?.extraction_date ? datesData.extraction_date.substring(0, 10) : '',
rectification_payment_date: datesData?.rectification_payment_date ? datesData.rectification_payment_date.substring(0, 10) : '',
original_date: datesData?.original_date ? datesData.original_date.substring(0, 10) : '',
payment_date: datesData?.payment_date ? datesData.payment_date.substring(0, 10) : ''
payment_date: datesData?.payment_date ? datesData.payment_date.substring(0, 10) : '',
// Campos de captura automática
fecha_captura: currentDate,
hora_captura: currentTime
};
}
@@ -236,10 +248,10 @@
// Obtener automáticamente el tipo de cambio cuando cambie la fecha de entrada
$effect(() => {
if (formData && formData.entry_date) {
if (formData && formData.entry_date && companyStore.activeCompany) {
console.log('Buscando tipo de cambio para fecha:', formData.entry_date);
getExchangeRateByDate(formData.entry_date)
getExchangeRateByDate(formData.entry_date, companyStore.activeCompany.id)
.then(usdRate => {
console.log('Tipo de cambio USD encontrado:', usdRate);
if (usdRate && formData) {
@@ -277,6 +289,14 @@
{ value: 'consolidado', label: 'Consolidado' },
{ value: 'normal', label: 'Normal' }
];
const tipoOperacionOptions = [
{ value: 'incrementables', label: 'Incrementables' },
{ value: 'identificadores', label: 'Identificadores' },
{ value: 'indices', label: 'Indices' },
{ value: 'adicional', label: 'Adicional' },
{ value: 'decrementable', label: 'Decrementable' }
];
</script>
<Card.Root>
@@ -346,7 +366,7 @@
<Select.Root
type="single"
value={formData.license || ''}
onValueChange={(v: string) => formData.license = v ?? ''}
onValueChange={(v: string | undefined) => formData.license = v || null}
>
<Select.Trigger class="w-full md:w-24">
<span class="truncate">
@@ -354,12 +374,17 @@
</span>
</Select.Trigger>
<Select.Content class="max-w-[300px] max-h-[300px]">
{@const validBrokers = customsBrokers.filter(b => b.license)}
{#if customsBrokers.length === 0}
<div class="px-2 py-1.5 text-sm text-muted-foreground">
No hay patentes disponibles
No hay agentes aduanales registrados
</div>
{:else if validBrokers.length === 0}
<div class="px-2 py-1.5 text-sm text-muted-foreground">
No hay patentes válidas. Edita el agente aduanal para agregar su patente.
</div>
{:else}
{#each customsBrokers as broker}
{#each validBrokers as broker}
{@const displayName = broker.name || broker.broker_key || ''}
<Select.Item value={broker.license}>
<span class="truncate overflow-hidden text-ellipsis whitespace-nowrap" title={displayName ? `${displayName} - ${broker.license}` : broker.license}>
@@ -560,7 +585,9 @@
</Select.Content>
</Select.Root>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Valor USD -->
<div class="space-y-2">
<Label for="usd_value">Valor USD</Label>
@@ -595,71 +622,540 @@
bind:value={formData.gross_weight}
placeholder="Ej: 100.00"
/>
</div>
</div>
</div>
<!-- Fechas -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Fecha de Entrada -->
<!-- Despacho -->
<div class="space-y-2">
<Label for="entry_date">Fecha de Entrada</Label>
<Label for="despacho">Despacho</Label>
<Input
id="entry_date"
type="date"
bind:value={formData.entry_date}
id="despacho"
type="number"
bind:value={formData.despacho}
placeholder="0"
/>
</div>
<!-- Fecha de Presentación -->
<!-- Aduana E/S -->
<div class="space-y-2">
<Label for="pedimento_date">Fecha de Presentación</Label>
<Label for="aduana_es">Aduana E/S</Label>
<Input
id="pedimento_date"
type="date"
bind:value={formData.pedimento_date}
id="aduana_es"
type="number"
bind:value={formData.aduana_es}
placeholder="0"
/>
</div>
<!-- Fecha de Extracción -->
<!-- E/S -->
<div class="space-y-2">
<Label for="extraction_date">Fecha de Extracción</Label>
<Label for="es">E/S</Label>
<Input
id="extraction_date"
type="date"
bind:value={formData.extraction_date}
id="es"
type="number"
bind:value={formData.es}
placeholder="0"
/>
</div>
<!-- Fecha de Pago Rectificación -->
<!-- Fecha de Captura -->
<div class="space-y-2">
<Label for="rectification_payment_date">Fecha de Pago R1</Label>
<Label for="fecha_captura">Fecha de Captura</Label>
<Input
id="rectification_payment_date"
id="fecha_captura"
type="date"
bind:value={formData.rectification_payment_date}
bind:value={formData.fecha_captura}
readonly
disabled
class="bg-muted cursor-not-allowed"
/>
</div>
<!-- Fecha Original -->
<!-- Hora de Captura -->
<div class="space-y-2">
<Label for="original_date">Fecha de Pago Original</Label>
<Label for="hora_captura">Hora de Captura</Label>
<Input
id="original_date"
type="date"
bind:value={formData.original_date}
/>
</div>
<!-- Fecha de Pago -->
<div class="space-y-2">
<Label for="payment_date">Fecha de Pago</Label>
<Input
id="payment_date"
type="date"
bind:value={formData.payment_date}
id="hora_captura"
type="time"
bind:value={formData.hora_captura}
readonly
disabled
class="bg-muted cursor-not-allowed"
/>
</div>
</div>
<!-- Lista de navegación rápida -->
<div class="w-full overflow-x-auto pb-2">
<div class="inline-flex md:grid md:w-full md:grid-cols-6 bg-muted p-1 text-muted-foreground rounded-md">
<button
type="button"
onclick={() => activeSection = 'fechas'}
data-state={activeSection === 'fechas' ? 'active' : ''}
class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer hover:bg-background/50 data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Fechas
</button>
<button
type="button"
onclick={() => activeSection = 'incrementables'}
data-state={activeSection === 'incrementables' ? 'active' : ''}
class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer hover:bg-background/50 data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Incrementables
</button>
<button
type="button"
onclick={() => activeSection = 'identificadores'}
data-state={activeSection === 'identificadores' ? 'active' : ''}
class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer hover:bg-background/50 data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Identificadores
</button>
<button
type="button"
onclick={() => activeSection = 'indices'}
data-state={activeSection === 'indices' ? 'active' : ''}
class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer hover:bg-background/50 data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Indices
</button>
<button
type="button"
onclick={() => activeSection = 'adicional'}
data-state={activeSection === 'adicional' ? 'active' : ''}
class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer hover:bg-background/50 data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Adicional
</button>
<button
type="button"
onclick={() => activeSection = 'decrementable'}
data-state={activeSection === 'decrementable' ? 'active' : ''}
class="inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 cursor-pointer hover:bg-background/50 data-[state=active]:border data-[state=active]:border-white/60 data-[state=active]:text-foreground"
>
Decrementable
</button>
</div>
</div>
<!-- Contenido dinámico según la sección activa -->
{#if activeSection === 'fechas'}
<!-- Fechas -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Fecha de Entrada -->
<div class="space-y-2">
<Label for="entry_date">Fecha de Entrada</Label>
<Input
id="entry_date"
type="date"
bind:value={formData.entry_date}
/>
</div>
<!-- Fecha de Presentación -->
<div class="space-y-2">
<Label for="pedimento_date">Fecha de Presentación</Label>
<Input
id="pedimento_date"
type="date"
bind:value={formData.pedimento_date}
/>
</div>
<!-- Fecha de Extracción -->
<div class="space-y-2">
<Label for="extraction_date">Fecha de Extracción</Label>
<Input
id="extraction_date"
type="date"
bind:value={formData.extraction_date}
/>
</div>
<!-- Fecha de Pago Rectificación -->
<div class="space-y-2">
<Label for="rectification_payment_date">Fecha de Pago R1</Label>
<Input
id="rectification_payment_date"
type="date"
bind:value={formData.rectification_payment_date}
/>
</div>
<!-- Fecha Original -->
<div class="space-y-2">
<Label for="original_date">Fecha de Pago Original</Label>
<Input
id="original_date"
type="date"
bind:value={formData.original_date}
/>
</div>
<!-- Fecha de Pago -->
<div class="space-y-2">
<Label for="payment_date">Fecha de Pago</Label>
<Input
id="payment_date"
type="date"
bind:value={formData.payment_date}
/>
</div>
</div>
{:else if activeSection === 'incrementables'}
<!-- Incrementables -->
<div class="space-y-4">
<!-- Factor informativo -->
<div class="text-sm font-medium">
Factor: 1.00000
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Valor Seguro -->
<div class="space-y-2">
<Label for="valor_seguro">Valor Seguro</Label>
<Input
id="valor_seguro"
type="number"
bind:value={formData.valor_seguro}
placeholder="0.00"
/>
</div>
<!-- Embalajes -->
<div class="space-y-2">
<Label for="embalajes">Embalajes</Label>
<Input
id="embalajes"
type="number"
bind:value={formData.embalajes}
placeholder="0.00"
/>
</div>
<!-- Fletes -->
<div class="space-y-2">
<Label for="fletes">Fletes</Label>
<Input
id="fletes"
type="number"
bind:value={formData.fletes}
placeholder="0.00"
/>
</div>
<!-- Deducibles -->
<div class="space-y-2">
<Label for="deducibles">Deducibles</Label>
<Input
id="deducibles"
type="number"
bind:value={formData.deducibles}
placeholder="0.00"
/>
</div>
<!-- Moneda -->
<div class="space-y-2">
<Label for="moneda_incrementables">Moneda</Label>
<Input
id="moneda_incrementables"
type="number"
bind:value={formData.moneda_incrementables}
placeholder="0.00"
/>
</div>
<!-- Checkboxes agrupados -->
<div class="space-y-2">
<Label class="invisible">Opciones</Label>
<div class="space-y-2">
<div class="flex items-center space-x-2">
<input
id="no_afectar_valor_dolares_inc"
type="checkbox"
bind:checked={formData.no_afectar_valor_dolares_inc}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-0"
/>
<Label for="no_afectar_valor_dolares_inc" class="!m-0 cursor-pointer">
No Afectar Valor en Dólares del Pedimento
</Label>
</div>
<div class="flex items-center space-x-2">
<input
id="no_afectar_valor_aduana"
type="checkbox"
bind:checked={formData.no_afectar_valor_aduana}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-0"
/>
<Label for="no_afectar_valor_aduana" class="!m-0 cursor-pointer">
No Afectar Valor Aduana del Pedimento
</Label>
</div>
</div>
</div>
</div>
</div>
{:else if activeSection === 'identificadores'}
<!-- Identificadores -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<p class="text-muted-foreground col-span-full">Campos de Identificadores - Por configurar</p>
</div>
{:else if activeSection === 'indices'}
<!-- Indices -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Tipo de Factor a Actualizar -->
<div class="space-y-2">
<Label for="tipo_factor">Tipo de Factor a Actualizar</Label>
<Select.Root
type="single"
value={formData.tipo_factor || ''}
onValueChange={(v: string) => formData.tipo_factor = v ?? ''}
>
<Select.Trigger id="tipo_factor" class="w-full">
<span class="truncate">
{formData.tipo_factor === 'INPC' ? 'I.N.P.C' : formData.tipo_factor === 'variacion_cambiaria' ? 'Variación Cambiaria' : 'Seleccionar'}
</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="INPC">I.N.P.C</Select.Item>
<Select.Item value="variacion_cambiaria">Variación Cambiaria</Select.Item>
</Select.Content>
</Select.Root>
</div>
<!-- Factor Actualización -->
<div class="space-y-2">
<Label for="factor_actualizacion">Factor Actualización</Label>
<Input
id="factor_actualizacion"
type="number"
bind:value={formData.factor_actualizacion}
placeholder="0.00"
/>
</div>
<!-- Factor Actualización Manual -->
<div class="space-y-2">
<Label for="factor_actualizacion_manual" class="invisible">Checkbox</Label>
<div class="flex items-center space-x-2 h-10">
<input
id="factor_actualizacion_manual"
type="checkbox"
bind:checked={formData.factor_actualizacion_manual}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-0"
/>
<Label for="factor_actualizacion_manual" class="!m-0 cursor-pointer">
Factor Actualización Manual
</Label>
</div>
</div>
</div>
{:else if activeSection === 'adicional'}
<!-- Adicional -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Año para la impresión del pedimento -->
<div class="space-y-2">
<Label for="anio_impresion">Año para la Impresión del Pedimento</Label>
<Input
id="anio_impresion"
type="text"
bind:value={formData.anio_impresion}
placeholder="25"
maxlength={2}
class="text-center w-40 focus-visible:ring-0 focus-visible:ring-offset-0"
/>
</div>
<!-- Agregar Identificador PO Automáticamente -->
<div class="space-y-2">
<Label for="agregar_po_auto" class="invisible">Checkbox</Label>
<div class="flex items-center space-x-2 h-10">
<input
id="agregar_po_auto"
type="checkbox"
bind:checked={formData.agregar_po_auto}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="agregar_po_auto" class="!m-0 cursor-pointer">
Agregar Identificador PO Automáticamente
</Label>
</div>
</div>
<!-- No Eximir Normas ComplementoX -->
<div class="space-y-2">
<Label for="no_eximir_normas" class="invisible">Checkbox</Label>
<div class="flex items-center space-x-2 h-10">
<input
id="no_eximir_normas"
type="checkbox"
bind:checked={formData.no_eximir_normas}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="no_eximir_normas" class="!m-0 cursor-pointer">
No Eximir Normas ComplementoX
</Label>
</div>
</div>
<!-- Activar Destinatario en Facturas -->
<div class="space-y-2">
<Label for="activar_destinatario" class="invisible">Checkbox</Label>
<div class="flex items-center space-x-2 h-10">
<input
id="activar_destinatario"
type="checkbox"
bind:checked={formData.activar_destinatario}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="activar_destinatario" class="!m-0 cursor-pointer">
Activar Destinatario en Facturas
</Label>
</div>
</div>
<!-- Agregar Registro 502 en Archivo de Validación (Consolidados) -->
<div class="space-y-2">
<Label for="agregar_registro_502" class="invisible">Checkbox</Label>
<div class="flex items-center space-x-2 h-10">
<input
id="agregar_registro_502"
type="checkbox"
bind:checked={formData.agregar_registro_502}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="agregar_registro_502" class="!m-0 cursor-pointer">
Agregar Registro 502 en Archivo de Validación (Consolidados)
</Label>
</div>
</div>
<!-- Agregar y Quitar Normas -->
<div class="space-y-2">
<Label for="agregar_quitar_normas" class="invisible">Checkbox</Label>
<div class="flex items-center space-x-2 h-10">
<input
id="agregar_quitar_normas"
type="checkbox"
bind:checked={formData.agregar_quitar_normas}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="agregar_quitar_normas" class="!m-0 cursor-pointer">
Agregar y Quitar Normas
</Label>
</div>
</div>
<!-- Elegir Facturar con COVE en Partidas -->
<div class="space-y-2">
<Label for="facturar_cove" class="invisible">Checkbox</Label>
<div class="flex items-center space-x-2 h-10">
<input
id="facturar_cove"
type="checkbox"
bind:checked={formData.facturar_cove}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label for="facturar_cove" class="!m-0 cursor-pointer">
Elegir Facturar con COVE en Partidas
</Label>
</div>
</div>
</div>
{:else if activeSection === 'decrementable'}
<!-- Decrementable -->
<div class="space-y-4">
<!-- Factor informativo -->
<div class="text-sm font-medium">
Factor: 0.05000
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Fletes -->
<div class="space-y-2">
<Label for="fletes_decrementable">Fletes</Label>
<Input
id="fletes_decrementable"
type="number"
bind:value={formData.fletes_decrementable}
placeholder="0.00"
/>
</div>
<!-- Seguros -->
<div class="space-y-2">
<Label for="seguros">Seguros</Label>
<Input
id="seguros"
type="number"
bind:value={formData.seguros}
placeholder="0.00"
/>
</div>
<!-- Carga -->
<div class="space-y-2">
<Label for="carga">Carga</Label>
<Input
id="carga"
type="number"
bind:value={formData.carga}
placeholder="0.00"
/>
</div>
<!-- Descarga -->
<div class="space-y-2">
<Label for="descarga">Descarga</Label>
<Input
id="descarga"
type="number"
bind:value={formData.descarga}
placeholder="0.00"
/>
</div>
<!-- Otros -->
<div class="space-y-2">
<Label for="otros">Otros</Label>
<Input
id="otros"
type="number"
bind:value={formData.otros}
placeholder="0.00"
/>
</div>
<!-- Moneda -->
<div class="space-y-2">
<Label for="moneda_decrementable">Moneda</Label>
<Input
id="moneda_decrementable"
type="text"
bind:value={formData.moneda_decrementable}
placeholder="USD"
/>
</div>
<!-- Afectar Valor en Dólares del Pedimento -->
<div class="space-y-2">
<Label for="afectar_valor_dolares" class="invisible">Checkbox</Label>
<div class="flex items-center space-x-2 h-10">
<input
id="afectar_valor_dolares"
type="checkbox"
bind:checked={formData.afectar_valor_dolares}
class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-0"
/>
<Label for="afectar_valor_dolares" class="!m-0 cursor-pointer">
Afectar Valor en Dólares del Pedimento
</Label>
</div>
</div>
</div>
</div>
{/if}
</div>
</Card.Content>
</Card.Root>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,543 @@
<script lang="ts">
import { Card, CardContent } from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { Label } from '$lib/components/ui/label';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow
} from '$lib/components/ui/table';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogFooter
} from '$lib/components/ui/dialog';
import * as Select from '$lib/components/ui/select';
import Checkbox from '$lib/components/ui/checkbox/checkbox.svelte';
import { Textarea } from '$lib/components/ui/textarea';
import {
Plus,
Pencil,
Trash2,
FileText,
RotateCw,
Info
} from 'lucide-svelte';
interface Partida {
id?: number;
linea: number;
fraccion: string;
sub: string;
descripcion: string;
tipo: string;
sector: string;
origen: string;
cantidad_comercial: number;
umc: string;
dolares: number;
}
let {
formData = $bindable({
partidas: [] as Partida[],
convertir_uma_umc: false
})
}: {
formData: {
partidas: Partida[];
convertir_uma_umc: boolean;
};
} = $props();
let isDialogOpen = $state(false);
let editingIndex = $state<number | null>(null);
let selectedPartidas = $state<Set<number>>(new Set());
let currentPartida = $state<Partida>({
linea: 0,
fraccion: '',
sub: '',
descripcion: '',
tipo: '',
sector: '',
origen: '',
cantidad_comercial: 0,
umc: '',
dolares: 0
});
const unidadesMedida = ['KG', 'LT', 'PZ', 'MT', 'M2', 'M3', 'TON', 'CAJ', 'PAR'];
const tipos = ['Importación', 'Exportación', 'Retorno'];
const sectores = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'];
const origenes = [
'Estados Unidos',
'México',
'Canadá',
'China',
'Alemania',
'Japón',
'Reino Unido',
'Francia',
'Italia',
'España'
];
const totalPartidas = $derived(formData?.partidas?.length ?? 0);
const totalDolares = $derived(
formData?.partidas?.reduce((sum, p) => sum + (p.dolares || 0), 0) ?? 0
);
const totalCantidadComercial = $derived(
formData?.partidas?.reduce((sum, p) => sum + (p.cantidad_comercial || 0), 0) ?? 0
);
function openNewPartida() {
editingIndex = null;
const nextLinea = formData?.partidas?.length ? Math.max(...formData.partidas.map(p => p.linea)) + 1 : 1;
currentPartida = {
linea: nextLinea,
fraccion: '',
sub: '',
descripcion: '',
tipo: '',
sector: '',
origen: '',
cantidad_comercial: 0,
umc: '',
dolares: 0
};
isDialogOpen = true;
}
function openEditPartida(index: number) {
if (!formData?.partidas) return;
editingIndex = index;
currentPartida = { ...formData.partidas[index] };
isDialogOpen = true;
}
function savePartida() {
if (!formData?.partidas) return;
if (editingIndex !== null) {
formData.partidas[editingIndex] = { ...currentPartida };
} else {
formData.partidas = [...formData.partidas, { ...currentPartida }];
}
isDialogOpen = false;
}
function deletePartida(index: number) {
if (!formData?.partidas) return;
if (confirm('¿Está seguro de eliminar esta partida?')) {
formData.partidas = formData.partidas.filter((_, i) => i !== index);
// Reordenar líneas
formData.partidas = formData.partidas.map((p, idx) => ({ ...p, linea: idx + 1 }));
}
}
function clearForm() {
currentPartida = {
linea: 0,
fraccion: '',
sub: '',
descripcion: '',
tipo: '',
sector: '',
origen: '',
cantidad_comercial: 0,
umc: '',
dolares: 0
};
}
function toggleSelectPartida(index: number) {
if (selectedPartidas.has(index)) {
selectedPartidas.delete(index);
} else {
selectedPartidas.add(index);
}
selectedPartidas = new Set(selectedPartidas);
}
function deleteSelectedPartidas() {
if (!formData?.partidas) return;
if (selectedPartidas.size === 0) {
alert('No hay partidas seleccionadas');
return;
}
if (confirm(`¿Está seguro de eliminar ${selectedPartidas.size} partida(s)?`)) {
formData.partidas = formData.partidas.filter((_, i) => !selectedPartidas.has(i));
// Reordenar líneas
formData.partidas = formData.partidas.map((p, idx) => ({ ...p, linea: idx + 1 }));
selectedPartidas = new Set();
}
}
function detallesConsolidacion() {
alert('Detalles de Consolidación no implementado aún');
}
function datosSonana() {
alert('Datos Sonana no implementado aún');
}
function refreshPartidas() {
alert('Actualizar partidas no implementado aún');
}
</script>
<Card>
<CardContent class="p-6 space-y-4">
<!-- Barra de herramientas superior -->
<div class="flex flex-wrap gap-2 border-b pb-4">
<Button size="sm" variant="outline" onclick={clearForm}>
<RotateCw class="mr-1.5" size={14} />
Limpiar
</Button>
<Button size="sm" onclick={openNewPartida}>
<Plus class="mr-1.5" size={14} />
Nuevo
</Button>
<Button
size="sm"
variant="outline"
disabled={selectedPartidas.size !== 1}
onclick={() => {
const index = Array.from(selectedPartidas)[0];
if (index !== undefined) openEditPartida(index);
}}
>
<Pencil class="mr-1.5" size={14} />
Editar
</Button>
<Button
size="sm"
variant="destructive"
disabled={selectedPartidas.size !== 1}
onclick={() => {
const index = Array.from(selectedPartidas)[0];
if (index !== undefined) deletePartida(index);
}}
>
<Trash2 class="mr-1.5" size={14} />
Borrar
</Button>
<Button
size="sm"
variant="destructive"
disabled={selectedPartidas.size === 0}
onclick={deleteSelectedPartidas}
>
<Trash2 class="mr-1.5" size={14} />
Borrar Partidas
</Button>
<Button size="sm" variant="outline" onclick={detallesConsolidacion}>
<Info class="mr-1.5" size={14} />
Detalles de Consolidación
</Button>
<Button size="sm" variant="outline" onclick={datosSonana}>
<FileText class="mr-1.5" size={14} />
Datos Sonana
</Button>
<Button size="sm" variant="outline" onclick={refreshPartidas}>
<RotateCw size={14} />
</Button>
</div>
<!-- Tabla de partidas -->
<div class="border rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead class="w-[50px]">Sel.</TableHead>
<TableHead class="w-[80px]">Línea</TableHead>
<TableHead>Fracción</TableHead>
<TableHead>Sub</TableHead>
<TableHead>Descripción</TableHead>
<TableHead>Tipo</TableHead>
<TableHead>Sector</TableHead>
<TableHead>Origen</TableHead>
<TableHead class="text-right">Cant. Comercial</TableHead>
<TableHead>UMC</TableHead>
<TableHead class="text-right">Dólares</TableHead>
<TableHead class="w-[100px]">Acciones</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#if !formData?.partidas || formData.partidas.length === 0}
<TableRow>
<TableCell colspan={12} class="text-center text-muted-foreground py-8">
No hay partidas registradas. Haga clic en "Nuevo" para agregar una.
</TableCell>
</TableRow>
{:else}
{#each formData.partidas as partida, index}
<TableRow>
<TableCell>
<Checkbox
checked={selectedPartidas.has(index)}
onCheckedChange={() => toggleSelectPartida(index)}
/>
</TableCell>
<TableCell class="font-medium">{partida.linea}</TableCell>
<TableCell>{partida.fraccion}</TableCell>
<TableCell>{partida.sub}</TableCell>
<TableCell class="max-w-[200px] truncate" title={partida.descripcion}>
{partida.descripcion}
</TableCell>
<TableCell>{partida.tipo}</TableCell>
<TableCell>{partida.sector}</TableCell>
<TableCell>{partida.origen}</TableCell>
<TableCell class="text-right"
>{partida.cantidad_comercial.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}</TableCell
>
<TableCell>{partida.umc}</TableCell>
<TableCell class="text-right"
>{partida.dolares.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}</TableCell
>
<TableCell>
<div class="flex gap-1">
<Button size="sm" variant="ghost" onclick={() => openEditPartida(index)}>
<Pencil size={14} />
</Button>
<Button size="sm" variant="ghost" onclick={() => deletePartida(index)}>
<Trash2 size={14} />
</Button>
</div>
</TableCell>
</TableRow>
{/each}
{/if}
</TableBody>
</Table>
</div>
<!-- Totales y opciones -->
<div class="grid grid-cols-2 gap-6 border-t pt-4">
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span>Partidas:</span>
<span class="font-medium text-primary">{totalPartidas}</span>
</div>
<div class="flex justify-between">
<span>Dólares:</span>
<span class="font-medium text-primary"
>{totalDolares.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})} USD</span
>
</div>
<div class="flex justify-between">
<span>Pesos:</span>
<span class="font-medium text-primary">0.00 MXN</span>
</div>
<div class="flex justify-between">
<span>Valor Comercial:</span>
<span class="font-medium text-primary">0.00</span>
</div>
</div>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span>Cant. UMC:</span>
<span class="font-medium text-primary"
>{totalCantidadComercial.toLocaleString('es-MX', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})}</span
>
</div>
<div class="flex justify-between">
<span>Cant. UMT:</span>
<span class="font-medium text-primary">0.00</span>
</div>
<div class="flex justify-between">
<span>Val Agreg. Pesos:</span>
<span class="font-medium text-primary">0.00 MXN</span>
</div>
<div class="flex justify-between">
<span>Val Agreg. Dólares:</span>
<span class="font-medium text-primary">0.00 USD</span>
</div>
</div>
</div>
<!-- Opciones adicionales -->
<div class="flex items-center space-x-2 border-t pt-4">
<Checkbox
id="convertir_uma_umc"
checked={formData?.convertir_uma_umc ?? false}
onCheckedChange={(checked: boolean | 'indeterminate') =>
formData && (formData.convertir_uma_umc = checked === true)}
/>
<Label for="convertir_uma_umc" class="font-normal cursor-pointer text-sm"
>Captura Partidas: Convertir de UMA a UMC</Label
>
</div>
</CardContent>
</Card>
<!-- Dialog para agregar/editar partida -->
<Dialog bind:open={isDialogOpen}>
<DialogContent class="max-w-3xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingIndex !== null ? 'Editar Partida' : 'Nueva Partida'}</DialogTitle>
</DialogHeader>
<div class="grid gap-4 py-4">
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2">
<Label for="linea">Línea *</Label>
<Input
id="linea"
type="number"
bind:value={currentPartida.linea}
placeholder="1"
readonly
/>
</div>
<div class="space-y-2">
<Label for="fraccion">Fracción *</Label>
<Input id="fraccion" bind:value={currentPartida.fraccion} placeholder="0000.00.00" />
</div>
<div class="space-y-2">
<Label for="sub">Sub</Label>
<Input id="sub" bind:value={currentPartida.sub} placeholder="00" />
</div>
</div>
<div class="space-y-2">
<Label for="descripcion">Descripción *</Label>
<Textarea
id="descripcion"
bind:value={currentPartida.descripcion}
placeholder="Descripción detallada de la partida"
rows={3}
/>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="tipo">Tipo *</Label>
<Select.Root
type="single"
value={currentPartida.tipo}
onValueChange={(v: string | undefined) => v && (currentPartida.tipo = v)}
>
<Select.Trigger id="tipo">
{currentPartida.tipo || 'Seleccionar tipo'}
</Select.Trigger>
<Select.Content>
{#each tipos as tipo}
<Select.Item value={tipo}>{tipo}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<div class="space-y-2">
<Label for="sector">Sector *</Label>
<Select.Root
type="single"
value={currentPartida.sector}
onValueChange={(v: string | undefined) => v && (currentPartida.sector = v)}
>
<Select.Trigger id="sector">
{currentPartida.sector || 'Seleccionar sector'}
</Select.Trigger>
<Select.Content>
{#each sectores as sector}
<Select.Item value={sector}>{sector}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
</div>
<div class="space-y-2">
<Label for="origen">Origen *</Label>
<Select.Root
type="single"
value={currentPartida.origen}
onValueChange={(v: string | undefined) => v && (currentPartida.origen = v)}
>
<Select.Trigger id="origen">
{currentPartida.origen || 'Seleccionar origen'}
</Select.Trigger>
<Select.Content>
{#each origenes as origen}
<Select.Item value={origen}>{origen}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2">
<Label for="cantidad_comercial">Cant. Comercial *</Label>
<Input
id="cantidad_comercial"
type="number"
step="0.01"
bind:value={currentPartida.cantidad_comercial}
placeholder="0.00"
/>
</div>
<div class="space-y-2">
<Label for="umc">UMC *</Label>
<Select.Root
type="single"
value={currentPartida.umc}
onValueChange={(v: string | undefined) => v && (currentPartida.umc = v)}
>
<Select.Trigger id="umc">
{currentPartida.umc || 'Seleccionar UMC'}
</Select.Trigger>
<Select.Content>
{#each unidadesMedida as unidad}
<Select.Item value={unidad}>{unidad}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<div class="space-y-2">
<Label for="dolares">Dólares *</Label>
<Input
id="dolares"
type="number"
step="0.01"
bind:value={currentPartida.dolares}
placeholder="0.00"
/>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isDialogOpen = false)}>Cancelar</Button>
<Button
onclick={savePartida}
disabled={!currentPartida.fraccion ||
!currentPartida.descripcion ||
!currentPartida.tipo ||
!currentPartida.sector ||
!currentPartida.origen ||
!currentPartida.umc}
>
{editingIndex !== null ? 'Actualizar' : 'Guardar'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import { Checkbox as CheckboxPrimitive } from "bits-ui";
import CheckIcon from "@lucide/svelte/icons/check";
import MinusIcon from "@lucide/svelte/icons/minus";
import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
let {
ref = $bindable(null),
checked = $bindable(false),
indeterminate = $bindable(false),
class: className,
...restProps
}: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
</script>
<CheckboxPrimitive.Root
bind:ref
data-slot="checkbox"
class={cn(
"border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-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 peer flex size-4 shrink-0 items-center justify-center rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
bind:checked
bind:indeterminate
{...restProps}
>
{#snippet children({ checked, indeterminate })}
<div data-slot="checkbox-indicator" class="text-current transition-none">
{#if checked}
<CheckIcon class="size-3.5" />
{:else if indeterminate}
<MinusIcon class="size-3.5" />
{/if}
</div>
{/snippet}
</CheckboxPrimitive.Root>

View File

@@ -0,0 +1,6 @@
import Root from "./checkbox.svelte";
export {
Root,
//
Root as Checkbox,
};

View File

@@ -10,10 +10,15 @@
CircleAlert,
CircleCheck,
FileText,
Calendar,
CreditCard,
Truck,
ShieldCheck,
MessageSquare,
FileSpreadsheet,
Package,
Box,
Download,
Coins,
Wallet,
FileStack,
Scan,
LoaderCircle,
Save
} from 'lucide-svelte';
@@ -22,6 +27,14 @@
// Importar los componentes de cada pestaña (ahora sin botones de guardar propios)
import GeneralTabForm from '$lib/components/dashboard/pedimentos/edit/general-tab-form.svelte';
import DatesTabForm from '$lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte';
import FacturasTabForm from '$lib/components/dashboard/pedimentos/edit/facturas-tab-form.svelte';
import PartidasTabForm from '$lib/components/dashboard/pedimentos/edit/partidas-tab-form.svelte';
import BultosTransportesTabForm from '$lib/components/dashboard/pedimentos/edit/bultos-transportes-tab-form.svelte';
import DescargasTabForm from '$lib/components/dashboard/pedimentos/edit/descargas-tab-form.svelte';
import ContribucionesTabForm from '$lib/components/dashboard/pedimentos/edit/contribuciones-tab-form.svelte';
import CuentasCompensacionTabForm from '$lib/components/dashboard/pedimentos/edit/cuentas-compensacion-tab-form.svelte';
import DigitalizacionTabForm from '$lib/components/dashboard/pedimentos/edit/digitalizacion-tab-form.svelte';
import OtrosDatosTabForm from '$lib/components/dashboard/pedimentos/edit/otros-datos-tab-form.svelte';
import PaymentsTabForm from '$lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte';
import TransportTabForm from '$lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte';
import ValidationTabForm from '$lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte';
@@ -63,16 +76,83 @@
// Referencias a los componentes de formulario para obtener sus datos
let generalFormData = $state<any>(null);
let datesFormData = $state<any>(null);
let paymentsFormData = $state<any>(null);
let transportFormData = $state<any>(null);
let validationFormData = $state<any>(null);
let observacionesFormData = $state<any>(null);
let facturasFormData = $state<any>(null);
let partidasFormData = $state<any>({ partidas: [], convertir_uma_umc: false });
let bultosTransportesFormData = $state<any>({
bultos: { cantidad: 0, marcas: 'S/M', numero: 'S/N', vehiculos: 1 },
transportes: [],
precintos: [],
contenedores: []
});
let descargasFormData = $state<any>({ descargos: [], destinatarios: [] });
let contribucionesFormData = $state<any>({
dta: { forma_pago_recargo: 0, tasa_recargo: 0.0, importe_recargo: 0 },
prev: { forma_pago_prevalidacion: 0 },
eci: { forma_pago_eci: 0, importe_eci: 0 },
mult: { forma_pago_multa: 0, importe_multa: 0 },
rec: { forma_pago_uia: 0, importe_compensar: 0 },
calculo_manual: false,
operaciones_regla_31_40: false,
contribuciones: []
});
let cuentasCompensacionFormData = $state<any>({
cuentas_garantia: [],
compensaciones: [],
documentos_pago: []
});
let digitalizacionFormData = $state({
digitalizaciones: []
});
let otrosDatosFormData = $state({
tipo_calculo: 'ninguno',
dta_por_operacion_ag_facturas: false,
dta_por_numero_vehiculos: false,
paga_iva: false,
paga_prevalidacion: false,
aplicar_dta_8_millar_partida: false,
incluir_eci: false,
deducible_recargos: false,
cuota_fija_adicional_vehiculo: false,
cuota_fija_adicional_fp: '0',
actualizar_iva: false,
actualizar_advalorem: false,
actualizar_dta: false,
actualizar_cc: false,
actualizar_ieps: false,
es_embajada: false,
embajada_dta: '0.00',
regla_3_1_21_factores: false,
cambio_tarifa_anterior: '',
actualizar_iva_rect: false,
actualizar_advalorem_rect: false,
actualizar_dta_rect: false,
actualizar_cc_rect: false,
actualizar_ieps_rect: false,
calcular_recargos_diferencias: false,
proveedor_nacional_modifico_dta: false,
calculo_valor_aduana_v2: false,
calculo_2_decimales_valor_unitario: false,
calcular_valor_aduana_base_partidas: false,
recargo_igi: false,
recargo_iva: false,
recargo_ieps: false,
recargo_isan: false,
recargo_cc: false,
factor_incrementable_manual: '0.00000000000000000000',
agregar_entidad_federativa_proveedor: false
});
// Estados para saber si existen datos previos (para compatibilidad con componentes hijos)
let datesExists = $state(false);
let paymentsExists = $state(false);
let transportExists = $state(false);
let validationExists = $state(false);
let observacionesExists = $state(false);
let facturasExists = $state(false);
let partidasExists = $state(false);
let bultosTransportesExists = $state(false);
let descargasExists = $state(false);
let contribucionesExists = $state(false);
let cuentasCompensacionExists = $state(false);
let otrosDatosExists = $state(false);
let digitalizacionExists = $state(false);
function handleBack() {
goto('/dashboard/pedimentos');
@@ -149,87 +229,86 @@
// Solo agregar sub-recursos en modo UPDATE (no en CREATE)
// Y solo si tienen valores reales (no enviar objetos vacíos/null)
// Dates - solo enviar si hay al menos un campo con valor
if (datesFormData) {
const hasDateValue = datesFormData.entry_date || datesFormData.pedimento_date ||
datesFormData.payment_date || datesFormData.rectification_payment_date ||
datesFormData.extraction_date || datesFormData.submission_date ||
datesFormData.eucan_date || datesFormData.original_date ||
datesFormData.start_date || datesFormData.end_date;
if (hasDateValue) {
payload.pedimento_dates = {
entry_date: datesFormData.entry_date || null,
pedimento_date: datesFormData.pedimento_date || null,
payment_date: datesFormData.payment_date || null,
rectification_payment_date: datesFormData.rectification_payment_date || null,
extraction_date: datesFormData.extraction_date || null,
submission_date: datesFormData.submission_date || null,
eucan_date: datesFormData.eucan_date || null,
original_date: datesFormData.original_date || null,
start_date: datesFormData.start_date || null,
end_date: datesFormData.end_date || null,
// Observaciones - solo enviar si hay valor
if (observacionesFormData?.observaciones) {
payload.observaciones = observacionesFormData.observaciones;
}
// Incrementables - solo enviar si hay al menos un campo con valor
if (generalFormData) {
const hasIncrementablesValue = generalFormData.valor_seguro || generalFormData.embalajes ||
generalFormData.fletes || generalFormData.deducibles || generalFormData.moneda_incrementables ||
generalFormData.no_afectar_valor_dolares_inc !== undefined || generalFormData.no_afectar_valor_aduana !== undefined;
if (hasIncrementablesValue) {
payload.pedimento_incrementables = {
insured_value: generalFormData.valor_seguro || null,
packaging: generalFormData.embalajes || null,
freight: generalFormData.fletes || null,
deductibles: generalFormData.deducibles || null,
currency: generalFormData.moneda_incrementables || null,
not_affect_usd_value: generalFormData.no_afectar_valor_dolares_inc ? 1 : 0,
not_affect_customs_value: generalFormData.no_afectar_valor_aduana ? 1 : 0
};
}
}
// Payments - solo enviar si hay al menos un campo con valor
if (paymentsFormData) {
const hasPaymentValue = paymentsFormData.acknowledgment || paymentsFormData.operation_number ||
paymentsFormData.bank_code || paymentsFormData.cashier || paymentsFormData.date ||
paymentsFormData.time || paymentsFormData.shift || paymentsFormData.total_cash_paid ||
paymentsFormData.total_contributions || paymentsFormData.counter_payment ||
paymentsFormData.pece_code;
if (hasPaymentValue) {
payload.pedimento_payments = {
acknowledgment: paymentsFormData.acknowledgment || null,
operation_number: paymentsFormData.operation_number || null,
bank_code: paymentsFormData.bank_code || null,
cashier: paymentsFormData.cashier || null,
date: paymentsFormData.date || null,
time: paymentsFormData.time || null,
shift: paymentsFormData.shift || null,
total_cash_paid: paymentsFormData.total_cash_paid || null,
total_contributions: paymentsFormData.total_contributions || null,
counter_payment: paymentsFormData.counter_payment || null,
pece_code: paymentsFormData.pece_code || null,
// Decrementables - solo enviar si hay al menos un campo con valor
if (generalFormData) {
const hasDecrementablesValue = generalFormData.fletes_decrementable || generalFormData.seguros ||
generalFormData.carga || generalFormData.descarga || generalFormData.otros ||
generalFormData.moneda_decrementable || generalFormData.afectar_valor_dolares !== undefined;
if (hasDecrementablesValue) {
payload.pedimento_decrementables = {
freight: generalFormData.fletes_decrementable || null,
insurance: generalFormData.seguros || null,
loading: generalFormData.carga || null,
unloading: generalFormData.descarga || null,
others: generalFormData.otros || null,
currency: generalFormData.moneda_decrementable || null,
not_affect_usd_value: generalFormData.afectar_valor_dolares ? 1 : 0
};
}
}
// Transport - solo enviar si hay al menos un campo con valor
if (transportFormData) {
const hasTransportValue = transportFormData.destination || transportFormData.entry_exit ||
transportFormData.arrival || transportFormData.departure;
if (hasTransportValue) {
payload.pedimento_transport_means = {
destination: transportFormData.destination || null,
entry_exit: transportFormData.entry_exit || null,
arrival: transportFormData.arrival || null,
departure: transportFormData.departure || null
// Indexes - solo enviar si hay al menos un campo con valor
if (generalFormData) {
const hasIndexesValue = generalFormData.tipo_factor || generalFormData.factor_actualizacion ||
generalFormData.factor_actualizacion_manual !== undefined;
if (hasIndexesValue) {
// Mapear tipo_factor de string a número
let updateFactorType = null;
if (generalFormData.tipo_factor === 'INPC') {
updateFactorType = 1;
} else if (generalFormData.tipo_factor === 'variacion_cambiaria') {
updateFactorType = 2;
}
payload.pedimento_indexes = {
update_factor_type: updateFactorType,
update_factor: generalFormData.factor_actualizacion || null,
manual_update_factor: generalFormData.factor_actualizacion_manual ? 1 : 0
};
}
}
// Validation - solo enviar si hay al menos un campo con valor
if (validationFormData) {
const hasValidationValue = validationFormData.validator || validationFormData.validation_ack ||
validationFormData.pre_ack || validationFormData.line_signature ||
validationFormData.electronic_signature || validationFormData.certificate_number ||
validationFormData.validator_id || validationFormData.responsible_id;
if (hasValidationValue) {
payload.pedimento_validation = {
validator: validationFormData.validator || null,
validation_ack: validationFormData.validation_ack || null,
pre_ack: validationFormData.pre_ack || null,
line_signature: validationFormData.line_signature || null,
electronic_signature: validationFormData.electronic_signature || null,
certificate_number: validationFormData.certificate_number || null,
validator_id: validationFormData.validator_id || null,
responsible_id: validationFormData.responsible_id || null
// Config Additional - solo enviar si hay al menos un campo con valor
if (generalFormData) {
const hasConfigAdditionalValue = generalFormData.anio_impresion ||
generalFormData.agregar_po_auto !== undefined || generalFormData.no_eximir_normas !== undefined ||
generalFormData.activar_destinatario !== undefined || generalFormData.agregar_registro_502 !== undefined ||
generalFormData.agregar_quitar_normas !== undefined || generalFormData.facturar_cove !== undefined;
if (hasConfigAdditionalValue) {
payload.pedimento_config_additional = {
manual_pedimento_year: generalFormData.anio_impresion || null,
add_po_identifier: generalFormData.agregar_po_auto ? 1 : 0,
do_not_exempt_norms_complement_x: generalFormData.no_eximir_normas ? 1 : 0,
enable_import_invoice_recipient: generalFormData.activar_destinatario ? 1 : 0,
send_502_validation_file_for_consolidated: generalFormData.agregar_registro_502 ? 1 : 0,
add_remove_norms: generalFormData.agregar_quitar_normas ? 1 : 0
};
}
}
}
// Eliminar campos undefined para no enviarlos
Object.keys(payload).forEach(key => {
@@ -296,14 +375,17 @@
</h1>
{#if data.isCreate}
<Badge variant="default">Nuevo</Badge>
{:else}
<Badge variant={getStatusColor(data.pedimento.status)}>
{data.pedimento.status || 'Sin estado'}
{/if}
{#if generalFormData?.status || data.pedimento?.status}
<Badge variant={getStatusColor(generalFormData?.status || data.pedimento?.status)}>
{generalFormData?.status || data.pedimento?.status}
</Badge>
{/if}
</div>
<p class="text-muted-foreground">
{#if !data.isCreate && data.pedimento.pedimento_number}
{#if generalFormData?.year && generalFormData?.customs_office && generalFormData?.license && generalFormData?.pedimento_number}
Número: {generalFormData.year}-{generalFormData.customs_office}-{generalFormData.license}-{generalFormData.pedimento_number}
{:else if !data.isCreate && data.pedimento?.pedimento_number}
Número: {data.pedimento.year}-{data.pedimento.customs_office}-{data.pedimento.license}-{data.pedimento.pedimento_number}
{:else}
Edita los detalles del pedimento
@@ -332,7 +414,7 @@
{/if}
<!-- Contenido de las tabs con padding inferior para el footer flotante -->
<div class="pb-48">
<div class="pb-56">
<Tabs.Root bind:value={activeTab} class="space-y-4">
<Tabs.Content value="general">
<GeneralTabForm
@@ -349,86 +431,130 @@
<Tabs.Content value="dates">
<DatesTabForm
pedimento={data.pedimento}
bind:formData={datesFormData}
bind:exists={datesExists}
/>
bind:formData={observacionesFormData}
bind:exists={observacionesExists}
pedimentoType={generalFormData?.pedimento_type || data.pedimento?.pedimento_type || ''}
pedimentoNumber={
generalFormData?.year && generalFormData?.customs_office && generalFormData?.license && generalFormData?.pedimento_number
? `${generalFormData.year}-${generalFormData.customs_office}-${generalFormData.license}-${generalFormData.pedimento_number}`
: (!data.isCreate && data.pedimento?.pedimento_number
? `${data.pedimento.year}-${data.pedimento.customs_office}-${data.pedimento.license}-${data.pedimento.pedimento_number}`
: '')
}
/>
</Tabs.Content>
<!-- <Tabs.Content value="facturas">
<FacturasTabForm
bind:formData={facturasFormData}
/>
</Tabs.Content> -->
<!-- <!-- <Tabs.Content value="partidas">
<PartidasTabForm
bind:formData={partidasFormData}
/>
</Tabs.Content> -->
<Tabs.Content value="bultos-transportes">
<BultosTransportesTabForm
bind:formData={bultosTransportesFormData}
/>
</Tabs.Content>
<!-- <Tabs.Content value="descargas">
<DescargasTabForm
bind:formData={descargasFormData}
/>
</Tabs.Content> -->
<Tabs.Content value="contribuciones">
<ContribucionesTabForm
bind:formData={contribucionesFormData}
/>
</Tabs.Content>
<Tabs.Content value="cuentas-compensacion">
<CuentasCompensacionTabForm
bind:formData={cuentasCompensacionFormData}
/>
</Tabs.Content>
<Tabs.Content value="otros-datos">
<OtrosDatosTabForm bind:formData={otrosDatosFormData} />
</Tabs.Content>
<Tabs.Content value="payments">
<PaymentsTabForm
pedimento={data.pedimento}
bind:formData={paymentsFormData}
bind:exists={paymentsExists}
/>
</Tabs.Content>
<Tabs.Content value="transport">
<TransportTabForm
pedimento={data.pedimento}
bind:formData={transportFormData}
bind:exists={transportExists}
/>
</Tabs.Content>
<Tabs.Content value="validation">
<ValidationTabForm
pedimento={data.pedimento}
bind:formData={validationFormData}
bind:exists={validationExists}
/>
<Tabs.Content value="digitalizacion">
<DigitalizacionTabForm bind:formData={digitalizacionFormData} />
</Tabs.Content>
</Tabs.Root>
</div>
</div>
<!-- Footer fijo en la parte inferior - Fuera del contenedor principal -->
<div
class="fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 border-t shadow-lg z-[5] transition-[left] duration-200 ease-linear"
style:left={sidebar.isMobile ? '0' : (sidebar.open ? 'var(--sidebar-width)' : '0')}
>
<div class="px-4 py-4 space-y-4 max-w-[1400px] mx-auto">
<!-- Tabs Navigation -->
<Tabs.Root bind:value={activeTab}>
<Tabs.Root bind:value={activeTab}>
<div
class="fixed bottom-0 left-0 right-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 border-t shadow-lg z-[5] transition-[left] duration-200 ease-linear"
style:left={sidebar.isMobile ? '0' : (sidebar.open ? 'var(--sidebar-width)' : '0')}
>
<div class="px-4 py-4 space-y-4 max-w-[1400px] mx-auto">
<!-- Tabs Navigation -->
<div class="w-full overflow-x-auto pb-2">
<Tabs.List class="inline-flex md:grid md:w-full md:grid-cols-5">
<Tabs.Trigger value="general" disabled={false} class="whitespace-nowrap">
<FileText size={16} class="mr-2" />
General
</Tabs.Trigger>
<Tabs.Trigger value="dates" disabled={false} class="whitespace-nowrap">
<Calendar size={16} class="mr-2" />
Fechas
</Tabs.Trigger>
<Tabs.Trigger value="payments" disabled={false} class="whitespace-nowrap">
<CreditCard size={16} class="mr-2" />
Pagos
</Tabs.Trigger>
<Tabs.Trigger value="transport" disabled={false} class="whitespace-nowrap">
<Truck size={16} class="mr-2" />
Transporte
</Tabs.Trigger>
<Tabs.Trigger value="validation" disabled={false} class="whitespace-nowrap">
<ShieldCheck size={16} class="mr-2" />
Validación
</Tabs.Trigger>
</Tabs.List>
<Tabs.List class="inline-flex w-full gap-1">
<Tabs.Trigger value="general" class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm">
<FileText size={14} />
<span>General</span>
</Tabs.Trigger>
<Tabs.Trigger value="dates" class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm">
<MessageSquare size={14} />
<span>Observ.</span>
</Tabs.Trigger>
<!-- <Tabs.Trigger value="facturas" class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm">
<FileSpreadsheet size={14} />
<span>Facturas</span>
</Tabs.Trigger> -->
<!-- <Tabs.Trigger value="partidas" class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm">
<Package size={14} />
<span>Partidas</span>
</Tabs.Trigger> -->
<Tabs.Trigger value="bultos-transportes" class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm">
<Box size={14} />
<span>Bultos/Trans.</span>
</Tabs.Trigger>
<!-- <Tabs.Trigger value="descargas" class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm">
<Download size={14} />
<span>Descargas</span>
</Tabs.Trigger> -->
<Tabs.Trigger value="contribuciones" class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm">
<Coins size={14} />
<span>Contribuc.</span>
</Tabs.Trigger>
<Tabs.Trigger value="cuentas-compensacion" class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm">
<Wallet size={14} />
<span>Cuentas/Comp.</span>
</Tabs.Trigger>
<Tabs.Trigger value="otros-datos" class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm">
<FileStack size={14} />
<span>Otros</span>
</Tabs.Trigger>
<Tabs.Trigger value="digitalizacion" class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm">
<Scan size={14} />
<span>Digital.</span>
</Tabs.Trigger>
</Tabs.List>
</div>
</Tabs.Root>
<!-- Botones de acción -->
<div class="flex justify-end gap-3">
<Button type="button" variant="outline" onclick={handleBack} disabled={saving}>
Cancelar
</Button>
<Button onclick={handleSaveAll} disabled={saving}>
{#if saving}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Guardando todos los cambios...
{:else}
<Save size={16} class="mr-2" />
Guardar Todos los Cambios
{/if}
</Button>
<!-- Botones de acción -->
<div class="flex justify-end gap-3">
<Button type="button" variant="outline" onclick={handleBack} disabled={saving}>
Cancelar
</Button>
<Button onclick={handleSaveAll} disabled={saving}>
{#if saving}
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
Guardando todos los cambios...
{:else}
<Save size={16} class="mr-2" />
Guardar Todos los Cambios
{/if}
</Button>
</div>
</div>
</div>
</div>
</div>
</Tabs.Root>