Files
CRM_AGENTES_CARGA/frontend/src/lib/components/crm/AccountFields.svelte
Ernesto Herrera de8557dec2 feat(crm): formularios Clientes/Prospectos y Proveedores por catálogo — T2026-07-081/082
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>
2026-07-22 10:31:59 -06:00

66 lines
7.7 KiB
Svelte

<script lang="ts">
import { onMount } from 'svelte';
import type { AccountInput } from '$lib/api/crm';
import { ACCOUNT_TYPES } from '$lib/components/crm/format';
import { crmCatalogs } from '$lib/stores/crm-catalogs.svelte';
// `form` es un objeto reactivo del padre; se mutan sus propiedades vía bind:value.
let { form = $bindable(), tab }: { form: AccountInput; tab: 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';
// Catálogos que usa este formulario (se precargan; los selects se llenan solos).
onMount(() => {
void crmCatalogs.preload([
'tipo_registro', 'tipo_persona', 'estatus', 'giro', 'clasificacion_cliente',
'medio_contacto', 'idioma', 'regimen_fiscal', 'uso_cfdi', 'metodo_pago',
'forma_pago', 'moneda'
]);
});
</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">CURP</span><input class="font-mono {inputCls}" maxlength="18" bind:value={form.curp} /></label>
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Tipo de registro</span><select class={inputCls} bind:value={form.record_type}>{#each crmCatalogs.options('tipo_registro') 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">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">Giro / Industria</span><select class={inputCls} bind:value={form.industry}><option value={undefined}>—</option>{#each crmCatalogs.options('giro') as g (g.value)}<option value={g.value}>{g.label}</option>{/each}</select></label>
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Tipo operativo</span><select class={inputCls} bind:value={form.account_type}><option value={undefined}>—</option>{#each ACCOUNT_TYPES as t (t.value)}<option value={t.value}>{t.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>
{: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">Clasificación del cliente</span><select class={inputCls} bind:value={form.commercial_classification}><option value={undefined}>—</option>{#each crmCatalogs.options('clasificacion_cliente') 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">Medio de contacto preferido</span><select class={inputCls} bind:value={form.preferred_contact_method}><option value={undefined}>—</option>{#each crmCatalogs.options('medio_contacto') as m (m.value)}<option value={m.value}>{m.label}</option>{/each}</select></label>
{#if form.preferred_contact_method === 'otro'}
<label class="flex flex-col gap-1 text-sm sm:col-span-2"><span class="font-medium">Especifica el medio de contacto</span><input class={inputCls} bind:value={form.preferred_contact_other} placeholder="Indica cuál" /></label>
{/if}
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Idioma</span><select class={inputCls} bind:value={form.language}><option value={undefined}>—</option>{#each crmCatalogs.options('idioma') as i (i.value)}<option value={i.value}>{i.label}</option>{/each}</select></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"><span class="font-medium">Sitio web</span><input class={inputCls} bind:value={form.website} /></label>
<label class="flex flex-col gap-1 text-sm sm:col-span-2"><span class="font-medium">Observaciones generales</span><textarea rows="3" class={inputCls} bind:value={form.commercial_observations}></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">Uso de CFDI</span><select class={inputCls} bind:value={form.cfdi_use}><option value={undefined}>—</option>{#each crmCatalogs.options('uso_cfdi') as u (u.value)}<option value={u.value}>{u.value} {u.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">Moneda</span><select class={inputCls} bind:value={form.currency}><option value={undefined}>—</option>{#each crmCatalogs.options('moneda') as c (c.value)}<option value={c.value}>{c.value} {c.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"><span class="font-medium">Patente aduanal</span><input class={inputCls} maxlength="20" bind:value={form.patente_aduanal} /></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}