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.
This commit is contained in:
@@ -16,8 +16,8 @@ async def list_countries(
|
||||
page: int = Query(1, ge=1, description="Número de página"),
|
||||
page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"),
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Endpoint público para obtener lista de países - no requiere autenticación"""
|
||||
skip = (page - 1) * page_size
|
||||
query = db.query(Country)
|
||||
items = query.offset(skip).limit(page_size).all()
|
||||
|
||||
@@ -51,6 +51,14 @@
|
||||
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 {
|
||||
@@ -132,6 +140,16 @@
|
||||
// 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: '',
|
||||
@@ -142,7 +160,8 @@
|
||||
gravamen: '',
|
||||
abreviacion: '',
|
||||
forma_pago_2: '',
|
||||
importe_2: 0
|
||||
importe_2: 0,
|
||||
contribuciones_generales: []
|
||||
});
|
||||
|
||||
let currentContribGen = $state<ContribucionGeneral>({
|
||||
@@ -165,7 +184,8 @@
|
||||
gravamen: '',
|
||||
abreviacion: '',
|
||||
forma_pago_2: '',
|
||||
importe_2: 0
|
||||
importe_2: 0,
|
||||
contribuciones_generales: []
|
||||
};
|
||||
isDialogOpen = true;
|
||||
}
|
||||
@@ -194,6 +214,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -527,24 +575,27 @@
|
||||
|
||||
<!-- Dialog para Contribuciones -->
|
||||
<Dialog bind:open={isDialogOpen}>
|
||||
<DialogContent class="max-w-3xl">
|
||||
<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="grid gap-4 py-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<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>
|
||||
<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 contribución'}</span>
|
||||
<span class="truncate">{currentContribucion.contribucion || 'Seleccionar'}</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each opcionesContribuciones as opcion}
|
||||
@@ -553,150 +604,169 @@
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</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" />
|
||||
<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="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="space-y-2">
|
||||
<Label for="tasa">Tasa:</Label>
|
||||
<Input
|
||||
id="tasa"
|
||||
step="0.00001"
|
||||
bind:value={currentContribucion.tasa}
|
||||
placeholder="0.00000"
|
||||
/>
|
||||
</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>
|
||||
<!-- Contribuciones Generales - Registro 510 -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="font-semibold text-sm">Contribuciones Generales - Registro 510</h3>
|
||||
|
||||
<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 class="border rounded-md">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Clave Contribución</TableHead>
|
||||
<TableHead>Forma de Pago</TableHead>
|
||||
<TableHead class="text-right">Importe</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#if !currentContribucion.contribuciones_generales || currentContribucion.contribuciones_generales.length === 0}
|
||||
<TableRow>
|
||||
<TableCell colspan={3} class="text-center text-muted-foreground py-4">
|
||||
Sin registros
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{:else}
|
||||
{#each currentContribucion.contribuciones_generales as contribGen, index}
|
||||
<TableRow>
|
||||
<TableCell>{contribGen.clave_contribucion}</TableCell>
|
||||
<TableCell>{contribGen.clave_forma_pago}</TableCell>
|
||||
<TableCell class="text-right">
|
||||
{contribGen.importe_contribucion.toLocaleString('es-MX', {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
})}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
{/if}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</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 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}
|
||||
disabled={!currentContribucion.contribucion ||
|
||||
!currentContribucion.tipo_tasa ||
|
||||
!currentContribucion.forma_pago}
|
||||
>
|
||||
{editingIndex !== null ? 'Actualizar' : 'Guardar'}
|
||||
</Button>
|
||||
<Button onclick={saveContribucion}>Aceptar</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- Dialog para Contribución General (510) -->
|
||||
<!-- Dialog para Contribución General (510) dentro del diálogo de Contribución -->
|
||||
<Dialog bind:open={isContribGenDialogOpen}>
|
||||
<DialogContent class="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{editingContribGenIndex !== null ? 'Editar Contribución General' : 'Nueva Contribución General'}
|
||||
</DialogTitle>
|
||||
<DialogTitle>Insertando</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="space-y-4 py-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="pedimento_sys_id">PedimentoSysID:</Label>
|
||||
<Label for="pedimento_sys_id_temp">PedimentoSysID:</Label>
|
||||
<Input
|
||||
id="pedimento_sys_id"
|
||||
bind:value={currentContribGen.pedimento_sys_id}
|
||||
placeholder="39,637"
|
||||
id="pedimento_sys_id_temp"
|
||||
bind:value={tempContribGen.pedimento_sys_id}
|
||||
placeholder="39,641"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="clave_contribucion_gen">Clave de Contribución:</Label>
|
||||
<Label for="clave_contribucion_temp">Clave de Contribución:</Label>
|
||||
<Input
|
||||
id="clave_contribucion_gen"
|
||||
bind:value={currentContribGen.clave_contribucion}
|
||||
id="clave_contribucion_temp"
|
||||
bind:value={tempContribGen.clave_contribucion}
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="clave_tasa">Clave de Tasa:</Label>
|
||||
<Label for="clave_tasa_temp">Clave de Tasa:</Label>
|
||||
<Input
|
||||
id="clave_tasa"
|
||||
bind:value={currentContribGen.clave_tasa}
|
||||
id="clave_tasa_temp"
|
||||
bind:value={tempContribGen.clave_tasa}
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="tasa_contribucion">Tasa de Contribución:</Label>
|
||||
<Label for="tasa_contribucion_temp">Tasa de Contribución:</Label>
|
||||
<Input
|
||||
id="tasa_contribucion"
|
||||
id="tasa_contribucion_temp"
|
||||
type="number"
|
||||
step="0.00001"
|
||||
bind:value={currentContribGen.tasa_contribucion}
|
||||
bind:value={tempContribGen.tasa_contribucion}
|
||||
placeholder="0.00000"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="clave_forma_pago_gen">Clave Forma de Pago:</Label>
|
||||
<Label for="clave_forma_pago_temp">Clave Forma de Pago:</Label>
|
||||
<Input
|
||||
id="clave_forma_pago_gen"
|
||||
bind:value={currentContribGen.clave_forma_pago}
|
||||
id="clave_forma_pago_temp"
|
||||
bind:value={tempContribGen.clave_forma_pago}
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="importe_contribucion_gen">Importe de Contribución:</Label>
|
||||
<Label for="importe_contribucion_temp">Importe de Contribución:</Label>
|
||||
<Input
|
||||
id="importe_contribucion_gen"
|
||||
id="importe_contribucion_temp"
|
||||
type="number"
|
||||
step="0.01"
|
||||
bind:value={currentContribGen.importe_contribucion}
|
||||
bind:value={tempContribGen.importe_contribucion}
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
@@ -704,12 +774,7 @@
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onclick={() => (isContribGenDialogOpen = false)}>Cancelar</Button>
|
||||
<Button
|
||||
onclick={saveContribGen}
|
||||
disabled={!currentContribGen.clave_contribucion}
|
||||
>
|
||||
{editingContribGenIndex !== null ? 'Actualizar' : 'Guardar'}
|
||||
</Button>
|
||||
<Button onclick={saveContribGenInDialog}>Aceptar</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
Copy,
|
||||
Upload
|
||||
} from 'lucide-svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
interface Transporte {
|
||||
id?: number;
|
||||
@@ -115,12 +116,276 @@
|
||||
};
|
||||
});
|
||||
|
||||
// Cargar países al montar el componente
|
||||
onMount(async () => {
|
||||
try {
|
||||
console.log('🌍 Cargando todos los países desde la base de datos...');
|
||||
const response = await fetch('/api-sveltekit/countries');
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('❌ Error al cargar países:', response.status);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
countries = data;
|
||||
console.log('✅ Países cargados:', countries.length);
|
||||
} catch (error) {
|
||||
console.error('❌ Error al cargar países:', error);
|
||||
}
|
||||
});
|
||||
|
||||
// Estados para diálogos
|
||||
let isTransporteDialogOpen = $state(false);
|
||||
let isPrecintoDialogOpen = $state(false);
|
||||
let isContenedorDialogOpen = $state(false);
|
||||
let isTransportesPrecintosDialogOpen = $state(false);
|
||||
let isGuiasDialogOpen = $state(false);
|
||||
let isTransportistasDialogOpen = $state(false);
|
||||
let isTransportistaFormDialogOpen = $state(false);
|
||||
let isGafeteDialogOpen = $state(false);
|
||||
let editingGafeteIndex = $state<number | null>(null);
|
||||
|
||||
// Estado para gafete actual
|
||||
let currentGafete = $state({
|
||||
claveTransportista: '',
|
||||
linea: '',
|
||||
numeroGafeteUnico: '',
|
||||
nombreTransportista: ''
|
||||
});
|
||||
|
||||
// Catálogo de transportistas (datos del catálogo del sistema)
|
||||
let transportistasCatalog = $state<any[]>([
|
||||
{
|
||||
id: 1,
|
||||
clave: '159A',
|
||||
nombre: 'MANUEL ALEJANDRO VENGA',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: 'MEX',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
clave: '159T',
|
||||
nombre: 'TRANSPORTES HERMA',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
clave: '159V',
|
||||
nombre: 'Concepción Peña Grijalva',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
clave: '158A',
|
||||
nombre: 'PERALTA',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
clave: '15C1',
|
||||
nombre: 'MOLSA MOVIMIENTO LOGISTI',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
clave: '15CG',
|
||||
nombre: 'UNION DE FLETEROS DE TAN',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
clave: '15D5',
|
||||
nombre: 'Carlos Alejandro Baeta Sánchez',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
clave: '15E6',
|
||||
nombre: 'JUAN SEBASTIAN MERA',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
clave: '15FB',
|
||||
nombre: 'FLORENTIN N HERNANDEZ',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
clave: '15G0',
|
||||
nombre: 'JOSE GABRIEL ZEPEDA DIAZ',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
clave: '15G2',
|
||||
nombre: 'PERALTA',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
clave: '15I9',
|
||||
nombre: 'Blanca Fabiola Figueroa Mende',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: 'MEX',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
clave: '15JD',
|
||||
nombre: 'José García Cruz',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
clave: '15JF',
|
||||
nombre: 'TRANSPORTES ESPAÑA',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: '',
|
||||
telefono: '',
|
||||
caat_code: '',
|
||||
gafetes: []
|
||||
}
|
||||
]);
|
||||
let searchTransportista = $state('');
|
||||
let currentCatalogoTransportista = $state<any | null>(null);
|
||||
let editingTransportistaIndex = $state<number | null>(null);
|
||||
|
||||
// Filtrar transportistas según búsqueda
|
||||
let filteredTransportistas = $derived(
|
||||
transportistasCatalog.filter((t) => {
|
||||
if (!searchTransportista) return true;
|
||||
const search = searchTransportista.toLowerCase();
|
||||
return (
|
||||
t.clave?.toLowerCase().includes(search) ||
|
||||
t.nombre?.toLowerCase().includes(search) ||
|
||||
t.rfc?.toLowerCase().includes(search)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
// Lista de países desde el backend
|
||||
let countries = $state<Array<{m3_key: string; description_es: string}>>([]);
|
||||
let searchCountry = $state('');
|
||||
let showCountryDropdown = $state(false);
|
||||
|
||||
// Filtrar países según búsqueda
|
||||
let filteredCountries = $derived(
|
||||
countries.filter((c) => {
|
||||
if (!searchCountry) return true;
|
||||
const search = searchCountry.toLowerCase();
|
||||
return (
|
||||
c.description_es?.toLowerCase().includes(search) ||
|
||||
c.m3_key?.toLowerCase().includes(search)
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
// Estados para diálogo de Transportes y Precintos separados por comas
|
||||
let transportesPrecintosData = $state({
|
||||
@@ -181,38 +446,38 @@
|
||||
];
|
||||
|
||||
const estadosMexico = [
|
||||
'Aguascalientes',
|
||||
'Baja California',
|
||||
'Baja California Sur',
|
||||
'Campeche',
|
||||
'Chiapas',
|
||||
'Chihuahua',
|
||||
'Ciudad de México',
|
||||
'Coahuila',
|
||||
'Colima',
|
||||
'Durango',
|
||||
'Guanajuato',
|
||||
'Guerrero',
|
||||
'Hidalgo',
|
||||
'Jalisco',
|
||||
'México',
|
||||
'Michoacán',
|
||||
'Morelos',
|
||||
'Nayarit',
|
||||
'Nuevo León',
|
||||
'Oaxaca',
|
||||
'Puebla',
|
||||
'Querétaro',
|
||||
'Quintana Roo',
|
||||
'San Luis Potosí',
|
||||
'Sinaloa',
|
||||
'Sonora',
|
||||
'Tabasco',
|
||||
'Tamaulipas',
|
||||
'Tlaxcala',
|
||||
'Veracruz',
|
||||
'Yucatán',
|
||||
'Zacatecas'
|
||||
{ clave: 'AGS', nombre: 'Aguascalientes' },
|
||||
{ clave: 'BC', nombre: 'Baja California' },
|
||||
{ clave: 'BCS', nombre: 'Baja California Sur' },
|
||||
{ clave: 'CAM', nombre: 'Campeche' },
|
||||
{ clave: 'CHS', nombre: 'Chiapas' },
|
||||
{ clave: 'CHI', nombre: 'Chihuahua' },
|
||||
{ clave: 'CDMX', nombre: 'Ciudad de México' },
|
||||
{ clave: 'COA', nombre: 'Coahuila' },
|
||||
{ clave: 'COL', nombre: 'Colima' },
|
||||
{ clave: 'DGO', nombre: 'Durango' },
|
||||
{ clave: 'GTO', nombre: 'Guanajuato' },
|
||||
{ clave: 'GRO', nombre: 'Guerrero' },
|
||||
{ clave: 'HGO', nombre: 'Hidalgo' },
|
||||
{ clave: 'JAL', nombre: 'Jalisco' },
|
||||
{ clave: 'MEX', nombre: 'México' },
|
||||
{ clave: 'MIC', nombre: 'Michoacán' },
|
||||
{ clave: 'MOR', nombre: 'Morelos' },
|
||||
{ clave: 'NAY', nombre: 'Nayarit' },
|
||||
{ clave: 'NL', nombre: 'Nuevo León' },
|
||||
{ clave: 'OAX', nombre: 'Oaxaca' },
|
||||
{ clave: 'PUE', nombre: 'Puebla' },
|
||||
{ clave: 'QRO', nombre: 'Querétaro' },
|
||||
{ clave: 'QR', nombre: 'Quintana Roo' },
|
||||
{ clave: 'SLP', nombre: 'San Luis Potosí' },
|
||||
{ clave: 'SIN', nombre: 'Sinaloa' },
|
||||
{ clave: 'SON', nombre: 'Sonora' },
|
||||
{ clave: 'TAB', nombre: 'Tabasco' },
|
||||
{ clave: 'TAM', nombre: 'Tamaulipas' },
|
||||
{ clave: 'TLX', nombre: 'Tlaxcala' },
|
||||
{ clave: 'VER', nombre: 'Veracruz' },
|
||||
{ clave: 'YUC', nombre: 'Yucatán' },
|
||||
{ clave: 'ZAC', nombre: 'Zacatecas' }
|
||||
];
|
||||
|
||||
const tiposContenedor = [
|
||||
@@ -328,7 +593,13 @@
|
||||
}
|
||||
|
||||
function saveGuia() {
|
||||
if (!formData?.guias) return;
|
||||
if (!formData) return;
|
||||
|
||||
// Asegurar que existe el array de guías
|
||||
if (!formData.guias) {
|
||||
formData.guias = [];
|
||||
}
|
||||
|
||||
if (editingGuiaIndex !== null) {
|
||||
formData.guias[editingGuiaIndex] = { ...currentGuia };
|
||||
} else {
|
||||
@@ -402,6 +673,119 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Funciones para gestionar catálogo de transportistas
|
||||
function openNewTransportistaForm() {
|
||||
currentCatalogoTransportista = {
|
||||
clave: '',
|
||||
nombre: '',
|
||||
rfc: '',
|
||||
curp: '',
|
||||
domicilio: '',
|
||||
ciudad: '',
|
||||
estado: '',
|
||||
pais: ''
|
||||
};
|
||||
editingTransportistaIndex = null;
|
||||
isTransportistaFormDialogOpen = true;
|
||||
}
|
||||
|
||||
function editCatalogoTransportista(index: number) {
|
||||
currentCatalogoTransportista = { ...transportistasCatalog[index] };
|
||||
editingTransportistaIndex = index;
|
||||
isTransportistaFormDialogOpen = true;
|
||||
}
|
||||
|
||||
function saveCatalogoTransportista() {
|
||||
if (editingTransportistaIndex !== null) {
|
||||
transportistasCatalog[editingTransportistaIndex] = { ...currentCatalogoTransportista };
|
||||
} else {
|
||||
transportistasCatalog.push({ ...currentCatalogoTransportista, id: Date.now() });
|
||||
}
|
||||
isTransportistaFormDialogOpen = false;
|
||||
currentCatalogoTransportista = null;
|
||||
editingTransportistaIndex = null;
|
||||
}
|
||||
|
||||
function deleteCatalogoTransportista(index: number) {
|
||||
if (confirm('¿Está seguro de eliminar este transportista?')) {
|
||||
transportistasCatalog.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function selectTransportista(transportista: any) {
|
||||
currentTransporte.transportista = transportista.nombre;
|
||||
currentTransporte.rfc = transportista.rfc;
|
||||
currentTransporte.curp = transportista.curp || '';
|
||||
isTransportistasDialogOpen = false;
|
||||
}
|
||||
|
||||
// Funciones para gafetes
|
||||
function openNewGafeteDialog() {
|
||||
editingGafeteIndex = null;
|
||||
currentGafete = {
|
||||
claveTransportista: currentCatalogoTransportista?.clave || '',
|
||||
linea: '0',
|
||||
numeroGafeteUnico: '',
|
||||
nombreTransportista: currentCatalogoTransportista?.nombre || ''
|
||||
};
|
||||
isGafeteDialogOpen = true;
|
||||
}
|
||||
|
||||
function editGafete(index: number) {
|
||||
if (!currentCatalogoTransportista?.gafetes) return;
|
||||
editingGafeteIndex = index;
|
||||
const gafete = currentCatalogoTransportista.gafetes[index];
|
||||
currentGafete = {
|
||||
claveTransportista: currentCatalogoTransportista.clave || '',
|
||||
linea: gafete.linea.toString(),
|
||||
numeroGafeteUnico: gafete.numeroGafeteUnico,
|
||||
nombreTransportista: gafete.nombreTransportista
|
||||
};
|
||||
isGafeteDialogOpen = true;
|
||||
}
|
||||
|
||||
function deleteGafete(index: number) {
|
||||
if (!currentCatalogoTransportista?.gafetes) return;
|
||||
|
||||
if (!confirm('¿Está seguro de que desea borrar este gafete?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentCatalogoTransportista.gafetes.splice(index, 1);
|
||||
// Reordenar las líneas
|
||||
currentCatalogoTransportista.gafetes.forEach((gafete: any, i: number) => {
|
||||
gafete.linea = i + 1;
|
||||
});
|
||||
}
|
||||
|
||||
function saveGafete() {
|
||||
if (!currentCatalogoTransportista) return;
|
||||
|
||||
// Asegurar que existe el array de gafetes
|
||||
if (!currentCatalogoTransportista.gafetes) {
|
||||
currentCatalogoTransportista.gafetes = [];
|
||||
}
|
||||
|
||||
if (editingGafeteIndex !== null) {
|
||||
// Editar gafete existente
|
||||
currentCatalogoTransportista.gafetes[editingGafeteIndex] = {
|
||||
linea: currentCatalogoTransportista.gafetes[editingGafeteIndex].linea,
|
||||
numeroGafeteUnico: currentGafete.numeroGafeteUnico,
|
||||
nombreTransportista: currentGafete.nombreTransportista
|
||||
};
|
||||
} else {
|
||||
// Agregar nuevo gafete
|
||||
currentCatalogoTransportista.gafetes.push({
|
||||
linea: currentCatalogoTransportista.gafetes.length + 1,
|
||||
numeroGafeteUnico: currentGafete.numeroGafeteUnico,
|
||||
nombreTransportista: currentGafete.nombreTransportista
|
||||
});
|
||||
}
|
||||
|
||||
editingGafeteIndex = null;
|
||||
isGafeteDialogOpen = false;
|
||||
}
|
||||
|
||||
// Funciones para Contenedores
|
||||
function openNewContenedor() {
|
||||
editingContenedorIndex = null;
|
||||
@@ -539,7 +923,7 @@
|
||||
{:else}
|
||||
{#each formData.transportes as transporte, index}
|
||||
<TableRow>
|
||||
<TableCell>{transporte.identificacion}</TableCell>
|
||||
<TableCell>{transporte.identificacion_fiscal}</TableCell>
|
||||
<TableCell>{transporte.pais}</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex gap-1">
|
||||
@@ -844,11 +1228,17 @@
|
||||
<div class="grid gap-4 py-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="t_transportista">Transportista:</Label>
|
||||
<Input
|
||||
id="t_transportista"
|
||||
bind:value={currentTransporte.transportista}
|
||||
placeholder="Transportista"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<Input
|
||||
id="t_transportista"
|
||||
bind:value={currentTransporte.transportista}
|
||||
placeholder="Transportista"
|
||||
class="flex-1"
|
||||
/>
|
||||
<Button size="icon" variant="outline" onclick={() => (isTransportistasDialogOpen = true)}>
|
||||
<FileText size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
@@ -889,24 +1279,52 @@
|
||||
onValueChange={(v: string | undefined) => v && (currentTransporte.estado = v)}
|
||||
>
|
||||
<Select.Trigger id="t_estado" class="w-full">
|
||||
<span class="truncate">{currentTransporte.estado || 'Seleccionar estado'}</span>
|
||||
<span class="truncate">{currentTransporte.estado ? estadosMexico.find(e => e.clave === currentTransporte.estado)?.nombre || currentTransporte.estado : 'Seleccionar estado'}</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each estadosMexico as estado}
|
||||
<Select.Item value={estado}>{estado}</Select.Item>
|
||||
<Select.Item value={estado.clave}>{estado.clave} - {estado.nombre}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="space-y-2 relative">
|
||||
<Label for="t_pais">País:</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input id="t_pais" bind:value={currentTransporte.pais} placeholder="País" class="flex-1" />
|
||||
<Button size="icon" variant="outline" onclick={() => alert('País en común')}>
|
||||
<FileText size={16} />
|
||||
</Button>
|
||||
<div class="relative">
|
||||
<Input
|
||||
id="t_pais"
|
||||
type="text"
|
||||
placeholder="Buscar país..."
|
||||
value={searchCountry}
|
||||
oninput={(e) => (searchCountry = (e.target as HTMLInputElement).value)}
|
||||
onfocus={() => (showCountryDropdown = true)}
|
||||
onblur={() => setTimeout(() => (showCountryDropdown = false), 200)}
|
||||
class="w-full"
|
||||
/>
|
||||
{#if currentTransporte.pais && !searchCountry}
|
||||
<div class="absolute inset-0 pointer-events-none flex items-center px-3 text-sm">
|
||||
<span class="truncate">{countries.find(c => c.m3_key === currentTransporte.pais)?.description_es || currentTransporte.pais}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if showCountryDropdown && filteredCountries.length > 0}
|
||||
<div class="absolute z-50 w-full mt-1 bg-popover border border-border rounded-md shadow-lg max-h-[300px] overflow-auto">
|
||||
{#each filteredCountries as country}
|
||||
<button
|
||||
type="button"
|
||||
class="w-full text-left px-3 py-2 hover:bg-accent hover:text-accent-foreground text-sm text-popover-foreground transition-colors"
|
||||
onclick={() => {
|
||||
currentTransporte.pais = country.m3_key;
|
||||
searchCountry = '';
|
||||
showCountryDropdown = false;
|
||||
}}
|
||||
>
|
||||
{country.description_es}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1098,6 +1516,291 @@
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- Diálogo Catálogo de Transportistas -->
|
||||
<Dialog open={isTransportistasDialogOpen} onOpenChange={(open) => (isTransportistasDialogOpen = open)}>
|
||||
<DialogContent class="max-w-4xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Catálogo de Transportistas</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="space-y-4">
|
||||
<div class="flex gap-2">
|
||||
<div class="flex-1 space-y-2">
|
||||
<Label for="search-transportista">Buscar:</Label>
|
||||
<Input
|
||||
id="search-transportista"
|
||||
bind:value={searchTransportista}
|
||||
placeholder="Buscar por clave, nombre o RFC..."
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<Button onclick={openNewTransportistaForm}>
|
||||
<Plus size={16} class="mr-2" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border rounded-lg">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Clave</TableHead>
|
||||
<TableHead>Nombre</TableHead>
|
||||
<TableHead>RFC</TableHead>
|
||||
<TableHead class="text-center">Acciones</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#if filteredTransportistas.length === 0}
|
||||
<TableRow>
|
||||
<TableCell colspan={4} class="text-center text-muted-foreground">
|
||||
{searchTransportista ? 'No se encontraron transportistas con ese criterio.' : 'No hay transportistas en el catálogo. Haz clic en "Nuevo" para agregar.'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{:else}
|
||||
{#each filteredTransportistas as transportista, index}
|
||||
<TableRow>
|
||||
<TableCell>{transportista.clave}</TableCell>
|
||||
<TableCell>{transportista.nombre}</TableCell>
|
||||
<TableCell>{transportista.rfc}</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex justify-center gap-1">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onclick={() => selectTransportista(transportista)}
|
||||
title="Seleccionar"
|
||||
>
|
||||
<FileText size={16} />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onclick={() => editCatalogoTransportista(transportistasCatalog.indexOf(transportista))}
|
||||
title="Editar"
|
||||
>
|
||||
<Pencil size={16} />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
onclick={() => deleteCatalogoTransportista(transportistasCatalog.indexOf(transportista))}
|
||||
title="Eliminar"
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
{/if}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter class="flex justify-end gap-2 mt-6">
|
||||
<Button variant="outline" onclick={() => (isTransportistasDialogOpen = false)}>
|
||||
Cerrar
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- Diálogo Formulario de Transportista -->
|
||||
<Dialog open={isTransportistaFormDialogOpen} onOpenChange={(open) => (isTransportistaFormDialogOpen = open)}>
|
||||
<DialogContent class="max-w-2xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{editingTransportistaIndex !== null ? 'Editar' : 'Nuevo'} Transportista</DialogTitle>
|
||||
</DialogHeader>
|
||||
{#if currentCatalogoTransportista}
|
||||
<div class="grid gap-4 py-4">
|
||||
<!-- Clave y Nombre -->
|
||||
<div class="space-y-2">
|
||||
<Label for="ct_clave">Clave:</Label>
|
||||
<Input
|
||||
id="ct_clave"
|
||||
bind:value={currentCatalogoTransportista.clave}
|
||||
placeholder="Clave"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="ct_nombre">Nombre:</Label>
|
||||
<Input
|
||||
id="ct_nombre"
|
||||
bind:value={currentCatalogoTransportista.nombre}
|
||||
placeholder="Nombre completo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- RFC y CURP -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="ct_rfc">RFC:</Label>
|
||||
<Input
|
||||
id="ct_rfc"
|
||||
bind:value={currentCatalogoTransportista.rfc}
|
||||
placeholder="RFC"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="ct_curp">CURP:</Label>
|
||||
<Input
|
||||
id="ct_curp"
|
||||
bind:value={currentCatalogoTransportista.curp}
|
||||
placeholder="CURP"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Domicilio -->
|
||||
<div class="space-y-2">
|
||||
<Label for="ct_domicilio">Domicilio:</Label>
|
||||
<Input
|
||||
id="ct_domicilio"
|
||||
bind:value={currentCatalogoTransportista.domicilio}
|
||||
placeholder="Domicilio"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- País y Entidad Federativa -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2 relative">
|
||||
<Label for="ct_pais">País:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={currentCatalogoTransportista.pais}
|
||||
onValueChange={(v: string | undefined) => v && (currentCatalogoTransportista.pais = v)}
|
||||
>
|
||||
<Select.Trigger id="ct_pais" class="w-full">
|
||||
<span class="truncate">{currentCatalogoTransportista.pais ? countries.find(c => c.m3_key === currentCatalogoTransportista.pais)?.description_es || currentCatalogoTransportista.pais : 'Seleccionar país'}</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#if countries.length === 0}
|
||||
<Select.Item value="" disabled>Cargando países...</Select.Item>
|
||||
{:else}
|
||||
{#each countries as country}
|
||||
<Select.Item value={country.m3_key}>{country.description_es}</Select.Item>
|
||||
{/each}
|
||||
{/if}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="ct_estado">Entidad Federativa:</Label>
|
||||
<Select.Root
|
||||
type="single"
|
||||
value={currentCatalogoTransportista.estado}
|
||||
onValueChange={(v: string | undefined) => v && (currentCatalogoTransportista.estado = v)}
|
||||
>
|
||||
<Select.Trigger id="ct_estado" class="w-full">
|
||||
<span class="truncate">{currentCatalogoTransportista.estado ? estadosMexico.find(e => e.clave === currentCatalogoTransportista.estado)?.nombre || currentCatalogoTransportista.estado : 'Seleccionar estado'}</span>
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
{#each estadosMexico as estado}
|
||||
<Select.Item value={estado.clave}>{estado.clave} - {estado.nombre}</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ciudad -->
|
||||
<div class="space-y-2">
|
||||
<Label for="ct_ciudad">Ciudad:</Label>
|
||||
<Input
|
||||
id="ct_ciudad"
|
||||
bind:value={currentCatalogoTransportista.ciudad}
|
||||
placeholder="Ciudad"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Teléfono y CAAT -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="ct_telefono">Teléfono:</Label>
|
||||
<Input
|
||||
id="ct_telefono"
|
||||
bind:value={currentCatalogoTransportista.telefono}
|
||||
placeholder="Teléfono"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="ct_caat">CAAT:</Label>
|
||||
<Input
|
||||
id="ct_caat"
|
||||
bind:value={currentCatalogoTransportista.caat_code}
|
||||
placeholder="(Código Alfanumérico Armonizado de Transportista)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sección DE GAFETES -->
|
||||
<div class="border-t pt-4 mt-4">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<h3 class="text-lg font-semibold"># DE GAFETES</h3>
|
||||
<div class="flex gap-2">
|
||||
<Button size="sm" variant="outline" onclick={openNewGafeteDialog}>
|
||||
<Plus class="h-4 w-4 mr-1" /> Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border rounded-md">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Línea</TableHead>
|
||||
<TableHead>Número Gafete Único</TableHead>
|
||||
<TableHead>Nombre</TableHead>
|
||||
<TableHead class="text-center">Acciones</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#if currentCatalogoTransportista.gafetes && currentCatalogoTransportista.gafetes.length > 0}
|
||||
{#each currentCatalogoTransportista.gafetes as gafete, index}
|
||||
<TableRow>
|
||||
<TableCell>{gafete.linea}</TableCell>
|
||||
<TableCell>{gafete.numeroGafeteUnico}</TableCell>
|
||||
<TableCell>{gafete.nombreTransportista}</TableCell>
|
||||
<TableCell class="text-center">
|
||||
<div class="flex gap-1 justify-center">
|
||||
<Button size="icon" variant="ghost" onclick={() => editGafete(index)}>
|
||||
<Pencil class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button size="icon" variant="ghost" onclick={() => deleteGafete(index)}>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
{:else}
|
||||
<TableRow>
|
||||
<TableCell colspan={4} class="text-center text-muted-foreground">
|
||||
Sin gafetes registrados
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/if}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<div class="mt-2">
|
||||
<Label for="buscar_gafete">Buscar:</Label>
|
||||
<Input
|
||||
id="buscar_gafete"
|
||||
placeholder="Buscar gafete..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<DialogFooter class="flex justify-end gap-2 mt-6">
|
||||
<Button variant="outline" onclick={() => (isTransportistaFormDialogOpen = false)}>Cancelar</Button>
|
||||
<Button onclick={saveCatalogoTransportista}>Guardar</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- Diálogo de Guías de Pedimento -->
|
||||
<Dialog open={isGuiasDialogOpen} onOpenChange={(open) => (isGuiasDialogOpen = open)}>
|
||||
<DialogContent class="max-w-md">
|
||||
@@ -1137,3 +1840,57 @@
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- Diálogo de Gafetes Únicos -->
|
||||
<Dialog open={isGafeteDialogOpen} onOpenChange={(open) => (isGafeteDialogOpen = open)}>
|
||||
<DialogContent class="max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>GAFETES UNICOS</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="gafete_clave_transportista">Clave Transportista:</Label>
|
||||
<Input
|
||||
id="gafete_clave_transportista"
|
||||
value={currentGafete.claveTransportista}
|
||||
placeholder="Clave"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="gafete_linea">Línea:</Label>
|
||||
<Input
|
||||
id="gafete_linea"
|
||||
bind:value={currentGafete.linea}
|
||||
placeholder="0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="gafete_numero">Número Gafete Único:</Label>
|
||||
<Input
|
||||
id="gafete_numero"
|
||||
bind:value={currentGafete.numeroGafeteUnico}
|
||||
placeholder="Número de gafete"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="gafete_nombre">Nombre Transportista:</Label>
|
||||
<Input
|
||||
id="gafete_nombre"
|
||||
value={currentGafete.nombreTransportista}
|
||||
placeholder="Nombre"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="flex justify-end gap-2 mt-6">
|
||||
<Button variant="outline" onclick={() => (isGafeteDialogOpen = false)}>Cancelar</Button>
|
||||
<Button onclick={saveGafete}>Aceptar</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -0,0 +1,771 @@
|
||||
<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>
|
||||
55
frontend/src/routes/api-sveltekit/countries/+server.ts
Normal file
55
frontend/src/routes/api-sveltekit/countries/+server.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const GET: RequestHandler = async ({ fetch }) => {
|
||||
// Configurar la URL de la API usando las variables de entorno
|
||||
let apiUrl = process.env.INTERNAL_API_URL;
|
||||
if (!apiUrl) {
|
||||
apiUrl = process.env.VITE_API_URL;
|
||||
// Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR)
|
||||
apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend');
|
||||
}
|
||||
|
||||
// Normalizar la URL
|
||||
const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`;
|
||||
|
||||
try {
|
||||
// Endpoint público de países - no requiere autenticación
|
||||
// Nota: La barra final "/" es importante para evitar redirects 307
|
||||
// El backend limita page_size a 100, así que necesitamos hacer múltiples llamadas
|
||||
const allCountries: any[] = [];
|
||||
let page = 1;
|
||||
let hasMore = true;
|
||||
|
||||
while (hasMore) {
|
||||
const response = await fetch(
|
||||
`${baseUrl}v1/public/refrence_data/countries/?page=${page}&page_size=100`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Error fetching countries:', response.status, response.statusText);
|
||||
const errorText = await response.text();
|
||||
console.error('Error response:', errorText);
|
||||
return json({ error: 'Failed to fetch countries' }, { status: response.status });
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
allCountries.push(...(data.items || []));
|
||||
|
||||
// Si hay menos items que el page_size, es la última página
|
||||
hasMore = data.items && data.items.length === 100;
|
||||
page++;
|
||||
}
|
||||
|
||||
return json(allCountries);
|
||||
} catch (error) {
|
||||
console.error('Error fetching countries:', error);
|
||||
return json({ error: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user