Files
plantillas-proyectos/frontend/src/lib/components/dashboard/pedimentos/edit/facturas-tab-form.svelte
Galindo97 7585cedd3e 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.
2025-12-24 09:10:10 -06:00

531 lines
14 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 {
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>