Files
plantillas-proyectos/frontend/src/lib/components/dashboard/pedimentos/edit/temp.svelte
Galindo97 57b7e6dcbf feat: add country fetching API endpoint and implement dashboard contributions management
- Implemented a new API endpoint for fetching countries with pagination support.
- Added a new Svelte component for managing contributions in the dashboard, including CRUD operations for contributions and general contributions.
- Introduced dynamic tab navigation for different contribution types (DTA, PREV, ECI, MULT, REC).
- Enhanced user interface with dialogs for adding and editing contributions and general contributions.
2025-12-31 13:51:17 -06:00

772 lines
22 KiB
Svelte

<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 * as Select from '$lib/components/ui/select';
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';
import {
Plus,
Pencil,
Trash2
} from 'lucide-svelte';
const opcionesContribuciones = [
'DTA ',
'REC G',
'OTROS',
'DTI',
'MULT',
'RT',
'PRV',
'REU',
'ECI',
'IVA PRV',
'DFC'
];
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;
contribuciones_generales?: Array<{
pedimento_sys_id: string;
clave_contribucion: string;
clave_tasa: string;
tasa_contribucion: number;
clave_forma_pago: string;
importe_contribucion: number;
}>;
}
interface ContribucionGeneral {
id?: number;
pedimento_sys_id: string;
clave_contribucion: string;
clave_tasa: string;
tasa_contribucion: number;
clave_forma_pago: string;
importe_contribucion: 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[],
contribuciones_generales: [] as ContribucionGeneral[]
})
}: {
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[];
contribuciones_generales: ContribucionGeneral[];
};
} = $props();
// Pestaña activa
let activeTab = $state('DTA');
// Estados para diálogo
let isDialogOpen = $state(false);
let editingIndex = $state<number | null>(null);
// Estados para contribuciones generales (dentro del diálogo)
let isContribGenDialogOpen = $state(false);
let editingContribGenIndex = $state<number | null>(null);
// Estado temporal para contribución general dentro del diálogo de contribución
let tempContribGen = $state({
pedimento_sys_id: '',
clave_contribucion: '',
clave_tasa: '',
tasa_contribucion: 0,
clave_forma_pago: '',
importe_contribucion: 0
});
let currentContribucion = $state<Contribucion>({
contribucion: '',
tipo_tasa: '',
tasa: 0,
forma_pago: '',
importe: 0,
gravamen: '',
abreviacion: '',
forma_pago_2: '',
importe_2: 0,
contribuciones_generales: []
});
let currentContribGen = $state<ContribucionGeneral>({
pedimento_sys_id: '',
clave_contribucion: '',
clave_tasa: '',
tasa_contribucion: 0,
clave_forma_pago: '',
importe_contribucion: 0
});
function openNewContribucion() {
editingIndex = null;
currentContribucion = {
contribucion: '',
tipo_tasa: '',
tasa: 0,
forma_pago: '',
importe: 0,
gravamen: '',
abreviacion: '',
forma_pago_2: '',
importe_2: 0,
contribuciones_generales: []
};
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);
}
}
// Funciones para Contribuciones Generales (Registro 510) dentro del diálogo de contribución
function openNewContribGenInDialog() {
tempContribGen = {
pedimento_sys_id: '',
clave_contribucion: '',
clave_tasa: '',
tasa_contribucion: 0,
clave_forma_pago: '',
importe_contribucion: 0
};
isContribGenDialogOpen = true;
}
function saveContribGenInDialog() {
if (!currentContribucion.contribuciones_generales) {
currentContribucion.contribuciones_generales = [];
}
currentContribucion.contribuciones_generales.push({
pedimento_sys_id: tempContribGen.pedimento_sys_id,
clave_contribucion: tempContribGen.clave_contribucion,
clave_tasa: tempContribGen.clave_tasa,
tasa_contribucion: tempContribGen.tasa_contribucion,
clave_forma_pago: tempContribGen.clave_forma_pago,
importe_contribucion: tempContribGen.importe_contribucion
});
isContribGenDialogOpen = false;
}
// Funciones para Contribuciones Generales (Registro 510)
function openNewContribGen() {
editingContribGenIndex = null;
currentContribGen = {
pedimento_sys_id: '',
clave_contribucion: '',
clave_tasa: '',
tasa_contribucion: 0,
clave_forma_pago: '',
importe_contribucion: 0
};
isContribGenDialogOpen = true;
}
function openEditContribGen(index: number) {
if (!formData?.contribuciones_generales) return;
editingContribGenIndex = index;
currentContribGen = { ...formData.contribuciones_generales[index] };
isContribGenDialogOpen = true;
}
function saveContribGen() {
if (!formData?.contribuciones_generales) return;
if (editingContribGenIndex !== null) {
formData.contribuciones_generales[editingContribGenIndex] = { ...currentContribGen };
} else {
formData.contribuciones_generales = [...formData.contribuciones_generales, { ...currentContribGen }];
}
isContribGenDialogOpen = false;
}
function deleteContribGen(index: number) {
if (!formData?.contribuciones_generales) return;
if (confirm('¿Está seguro de eliminar esta contribución general?')) {
formData.contribuciones_generales = formData.contribuciones_generales.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-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>
{editingIndex !== null ? 'Editar Contribución' : 'Nueva Contribución'}
</DialogTitle>
</DialogHeader>
<div class="space-y-6 py-4">
<!-- Tasas Pedimento - Registro 509 -->
<div class="space-y-4">
<h3 class="font-semibold text-sm">Tasas Pedimento - Registro 509</h3>
<div class="space-y-2">
<Label for="contribucion">Contribución:</Label>
<Select.Root
type="single"
value={currentContribucion.contribucion}
onValueChange={(v: string | undefined) => v && (currentContribucion.contribucion = v)}
>
<Select.Trigger id="contribucion">
<span class="truncate">{currentContribucion.contribucion || 'Seleccionar'}</span>
</Select.Trigger>
<Select.Content>
{#each opcionesContribuciones as opcion}
<Select.Item value={opcion}>{opcion}</Select.Item>
{/each}
</Select.Content>
</Select.Root>
</div>
<div class="space-y-2">
<Label for="tipo_tasa">Tipo de Tasa:</Label>
<div class="flex gap-2">
<Select.Root
type="single"
value={currentContribucion.tipo_tasa}
onValueChange={(v: string | undefined) => v && (currentContribucion.tipo_tasa = v)}
>
<Select.Trigger id="tipo_tasa" class="flex-1">
<span class="truncate">{currentContribucion.tipo_tasa || 'Seleccionar'}</span>
</Select.Trigger>
<Select.Content>
<Select.Item value="Porcentual">Porcentual</Select.Item>
<Select.Item value="Especifico">Específico</Select.Item>
<Select.Item value="CuotaMinimaDTA">Cuota minima (DTA)</Select.Item>
<Select.Item value="CuotaFijaDTA">Cuota fija (DTA)</Select.Item>
<Select.Item value="TasaDescuentoAdValorem">Tasa de descuento sobre ad valorem</Select.Item>
<Select.Item value="FactorAplicacionTIGIE">Factor de aplicación sobre tigie.</Select.Item>
<Select.Item value="AlMillarDTA">Al millar (DTA)</Select.Item>
<Select.Item value="TasaDescuentoArancelEspecifico">Tasa de descuento sobre el arancel específico</Select.Item>
<Select.Item value="TasaEspecificaPreciosReferencia">Tasa especifica sobre precios de referencia</Select.Item>
<Select.Item value="TasaEspecificaPreciosReferenciaUM">Tasa especifica sobre precios de referencia con UM</Select.Item>
</Select.Content>
</Select.Root>
<Button size="icon" variant="outline">
<Plus class="h-4 w-4" />
</Button>
</div>
</div>
<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>
<!-- Contribuciones Generales - Registro 510 -->
<div class="space-y-4">
<h3 class="font-semibold text-sm">Contribuciones Generales - Registro 510</h3>
<!-- Tabla de Contribuciones Generales -->
{#if currentContribucion.contribuciones_generales && currentContribucion.contribuciones_generales.length > 0}
<Table>
<TableHeader>
<TableRow>
<TableHead>Clave Contribución</TableHead>
<TableHead>Forma de Pago</TableHead>
<TableHead>Importe</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{#each currentContribucion.contribuciones_generales as contribGen, i}
<TableRow>
<TableCell>{contribGen.clave_contribucion}</TableCell>
<TableCell>{contribGen.clave_forma_pago}</TableCell>
<TableCell>${contribGen.importe_contribucion.toFixed(2)}</TableCell>
</TableRow>
{/each}
</TableBody>
</Table>
{:else}
<p class="text-sm text-muted-foreground">No hay contribuciones generales registradas</p>
{/if}
<div class="flex gap-2">
<Button size="sm" variant="outline" onclick={openNewContribGenInDialog}>
<Plus class="h-4 w-4 mr-1" /> Nuevo
</Button>
<Button size="sm" variant="outline" disabled>
<Pencil class="h-4 w-4 mr-1" /> Editar
</Button>
<Button size="sm" variant="outline" disabled>
<Trash2 class="h-4 w-4 mr-1" /> Borrar
</Button>
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isDialogOpen = false)}>Cancelar</Button>
<Button onclick={saveContribucion}>Aceptar</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<!-- Dialog para Contribución General (510) dentro del diálogo de Contribución -->
<Dialog bind:open={isContribGenDialogOpen}>
<DialogContent class="max-w-md">
<DialogHeader>
<DialogTitle>Insertando</DialogTitle>
</DialogHeader>
<div class="space-y-4 py-4">
<div class="space-y-2">
<Label for="pedimento_sys_id_temp">PedimentoSysID:</Label>
<Input
id="pedimento_sys_id_temp"
bind:value={tempContribGen.pedimento_sys_id}
placeholder="39,641"
/>
</div>
<div class="space-y-2">
<Label for="clave_contribucion_temp">Clave de Contribución:</Label>
<Input
id="clave_contribucion_temp"
bind:value={tempContribGen.clave_contribucion}
placeholder="0"
/>
</div>
<div class="space-y-2">
<Label for="clave_tasa_temp">Clave de Tasa:</Label>
<Input
id="clave_tasa_temp"
bind:value={tempContribGen.clave_tasa}
placeholder=""
/>
</div>
<div class="space-y-2">
<Label for="tasa_contribucion_temp">Tasa de Contribución:</Label>
<Input
id="tasa_contribucion_temp"
type="number"
step="0.00001"
bind:value={tempContribGen.tasa_contribucion}
placeholder="0.00000"
/>
</div>
<div class="space-y-2">
<Label for="clave_forma_pago_temp">Clave Forma de Pago:</Label>
<Input
id="clave_forma_pago_temp"
bind:value={tempContribGen.clave_forma_pago}
placeholder="0"
/>
</div>
<div class="space-y-2">
<Label for="importe_contribucion_temp">Importe de Contribución:</Label>
<Input
id="importe_contribucion_temp"
type="number"
step="0.01"
bind:value={tempContribGen.importe_contribucion}
placeholder="0"
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onclick={() => (isContribGenDialogOpen = false)}>Cancelar</Button>
<Button onclick={saveContribGenInDialog}>Aceptar</Button>
</DialogFooter>
</DialogContent>
</Dialog>