Merge branch 'development' into feature/SSR-views

This commit is contained in:
2026-02-13 08:13:35 -06:00
123 changed files with 12052 additions and 1047 deletions

View File

@@ -1,6 +1,91 @@
import { api } from '$lib/api';
import type { ApiResponse } from '$lib/api';
export interface CompanyAddress {
id?: number;
address_type: string;
street?: string | null;
exterior_number?: string | null;
interior_number?: string | null;
postal_code?: string | null;
neighborhood?: string | null;
city?: string | null;
municipality?: string | null;
state?: string | null;
country?: string | null;
phone?: string | null;
fax?: string | null;
email?: string | null;
}
export interface CompanyCertification {
id?: number;
is_certified_company?: string | null;
certified_company_registration?: string | null;
certified_company_start_date?: number | null;
certified_company_end_date?: number | null;
annex31_certification_date?: number | null;
annex31_certification_number?: string | null;
annex31_modality?: string | null;
annex31_company_type?: string | null;
annex31_renewal_date?: number | null;
annex31_final_certification_date?: number | null;
is_oea_company?: number | null;
neec_company?: number | null;
}
export interface CompanyDigitalCertificate {
id?: number;
certificate_type: string;
cer_file_path?: string | null;
key_file_path?: string | null;
password?: string | null;
access_key?: string | null;
cer_expiration_date?: number | null;
key_expiration_date?: number | null;
}
export interface CompanyVU {
id?: number;
webservice_user?: string | null;
webservice_password?: string | null;
email?: string | null;
figure_type?: string | null;
central_path?: string | null;
xml_files_path?: string | null;
query_rfc?: string | null;
validation_rfc?: string | null;
configuration_source?: string | null;
measurement_units?: string | null;
}
export interface CompanyElectronicAgent {
id?: number;
input_folder?: string | null;
output_folder?: string | null;
send_mask?: string | null;
response_mask?: string | null;
response_extension?: string | null;
counter_start?: number | null;
counter_end?: number | null;
counter_next?: number | null;
}
export interface CompanyPrevalidator {
id?: number;
customs?: string | null;
key?: string | null;
patent?: string | null;
description?: string | null;
}
export interface CompanyCFDI {
id?: number;
xml_save_path?: string | null;
cfdi_app_path?: string | null;
pac_app_path?: string | null;
}
export interface Company {
id: number;
tenant_id: number;
@@ -32,6 +117,116 @@ export interface Company {
inter_db_name?: string | null;
prevalidator_key?: string | null;
seventh_amendment?: boolean;
// Sectores
sector1?: string | null;
sector2?: string | null;
sector3?: string | null;
// Flags técnicos
active_labels?: number | null;
active_fractions?: number | null;
activate_caat?: number | null;
trans_interface?: number | null;
american_costs?: number | null;
scaf_readonly?: number | null;
parts_replacement?: number | null;
activate_facmexame?: number | null;
part_reference?: number | null;
international_firm?: number | null;
// Configuraciones
ftp_key?: string | null;
sifra_path?: string | null;
version_type?: string | null;
sql_language?: string | null;
balance_operation_mode?: string | null;
// Campos de certificación (CompanyCertification aplanado)
is_certified_company?: string | null;
certified_company_registration?: string | null;
certified_company_start_date?: number | null;
certified_company_end_date?: number | null;
annex31_certification_date?: number | null;
annex31_certification_number?: string | null;
annex31_modality?: string | null;
annex31_company_type?: string | null;
annex31_renewal_date?: number | null;
annex31_final_certification_date?: number | null;
is_oea_company?: number | null;
neec_company?: number | null;
// Campos de dirección principal (CompanyAddress - main)
main_street?: string | null;
main_exterior_number?: string | null;
main_interior_number?: string | null;
main_postal_code?: string | null;
main_neighborhood?: string | null;
main_city?: string | null;
main_municipality?: string | null;
main_state?: string | null;
main_country?: string | null;
main_phone?: string | null;
main_fax?: string | null;
main_email?: string | null;
// Campos de dirección industrial 1 (CompanyAddress - industrial1)
ind1_street?: string | null;
ind1_exterior_number?: string | null;
ind1_interior_number?: string | null;
ind1_postal_code?: string | null;
ind1_neighborhood?: string | null;
ind1_city?: string | null;
ind1_municipality?: string | null;
ind1_state?: string | null;
ind1_country?: string | null;
ind1_phone?: string | null;
ind1_fax?: string | null;
ind1_email?: string | null;
// Campos de dirección industrial 2 (CompanyAddress - industrial2)
ind2_street?: string | null;
ind2_exterior_number?: string | null;
ind2_interior_number?: string | null;
ind2_postal_code?: string | null;
ind2_neighborhood?: string | null;
ind2_city?: string | null;
ind2_municipality?: string | null;
ind2_state?: string | null;
ind2_country?: string | null;
ind2_phone?: string | null;
ind2_fax?: string | null;
ind2_email?: string | null;
// Campos de prevalidador (CompanyPrevalidator aplanado)
prev_customs?: string | null;
prev_key?: string | null;
prev_patent?: string | null;
prev_description?: string | null;
// Campos de ventanilla única (CompanyVU aplanado)
vu_webservice_user?: string | null;
vu_webservice_password?: string | null;
vu_email?: string | null;
vu_figure_type?: string | null;
vu_central_path?: string | null;
vu_xml_files_path?: string | null;
vu_query_rfc?: string | null;
vu_validation_rfc?: string | null;
vu_configuration_source?: string | null;
vu_measurement_units?: string | null;
// Campos de agente aduanal electrónico (CompanyElectronicAgent aplanado)
ea_input_folder?: string | null;
ea_output_folder?: string | null;
ea_send_mask?: string | null;
ea_response_mask?: string | null;
ea_response_extension?: string | null;
ea_counter_start?: number | null;
ea_counter_end?: number | null;
ea_counter_next?: number | null;
// Campos de CFDI (CompanyCFDI aplanado)
cfdi_xml_save_path?: string | null;
cfdi_app_path?: string | null;
cfdi_pac_app_path?: string | null;
// Submodelos
addresses?: CompanyAddress[];
certification?: CompanyCertification | null;
digital_certificates?: CompanyDigitalCertificate[];
ventanilla_unica?: CompanyVU | null;
electronic_agent?: CompanyElectronicAgent | null;
prevalidator?: CompanyPrevalidator | null;
cfdi?: CompanyCFDI | null;
created_at: string | null;
updated_at: string | null;
}

View File

@@ -6,16 +6,15 @@ export const obtenerAtajosFormularioEmpresa = (acciones: {
manejarCerrar: () => void;
}): ShortcutDef[] => [
{ key: 'Alt+Digit1', description: 'Pestaña General', action: () => acciones.cambiarPestana('general') },
{ key: 'Alt+Digit2', description: 'Pestaña Programas', action: () => acciones.cambiarPestana('programas') },
{ key: 'Alt+Digit2', description: 'Pestaña Programa', action: () => acciones.cambiarPestana('programa') },
{ key: 'Alt+Digit3', description: 'Pestaña Responsable', action: () => acciones.cambiarPestana('responsable') },
{ key: 'Alt+Digit4', description: 'Pestaña Config', action: () => acciones.cambiarPestana('config') },
{ key: 'Alt+Digit4', description: 'Pestaña Certificaciones', action: () => acciones.cambiarPestana('certificaciones') },
{ key: 'Alt+Digit5', description: 'Pestaña Direcciones', action: () => acciones.cambiarPestana('direcciones') },
{ key: 'Alt+Digit6', description: 'Pestaña Config', action: () => acciones.cambiarPestana('config') },
{
key: 'Control+KeyS',
description: 'Guardar Empresa',
action: (e) => {
e.preventDefault();
acciones.manejarGuardar();
}
action: () => acciones.manejarGuardar()
},
{
key: 'Escape',

View File

@@ -49,7 +49,7 @@
name: '',
short_name: '',
curp: '',
client_or_provider: 'client',
client_or_provider: 'both',
type_nat_foreign: 'N',
responsible: '',
position: '',
@@ -224,7 +224,7 @@
} catch (e: any) {
console.error(e);
error = e.message || 'Error al guardar';
toast.error(error);
toast.error(error || 'Error al guardar');
} finally {
loading = false;
}
@@ -314,7 +314,11 @@
</div>
<div class="grid gap-2">
<Label for="type">Tipo de Relación <span class="text-destructive">*</span></Label>
<Select.Root type="single" bind:value={formData.client_or_provider}>
<Select.Root
type="single"
value={formData.client_or_provider}
onValueChange={(v) => (formData.client_or_provider = v)}
>
<Select.Trigger id="type">
{typeLabels[formData.client_or_provider] || 'Selecciona un tipo'}
</Select.Trigger>
@@ -357,7 +361,11 @@
</div>
<div class="grid gap-2">
<Label for="type_nat">Tipo Origen</Label>
<Select.Root type="single" bind:value={formData.type_nat_foreign}>
<Select.Root
type="single"
value={formData.type_nat_foreign}
onValueChange={(v) => (formData.type_nat_foreign = v)}
>
<Select.Trigger id="type_nat">
{formData.type_nat_foreign === 'N'
? 'Nacional'

View File

@@ -1,184 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
import { partsApi, type Part } from '$lib/api/dashboard/a76/parts';
import DataTable from '$lib/components/dashboard/goods/parts/data-table.svelte';
import { createColumns } from '$lib/components/dashboard/goods/parts/columns.js';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
import { companyStore } from '$lib/stores/company.svelte';
import { Plus, Search, RefreshCw, Loader2, Trash2 } from 'lucide-svelte';
// 1. ESTADOS
let partsList = $state<Part[]>([]);
let listLoading = $state(false);
let listError = $state<string | null>(null);
// Estado Búsqueda
let searchCode = $state('');
let searchResults = $state<Part[]>([]); // Array para guardar los resultados
let searchLoading = $state(false);
let isSearching = $state(false); // Para saber si estamos viendo resultados de búsqueda
// 2. CARGA DE DATOS (Lista Completa)
async function loadParts() {
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
listLoading = true;
listError = null;
isSearching = false; // Reseteamos modo búsqueda
searchResults = [];
try {
const response = await partsApi.list({
company_id: companyId,
page: 1,
page_size: 100
});
if (response.data) {
partsList = response.data.items || [];
} else if (response.error) {
listError = response.error;
}
} catch (e) {
listError = 'Error de conexión con el servidor';
} finally {
listLoading = false;
}
}
// 3. REACTIVIDAD DE COMPAÑÍA
$effect(() => {
if (companyStore.activeCompany) {
loadParts();
}
});
// 4. BÚSQUEDA MANUAL
async function handleSearch() {
if (!searchCode.trim()) return;
searchLoading = true;
listError = null; // Limpiamos errores previos
const companyId = companyStore.activeCompany?.id;
if (!companyId) return;
try {
const response = await partsApi.list({
company_id: companyId,
q: searchCode.trim()
});
const results = response.data?.items || [];
if (results.length > 0) {
searchResults = results; // Guardamos TODOS los resultados
isSearching = true; // Activamos modo búsqueda
} else {
listError = "No se encontraron partes con ese criterio";
searchResults = [];
isSearching = true; // Aún en modo búsqueda, pero vacía
}
} catch (e) {
listError = "Error en la búsqueda";
} finally {
searchLoading = false;
}
}
function clearSearch() {
searchCode = '';
searchResults = [];
isSearching = false;
listError = null;
loadParts(); // Recargamos la lista completa
}
const columns = createColumns(loadParts);
// Si estamos buscando, mostramos searchResults, si no, la lista completa
const tableData = $derived(isSearching ? searchResults : partsList);
</script>
<div class="space-y-6 p-4">
<div class="flex items-center justify-between">
<div>
<h1 class="text-3xl font-bold tracking-tight">Catálogo de Partes</h1>
<p class="text-muted-foreground">Gestiona las partes y componentes del sistema.</p>
</div>
<div class="flex gap-2">
<Button variant="outline" onclick={loadParts} disabled={listLoading}>
<RefreshCw class="mr-2 h-4 w-4 {listLoading ? 'animate-spin' : ''}" />
Actualizar
</Button>
<Button href="/dashboard/goods/parts/edit">
<Plus class="mr-2 h-4 w-4" />
Nueva Parte
</Button>
</div>
</div>
<Card.Root>
<Card.Header>
<Card.Title>Búsqueda Rápida</Card.Title>
<Card.Description>Ingresa el número de parte o descripción para filtrar.</Card.Description>
</Card.Header>
<Card.Content>
<div class="flex gap-4">
<div class="flex-1 max-w-xl">
<Input
bind:value={searchCode}
placeholder="Buscar por número de parte o descripción..."
onkeydown={(e) => e.key === 'Enter' && handleSearch()}
/>
</div>
<Button onclick={handleSearch} disabled={searchLoading}>
{#if searchLoading}
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
Buscando...
{:else}
<Search class="mr-2 h-4 w-4" />
Buscar
{/if}
</Button>
{#if isSearching || searchCode}
<Button variant="ghost" onclick={clearSearch}>
<Trash2 class="mr-2 h-4 w-4" />
Limpiar
</Button>
{/if}
</div>
</Card.Content>
</Card.Root>
<Card.Root>
<Card.Header>
<Card.Title>
{#if isSearching}
Resultados de la búsqueda ({searchResults.length})
{:else}
Listado General ({partsList.length} registros)
{/if}
</Card.Title>
</Card.Header>
<Card.Content>
{#if listError}
<div class="bg-destructive/10 text-destructive p-4 rounded-lg border border-destructive/20 mb-4">
{listError}
</div>
{/if}
<DataTable
data={tableData}
{columns}
loading={listLoading || searchLoading}
hasMore={false}
loadMore={() => {}}
/>
</Card.Content>
</Card.Root>
</div>