feature/digitalizacion-api

This commit is contained in:
2026-04-20 15:13:48 -06:00
parent 32f0dcc35f
commit 1cd81ebc03
29 changed files with 3275 additions and 1004 deletions

View File

@@ -0,0 +1,88 @@
import type { PageServerLoad } from './$types';
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
const parentData = await parent();
const { accessToken } = getAuthTokens(cookies);
if (!accessToken) {
return {
error: 'No authenticated',
items: [],
total: 0,
page: 1,
page_size: 50
};
}
try {
const page = parseInt(url.searchParams.get('page') || '1');
const pageSize = parseInt(url.searchParams.get('page_size') || '50');
const search = url.searchParams.get('search') || '';
const cookieCompanyId = cookies.get('active_company_id');
const companyId = cookieCompanyId ? parseInt(cookieCompanyId) : parentData.companies?.[0]?.id;
if (!companyId) {
return {
error: 'No se encontró una compañía seleccionada',
items: [],
total: 0,
page,
page_size: pageSize
};
}
const params = new URLSearchParams({
page: page.toString(),
page_size: pageSize.toString(),
company_id: companyId.toString()
});
if (search) {
params.append('search', search);
}
const response = await authenticatedFetch(
`v1/a76/document-types-digitization/?${params.toString()}`,
{},
cookies,
fetch
);
if (!response.ok) {
const errorText = await response.text();
console.error('📊 [Document Types Digitization] API Error:', {
status: response.status,
statusText: response.statusText,
error: errorText
});
return {
error: `Error ${response.status}: ${response.statusText}`,
items: [],
total: 0,
page,
page_size: pageSize
};
}
const data = await response.json();
return {
items: data.items || [],
total: data.total || 0,
page: data.page || page,
page_size: data.page_size || pageSize,
error: null
};
} catch (error) {
console.error('📊 [Document Types Digitization] Load error:', error);
return {
error: 'Error loading data',
items: [],
total: 0,
page: 1,
page_size: 50
};
}
};

View File

@@ -0,0 +1,183 @@
<script lang="ts">
import { onMount } from 'svelte';
import { browser } from '$app/environment';
import { page } from '$app/stores';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { RefreshCw } from 'lucide-svelte';
import { companyStore } from '$lib/stores/company.svelte';
import {
documentTypesDigitizationApi,
type DocumentTypeDigitization
} from '$lib/api/dashboard/reference_data/document_types_digitization';
import DataTable from '$lib/components/dashboard/reference_data/invoice_types/data-table.svelte';
import { createColumns } from '$lib/components/dashboard/reference_data/document_types_digitization/columns';
import { useShortcuts } from '$lib/hooks/use-shortcuts';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
onMount(() => {
if (browser) {
const getCookie = (name: string): string | null => {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop()?.split(';').shift() || null;
return null;
};
const cookieToken = getCookie('access_token');
const localToken = localStorage.getItem('access_token');
if (cookieToken && cookieToken !== localToken) localStorage.setItem('access_token', cookieToken);
}
});
let allItems = $state<DocumentTypeDigitization[]>(data.items || []);
let currentPage = $state(data.page || 1);
let pageSize = $state(data.page_size || 50);
let totalItems = $state(data.total || 0);
let loading = $state(false);
let error = $state<string | null>(data.error || null);
let searchQuery = $state($page.url.searchParams.get('search') || '');
let timeout: ReturnType<typeof setTimeout>;
let hasMore = $derived(allItems.length < totalItems);
function getActiveCompanyId(): number | null {
const fromStore = companyStore.activeCompany?.id;
if (fromStore) return fromStore;
if (!browser) return null;
const cookie = document.cookie
.split('; ')
.find((row) => row.startsWith('active_company_id='))
?.split('=')[1];
if (!cookie) return null;
const parsed = Number(cookie);
return Number.isFinite(parsed) ? parsed : null;
}
function handleSearch() {
if (!browser) return;
clearTimeout(timeout);
timeout = setTimeout(async () => {
loading = true;
error = null;
try {
const companyId = getActiveCompanyId();
if (!companyId) {
error = 'No hay empresa activa seleccionada';
return;
}
const response = await documentTypesDigitizationApi.list(1, pageSize, companyId, searchQuery);
if (!response.error && response.data) {
allItems = response.data.items;
currentPage = 1;
totalItems = response.data.total;
}
} catch (err) {
error = 'Error aplicando filtros';
console.error('Error applying filters:', err);
} finally {
loading = false;
}
const url = new URL($page.url);
if (searchQuery) url.searchParams.set('search', searchQuery);
else url.searchParams.delete('search');
history.replaceState(history.state, '', url);
}, 500);
}
async function loadMore() {
if (loading || !hasMore) return;
loading = true;
error = null;
try {
const companyId = getActiveCompanyId();
if (!companyId) {
error = 'No hay empresa activa seleccionada';
return;
}
const response = await documentTypesDigitizationApi.list(
currentPage + 1,
pageSize,
companyId,
searchQuery
);
if (response.error) {
if (response.status === 401 || response.status === 403) {
error = 'Sesión expirada. Recargando página...';
setTimeout(() => window.location.reload(), 2000);
} else {
error = response.error;
}
return;
}
if (response.data?.items) {
allItems = [...allItems, ...response.data.items];
currentPage += 1;
totalItems = response.data.total;
}
} catch (err) {
error = 'Error cargando más datos';
console.error('Error loading more document types:', err);
} finally {
loading = false;
}
}
function reloadData() {
window.location.reload();
}
useShortcuts('Tipos de documento para digitalización', [
{ key: 'Alt+Shift+R', description: 'Actualizar Lista', action: reloadData }
]);
const columns = createColumns();
</script>
<div class="flex h-[calc(100svh-4rem)] flex-col gap-6 overflow-hidden p-6 group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)]">
<div class="flex-none flex items-center justify-between">
<div class="space-y-1">
<h1 class="text-2xl font-bold tracking-tight">Tipos de documento para digitalización</h1>
<p class="text-muted-foreground">
Consulta el catálogo fijo de solo lectura utilizado por digitalización y pedimentos.
</p>
</div>
<div class="flex items-center gap-3">
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
<RefreshCw class="mr-2 h-4 w-4" />
Actualizar
</Button>
</div>
</div>
{#if error}
<div class="flex-none rounded-md border border-destructive px-4 py-3 text-sm text-destructive">
{error}
</div>
{/if}
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col">
<Card.Header>
<div class="flex flex-wrap items-center justify-between gap-3">
<Card.Title>Listado de tipos de documento</Card.Title>
<div class="flex flex-wrap items-center gap-2">
<Input
placeholder="Buscar por código o descripción"
bind:value={searchQuery}
oninput={handleSearch}
class="h-9 w-56 bg-card lg:w-72"
/>
</div>
</div>
</Card.Header>
<Card.Content class="min-h-0 p-0">
<div class="rounded-md border bg-background overflow-hidden flex min-h-0 flex-1 flex-col">
<DataTable data={allItems} {columns} {loading} {hasMore} {loadMore} />
</div>
</Card.Content>
</Card.Root>
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
</div>