feature/client-provider-taxid-rfc-fix
This commit is contained in:
@@ -25,7 +25,6 @@ export interface ClientProviderPrograms {
|
||||
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;
|
||||
}
|
||||
@@ -45,8 +44,6 @@ export interface ClientProvider {
|
||||
responsible?: string | null;
|
||||
position?: string | null;
|
||||
|
||||
// Booleanos (Coincidiendo con la BD)
|
||||
is_national_provider?: boolean | null;
|
||||
is_active?: boolean;
|
||||
|
||||
// Relaciones Anidadas
|
||||
|
||||
@@ -17,7 +17,7 @@ export function createColumns(onSuccess) {
|
||||
},
|
||||
{
|
||||
accessorKey: "rfc",
|
||||
header: "RFC",
|
||||
header: "RFC / TAX-ID",
|
||||
cell: ({ row }) => {
|
||||
const snippet = createRawSnippet((getData) => {
|
||||
const { val } = getData();
|
||||
|
||||
@@ -21,21 +21,26 @@
|
||||
rfc: "",
|
||||
name: "",
|
||||
curp: "",
|
||||
residence_country: "",
|
||||
domicile_fiscal: "",
|
||||
foreign_tax_id: "",
|
||||
type_nat_foreign: "N",
|
||||
client_or_provider: "client",
|
||||
is_active: true,
|
||||
// Address fields
|
||||
street: "",
|
||||
streets: "",
|
||||
exterior_number: "",
|
||||
interior_number: "",
|
||||
neighborhood: "",
|
||||
municipality: "",
|
||||
city: "",
|
||||
state: "",
|
||||
country: "",
|
||||
zip_code: "",
|
||||
// Programs fields
|
||||
program_code: "",
|
||||
authorization_date: ""
|
||||
postal_code: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
contact: "",
|
||||
// Programs fields (subset)
|
||||
program: "",
|
||||
program_number: "",
|
||||
manufacturer_id: ""
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
@@ -48,43 +53,59 @@
|
||||
rfc: item.rfc,
|
||||
name: item.name,
|
||||
curp: item.curp || "",
|
||||
residence_country: item.residence_country || "",
|
||||
domicile_fiscal: item.domicile_fiscal || "",
|
||||
foreign_tax_id: item.foreign_tax_id || "",
|
||||
type_nat_foreign: item.type_nat_foreign || "N",
|
||||
client_or_provider: item.client_or_provider || "client",
|
||||
is_active: item.is_active ?? true,
|
||||
street: item.address?.street || "",
|
||||
streets: item.address?.streets || "",
|
||||
exterior_number: item.address?.exterior_number || "",
|
||||
interior_number: item.address?.interior_number || "",
|
||||
neighborhood: item.address?.neighborhood || "",
|
||||
municipality: item.address?.municipality || "",
|
||||
city: item.address?.city || "",
|
||||
state: item.address?.state || "",
|
||||
country: item.address?.country || "",
|
||||
zip_code: item.address?.zip_code || "",
|
||||
program_code: item.programs?.program_code || "",
|
||||
authorization_date: item.programs?.authorization_date || ""
|
||||
postal_code: item.address?.postal_code || "",
|
||||
email: item.address?.email || "",
|
||||
phone: item.address?.phone || "",
|
||||
contact: item.address?.contact || "",
|
||||
program: item.programs?.program || "",
|
||||
program_number: item.programs?.program_number || "",
|
||||
manufacturer_id: item.programs?.manufacturer_id || ""
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
rfc: "",
|
||||
name: "",
|
||||
curp: "",
|
||||
residence_country: "",
|
||||
domicile_fiscal: "",
|
||||
foreign_tax_id: "",
|
||||
type_nat_foreign: "N",
|
||||
client_or_provider: "client",
|
||||
is_active: true,
|
||||
street: "",
|
||||
streets: "",
|
||||
exterior_number: "",
|
||||
interior_number: "",
|
||||
neighborhood: "",
|
||||
municipality: "",
|
||||
city: "",
|
||||
state: "",
|
||||
country: "",
|
||||
zip_code: "",
|
||||
program_code: "",
|
||||
authorization_date: ""
|
||||
postal_code: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
contact: "",
|
||||
program: "",
|
||||
program_number: "",
|
||||
manufacturer_id: ""
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const isEditing = $derived(!!item);
|
||||
const isForeign = $derived((formData.type_nat_foreign || "N").toUpperCase() === "E");
|
||||
|
||||
// Validación de formato: RFC (Nacional) o TAX-ID (Extranjero)
|
||||
const RFC_REGEX = /^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$/i;
|
||||
// TAX-ID: 2 dígitos, guión, resto alfanumérico. Ej: 12-3456789. Máx 30 caracteres.
|
||||
const TAX_ID_REGEX = /^\d{2}-[A-Z0-9]{1,27}$/i;
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
@@ -94,6 +115,19 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const rfcVal = (formData.rfc || "").trim();
|
||||
if (isForeign) {
|
||||
if (!TAX_ID_REGEX.test(rfcVal)) {
|
||||
error = "El TAX-ID debe tener formato: 2 dígitos, guión y resto (ej. 12-3456789). Máx 30 caracteres.";
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!RFC_REGEX.test(rfcVal)) {
|
||||
error = "El RFC no tiene el formato correcto. Ejemplo: XAXX010101000.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
@@ -104,23 +138,27 @@
|
||||
rfc: formData.rfc,
|
||||
name: formData.name,
|
||||
curp: formData.curp || null,
|
||||
residence_country: formData.residence_country || null,
|
||||
domicile_fiscal: formData.domicile_fiscal || null,
|
||||
foreign_tax_id: formData.foreign_tax_id || null,
|
||||
client_or_provider: formData.client_or_provider as "client" | "provider" | "both" | null,
|
||||
type_nat_foreign: (formData.type_nat_foreign || null) as any,
|
||||
is_active: formData.is_active,
|
||||
address: {
|
||||
street: formData.street || null,
|
||||
streets: formData.streets || null,
|
||||
exterior_number: formData.exterior_number || null,
|
||||
interior_number: formData.interior_number || null,
|
||||
neighborhood: formData.neighborhood || null,
|
||||
municipality: formData.municipality || null,
|
||||
city: formData.city || null,
|
||||
state: formData.state || null,
|
||||
country: formData.country || null,
|
||||
zip_code: formData.zip_code || null,
|
||||
postal_code: formData.postal_code || null,
|
||||
email: formData.email || null,
|
||||
phone: formData.phone || null,
|
||||
contact: formData.contact || null,
|
||||
},
|
||||
programs: {
|
||||
program_code: formData.program_code || null,
|
||||
authorization_date: formData.authorization_date || null,
|
||||
|
||||
program: formData.program || null,
|
||||
program_number: formData.program_number || null,
|
||||
manufacturer_id: formData.manufacturer_id || null,
|
||||
}
|
||||
};
|
||||
response = await clientsProvidersApi.update(item.id, companyStore.activeCompany.id, payload);
|
||||
@@ -129,22 +167,27 @@
|
||||
rfc: formData.rfc,
|
||||
name: formData.name,
|
||||
curp: formData.curp || null,
|
||||
residence_country: formData.residence_country || null,
|
||||
domicile_fiscal: formData.domicile_fiscal || null,
|
||||
foreign_tax_id: formData.foreign_tax_id || null,
|
||||
client_or_provider: formData.client_or_provider as "client" | "provider" | "both" | null,
|
||||
type_nat_foreign: (formData.type_nat_foreign || null) as any,
|
||||
is_active: formData.is_active,
|
||||
address: {
|
||||
street: formData.street || null,
|
||||
streets: formData.streets || null,
|
||||
exterior_number: formData.exterior_number || null,
|
||||
interior_number: formData.interior_number || null,
|
||||
neighborhood: formData.neighborhood || null,
|
||||
municipality: formData.municipality || null,
|
||||
city: formData.city || null,
|
||||
state: formData.state || null,
|
||||
country: formData.country || null,
|
||||
zip_code: formData.zip_code || null,
|
||||
postal_code: formData.postal_code || null,
|
||||
email: formData.email || null,
|
||||
phone: formData.phone || null,
|
||||
contact: formData.contact || null,
|
||||
},
|
||||
programs: {
|
||||
program_code: formData.program_code || null,
|
||||
authorization_date: formData.authorization_date || null,
|
||||
program: formData.program || null,
|
||||
program_number: formData.program_number || null,
|
||||
manufacturer_id: formData.manufacturer_id || null,
|
||||
}
|
||||
};
|
||||
response = await clientsProvidersApi.create(companyStore.activeCompany.id, payload);
|
||||
@@ -182,19 +225,24 @@
|
||||
rfc: "",
|
||||
name: "",
|
||||
curp: "",
|
||||
residence_country: "",
|
||||
domicile_fiscal: "",
|
||||
foreign_tax_id: "",
|
||||
type_nat_foreign: "N",
|
||||
client_or_provider: "client",
|
||||
is_active: true,
|
||||
street: "",
|
||||
streets: "",
|
||||
exterior_number: "",
|
||||
interior_number: "",
|
||||
neighborhood: "",
|
||||
municipality: "",
|
||||
city: "",
|
||||
state: "",
|
||||
country: "",
|
||||
zip_code: "",
|
||||
program_code: "",
|
||||
authorization_date: ""
|
||||
postal_code: "",
|
||||
email: "",
|
||||
phone: "",
|
||||
contact: "",
|
||||
program: "",
|
||||
program_number: "",
|
||||
manufacturer_id: ""
|
||||
};
|
||||
error = null;
|
||||
}
|
||||
@@ -226,14 +274,27 @@
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-semibold">Información Básica</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="rfc">RFC *</Label>
|
||||
<Label for="type_nat_foreign">Procedencia *</Label>
|
||||
<select
|
||||
id="type_nat_foreign"
|
||||
bind:value={formData.type_nat_foreign}
|
||||
required
|
||||
disabled={loading}
|
||||
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="N">Nacional</option>
|
||||
<option value="E">Extranjero</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="rfc">{isForeign ? "TAX-ID" : "RFC"} *</Label>
|
||||
<Input
|
||||
id="rfc"
|
||||
bind:value={formData.rfc}
|
||||
placeholder="Ej: XAXX010101000"
|
||||
maxlength={13}
|
||||
placeholder={isForeign ? "Ej: 12-3456789 (según país)" : "Ej: XAXX010101000"}
|
||||
maxlength={30}
|
||||
required
|
||||
disabled={loading}
|
||||
/>
|
||||
@@ -292,48 +353,45 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Información fiscal -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-semibold">Información Fiscal</h3>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="domicile_fiscal">Domicilio Fiscal</Label>
|
||||
<Input
|
||||
id="domicile_fiscal"
|
||||
bind:value={formData.domicile_fiscal}
|
||||
placeholder="Domicilio fiscal completo"
|
||||
maxlength={256}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="foreign_tax_id">ID Fiscal Extranjero</Label>
|
||||
<Input
|
||||
id="foreign_tax_id"
|
||||
bind:value={formData.foreign_tax_id}
|
||||
placeholder="Para contribuyentes extranjeros"
|
||||
maxlength={64}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dirección -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-semibold">Dirección</h3>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="street">Calle</Label>
|
||||
<Label for="streets">Calles</Label>
|
||||
<Input
|
||||
id="street"
|
||||
bind:value={formData.street}
|
||||
placeholder="Calle y número"
|
||||
id="streets"
|
||||
bind:value={formData.streets}
|
||||
placeholder="Calle y referencias"
|
||||
maxlength={256}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="exterior_number">Número exterior</Label>
|
||||
<Input
|
||||
id="exterior_number"
|
||||
bind:value={formData.exterior_number}
|
||||
placeholder="Ej: 123"
|
||||
maxlength={20}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="interior_number">Número interior</Label>
|
||||
<Input
|
||||
id="interior_number"
|
||||
bind:value={formData.interior_number}
|
||||
placeholder="Ej: 4B"
|
||||
maxlength={20}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="neighborhood">Colonia</Label>
|
||||
@@ -347,10 +405,10 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="zip_code">Código Postal</Label>
|
||||
<Label for="postal_code">Código Postal</Label>
|
||||
<Input
|
||||
id="zip_code"
|
||||
bind:value={formData.zip_code}
|
||||
id="postal_code"
|
||||
bind:value={formData.postal_code}
|
||||
placeholder="Ej: 12345"
|
||||
maxlength={10}
|
||||
disabled={loading}
|
||||
@@ -359,6 +417,17 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="municipality">Municipio</Label>
|
||||
<Input
|
||||
id="municipality"
|
||||
bind:value={formData.municipality}
|
||||
placeholder="Municipio"
|
||||
maxlength={150}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="city">Ciudad</Label>
|
||||
<Input
|
||||
@@ -392,30 +461,76 @@
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
bind:value={formData.email}
|
||||
placeholder="correo@dominio.com"
|
||||
maxlength={100}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="phone">Teléfono</Label>
|
||||
<Input
|
||||
id="phone"
|
||||
bind:value={formData.phone}
|
||||
placeholder="Teléfono"
|
||||
maxlength={30}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="contact">Contacto</Label>
|
||||
<Input
|
||||
id="contact"
|
||||
bind:value={formData.contact}
|
||||
placeholder="Persona de contacto"
|
||||
maxlength={50}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Programas -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-sm font-semibold">Programas</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="program_code">Código de Programa</Label>
|
||||
<Label for="program">Programa</Label>
|
||||
<Input
|
||||
id="program_code"
|
||||
bind:value={formData.program_code}
|
||||
placeholder="Código del programa"
|
||||
maxlength={32}
|
||||
id="program"
|
||||
bind:value={formData.program}
|
||||
placeholder="Ej: IMMEX"
|
||||
maxlength={7}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="authorization_date">Fecha de Autorización</Label>
|
||||
<Label for="program_number">Número de Programa</Label>
|
||||
<Input
|
||||
id="authorization_date"
|
||||
type="date"
|
||||
bind:value={formData.authorization_date}
|
||||
id="program_number"
|
||||
bind:value={formData.program_number}
|
||||
placeholder="Número"
|
||||
maxlength={40}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="manufacturer_id">Manufacturer ID</Label>
|
||||
<Input
|
||||
id="manufacturer_id"
|
||||
bind:value={formData.manufacturer_id}
|
||||
placeholder="Manufacturer ID"
|
||||
maxlength={25}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import EllipsisIcon from "@lucide/svelte/icons/ellipsis";
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { ClientProvider } from "./columns.js";
|
||||
import type { ClientProvider } from "$lib/api/dashboard/a76/clients-providers";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
import { clientsProvidersApi } from "$lib/api/dashboard/a76/clients-providers";
|
||||
@@ -29,6 +29,9 @@
|
||||
navigator.clipboard.writeText(item.rfc);
|
||||
}
|
||||
|
||||
const isForeign = (cp: ClientProvider) =>
|
||||
(cp.type_nat_foreign || "N").toUpperCase() === "E";
|
||||
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
@@ -77,7 +80,7 @@
|
||||
Copiar ID
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleCopyRfc}>
|
||||
Copiar RFC
|
||||
Copiar {isForeign(item) ? 'TAX-ID' : 'RFC'}
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
@@ -51,6 +51,9 @@
|
||||
}
|
||||
open = newOpen;
|
||||
}
|
||||
|
||||
const isForeign = (cp: ClientProvider) =>
|
||||
(cp.type_nat_foreign || "N").toUpperCase() === "E";
|
||||
</script>
|
||||
|
||||
<AlertDialog.Root bind:open onOpenChange={handleOpenChange}>
|
||||
@@ -66,7 +69,7 @@
|
||||
<span class="font-semibold">{item.id}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="font-medium">RFC:</span>
|
||||
<span class="font-medium">{item ? (isForeign(item) ? 'TAX-ID:' : 'RFC:') : 'RFC:'}</span>
|
||||
<code class="font-mono font-semibold">{item.rfc}</code>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1 text-sm">
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
function handleOpenChange(newOpen: boolean) {
|
||||
open = newOpen;
|
||||
}
|
||||
|
||||
const isForeign = (cp: ClientProvider) =>
|
||||
(cp.type_nat_foreign || "N").toUpperCase() === "E";
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open onOpenChange={handleOpenChange}>
|
||||
@@ -39,7 +42,9 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">RFC</span>
|
||||
<span class="text-xs font-medium text-muted-foreground">
|
||||
{isForeign(item) ? "TAX-ID" : "RFC"}
|
||||
</span>
|
||||
<code class="text-sm font-mono font-semibold">{item.rfc}</code>
|
||||
</div>
|
||||
</div>
|
||||
@@ -63,7 +68,7 @@
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-sm font-semibold">Clasificación</h3>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Tipo</span>
|
||||
{#if item.client_or_provider === 'client'}
|
||||
@@ -82,6 +87,13 @@
|
||||
<span class="text-sm text-muted-foreground">No especificado</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Procedencia</span>
|
||||
<span class="text-sm font-medium">
|
||||
{isForeign(item) ? "Extranjero" : "Nacional"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Estado</span>
|
||||
@@ -100,44 +112,16 @@
|
||||
<Separator />
|
||||
</div>
|
||||
|
||||
<!-- Información fiscal -->
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-sm font-semibold">Información Fiscal</h3>
|
||||
|
||||
{#if item.residence_country}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">País de Residencia</span>
|
||||
<span class="text-sm">{item.residence_country}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if item.domicile_fiscal}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Domicilio Fiscal</span>
|
||||
<span class="text-sm">{item.domicile_fiscal}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if item.foreign_tax_id}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">ID Fiscal Extranjero</span>
|
||||
<code class="text-sm font-mono">{item.foreign_tax_id}</code>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Separator />
|
||||
</div>
|
||||
|
||||
<!-- Dirección (si existe) -->
|
||||
{#if item.address}
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-sm font-semibold">Dirección</h3>
|
||||
|
||||
<div class="space-y-1">
|
||||
{#if item.address.street}
|
||||
{#if item.address.streets}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Calle</span>
|
||||
<span class="text-sm">{item.address.street}</span>
|
||||
<span class="text-xs font-medium text-muted-foreground">Calles</span>
|
||||
<span class="text-sm">{item.address.streets}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -149,15 +133,22 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if item.address.zip_code}
|
||||
{#if item.address.postal_code}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Código Postal</span>
|
||||
<span class="text-sm">{item.address.zip_code}</span>
|
||||
<span class="text-sm">{item.address.postal_code}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
{#if item.address.municipality}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Municipio</span>
|
||||
<span class="text-sm">{item.address.municipality}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if item.address.city}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Ciudad</span>
|
||||
@@ -179,6 +170,29 @@
|
||||
<span class="text-sm">{item.address.country}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if item.address.email || item.address.phone || item.address.contact}
|
||||
<div class="grid grid-cols-2 gap-2 pt-2">
|
||||
{#if item.address.email}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Email</span>
|
||||
<span class="text-sm">{item.address.email}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if item.address.phone}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Teléfono</span>
|
||||
<span class="text-sm">{item.address.phone}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if item.address.contact}
|
||||
<div class="flex flex-col gap-1 col-span-2">
|
||||
<span class="text-xs font-medium text-muted-foreground">Contacto</span>
|
||||
<span class="text-sm">{item.address.contact}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
@@ -190,17 +204,27 @@
|
||||
<div class="space-y-2">
|
||||
<h3 class="text-sm font-semibold">Programas</h3>
|
||||
|
||||
{#if item.programs.program_code}
|
||||
{#if item.programs.program}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Código de Programa</span>
|
||||
<code class="text-sm font-mono">{item.programs.program_code}</code>
|
||||
<span class="text-xs font-medium text-muted-foreground">Programa</span>
|
||||
<code class="text-sm font-mono">{item.programs.program}</code>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if item.programs.authorization_date}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Fecha de Autorización</span>
|
||||
<span class="text-sm">{new Date(item.programs.authorization_date).toLocaleDateString()}</span>
|
||||
{#if item.programs.program_number || item.programs.manufacturer_id}
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
{#if item.programs.program_number}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Número</span>
|
||||
<span class="text-sm">{item.programs.program_number}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if item.programs.manufacturer_id}
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">Manufacturer ID</span>
|
||||
<span class="text-sm font-mono">{item.programs.manufacturer_id}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -118,6 +118,12 @@
|
||||
selectedItem = item;
|
||||
}
|
||||
|
||||
function taxIdOrRfcLabel(cp: ClientProvider | null): string {
|
||||
if (!cp) return 'RFC';
|
||||
const proc = (cp.type_nat_foreign || 'N').toUpperCase();
|
||||
return proc === 'E' ? 'TAX-ID' : 'RFC';
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
if (selectedItem) goto(`/dashboard/clients_and_providers/edit/${selectedItem.id}`);
|
||||
}
|
||||
@@ -182,7 +188,7 @@
|
||||
<div class="p-4 space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold">Filtros</h2>
|
||||
<span class="text-xs text-muted-foreground">Busque por nombre, RFC o tipo</span>
|
||||
<span class="text-xs text-muted-foreground">Busque por nombre, RFC/TAX-ID o tipo</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div class="space-y-2">
|
||||
@@ -195,10 +201,10 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label class="text-xs">RFC</Label>
|
||||
<Label class="text-xs">RFC / TAX-ID</Label>
|
||||
<Input
|
||||
bind:value={searchRfc}
|
||||
placeholder="RFC..."
|
||||
placeholder="RFC / TAX-ID..."
|
||||
class="h-9"
|
||||
onkeydown={(e) => e.key === 'Enter' && handleSearch()}
|
||||
/>
|
||||
@@ -249,7 +255,7 @@
|
||||
<thead class="bg-muted text-muted-foreground border-b">
|
||||
<tr>
|
||||
<th class="px-3 py-2 text-left w-8">#</th>
|
||||
<th class="px-3 py-2 text-left">RFC</th>
|
||||
<th class="px-3 py-2 text-left">RFC / TAX-ID</th>
|
||||
<th class="px-3 py-2 text-left">Nombre</th>
|
||||
<th class="px-3 py-2 text-left">Tipo</th>
|
||||
<th class="px-3 py-2 text-left">Estatus</th>
|
||||
@@ -352,6 +358,7 @@
|
||||
{selectedItem?.name || '---'}
|
||||
</h2>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<span class="text-xs font-medium text-muted-foreground">{taxIdOrRfcLabel(selectedItem)}:</span>
|
||||
<span class="text-xs font-mono text-muted-foreground">{selectedItem?.rfc || ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -434,10 +441,6 @@
|
||||
<span class="text-xs text-muted-foreground block">Número</span>
|
||||
<span class="font-medium">{selectedItem.programs.program_number || '-'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-xs text-muted-foreground block">TAX ID</span>
|
||||
<span class="font-medium">{selectedItem.programs.tax_id || '-'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
responsible: '',
|
||||
position: '',
|
||||
is_active: true,
|
||||
is_national_provider: false,
|
||||
|
||||
streets: '',
|
||||
exterior_number: '',
|
||||
@@ -94,7 +93,6 @@
|
||||
authorization_date_str: '', // String para input date
|
||||
prosec: '',
|
||||
manufacturer_id: '',
|
||||
tax_id: '',
|
||||
ctpat_svi: '',
|
||||
is_certified_company: false
|
||||
});
|
||||
@@ -152,7 +150,6 @@
|
||||
responsible: item.responsible || '',
|
||||
position: item.position || '',
|
||||
is_active: !!item.is_active,
|
||||
is_national_provider: !!item.is_national_provider,
|
||||
|
||||
streets: addr.streets || '',
|
||||
exterior_number: addr.exterior_number || '',
|
||||
@@ -172,7 +169,6 @@
|
||||
authorization_date_str: intDateToString(prog.secon_auth_date),
|
||||
prosec: prog.prosec ? String(prog.prosec) : '',
|
||||
manufacturer_id: prog.manufacturer_id || '',
|
||||
tax_id: prog.tax_id || '',
|
||||
ctpat_svi: prog.ctpat_svi || '',
|
||||
is_certified_company: prog.is_certified_company === '1'
|
||||
};
|
||||
@@ -185,6 +181,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Validación de formato: RFC (Nacional) o TAX-ID (Extranjero)
|
||||
const RFC_REGEX = /^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$/i;
|
||||
// TAX-ID: 2 dígitos, guión, resto alfanumérico. Ej: 12-3456789. Máx 30 caracteres.
|
||||
const TAX_ID_REGEX = /^\d{2}-[A-Z0-9]{1,27}$/i;
|
||||
|
||||
// --- ENVÍO DE DATOS ---
|
||||
async function handleSubmit() {
|
||||
if (!companyStore.activeCompany) {
|
||||
@@ -193,11 +194,27 @@
|
||||
return;
|
||||
}
|
||||
if (!formData.rfc.trim() || !formData.name.trim()) {
|
||||
error = 'RFC y Nombre obligatorios';
|
||||
error = 'Identificador fiscal (RFC/TAX-ID) y Nombre son obligatorios';
|
||||
toast.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const rfcVal = formData.rfc.trim();
|
||||
const isForeign = (formData.type_nat_foreign || 'N').toUpperCase() === 'E';
|
||||
if (isForeign) {
|
||||
if (!TAX_ID_REGEX.test(rfcVal)) {
|
||||
error = 'El TAX-ID debe tener formato: 2 dígitos, guión y resto (ej. 12-3456789). Máx 30 caracteres.';
|
||||
toast.error(error);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!RFC_REGEX.test(rfcVal)) {
|
||||
error = 'El RFC no tiene el formato correcto. Ejemplo: XAXX010101000.';
|
||||
toast.error(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
@@ -213,7 +230,6 @@
|
||||
responsible: clean(formData.responsible),
|
||||
position: clean(formData.position),
|
||||
is_active: formData.is_active,
|
||||
is_national_provider: formData.is_national_provider,
|
||||
|
||||
address: {
|
||||
streets: clean(formData.streets),
|
||||
@@ -236,7 +252,6 @@
|
||||
secon_auth_date: stringDateToInt(formData.authorization_date_str),
|
||||
prosec: clean(formData.prosec),
|
||||
manufacturer_id: clean(formData.manufacturer_id),
|
||||
tax_id: clean(formData.tax_id),
|
||||
ctpat_svi: clean(formData.ctpat_svi),
|
||||
is_certified_company: formData.is_certified_company ? '1' : '0'
|
||||
}
|
||||
@@ -333,12 +348,12 @@
|
||||
<Card.Content class="space-y-4">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="rfc">RFC <span class="text-destructive">*</span></Label>
|
||||
<Label for="rfc">{formData.type_nat_foreign?.toUpperCase() === 'E' ? 'TAX-ID' : 'RFC'} <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="rfc"
|
||||
bind:value={formData.rfc}
|
||||
placeholder="XAXX010101000"
|
||||
maxlength={13}
|
||||
placeholder={formData.type_nat_foreign?.toUpperCase() === 'E' ? 'Ej: 12-3456789 (según país)' : 'XAXX010101000'}
|
||||
maxlength={30}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
@@ -659,19 +674,6 @@
|
||||
</div>
|
||||
<Separator />
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="tax_id" class="flex items-center gap-2">
|
||||
<Globe size={14} class="text-muted-foreground" />
|
||||
Tax ID (Extranjero)
|
||||
</Label>
|
||||
<Input
|
||||
id="tax_id"
|
||||
bind:value={formData.tax_id}
|
||||
maxlength={30}
|
||||
disabled={loading}
|
||||
placeholder="Identificador fiscal extranjero"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="man_id" class="flex items-center gap-2">
|
||||
<FileText size={14} class="text-muted-foreground" />
|
||||
@@ -746,17 +748,6 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 rounded-lg border bg-card p-4">
|
||||
<Switch
|
||||
id="is_national"
|
||||
bind:checked={formData.is_national_provider}
|
||||
disabled={loading}
|
||||
/>
|
||||
<div class="grid gap-0.5">
|
||||
<Label for="is_national">Proveedor Nacional</Label>
|
||||
<p class="text-xs text-muted-foreground">Marcar si es un proveedor nacional</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
Reference in New Issue
Block a user