Merge pull request 'fix/agentes_aduanales_regex' (#190) from fix/agentes_aduanales_regex into development

Reviewed-on: ADUANASOFT/anexo76#190
This commit is contained in:
2026-03-06 15:52:48 +00:00
7 changed files with 420 additions and 83 deletions

View File

@@ -38,6 +38,7 @@
city: '',
state: '',
country: '',
personal_id: '',
tenant_id: '', // Se llenará en el submit o por defecto
company_id: ''
};
@@ -62,6 +63,7 @@
contact: initialData.contact || '',
address: initialData.address || '',
postal_code: initialData.postal_code || '',
personal_id: initialData.personal_id || '',
city: initialData.city || '',
state: initialData.state || '',
country: initialData.country || '',
@@ -80,6 +82,7 @@
contact: '',
address: '',
postal_code: '',
personal_id: '',
city: '',
state: '',
country: 'MEX', // Valor por defecto sugerido
@@ -116,6 +119,48 @@
loading = false;
return;
}
if (
formData.license === '0' ||
formData.license === '0000' ||
/^0+$/.test(formData.license)
) {
toast.error('La Patente no puede ser 0');
loading = false;
return;
}
if (formData.tax_id && !/^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$/i.test(formData.tax_id)) {
toast.error('El formato del RFC es inválido');
loading = false;
return;
}
if (
formData.personal_id &&
!/^[A-Z][AEIOUX][A-Z]{2}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])[HM](AS|BC|BS|CC|CS|CH|CL|CM|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE)[B-DF-HJ-NP-TV-Z]{3}[0-9A-Z]\d$/i.test(
formData.personal_id
)
) {
toast.error('El formato de la CURP es inválido');
loading = false;
return;
}
if (
formData.email &&
!/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(formData.email)
) {
toast.error('El formato del correo electrónico es inválido');
loading = false;
return;
}
if (formData.phone && !/^[\d\s\-\+\(\)]+$/.test(formData.phone)) {
toast.error('El teléfono contiene caracteres no válidos');
loading = false;
return;
}
if (formData.fax && !/^[\d\s\-\+\(\)]+$/.test(formData.fax)) {
toast.error('El fax contiene caracteres no válidos');
loading = false;
return;
}
// Inyectar company_id si no viene
const payload = { ...formData, company_id: companyId };
@@ -125,9 +170,14 @@
toast.success(
mode === 'create' ? 'Agente creado correctamente' : 'Agente actualizado correctamente'
);
} catch (error) {
} catch (error: unknown) {
console.error(error);
toast.error('Error al guardar el agente aduanal');
// Intentar extraer el mensaje del servidor si lo hay
let msg = 'Error al guardar el agente aduanal';
if (error && typeof error === 'object' && 'message' in error) {
msg = (error as { message: string }).message || msg;
}
toast.error(msg);
} finally {
loading = false;
}
@@ -135,7 +185,7 @@
</script>
<Dialog.Root bind:open>
<Dialog.Content class="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
<Dialog.Content class="max-h-[90vh] overflow-y-auto sm:max-w-[700px]">
<Dialog.Header>
<Dialog.Title>
{mode === 'create' ? 'Nuevo Agente Aduanal' : 'Editar Agente Aduanal'}
@@ -148,7 +198,7 @@
<div class="grid gap-6 py-4">
<div class="space-y-4">
<h4 class="text-sm font-medium leading-none text-muted-foreground">Identificación</h4>
<h4 class="text-sm leading-none font-medium text-muted-foreground">Identificación</h4>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2">
<Label for="broker_key">Clave Agente *</Label>
@@ -156,7 +206,7 @@
id="broker_key"
value={formData.broker_key}
oninput={(e) => {
const val = e.currentTarget.value.toUpperCase();
const val = e.currentTarget.value.toUpperCase().replace(/[^A-Z0-9]/g, '');
if (val.length > 5) {
brokerKeyError = true;
formData.broker_key = val.slice(0, 5);
@@ -169,10 +219,11 @@
} else {
brokerKeyError = false;
formData.broker_key = val;
e.currentTarget.value = formData.broker_key;
}
}}
placeholder="Ej. 550"
maxlength="6"
maxlength={6}
class={brokerKeyError ? 'border-red-500 focus-visible:ring-red-500' : ''}
disabled={mode === 'edit' || loading}
/>
@@ -204,7 +255,7 @@
}
}}
placeholder="Ej. 3421"
maxlength="5"
maxlength={5}
class={licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''}
disabled={loading}
/>
@@ -216,7 +267,7 @@
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="space-y-2 col-span-2">
<div class="col-span-2 space-y-2">
<Label for="name">Nombre / Razón Social</Label>
<Input
id="name"
@@ -229,16 +280,48 @@
<Label for="tax_id">RFC</Label>
<Input
id="tax_id"
bind:value={formData.tax_id}
value={formData.tax_id}
oninput={(e) => {
formData.tax_id = e.currentTarget.value
.toUpperCase()
.replace(/[^A-Z0-9&Ñ]/g, '')
.slice(0, 13);
e.currentTarget.value = formData.tax_id;
}}
placeholder="RFC de la agencia"
maxlength={13}
disabled={loading}
/>
</div>
<div class="space-y-2">
<Label for="personal_id">CURP</Label>
<Input
id="personal_id"
value={formData.personal_id}
oninput={(e) => {
formData.personal_id = e.currentTarget.value
.toUpperCase()
.replace(/[^A-Z0-9]/g, '')
.slice(0, 18);
e.currentTarget.value = formData.personal_id;
}}
placeholder="CURP (Opcional)"
maxlength={18}
disabled={loading}
/>
</div>
<div class="col-span-2 space-y-2">
<Label for="contact">Nombre Contacto</Label>
<Input
id="contact"
bind:value={formData.contact}
value={formData.contact}
oninput={(e) => {
formData.contact = e.currentTarget.value.replace(
/[^a-zA-Z0-9\sñÑáéíóúÁÉÍÓÚ\-\.,]/g,
''
);
e.currentTarget.value = formData.contact;
}}
placeholder="Persona de contacto"
disabled={loading}
/>
@@ -249,13 +332,33 @@
<Separator />
<div class="space-y-4">
<h4 class="text-sm font-medium leading-none text-muted-foreground">Contacto</h4>
<h4 class="text-sm leading-none font-medium text-muted-foreground">Contacto</h4>
<div class="grid grid-cols-3 gap-4">
<div class="space-y-2 col-span-1">
<div class="col-span-1 space-y-2">
<Label for="phone">Teléfono</Label>
<Input id="phone" bind:value={formData.phone} disabled={loading} />
<Input
id="phone"
value={formData.phone}
oninput={(e) => {
formData.phone = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, '');
e.currentTarget.value = formData.phone;
}}
disabled={loading}
/>
</div>
<div class="space-y-2 col-span-2">
<div class="col-span-1 space-y-2">
<Label for="fax">Fax</Label>
<Input
id="fax"
value={formData.fax}
oninput={(e) => {
formData.fax = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, '');
e.currentTarget.value = formData.fax;
}}
disabled={loading}
/>
</div>
<div class="col-span-1 space-y-2">
<Label for="email">Correo Electrónico</Label>
<Input id="email" type="email" bind:value={formData.email} disabled={loading} />
</div>
@@ -265,7 +368,7 @@
<Separator />
<div class="space-y-4">
<h4 class="text-sm font-medium leading-none text-muted-foreground">Dirección Fiscal</h4>
<h4 class="text-sm leading-none font-medium text-muted-foreground">Dirección Fiscal</h4>
<div class="space-y-2">
<Label for="address">Calle y Número</Label>
@@ -275,9 +378,16 @@
<div class="grid grid-cols-4 gap-4">
<div class="space-y-2">
<Label for="postal_code">C.P.</Label>
<Input id="postal_code" bind:value={formData.postal_code} disabled={loading} oninput={(e) => { formData.postal_code = e.currentTarget.value.replace(/[^a-zA-Z0-9]/g, ''); }} />
<Input
id="postal_code"
bind:value={formData.postal_code}
disabled={loading}
oninput={(e) => {
formData.postal_code = e.currentTarget.value.replace(/[^a-zA-Z0-9]/g, '');
}}
/>
</div>
<div class="space-y-2 col-span-2">
<div class="col-span-2 space-y-2">
<Label for="city">Ciudad</Label>
<Input id="city" bind:value={formData.city} disabled={loading} />
</div>

