From ec8f6606ad094feef62a52ee63c6b039d919ee79 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Tue, 6 Jan 2026 12:04:04 -0600 Subject: [PATCH] Se corrigio y expandio los datos de clientes y proveedores --- .../modules/a76/clients_and_providers/dto.py | 10 +- .../api/dashboard/a76/clients-providers.ts | 220 ++---- .../clients_and_providers/columns.ts | 202 +++--- .../edit/[[id]]/+page.svelte | 638 ++++++++++-------- 4 files changed, 535 insertions(+), 535 deletions(-) diff --git a/backend/api/v1/modules/a76/clients_and_providers/dto.py b/backend/api/v1/modules/a76/clients_and_providers/dto.py index f77df843..624c99f1 100644 --- a/backend/api/v1/modules/a76/clients_and_providers/dto.py +++ b/backend/api/v1/modules/a76/clients_and_providers/dto.py @@ -116,9 +116,7 @@ class ClientProviderCreateDTO(BaseModel): ) position: Optional[str] = Field(None, max_length=30, description="Position") incoterm: Optional[str] = Field(None, max_length=19, description="Incoterm") - is_national_provider: Optional[str] = Field( - None, max_length=2, description="Is national provider" - ) + is_national_provider: Optional[bool] = None is_active: Optional[bool] = Field(None, description="Enabled/Disabled status") # Nested DTOs @@ -159,9 +157,7 @@ class ClientProviderUpdateDTO(BaseModel): ) position: Optional[str] = Field(None, max_length=30, description="Position") incoterm: Optional[str] = Field(None, max_length=19, description="Incoterm") - is_national_provider: Optional[str] = Field( - None, max_length=2, description="Is national provider" - ) + is_national_provider: Optional[bool] = None is_active: Optional[bool] = Field(None, description="Enabled/Disabled status") # Nested DTOs @@ -193,7 +189,7 @@ class ClientProviderResponseDTO(BaseModel): responsible: Optional[str] = None position: Optional[str] = None incoterm: Optional[str] = None - is_national_provider: Optional[str] = None + is_national_provider: Optional[bool] = None is_active: Optional[bool] = None tenant_id: int company_id: int diff --git a/frontend/src/lib/api/dashboard/a76/clients-providers.ts b/frontend/src/lib/api/dashboard/a76/clients-providers.ts index 292e9844..af66c1c5 100644 --- a/frontend/src/lib/api/dashboard/a76/clients-providers.ts +++ b/frontend/src/lib/api/dashboard/a76/clients-providers.ts @@ -1,173 +1,97 @@ -/** - * API Client para Clientes y Proveedores - * Gestiona las operaciones CRUD para clientes y proveedores - */ import { api } from '$lib/api'; +// --- Interfaces para Tablas Hijas --- + export interface ClientProviderAddress { id?: number; - streets?: string | null; + streets?: string | null; + exterior_number?: string | null; + interior_number?: string | null; neighborhood?: string | null; + municipality?: string | null; city?: string | null; state?: string | null; country?: string | null; - zip_code?: string | null; - client_id?: number; - interior_number?: string | null; - exterior_number?: string | null; - municipality?: string | null; + postal_code?: string | null; + email?: string | null; + phone?: string | null; + contact?: string | null; } export interface ClientProviderPrograms { - id?: number; - program_code?: string | null; - authorization_date?: string | null; - client_id?: number; + id?: number; + program?: string | null; + program_number?: string | null; + secon_auth_date?: number | null; // YYYYMMDD + prosec?: number | null; + manufacturer_id?: string | null; + tax_id?: string | null; + ctpat_svi?: string | null; + is_certified_company?: string | null; } +// --- Interfaz Principal --- + export interface ClientProvider { - id: number; - rfc: string; - name: string; - curp?: string | null; - residence_country?: string | null; - domicile_fiscal?: string | null; - foreign_tax_id?: string | null; - client_or_provider?: string | null; - is_active?: boolean; - tenant_id: number; - address?: ClientProviderAddress | null; - programs?: ClientProviderPrograms | null; -} - -export interface ClientProviderBasic { - id: number; - rfc: string; - name: string; - curp?: string | null; - residence_country?: string | null; - domicile_fiscal?: string | null; - foreign_tax_id?: string | null; - client_or_provider?: string | null; - is_active?: boolean; - tenant_id: number; + id: number; + rfc: string; + name: string; + short_name?: string | null; + curp?: string | null; + client_or_provider: 'client' | 'provider' | 'both'; + + // Campos planos de la tabla principal + type_nat_foreign?: string | null; + responsible?: string | null; + position?: string | null; + + // Booleanos (Coincidiendo con la BD) + is_national_provider?: boolean | null; + is_active?: boolean; + + // Relaciones Anidadas + address?: ClientProviderAddress | null; + programs?: ClientProviderPrograms | null; } export interface ClientProviderListResponse { - items: ClientProvider[]; - total: number; - page: number; - page_size: number; + items: ClientProvider[]; + total: number; + page: number; + page_size: number; } -export interface CreateClientProviderData { - rfc: string; - name: string; - curp?: string | null; - residence_country?: string | null; - domicile_fiscal?: string | null; - foreign_tax_id?: string | null; - client_or_provider?: string | null; - is_active?: boolean; - address?: Omit | null; - programs?: Omit | null; +// DTOs de Envío (excluyendo IDs automáticos) +export interface CreateClientProviderData extends Omit { + address?: ClientProviderAddress | null; + programs?: ClientProviderPrograms | null; } -export interface UpdateClientProviderData { - rfc?: string; - name?: string; - curp?: string | null; - residence_country?: string | null; - domicile_fiscal?: string | null; - foreign_tax_id?: string | null; - client_or_provider?: string | null; - is_active?: boolean; - address?: Partial | null; - programs?: Partial | null; -} +export interface UpdateClientProviderData extends Partial {} -/** - * API para Clientes y Proveedores - */ export const clientsProvidersApi = { - /** - * Lista todos los clientes y proveedores con paginación - * @param companyId - ID de la compañía - * @param page - Número de página (por defecto 1) - * @param pageSize - Tamaño de página (por defecto 50) - * @param filters - Filtros opcionales - */ - list: (companyId: number, page = 1, pageSize = 50, filters?: Record) => { - const params = new URLSearchParams({ - company_id: companyId.toString(), - page: page.toString(), - page_size: pageSize.toString() - }); + list: (companyId: number, page = 1, pageSize = 50, filters?: Record) => { + const params = new URLSearchParams({ + company_id: companyId.toString(), + page: page.toString(), + page_size: pageSize.toString() + }); + if (filters) { + Object.entries(filters).forEach(([key, value]) => { + if (value) params.append(key, value.toString()); + }); + } + return api.get(`/v1/a76/clients-providers?${params.toString()}`); + }, + get: (id: number, companyId: number) => + api.get(`/v1/a76/clients-providers/${id}?company_id=${companyId}`), + + create: (companyId: number, data: any) => + api.post(`/v1/a76/clients-providers?company_id=${companyId}`, data), - if (filters) { - Object.entries(filters).forEach(([key, value]) => { - if (value !== undefined && value !== null && value !== '') { - params.append(key, value.toString()); - } - }); - } + update: (id: number, companyId: number, data: any) => + api.patch(`/v1/a76/clients-providers/${id}?company_id=${companyId}`, data), - return api.get( - `/v1/a76/clients-providers?${params.toString()}` - ); - }, - - /** - * Obtiene un cliente/proveedor por ID - * @param id - ID del cliente/proveedor - * @param companyId - ID de la compañía - */ - get: (id: number, companyId: number) => - api.get(`/v1/a76/clients-providers/${id}?company_id=${companyId}`), - - /** - * Obtiene información básica de un cliente/proveedor - * @param id - ID del cliente/proveedor - * @param companyId - ID de la compañía - */ - getBasic: (id: number, companyId: number) => - api.get( - `/v1/a76/clients-providers/${id}/basic?company_id=${companyId}` - ), - - /** - * Crea un nuevo cliente/proveedor - * @param companyId - ID de la compañía - * @param data - Datos del cliente/proveedor a crear - */ - create: (companyId: number, data: CreateClientProviderData) => - api.post(`/v1/a76/clients-providers?company_id=${companyId}`, data), - - /** - * Actualiza un cliente/proveedor existente - * @param id - ID del cliente/proveedor a actualizar - * @param companyId - ID de la compañía - * @param data - Datos a actualizar - */ - update: (id: number, companyId: number, data: UpdateClientProviderData) => - api.patch(`/v1/a76/clients-providers/${id}?company_id=${companyId}`, data), - - /** - * Alterna el estado activo/inactivo de un cliente/proveedor - * @param id - ID del cliente/proveedor - * @param companyId - ID de la compañía - */ - toggleStatus: (id: number, companyId: number) => - api.put( - `/v1/a76/clients-providers/${id}/toggle-status?company_id=${companyId}`, - {} - ), - - /** - * Elimina un cliente/proveedor - * @param id - ID del cliente/proveedor a eliminar - * @param companyId - ID de la compañía - */ - delete: (id: number, companyId: number) => - api.delete(`/v1/a76/clients-providers/${id}?company_id=${companyId}`) -}; + delete: (id: number, companyId: number) => + api.delete(`/v1/a76/clients-providers/${id}?company_id=${companyId}`) +}; \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts b/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts index ef8ea700..abc3af01 100644 --- a/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts +++ b/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts @@ -1,111 +1,101 @@ -import type { ColumnDef } from "@tanstack/table-core"; import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js"; import { createRawSnippet } from "svelte"; import DataTableActions from "./data-table-actions.svelte"; -import type { ClientProvider } from "$lib/api/dashboard/a76/clients-providers"; -export type { ClientProvider }; +export function createColumns(onSuccess) { + return [ + { + accessorKey: "id", + header: "ID", + cell: ({ row }) => { + const snippet = createRawSnippet((getData) => { + const { val } = getData(); + return { render: () => `${val}` }; + }); + return renderSnippet(snippet, { val: row.original.id }); + } + }, + { + accessorKey: "rfc", + header: "RFC", + cell: ({ row }) => { + const snippet = createRawSnippet((getData) => { + const { val } = getData(); + return { render: () => `${val}` }; + }); + return renderSnippet(snippet, { val: row.original.rfc }); + } + }, + { + accessorKey: "name", + header: "Nombre", + cell: ({ row }) => { + const snippet = createRawSnippet((getData) => { + const { val } = getData(); + return { render: () => `
${val}
` }; + }); + return renderSnippet(snippet, { val: row.original.name }); + } + }, + { + id: "country", + header: "País", + cell: ({ row }) => { + const snippet = createRawSnippet((getData) => { + const { val } = getData(); + return { render: () => `
${val || '-'}
` }; + }); + // Busca en address.country, si no existe pone null + const country = row.original.address?.country; + return renderSnippet(snippet, { val: country }); + } + }, + { + accessorKey: "client_or_provider", + header: "Tipo", + cell: ({ row }) => { + const snippet = createRawSnippet((getData) => { + const { val } = getData(); + const map = { client: 'Cliente', provider: 'Proveedor', both: 'Ambos' }; + const colors = { + client: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300', + provider: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', + both: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300' + }; + return { + render: () => `${map[val] || val}` + }; + }); + return renderSnippet(snippet, { val: row.original.client_or_provider }); + } + }, + // --- CORRECCIÓN AQUÍ --- + { + accessorKey: "is_active", + header: "Estado", + cell: ({ row }) => { + const snippet = createRawSnippet((getData) => { + const { val } = getData(); + + // CORRECCIÓN: Usamos !!val para convertir cualquier valor positivo (true, 1) a true. + // Si val es null, undefined, false o 0, será false. + const isActive = !!val; -export function createColumns(onSuccess?: () => void): ColumnDef[] { - return [ - { - accessorKey: "id", - header: "ID", - cell: ({ row }) => { - const idSnippet = createRawSnippet<[{ id: number }]>((getId) => { - const { id } = getId(); - return { - render: () => - `${id}` - }; - }); - return renderSnippet(idSnippet, { id: row.original.id }); - } - }, - { - accessorKey: "rfc", - header: "RFC", - cell: ({ row }) => { - const rfcSnippet = createRawSnippet<[{ rfc: string }]>((getRfc) => { - const { rfc } = getRfc(); - return { - render: () => - `${rfc}` - }; - }); - return renderSnippet(rfcSnippet, { rfc: row.original.rfc }); - } - }, - { - accessorKey: "name", - header: "Nombre", - cell: ({ row }) => { - const nameSnippet = createRawSnippet<[{ name: string }]>((getName) => { - const { name } = getName(); - return { - render: () => `
${name}
` - }; - }); - return renderSnippet(nameSnippet, { name: row.original.name }); - } - }, - { - accessorKey: "client_or_provider", - header: "Tipo", - cell: ({ row }) => { - const typeSnippet = createRawSnippet<[{ type: string | null | undefined }]>((getType) => { - const { type } = getType(); - const displayType = type === 'client' ? 'Cliente' : type === 'provider' ? 'Proveedor' : type === 'both' ? 'Ambos' : 'N/A'; - const colorClass = type === 'client' ? 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300' - : type === 'provider' ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' - : type === 'both' ? 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300' - : 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300'; - return { - render: () => `${displayType}` - }; - }); - return renderSnippet(typeSnippet, { type: row.original.client_or_provider }); - } - }, - { - accessorKey: "residence_country", - header: "País", - cell: ({ row }) => { - const countrySnippet = createRawSnippet<[{ country: string | null | undefined }]>((getCountry) => { - const { country } = getCountry(); - return { - render: () => `
${country || '-'}
` - }; - }); - return renderSnippet(countrySnippet, { country: row.original.residence_country }); - } - }, - { - accessorKey: "is_active", - header: "Estado", - cell: ({ row }) => { - const statusSnippet = createRawSnippet<[{ status: number | undefined }]>((getStatus) => { - const { status } = getStatus(); - const isEnabled = status === 1; - const statusText = isEnabled ? 'Activo' : 'Inactivo'; - const colorClass = isEnabled - ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' - : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300'; - return { - render: () => `${statusText}` - }; - }); - return renderSnippet(statusSnippet, { status: row.original.is_active }); - } - }, - { - id: "actions", - cell: ({ row }) => { - return renderComponent(DataTableActions, { item: row.original, onSuccess }); - } - } - ]; -} - -// Mantener compatibilidad hacia atrás -export const columns = createColumns(); + const text = isActive ? 'Activo' : 'Inactivo'; + const color = isActive + ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' + : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300'; + + return { + render: () => `${text}` + }; + }); + return renderSnippet(snippet, { val: row.original.is_active }); + } + }, + { + id: "actions", + cell: ({ row }) => renderComponent(DataTableActions, { item: row.original, onSuccess }) + } + ]; +} \ No newline at end of file diff --git a/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte b/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte index bb94079f..d2953bb1 100644 --- a/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte +++ b/frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte @@ -1,363 +1,453 @@ -
+
-
-

{title}

-

- {isEditing ? "Modifica los datos del registro existente." : "Completa los datos para el nuevo registro."} -

+

{title}

+

Gestión integral del catálogo de socios comerciales.

- - - Información General - Los campos marcados con * son obligatorios - - - {#if loading && isEditing && !formData.name} -
- -
- {:else} -
- {#if error} -
- 🚨 {error} -
- {/if} - -
-

Información Básica

- -
-
- - -
- -
- - - - {typeLabels[formData.client_or_provider] || "Selecciona un tipo"} - - - Cliente - Proveedor - Ambos - - -
-
- -
- - -
- -
-
- - -
-
- - -
-
-
- -
-

Datos Fiscales

-
-
- - -
-
- - -
-
-
- -
-

Dirección Física

- -
- - -
- -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- - -
-
-
- -
-

Programas de Fomento

-
-
- - -
-
- - -
-
-
- -
- {/if} -
-
-
- -
- - - {#if !isEditing} - + {#if error} +
+ ⚠️ {error} +
{/if} - +
{ e.preventDefault(); handleSubmit(); }} class="space-y-6"> + + + + +
+ + +
+
+ + +
+
+ + + + {typeLabels[formData.client_or_provider] || "Selecciona un tipo"} + + + Cliente + Proveedor + Ambos + + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + + + {formData.type_nat_foreign === 'N' ? 'Nacional' : formData.type_nat_foreign === 'E' ? 'Extranjero' : 'Seleccione'} + + + Nacional + Extranjero + + +
+
+ +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+ +

Habilitar o deshabilitar este socio comercial

+
+
+
+ +
+ +

Marcar si es un proveedor nacional

+
+
+
+
+
+ + + + + + + + + + + + +
+
+
+ +
+
+ + + {#if !isEditing} + + {/if} + + +
+
+
\ No newline at end of file