Cotizador CargoBeta

import React, { useState, useEffect, useRef } from 'react'; import { Ship, Plane, Package, MapPin, Calculator, FileText, Shield, Truck, Warehouse, DollarSign, Download, Mail } from 'lucide-react'; import jsPDF from 'jspdf'; import html2canvas from 'html2canvas'; const App = () => { const [activeTab, setActiveTab] = useState('air'); const [formData, setFormData] = useState({ transportType: 'air', incoterm: 'FOB', originCountry: '', originCity: '', destinationCountry: 'Chile', destinationCity: '', cargoType: 'general', weight: '', volume: '', value: '', pickup: false, customsAgency: false, taxPayment: false, lastMile: false, insurance: false, warehousing: false, consolidation: false, wireTransfer: false, specialRequirements: '', expectedDate: '' }); const [quoteResult, setQuoteResult] = useState(null); const quoteRef = useRef(); // Para capturar el contenido del PDF useEffect(() => { setFormData(prev => ({ ...prev, transportType: activeTab })); }, [activeTab]); // ... (mismos datos: originCities, destinationCities, incoterms, etc.) const originCities = { 'China': ['Shanghai', 'Beijing', 'Guangzhou', 'Shenzhen', 'Tianjin', 'Qingdao', 'Ningbo', 'Xiamen'], 'Estados Unidos': ['New York', 'Los Angeles', 'Chicago', 'Houston', 'Miami', 'Seattle', 'Atlanta', 'Dallas'], 'Colombia': ['Bogotá', 'Medellín', 'Cali', 'Barranquilla', 'Cartagena'], 'Perú': ['Lima', 'Arequipa', 'Trujillo', 'Chiclayo', 'Piura'], 'Unión Europea': ['Amsterdam', 'Antwerp', 'Barcelona', 'Berlin', 'Brussels', 'Copenhagen', 'Dublin', 'Frankfurt', 'Hamburg', 'Helsinki', 'Lisbon', 'Madrid', 'Milan', 'Paris', 'Rotterdam', 'Stockholm'] }; const destinationCities = [ 'Arica', 'Iquique', 'Antofagasta', 'La Serena', 'Santiago', 'Valparaíso/Viña Del Mar', 'Rancagua', 'Curicó', 'Talca', 'Concepción', 'Chillán', 'Temuco', 'Puerto Montt', 'Coyhaique', 'Punta Arenas' ]; const incoterms = [ { value: 'EXW', label: 'EXW - En fábrica' }, { value: 'FCA', label: 'FCA - Libre transportista' }, { value: 'FOB', label: 'FOB - Libre a bordo' }, { value: 'CFR', label: 'CFR - Costo y flete' }, { value: 'CIF', label: 'CIF - Costo, seguro y flete' }, { value: 'DAP', label: 'DAP - Entregado en lugar' }, { value: 'DDP', label: 'DDP - Entregado derechos pagados' } ]; const cargoTypes = [ { value: 'general', label: 'Carga general' }, { value: 'dangerous', label: 'Carga peligrosa' }, { value: 'refrigerated', label: 'Refrigerada' }, { value: 'oversized', label: 'Sobredimensionada' }, { value: 'bulk', label: 'A granel' } ]; const originCountries = ['China', 'Estados Unidos', 'Colombia', 'Perú', 'Unión Europea']; const handleInputChange = (field, value) => { setFormData(prev => { const newFormData = { ...prev, [field]: value }; if (field === 'originCountry') { newFormData.originCity = ''; } return newFormData; }); }; const calculateQuote = () => { const weight = parseFloat(formData.weight) || 0; const volume = parseFloat(formData.volume) || 0; const value = parseFloat(formData.value) || 0; let transportCost = 0; let chargeableWeight = null; let volumeUsed = null; let modeLabel = ''; if (activeTab === 'air') { const volumetricWeight = volume * 167; chargeableWeight = Math.max(weight, volumetricWeight); transportCost = chargeableWeight * 4.5; modeLabel = 'Aéreo'; } else { volumeUsed = volume; transportCost = volume * 80; // USD/m³ modeLabel = 'Marítimo'; } const servicesCost = { pickup: formData.pickup ? 150 : 0, customsAgency: formData.customsAgency ? 200 : 0, taxPayment: formData.taxPayment ? value * 0.15 : 0, lastMile: formData.lastMile ? 250 : 0, insurance: formData.insurance ? value * 0.02 : 0, warehousing: formData.warehousing ? 50 : 0, }; const totalServices = Object.values(servicesCost).reduce((sum, cost) => sum + cost, 0); const totalCost = transportCost + totalServices; setQuoteResult({ transportCost: Math.round(transportCost * 100) / 100, servicesCost: Math.round(totalServices * 100) / 100, totalCost: Math.round(totalCost * 100) / 100, breakdown: servicesCost, chargeableWeight, volumeUsed, modeLabel }); }; const handleSubmit = (e) => { e.preventDefault(); const { originCountry, originCity, destinationCity, weight, volume, value } = formData; if (!originCountry || !originCity || !destinationCity) { alert('Por favor complete todos los campos de ubicación'); return; } if (!weight && !volume) { alert('Debe ingresar al menos el peso (kg) o el volumen (m³) de la carga'); return; } if (activeTab === 'sea' && !volume) { alert('En transporte marítimo, el volumen (m³) es obligatorio'); return; } if (!value) { alert('Por favor ingrese el valor de la mercadería (USD)'); return; } calculateQuote(); }; // 📧 Enviar por email const handleEmail = () => { if (!quoteResult) return; const subject = encodeURIComponent(`Cotización de carga - CargoSud Pro`); const bodyLines = [ `Cotización generada el ${new Date().toLocaleDateString('es-ES')}`, ``, `Origen: ${formData.originCity}, ${formData.originCountry}`, `Destino: ${formData.destinationCity}, Chile`, `Modo: ${quoteResult.modeLabel}`, `Incoterm: ${formData.incoterm}`, `Tipo de carga: ${cargoTypes.find(c => c.value === formData.cargoType)?.label || formData.cargoType}`, ``, `DETALLE DE COSTOS:`, `Transporte ${quoteResult.modeLabel}: $${quoteResult.transportCost.toLocaleString()}`, ...(quoteResult.breakdown.pickup > 0 ? [`Recogida en origen: $${quoteResult.breakdown.pickup.toLocaleString()}`] : []), ...(quoteResult.breakdown.customsAgency > 0 ? [`Agencia de aduanas: $${quoteResult.breakdown.customsAgency.toLocaleString()}`] : []), ...(quoteResult.breakdown.taxPayment > 0 ? [`Impuestos: $${quoteResult.breakdown.taxPayment.toLocaleString()}`] : []), ...(quoteResult.breakdown.lastMile > 0 ? [`Última milla: $${quoteResult.breakdown.lastMile.toLocaleString()}`] : []), ...(quoteResult.breakdown.insurance > 0 ? [`Seguro: $${quoteResult.breakdown.insurance.toLocaleString()}`] : []), ...(quoteResult.breakdown.warehousing > 0 ? [`Almacenamiento: $${quoteResult.breakdown.warehousing.toLocaleString()}`] : []), ``, `TOTAL: $${quoteResult.totalCost.toLocaleString()}`, ``, `Requisitos especiales: ${formData.specialRequirements || 'Ninguno'}`, `Fecha de embarque estimada: ${formData.expectedDate || 'No especificada'}` ]; const body = encodeURIComponent(bodyLines.join('\n')); window.location.href = `mailto:?subject=${subject}&body=${body}`; }; // 📄 Descargar PDF const handleDownloadPDF = () => { if (!quoteRef.current) return; const input = quoteRef.current; html2canvas(input, { scale: 2, useCORS: true }).then(canvas => { const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF('p', 'mm', 'a4'); const width = pdf.internal.pageSize.getWidth(); const height = (canvas.height * width) / canvas.width; pdf.addImage(imgData, 'PNG', 0, 0, width, height); pdf.save(`cotizacion-cargosud-${new Date().toISOString().split('T')[0]}.pdf`); }); }; return (
{/* Header */}
{activeTab === 'air' ? : }

CargoSud Pro

Cotización detallada de importaciones

{/* Form */}

Detalles de la Carga

{/* ... (el formulario es idéntico al anterior, se omite por brevedad) */} {/* Origen/Destino */}
{/* Incoterm y Tipo de Carga */}
{/* Detalles de carga */}
handleInputChange('weight', e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
handleInputChange('volume', e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required={activeTab === 'sea'} />
handleInputChange('value', e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" required />
{/* Servicios */}

Servicios Adicionales

{[ { key: 'pickup', label: 'Recogida en origen', icon: Truck }, { key: 'customsAgency', label: 'Agencia de aduanas', icon: FileText }, { key: 'taxPayment', label: 'Pago de impuestos', icon: DollarSign }, { key: 'lastMile', label: 'Última milla', icon: Truck }, { key: 'insurance', label: 'Seguro de carga', icon: Shield }, { key: 'warehousing', label: 'Almacenamiento', icon: Warehouse }, { key: 'consolidation', label: 'Consolidación', icon: Warehouse }, { key: 'wireTransfer', label: 'Pago a proveedor', icon: DollarSign } ].map(service => ( ))}
{/* Detalles adicionales */}