Frontend de los catálogos de referencia en BD: - Store crm-catalogs + cliente API; selects poblados desde /v1/crm/catalogs (fiscal: régimen, uso CFDI, forma/método de pago SAT, moneda ISO; comercial; país/estado/tipo de domicilio/área). Muestran la descripción, no la clave. - "Otro → especificar" (medio de contacto y clasificación), observaciones generales, y datos de auditoría (ID, fechas, usuarios) en la vista de edición. - Botón "Editar" explícito en los listados. - Pantalla de administración de catálogos (insertar/editar/borrar): globales solo activar/desactivar; los de la empresa con alta/edición/borrado. - addresses.country ampliado a 3 (país ISO alfa-3) + migración con down(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
85 lines
7.2 KiB
Svelte
85 lines
7.2 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte';
|
|
import type { SupplierInput } from '$lib/api/crm';
|
|
import { crmCatalogs } from '$lib/stores/crm-catalogs.svelte';
|
|
|
|
// listas separadas por coma también son bindables (el padre las convierte a arreglo)
|
|
let {
|
|
form = $bindable(),
|
|
tab,
|
|
countriesStr = $bindable(),
|
|
portsStr = $bindable(),
|
|
airportsStr = $bindable(),
|
|
customsStr = $bindable()
|
|
}: {
|
|
form: SupplierInput;
|
|
tab: string;
|
|
countriesStr: string;
|
|
portsStr: string;
|
|
airportsStr: string;
|
|
customsStr: string;
|
|
} = $props();
|
|
|
|
const inputCls =
|
|
'rounded-md border bg-transparent px-3 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring';
|
|
|
|
const hasOtro = $derived((form.classifications ?? []).includes('otro'));
|
|
|
|
onMount(() => {
|
|
void crmCatalogs.preload([
|
|
'tipo_persona', 'estatus', 'clasificacion_proveedor', 'cobertura', 'moneda',
|
|
'regimen_fiscal', 'metodo_pago', 'forma_pago'
|
|
]);
|
|
});
|
|
</script>
|
|
|
|
{#if tab === 'generales'}
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<label class="flex flex-col gap-1 text-sm sm:col-span-2"><span class="font-medium">Razón social *</span><input class={inputCls} bind:value={form.name} required /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Nombre comercial</span><input class={inputCls} bind:value={form.trade_name} /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">RFC</span><input class="font-mono {inputCls}" maxlength="13" bind:value={form.rfc} /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Tipo de persona</span><select class={inputCls} bind:value={form.person_type}><option value={undefined}>—</option>{#each crmCatalogs.options('tipo_persona') as p (p.value)}<option value={p.value}>{p.label}</option>{/each}</select></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Estatus</span><select class={inputCls} bind:value={form.status}>{#each crmCatalogs.options('estatus') as s (s.value)}<option value={s.value}>{s.label}</option>{/each}</select></label>
|
|
</div>
|
|
<div class="mt-4">
|
|
<p class="mb-2 text-sm font-medium">Clasificación del proveedor (una o varias)</p>
|
|
<div class="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
|
{#each crmCatalogs.options('clasificacion_proveedor') as c (c.value)}
|
|
<label class="flex items-center gap-2 text-sm"><input type="checkbox" class="h-4 w-4 rounded border" value={c.value} bind:group={form.classifications} /><span>{c.label}</span></label>
|
|
{/each}
|
|
</div>
|
|
{#if hasOtro}
|
|
<label class="mt-3 flex max-w-md flex-col gap-1 text-sm"><span class="font-medium">Especifica la clasificación "Otro"</span><input class={inputCls} bind:value={form.classification_other} placeholder="Indica cuál" /></label>
|
|
{/if}
|
|
</div>
|
|
{:else if tab === 'comercial'}
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Cobertura</span><select class={inputCls} bind:value={form.coverage}><option value={undefined}>—</option>{#each crmCatalogs.options('cobertura') as c (c.value)}<option value={c.value}>{c.label}</option>{/each}</select></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Moneda de cotización</span><select class={inputCls} bind:value={form.quote_currency}><option value={undefined}>—</option>{#each crmCatalogs.options('moneda') as m (m.value)}<option value={m.value}>{m.value} — {m.label}</option>{/each}</select></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Países donde opera (separados por coma)</span><input class={inputCls} bind:value={countriesStr} placeholder="México, Estados Unidos" /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Puertos donde opera</span><input class={inputCls} bind:value={portsStr} placeholder="Veracruz, Manzanillo" /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Aeropuertos donde opera</span><input class={inputCls} bind:value={airportsStr} /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Aduanas donde opera</span><input class={inputCls} bind:value={customsStr} placeholder="Nuevo Laredo, Colombia" /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Horario de atención</span><input class={inputCls} bind:value={form.business_hours} /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Tiempo prom. de respuesta</span><input class={inputCls} bind:value={form.avg_response_time} /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Email</span><input type="email" class={inputCls} bind:value={form.email} /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Teléfono</span><input class={inputCls} bind:value={form.phone} /></label>
|
|
<label class="flex flex-col gap-1 text-sm sm:col-span-2"><span class="font-medium">Servicios que ofrece</span><textarea rows="2" class={inputCls} bind:value={form.services_offered}></textarea></label>
|
|
<label class="flex flex-col gap-1 text-sm sm:col-span-2"><span class="font-medium">Observaciones comerciales</span><textarea rows="2" class={inputCls} bind:value={form.commercial_notes}></textarea></label>
|
|
</div>
|
|
{:else if tab === 'fiscal'}
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Régimen fiscal</span><select class={inputCls} bind:value={form.tax_regime}><option value={undefined}>—</option>{#each crmCatalogs.options('regimen_fiscal') as r (r.value)}<option value={r.value}>{r.label}</option>{/each}</select></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Método de pago</span><select class={inputCls} bind:value={form.payment_method}><option value={undefined}>—</option>{#each crmCatalogs.options('metodo_pago') as m (m.value)}<option value={m.value}>{m.value} — {m.label}</option>{/each}</select></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Forma de pago</span><select class={inputCls} bind:value={form.payment_form}><option value={undefined}>—</option>{#each crmCatalogs.options('forma_pago') as f (f.value)}<option value={f.value}>{f.value} — {f.label}</option>{/each}</select></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Límite de crédito</span><input type="number" min="0" step="0.01" class={inputCls} bind:value={form.credit_limit} /></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Días de crédito</span><input type="number" min="0" class={inputCls} bind:value={form.credit_days} /></label>
|
|
<label class="flex flex-col gap-1 text-sm sm:col-span-2"><span class="font-medium">Condiciones comerciales</span><textarea rows="3" class={inputCls} bind:value={form.commercial_terms}></textarea></label>
|
|
</div>
|
|
{:else if tab === 'observaciones'}
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Comentarios generales</span><textarea rows="4" class={inputCls} bind:value={form.notes}></textarea></label>
|
|
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Notas internas</span><textarea rows="4" class={inputCls} bind:value={form.internal_notes}></textarea></label>
|
|
</div>
|
|
{/if}
|