Files
CRM_AGENTES_CARGA/frontend/src/lib/components/crm/format.ts
Ernesto Herrera f1e6fba75d 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>
2026-08-04 07:30:25 -06:00

299 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Utilidades de formato y catálogos de etiquetas para el CRM.
*/
export interface Option {
value: string;
label: string;
}
export function formatMoney(value: number | null | undefined, currency = 'MXN'): string {
if (value === null || value === undefined) return '—';
return new Intl.NumberFormat('es-MX', { style: 'currency', currency }).format(Number(value));
}
export function formatDate(value: string | null | undefined): string {
if (!value) return '—';
const d = new Date(value);
if (Number.isNaN(d.getTime())) return '—';
return d.toLocaleDateString('es-MX', { year: 'numeric', month: 'short', day: 'numeric' });
}
export function labelOf(list: Option[], value: string | null | undefined): string {
if (!value) return '—';
return list.find((x) => x.value === value)?.label ?? value;
}
// ----- Clientes / Prospectos -----
export const RECORD_TYPES: Option[] = [
{ value: 'cliente', label: 'Cliente' },
{ value: 'prospecto', label: 'Prospecto' }
];
export const PERSON_TYPES: Option[] = [
{ value: 'fisica', label: 'Física' },
{ value: 'moral', label: 'Moral' }
];
export const ACCOUNT_STATUS: Option[] = [
{ value: 'active', label: 'Activo' },
{ value: 'inactive', label: 'Inactivo' }
];
export const COMMERCIAL_CLASSIFICATION: Option[] = [
{ value: 'importador', label: 'Importador' },
{ value: 'exportador', label: 'Exportador' },
{ value: 'importador_exportador', label: 'Importador / Exportador' }
];
export const ACCOUNT_TYPES: Option[] = [
{ value: 'importador', label: 'Importador' },
{ value: 'exportador', label: 'Exportador' },
{ value: 'immex', label: 'IMMEX / Maquila' },
{ value: 'agencia_aduanal', label: 'Agencia aduanal' },
{ value: 'transportista', label: 'Transportista' },
{ value: 'otro', label: 'Otro' }
];
export const CONTACT_METHODS: Option[] = [
{ value: 'llamada', label: 'Llamada telefónica' },
{ value: 'correo', label: 'Correo electrónico' },
{ value: 'videoconferencia', label: 'Videoconferencia' },
{ value: 'whatsapp', label: 'WhatsApp' },
{ value: 'otro', label: 'Otro' }
];
// ----- Proveedores -----
export const SUPPLIER_CLASSIFICATIONS: Option[] = [
{ value: 'naviera', label: 'Naviera' },
{ value: 'aerolinea', label: 'Aerolínea' },
{ value: 'transportista_terrestre', label: 'Transportista terrestre' },
{ value: 'ferrocarril', label: 'Ferrocarril' },
{ value: 'agente_aduanal', label: 'Agente aduanal' },
{ value: 'agente_carga', label: 'Agente de carga' },
{ value: 'agente_corresponsal', label: 'Agente corresponsal' },
{ value: 'almacen', label: 'Almacén' },
{ value: 'aseguradora', label: 'Aseguradora' },
{ value: 'paqueteria', label: 'Paquetería' },
{ value: 'otro', label: 'Otro' }
];
export const COVERAGE: Option[] = [
{ value: 'nacional', label: 'Nacional' },
{ value: 'internacional', label: 'Internacional' },
{ value: 'ambos', label: 'Nacional e internacional' }
];
// ----- Direcciones -----
export const ADDRESS_TYPES: Option[] = [
{ value: 'fiscal', label: 'Fiscal' },
{ value: 'oficina', label: 'Oficina' },
{ value: 'sucursal', label: 'Sucursal' },
{ value: 'bodega', label: 'Bodega' },
{ value: 'patio', label: 'Patio' },
{ value: 'terminal', label: 'Terminal' },
{ value: 'almacen', label: 'Almacén' }
];
// ----- Documentos -----
export const DOC_TYPES: Option[] = [
{ value: 'constancia_fiscal', label: 'Constancia de Situación Fiscal' },
{ value: 'acta_constitutiva', label: 'Acta Constitutiva' },
{ value: 'identificacion', label: 'Identificación Oficial' },
{ value: 'comprobante_domicilio', label: 'Comprobante de Domicilio' },
{ value: 'contrato', label: 'Contrato' },
{ value: 'presentacion', label: 'Presentación Comercial' },
{ value: 'certificacion', label: 'Certificación' },
{ value: 'licencia', label: 'Licencia' },
{ value: 'convenio', label: 'Convenio' },
{ value: 'tarifario', label: 'Tarifario' },
{ value: 'otro', label: 'Otro' }
];
// ----- Contactos -----
export const CONTACT_AREAS: Option[] = [
{ value: 'ventas', label: 'Ventas' },
{ value: 'operaciones', label: 'Operaciones' },
{ value: 'facturacion', label: 'Facturación' },
{ value: 'cobranza', label: 'Cobranza' },
{ value: 'servicio_cliente', label: 'Servicio al Cliente' },
{ value: 'otro', label: 'Otro' }
];
export const CONTACT_STATUS: Option[] = [
{ value: 'active', label: 'Activo' },
{ value: 'inactive', label: 'Inactivo' }
];
// ----- Prospectos (leads / funnel) -----
export const LEAD_SOURCES: Option[] = [
{ value: 'web', label: 'Web' },
{ value: 'referido', label: 'Referido' },
{ value: 'evento', label: 'Evento' },
{ value: 'llamada', label: 'Llamada' },
{ value: 'email', label: 'Email' },
{ value: 'otro', label: 'Otro' }
];
export const LEAD_STATUS: Option[] = [
{ value: 'new', label: 'Nuevo' },
{ value: 'contacted', label: 'Contactado' },
{ value: 'qualified', label: 'Calificado' },
{ value: 'unqualified', label: 'No calificado' },
{ value: 'converted', label: 'Convertido' }
];
// ----- Actividades -----
export const ACTIVITY_TYPES: Option[] = [
{ value: 'call', label: 'Llamada' },
{ value: 'meeting', label: 'Reunión' },
{ value: 'task', label: 'Tarea' },
{ value: 'email', label: 'Correo' },
{ value: 'note', label: 'Nota' }
];
export const ACTIVITY_STATUS: Option[] = [
{ value: 'pending', label: 'Pendiente' },
{ value: 'completed', label: 'Completada' },
{ value: 'canceled', label: 'Cancelada' }
];
// ----- Comercial: Solicitudes / Cotizaciones -----
export const OPERATION_TYPES: Option[] = [
{ value: 'importacion', label: 'Importación' },
{ value: 'exportacion', label: 'Exportación' }
];
export const TRANSPORT_MODES: Option[] = [
{ value: 'maritimo', label: 'Marítimo' },
{ value: 'aereo', label: 'Aéreo' },
{ value: 'terrestre', label: 'Terrestre' },
{ value: 'ferroviario', label: 'Ferroviario' },
{ value: 'multimodal', label: 'Multimodal' }
];
export const SERVICE_TYPES: Option[] = [
{ value: 'puerto_puerto', label: 'Puerto Puerto' },
{ value: 'puerto_puerta', label: 'Puerto Puerta' },
{ value: 'puerta_puerto', label: 'Puerta Puerto' },
{ value: 'puerta_puerta', label: 'Puerta Puerta (Door to Door)' }
];
export const LOAD_TYPES: Option[] = [
{ value: 'FCL', label: 'FCL (contenedor completo)' },
{ value: 'LCL', label: 'LCL (carga consolidada)' },
{ value: 'AMBAS', label: 'Ambas (comparar FCL y LCL)' },
{ value: 'AEREO', label: 'Aéreo (carga aérea)' }
];
export const PRIORITIES: Option[] = [
{ value: 'baja', label: 'Baja' },
{ value: 'normal', label: 'Normal' },
{ value: 'alta', label: 'Alta' },
{ value: 'urgente', label: 'Urgente' }
];
// Pestañas del formulario de solicitud de servicio (documento maestro de cotización)
export const SR_FORM_TABS: Option[] = [
{ value: 'datos', label: 'Datos' },
{ value: 'ruta', label: 'Servicio y ruta' },
{ value: 'mercancia', label: 'Mercancía' },
{ value: 'dimensiones', label: 'Dimensiones' },
{ value: 'servicios', label: 'Servicios' },
{ value: 'notas', label: 'Notas' }
];
export const SR_STATUS: Option[] = [
{ value: 'nueva', label: 'Nueva' },
{ value: 'contacto', label: 'Contacto realizado' },
{ value: 'en_analisis', label: 'En análisis' },
{ value: 'cotizada', label: 'Cotizada' },
{ value: 'aceptada', label: 'Aceptada' },
{ value: 'rechazada', label: 'Rechazada' },
{ value: 'liberada', label: 'Liberada a operaciones' }
];
export const QUOTE_STATUS: Option[] = [
{ value: 'borrador', label: 'Borrador' },
{ value: 'enviada', label: 'Enviada' },
{ value: 'aceptada', label: 'Aceptada' },
{ value: 'rechazada', label: 'Rechazada' }
];
export const QUOTE_CONCEPTS: Option[] = [
{ value: 'flete_internacional', label: 'Flete internacional' },
{ value: 'transporte_terrestre', label: 'Transporte terrestre' },
{ value: 'despacho_aduanal', label: 'Despacho aduanal' },
{ value: 'gastos_destino', label: 'Gastos en destino' },
{ value: 'otros', label: 'Otros cargos' }
];
export const RATE_STATUS: Option[] = [
{ value: 'solicitada', label: 'Solicitada' },
{ value: 'recibida', label: 'Recibida' },
{ value: 'declinada', label: 'Declinada' }
];
// ----- Operaciones: Embarques -----
export const SHIPMENT_STATUS: Option[] = [
{ value: 'abierta', label: 'Abierta' },
{ value: 'booking', label: 'Booking' },
{ value: 'en_transito', label: 'En tránsito' },
{ value: 'arribado', label: 'Arribado' },
{ value: 'entregada', label: 'Entregada' },
{ value: 'cerrada', label: 'Cerrada' },
{ value: 'cancelada', label: 'Cancelada' }
];
export const DOC_KINDS: Option[] = [
{ value: 'master', label: 'Master' },
{ value: 'house', label: 'House' },
{ value: 'otro', label: 'Otro' }
];
// ----- Bitácora del embarque: hitos y decisiones -----
export const EVENT_STATUS: Option[] = [
{ value: 'pendiente', label: 'Pendiente' },
{ value: 'completado', label: 'Completado' },
{ value: 'omitido', label: 'Omitido' },
{ value: 'rechazado', label: 'Rechazado' },
{ value: 'en_correccion', label: 'En corrección' }
];
export const EVENT_OUTCOME: Option[] = [
{ value: 'autorizado', label: 'Autorizado' },
{ value: 'rechazado', label: 'Rechazado' }
];
// ----- Facturación -----
export const INVOICE_STATUS: Option[] = [
{ value: 'borrador', label: 'Borrador' },
{ value: 'emitida', label: 'Emitida' },
{ value: 'enviada', label: 'Enviada' },
{ value: 'en_revision_cliente', label: 'En revisión del cliente' },
{ value: 'pagada', label: 'Pagada' },
{ value: 'cancelada', label: 'Cancelada' }
];
export const PAYMENT_METHODS: Option[] = [
{ value: 'transferencia', label: 'Transferencia' },
{ value: 'efectivo', label: 'Efectivo' },
{ value: 'cheque', label: 'Cheque' },
{ value: 'tarjeta', label: 'Tarjeta' },
{ value: 'otro', label: 'Otro' }
];
export const SHIPMENT_DOC_TYPES: Option[] = [
{ value: 'MBL', label: 'MBL (Master Bill of Lading)' },
{ value: 'HBL', label: 'HBL (House Bill of Lading)' },
{ value: 'MAWB', label: 'MAWB (Master Air Waybill)' },
{ value: 'HAWB', label: 'HAWB (House Air Waybill)' },
{ value: 'CMR', label: 'CMR (Carta Porte Internacional)' },
{ value: 'factura_comercial', label: 'Factura Comercial' },
{ value: 'packing_list', label: 'Packing List' },
{ value: 'carta_encomienda', label: 'Carta Encomienda' },
{ value: 'carta_garantia', label: 'Carta Garantía' },
{ value: 'certificado_permiso', label: 'Certificado / Permiso' },
{ value: 'otro', label: 'Otro' }
];