View File

@@ -79,6 +79,41 @@
loading = true;
error = null;
// Validaciones Básicas
if (formData.license === '0' || formData.license === '0000' || /^0+$/.test(formData.license)) {
error = 'La Patente no puede ser 0';
loading = false;
return;
}
if (formData.tax_id && !/^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$/i.test(formData.tax_id)) {
error = 'El formato del RFC es inválido';
loading = false;
return;
}
if (
formData.personal_id &&
!/^[A-Z][AEIOUX][A-Z]{2}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])[HM](AS|BC|BS|CC|CS|CH|CL|CM|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE)[B-DF-HJ-NP-TV-Z]{3}[0-9A-Z]\d$/i.test(
formData.personal_id
)
) {
error = 'El formato de la CURP es inválido';
loading = false;
return;
}
if (
formData.email &&
!/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.test(formData.email)
) {
error = 'El formato del correo electrónico es inválido';
loading = false;
return;
}
if (formData.phone && !/^[\d\s\-\+\(\)]+$/.test(formData.phone)) {
error = 'El teléfono contiene caracteres no válidos';
loading = false;
return;
}
try {
const payload: CreateCustomsBrokerData = {
broker_key: broker.broker_key, // La clave no se edita
@@ -138,7 +173,7 @@
</script>
<Dialog.Root bind:open onOpenChange={handleOpenChange}>
<Dialog.Content class="sm:max-w-[700px] max-h-[90vh] overflow-y-auto">
<Dialog.Content class="max-h-[90vh] overflow-y-auto sm:max-w-[700px]">
<Dialog.Header>
<Dialog.Title>Editar Agente Aduanal</Dialog.Title>
<Dialog.Description>
@@ -218,7 +253,7 @@
}
}}
placeholder="Número de patente"
maxlength="5"
maxlength={5}
class={licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''}
disabled={loading}
/>
@@ -251,7 +286,11 @@
<Label for="edit-phone">Teléfono</Label>
<Input
id="edit-phone"
bind:value={formData.phone}
value={formData.phone}
oninput={(e) => {
formData.phone = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, '');
e.currentTarget.value = formData.phone;
}}
placeholder="Número telefónico"
maxlength={30}
disabled={loading}
@@ -262,7 +301,11 @@
<Label for="edit-fax">Fax</Label>
<Input
id="edit-fax"
bind:value={formData.fax}
value={formData.fax}
oninput={(e) => {
formData.fax = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, '');
e.currentTarget.value = formData.fax;
}}
placeholder="Número de fax"
maxlength={30}
disabled={loading}
@@ -286,7 +329,14 @@
<Label for="edit-contact">Contacto</Label>
<Input
id="edit-contact"
bind:value={formData.contact}
value={formData.contact}
oninput={(e) => {
formData.contact = e.currentTarget.value.replace(
/[^a-zA-Z0-9\sñÑáéíóúÁÉÍÓÚ\-\.,]/g,
''
);
e.currentTarget.value = formData.contact;
}}
placeholder="Nombre del contacto"
maxlength={80}
disabled={loading}
@@ -318,7 +368,9 @@
placeholder="C.P."
maxlength={15}
disabled={loading}
oninput={(e) => { formData.postal_code = e.currentTarget.value.replace(/[^a-zA-Z0-9]/g, ''); }}
oninput={(e) => {
formData.postal_code = e.currentTarget.value.replace(/[^a-zA-Z0-9]/g, '');
}}
/>
</div>
@@ -366,9 +418,16 @@
<Label for="edit-tax_id">RFC</Label>
<Input
id="edit-tax_id"
bind:value={formData.tax_id}
value={formData.tax_id}
oninput={(e) => {
formData.tax_id = e.currentTarget.value
.toUpperCase()
.replace(/[^A-Z0-9&Ñ]/g, '')
.slice(0, 13);
e.currentTarget.value = formData.tax_id;
}}
placeholder="RFC"
maxlength={30}
maxlength={13}
disabled={loading}
/>
</div>
@@ -377,9 +436,16 @@
<Label for="edit-personal_id">CURP</Label>
<Input
id="edit-personal_id"
bind:value={formData.personal_id}
value={formData.personal_id}
oninput={(e) => {
formData.personal_id = e.currentTarget.value
.toUpperCase()
.replace(/[^A-Z0-9]/g, '')
.slice(0, 18);
e.currentTarget.value = formData.personal_id;
}}
placeholder="CURP"
maxlength={20}
maxlength={18}
disabled={loading}
/>
</div>