feat(crm): cotización aérea con peso/volumen (P/Vol) operacional
- Modalidad AÉREO (4ª opción en "¿Cómo desea cotizar?"): en la solicitud muestra la sección aérea con el cálculo en vivo P/Vol = (L×A×H cm × bultos)/6000 y el peso a cobrar = max(peso bruto, P/Vol). Fija el transporte en aéreo. - Utilidad compartida crm/common/pricing.py (air_volumetric_kg / air_chargeable_kg, factor internacional 6000). - Motor de costeo (rates): la rama aérea usa el P/Vol por dimensiones si vienen (CostRequest ahora acepta length/width/height_cm); respaldo m³×167 cuando no. - Cotizador: captura por dimensiones (L×A×H + bultos) en modo aéreo y muestra el P/Vol. - Solicitud→Cotización: si es AÉREO, siembra el concepto de flete con cantidad = peso a cobrar (P/Vol) para capturar la tarifa por kg. - Pruebas: test_pricing (ejemplo del doc → 720; max bruto/volumétrico) + cotización aérea desde solicitud. Suite en verde (108). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -21,9 +21,24 @@
|
||||
const inputCls =
|
||||
'rounded-md border bg-transparent px-3 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring';
|
||||
|
||||
// FCL/LCL condicionales; "AMBAS" muestra ambas secciones
|
||||
// FCL/LCL condicionales; "AMBAS" muestra ambas secciones; "AEREO" muestra la sección aérea
|
||||
const isFcl = $derived(form.load_type === 'FCL' || form.load_type === 'AMBAS');
|
||||
const isLcl = $derived(form.load_type === 'LCL' || form.load_type === 'AMBAS');
|
||||
const isAir = $derived(form.load_type === 'AEREO');
|
||||
|
||||
// Peso/Volumen aéreo (P/Vol) = (L×A×H cm × cantidad de bultos) / 6000; a cobrar = max(bruto, P/Vol)
|
||||
const airQty = $derived(Number(form.pallets_count) || Number(form.pieces_count) || 1);
|
||||
const airVolumetric = $derived(
|
||||
Number(form.length_cm) > 0 && Number(form.width_cm) > 0 && Number(form.height_cm) > 0
|
||||
? (Number(form.length_cm) * Number(form.width_cm) * Number(form.height_cm) * airQty) / 6000
|
||||
: 0
|
||||
);
|
||||
const airChargeable = $derived(Math.max(Number(form.weight) || 0, airVolumetric));
|
||||
|
||||
// La modalidad aérea fija el medio de transporte en "aéreo"
|
||||
$effect(() => {
|
||||
if (form.load_type === 'AEREO' && form.transport_mode !== 'aereo') form.transport_mode = 'aereo';
|
||||
});
|
||||
|
||||
// Contactos del cliente seleccionado (o todos si no hay cliente)
|
||||
const clientContacts = $derived(
|
||||
@@ -151,6 +166,18 @@
|
||||
<label class="flex flex-col gap-1 text-sm"><span class="font-medium">Volumen por pallet (m³)</span><input type="number" min="0" step="0.001" class={inputCls} bind:value={form.volume_per_pallet} /></label>
|
||||
</div>
|
||||
{/if}
|
||||
{#if isAir}
|
||||
<div class="mt-4 grid gap-3 rounded-md border p-4 sm:grid-cols-2">
|
||||
<p class="text-sm font-semibold sm:col-span-2">Aéreo — Peso / Volumen (P/Vol)</p>
|
||||
<p class="text-xs text-muted-foreground sm:col-span-2">P/Vol = (Largo × Ancho × Alto en cm) × cantidad de bultos ÷ 6000 (factor internacional). Se cobra el mayor entre el peso bruto y el P/Vol. Captura Largo/Ancho/Alto y piezas/pallets arriba; el resultado se recalcula solo.</p>
|
||||
<div class="rounded-md bg-muted/40 p-3 text-sm sm:col-span-2">
|
||||
<div class="flex justify-between"><span class="text-muted-foreground">Cantidad de bultos</span><span class="font-medium">{airQty}</span></div>
|
||||
<div class="flex justify-between"><span class="text-muted-foreground">Peso volumétrico (P/Vol)</span><span class="font-medium">{airVolumetric.toFixed(2)}</span></div>
|
||||
<div class="flex justify-between"><span class="text-muted-foreground">Peso bruto</span><span class="font-medium">{(Number(form.weight) || 0).toFixed(2)} kg</span></div>
|
||||
<div class="mt-1 flex justify-between border-t pt-1"><span class="font-medium">Peso a cobrar</span><span class="font-semibold">{airChargeable.toFixed(2)} kg</span></div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if tab === 'servicios'}
|
||||
<p class="mb-1 text-sm font-medium">Servicios adicionales</p>
|
||||
<p class="mb-3 text-xs text-muted-foreground">Marca los servicios requeridos e indica su costo estimado (opcional). Al cotizar, cada servicio marcado se agrega como concepto de la cotización con ese costo de partida.</p>
|
||||
|
||||
Reference in New Issue
Block a user