/home/crealab/cntxt.brainware.com.co/cotizador-contexto.html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cotizador Contexto Arquitectura</title>
<!-- React & ReactDOM (Versión de Desarrollo para ver errores si los hay, luego cambia a .production.min.js) -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<!-- Babel para traducir JSX -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { margin: 0; background-color: #000; color: #fff; font-family: sans-serif; }
.cursor-wait { cursor: wait; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in { animation: fadeIn 0.5s ease-out forwards; }
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: #1a1a1a; }
::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #D4AF37; }
/* Estilos de impresión */
@media print {
body * { visibility: hidden; }
#printable-area, #printable-area * { visibility: visible; }
#printable-area { position: absolute; left: 0; top: 0; width: 100%; background: white !important; color: black !important; padding: 0; margin: 0; }
.no-print { display: none !important; }
}
</style>
</head>
<body>
<!-- Contenedor Principal -->
<div id="root">
<div style="padding: 20px; text-align: center; color: #666;">
Cargando cotizador...
</div>
</div>
<!-- Script de Error Global -->
<script>
window.onerror = function(message, source, lineno, colno, error) {
document.getElementById('root').innerHTML = `
<div style="color: #ff4444; padding: 20px; border: 1px solid #ff4444; background: #220000; margin: 20px;">
<h3>Error de Carga</h3>
<p><strong>Mensaje:</strong> ${message}</p>
<p><strong>Línea:</strong> ${lineno}</p>
<p>Por favor verifica que tu navegador no esté bloqueando scripts (AdBlockers o configuraciones de seguridad).</p>
</div>
`;
};
</script>
<script type="text/babel">
// --- REACT SETUP ---
const { useState, useEffect, useRef } = React;
// --- ICONOS SVG NATIVOS (Para evitar dependencias externas) ---
const Icons = {
Map: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="3 6 9 12 15 6 21 12 21 18 15 12 9 18 3 12"/><line x1="9" y1="18" x2="9" y2="12"/><line x1="15" y1="12" x2="15" y2="6"/></svg>,
Building2: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"/><path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"/><path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"/><path d="M10 6h4"/><path d="M10 10h4"/><path d="M10 14h4"/><path d="M10 18h4"/></svg>,
Home: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>,
Check: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>,
ChevronRight: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"/></svg>,
ArrowLeft: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>,
Send: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>,
ShoppingCart: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="8" cy="21" r="1"/><circle cx="19" cy="21" r="1"/><path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"/></svg>,
Loader2: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>,
Mail: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>,
Printer: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect width="12" height="8" x="6" y="14"/></svg>,
Share2: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>,
Plus: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>,
MonitorPlay: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m10 7 5 3-5 3Z"/><rect width="20" height="14" x="2" y="3" rx="2"/><path d="M12 17v4"/><path d="M8 21h8"/></svg>,
Image: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>,
Layers: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/></svg>,
Box: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>,
Zap: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>,
FileText: (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
};
// --- DATA SOURCE ---
const SERVICES = {
b2b_parcelacion: {
id: 'b2b_parcelacion',
title: 'Parcelaciones y Condominios',
description: 'Estructuración y diseño de proyectos inmobiliarios horizontales.',
icon: <Icons.Map className="w-8 h-8" />,
tiers: [
{
id: 'lite',
name: 'LITE ACCESS',
tagline: 'Acceso Ligero',
price: 4500000,
description: 'Impacto visual con inversión eficiente.',
features: [
'Set de imágenes fotorrealistas (Alta Resolución)',
'Plantas comerciales renderizadas (Vista superior)',
'Adaptación a formatos (Web, RRSS, Impresión)',
'Ficha Maestra del Proyecto en la Nube',
'Organización de información técnica y comercial'
],
idealFor: 'Desarrolladores que buscan rapidez y eficiencia.'
},
{
id: 'essential',
name: 'ESSENTIAL ACCESS',
tagline: 'Acceso Esencial',
price: 8500000,
description: 'Experiencia inmersiva y confianza digital.',
features: [
'Todo lo incluido en Lite',
'Recorrido Virtual 360° Interactivo',
'Landing Page Personalizada',
'Dominio Propio + Hosting (1 año)',
'Identidad Visual y Storytelling',
'Tráiler Comercial (Video 30-60s)'
],
idealFor: 'Proyectos que requieren engagement y recorridos virtuales.',
recommended: true
},
{
id: 'premium',
name: 'PREMIUM ACCESS',
tagline: 'Acceso Premium',
price: 15000000,
description: 'La máxima expresión del realismo visual.',
features: [
'Todo lo incluido en Essential',
'Renders Hiperrealistas (Nivel Cinematográfico)',
'Video Arquitectónico de Alto Impacto',
'Plataforma Web 3D Interactiva Avanzada',
'Visualización 3D con interacción en tiempo real',
'Conexión con CRM para leads',
'Acceso privado para inversionistas'
],
idealFor: 'Ventas antes de construir y diferenciación absoluta.'
}
]
},
b2b_altura: {
id: 'b2b_altura',
title: 'Edificación en Altura',
description: 'Visualización estratégica para torres y edificios.',
icon: <Icons.Building2 className="w-8 h-8" />,
tiers: [
{
id: 'lite',
name: 'LITE ACCESS',
tagline: 'Acceso Ligero',
price: 4200000,
description: 'Imágenes impactantes para edificios.',
features: [
'Renders estratégicos con postproducción avanzada',
'Plantas comerciales renderizadas',
'Adaptación a formatos JPG/PNG',
'Integración con Experiencia Lite Web (Nube)',
'Ficha técnica centralizada'
],
idealFor: 'Lanzamientos rápidos y presupuestos optimizados.'
},
{
id: 'essential',
name: 'ESSENTIAL ACCESS',
tagline: 'Acceso Esencial',
price: 7800000,
description: 'Experiencia completa para edificios.',
features: [
'Imágenes fotorrealistas de alta gama',
'Recorridos 360° de apartamentos modelo',
'Landing Page del proyecto',
'Integración web de recorridos',
'Dominio y Hosting incluidos'
],
idealFor: 'Mostrar el estilo de vida y espacios interiores.'
}
]
},
b2c_vivienda: {
id: 'b2c_vivienda',
title: 'Vivienda Campestre',
description: 'Visualización para particulares y arquitectos.',
icon: <Icons.Home className="w-8 h-8" />,
tiers: [
{
id: 'lite',
name: 'LITE ACCESS',
tagline: 'Acceso Ligero',
price: 2500000,
description: 'Entiende y visualiza tu vivienda desde el primer momento.',
features: [
'Modelado tridimensional exterior detallado',
'Pack Ilimitado de imágenes renderizadas (Exteriores)',
'Planta comercial ambientada',
'Estudio de integración con el terreno',
'Ajuste de materiales y acabados'
],
idealFor: 'Propietarios que quieren visualizar su futura casa.',
recommended: true
}
]
}
};
const ADDONS = {
b2b_parcelacion: [
{ id: 'drone_video', name: 'Video Aéreo con Drone', price: 1800000, description: 'Integración de video real con modelo 3D.', icon: <Icons.MonitorPlay className="w-5 h-5" /> },
{ id: 'social_kit', name: 'Kit Redes Sociales (10 Posts)', price: 1200000, description: 'Clips optimizados para Instagram/Facebook.', icon: <Icons.Image className="w-5 h-5" /> },
{ id: 'brochure', name: 'Diseño de Brochure Digital', price: 950000, description: 'PDF interactivo listo para WhatsApp.', icon: <Icons.Layers className="w-5 h-5" /> }
],
b2b_altura: [
{ id: 'vr_setup', name: 'Setup Realidad Virtual (VR)', price: 3500000, description: 'Configuración para Oculus en sala de ventas.', icon: <Icons.Box className="w-5 h-5" /> },
{ id: 'interior_extra', name: 'Pack Interiorismo Extra', price: 1500000, description: 'Detallado de mobiliario en zonas comunes.', icon: <Icons.Home className="w-5 h-5" /> },
{ id: 'app_offline', name: 'App de Ventas Offline', price: 4500000, description: 'App para pantallas táctiles sin internet.', icon: <Icons.Zap className="w-5 h-5" /> }
],
b2c_vivienda: [
{ id: 'interior_consult', name: 'Asesoría de Interiorismo', price: 1200000, description: 'Moodboard y materiales interiores.', icon: <Icons.Home className="w-5 h-5" /> },
{ id: 'landscape', name: 'Diseño Paisajismo Básico', price: 900000, description: 'Propuesta de vegetación exterior.', icon: <Icons.Map className="w-5 h-5" /> },
{ id: 'print', name: 'Impresión Fine Art', price: 450000, description: 'Cuadro 100x70cm para tu hogar.', icon: <Icons.Image className="w-5 h-5" /> }
]
};
const COMMON_ADDONS = [
{ id: 'fast_track', name: 'Fast Track (Entrega Express)', price: 1500000, description: 'Prioridad en renderizado. Entrega 30% más rápido.', icon: <Icons.Zap className="w-5 h-5 text-yellow-400" /> }
];
// --- UTILS ---
const formatCurrency = (value) => new Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP', minimumFractionDigits: 0 }).format(value);
const generateOrderId = () => `ORD-${Math.floor(1000 + Math.random() * 9000)}-${new Date().getFullYear()}`;
// --- COMPONENTS ---
// Header Removed as per user request
const ProgressBar = ({ step, totalSteps }) => (
<div className="print:hidden w-full h-1 bg-gray-900 sticky top-0 z-50">
<div className="h-full bg-[#D4AF37] transition-all duration-500 ease-out" style={{ width: `${(step / totalSteps) * 100}%` }} />
</div>
);
const ServiceCard = ({ service, onClick }) => (
<button onClick={onClick} className="group relative flex flex-col items-center justify-center p-8 bg-neutral-900 border border-neutral-800 hover:border-[#D4AF37] transition-all duration-300 rounded-sm w-full text-center h-64">
<div className="mb-4 text-gray-400 group-hover:text-[#D4AF37] transition-colors">{service.icon}</div>
<h3 className="text-xl font-light text-white tracking-wide mb-2 group-hover:text-[#D4AF37]">{service.title}</h3>
<p className="text-sm text-gray-500 font-light">{service.description}</p>
<div className="absolute bottom-4 opacity-0 group-hover:opacity-100 transition-opacity text-[#D4AF37] text-xs uppercase tracking-widest flex items-center gap-2">
Seleccionar <Icons.ChevronRight size={12} />
</div>
</button>
);
const TierCard = ({ tier, onSelect, selected }) => {
const isSelected = selected === tier.id;
return (
<div className={`relative flex flex-col p-6 border transition-all duration-300 h-full ${isSelected ? 'bg-neutral-900 border-[#D4AF37] transform scale-[1.02] shadow-2xl shadow-[#D4AF37]/10' : 'bg-black border-neutral-800 hover:border-neutral-600'}`}>
{tier.recommended && <div className="absolute top-0 right-0 bg-[#D4AF37] text-black text-[10px] font-bold px-2 py-1 uppercase tracking-wider">Recomendado</div>}
<div className="mb-6">
<h4 className={`text-sm tracking-widest uppercase mb-1 ${isSelected ? 'text-[#D4AF37]' : 'text-gray-400'}`}>{tier.tagline}</h4>
<div className="flex flex-col mb-2">
<h3 className="text-2xl font-bold text-white">{tier.name}</h3>
<span className="text-xl text-[#D4AF37] font-light mt-1">{formatCurrency(tier.price)}</span>
</div>
<p className="text-xs text-gray-500 h-8">{tier.description}</p>
</div>
<div className="flex-grow space-y-3 mb-8">
{tier.features.map((feature, idx) => (
<div key={idx} className="flex items-start gap-3">
<Icons.Check className="w-4 h-4 text-[#D4AF37] mt-0.5 flex-shrink-0" />
<span className="text-sm text-gray-300 font-light">{feature}</span>
</div>
))}
</div>
<button onClick={() => onSelect(tier.id)} className={`w-full py-3 text-xs uppercase tracking-widest font-bold border transition-colors ${isSelected ? 'bg-[#D4AF37] text-black border-[#D4AF37]' : 'bg-transparent text-white border-white/20 hover:border-white'}`}>
{isSelected ? 'Seleccionado' : 'Elegir Access'}
</button>
</div>
);
};
const AddonCard = ({ addon, isSelected, onToggle }) => (
<div onClick={() => onToggle(addon.id)} className={`flex items-center justify-between p-4 border cursor-pointer transition-all duration-200 ${isSelected ? 'bg-neutral-900 border-[#D4AF37] shadow-lg shadow-[#D4AF37]/5' : 'bg-black border-neutral-800 hover:border-neutral-600'}`}>
<div className="flex items-center gap-4">
<div className={`w-5 h-5 border flex items-center justify-center transition-colors ${isSelected ? 'bg-[#D4AF37] border-[#D4AF37]' : 'border-neutral-600'}`}>
{isSelected && <Icons.Check size={14} className="text-black" />}
</div>
<div>
<div className="flex items-center gap-2">
<h4 className={`text-sm font-bold ${isSelected ? 'text-white' : 'text-gray-300'}`}>{addon.name}</h4>
<span className="text-neutral-500">{addon.icon}</span>
</div>
<p className="text-xs text-gray-500 mt-1">{addon.description}</p>
</div>
</div>
<div className="text-right"><span className="text-sm font-light text-[#D4AF37]">{formatCurrency(addon.price)}</span></div>
</div>
);
// --- MAIN APP COMPONENT ---
const App = () => {
const [step, setStep] = useState(1);
const [orderId] = useState(generateOrderId());
const summaryRef = useRef(null);
const [selection, setSelection] = useState({
category: null, tier: null, addons: [],
details: { name: '', email: '', phone: '', projectType: '', location: '' }
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitSuccess, setSubmitSuccess] = useState(false);
// Handlers
const handleCategorySelect = (catId) => { setSelection(prev => ({ ...prev, category: catId, tier: null, addons: [] })); setStep(2); };
const handleTierSelect = (tierId) => setSelection(prev => ({ ...prev, tier: tierId }));
const handleAddonToggle = (addonId) => {
setSelection(prev => {
const isSelected = prev.addons.includes(addonId);
return isSelected ? { ...prev, addons: prev.addons.filter(id => id !== addonId) } : { ...prev, addons: [...prev.addons, addonId] };
});
};
const handleDetailsChange = (e) => {
const { name, value } = e.target;
setSelection(prev => ({ ...prev, details: { ...prev.details, [name]: value } }));
};
const nextStep = () => { if (step === 2 && !selection.tier) return; setStep(prev => prev + 1); };
const prevStep = () => { setStep(prev => prev - 1); setSubmitSuccess(false); };
// Helpers
const getSelectedTierData = () => selection.category && selection.tier ? SERVICES[selection.category].tiers.find(t => t.id === selection.tier) : null;
const getAvailableAddons = () => selection.category ? [...(ADDONS[selection.category] || []), ...COMMON_ADDONS] : [];
const getSelectedAddonsData = () => getAvailableAddons().filter(addon => selection.addons.includes(addon.id));
const calculateTotal = () => {
const tier = getSelectedTierData();
const addons = getSelectedAddonsData();
const tierPrice = tier ? tier.price : 0;
const addonsPrice = addons.reduce((sum, addon) => sum + addon.price, 0);
return { subtotal: tierPrice + addonsPrice, iva: (tierPrice + addonsPrice) * 0.19, total: (tierPrice + addonsPrice) * 1.19 };
};
// Actions
const handlePrint = () => window.print();
const handleEmail = () => {
const tier = getSelectedTierData();
const totals = calculateTotal();
const subject = `Orden de Servicio: ${orderId} - ${selection.details.name}`;
const body = `Hola, este es el resumen de mi cotización en Contexto Arquitectura:\n\nOrden: ${orderId}\nServicio: ${SERVICES[selection.category].title}\nNivel: ${tier.name}\nTotal: ${formatCurrency(totals.total)}\n\nPor favor contáctenme para proceder.`;
window.location.href = `mailto:${selection.details.email || ''}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
};
const sendWhatsapp = () => {
const totals = calculateTotal();
const text = encodeURIComponent(`Hola Contexto Arquitectura, envío referencia de mi Orden: ${orderId}\n\nCliente: ${selection.details.name}\nTotal: ${formatCurrency(totals.total)}\n\nMe gustaría finalizar el proceso.`);
window.open(`https://wa.me/573000000000?text=${text}`, '_blank');
};
const handleSubmitOrder = async () => {
setIsSubmitting(true);
setTimeout(() => { setIsSubmitting(false); setSubmitSuccess(true); }, 2000);
};
return (
<div className="min-h-screen bg-black text-white font-sans selection:bg-[#D4AF37] selection:text-black">
{!submitSuccess && <ProgressBar step={step} totalSteps={4} />}
<main className="max-w-7xl mx-auto px-4 py-8 md:py-12">
{step === 1 && (
<div className="animate-fade-in">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-light mb-4">Cotizador de Proyectos</h2>
<p className="text-gray-400 font-light max-w-2xl mx-auto">Selecciona la tipología de tu proyecto.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{Object.values(SERVICES).map((service) => (
<ServiceCard key={service.id} service={service} onClick={() => handleCategorySelect(service.id)} />
))}
</div>
</div>
)}
{step === 2 && selection.category && (
<div className="animate-fade-in">
<button onClick={prevStep} className="flex items-center gap-2 text-gray-500 hover:text-white mb-8 transition-colors text-sm uppercase tracking-widest"><Icons.ArrowLeft size={16} /> Volver</button>
<div className="text-center mb-12">
<h2 className="text-3xl md:text-4xl font-light mb-2">{SERVICES[selection.category].title}</h2>
<p className="text-[#D4AF37] font-medium tracking-wide">Selecciona el nivel de experiencia</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-12">
{SERVICES[selection.category].tiers.map((tier) => (
<TierCard key={tier.id} tier={tier} selected={selection.tier} onSelect={handleTierSelect} />
))}
</div>
<div className="flex justify-center">
<button onClick={nextStep} disabled={!selection.tier} className={`flex items-center gap-3 px-8 py-4 text-sm font-bold uppercase tracking-widest transition-all ${selection.tier ? 'bg-white text-black hover:bg-[#D4AF37] cursor-pointer' : 'bg-neutral-800 text-gray-500 cursor-not-allowed'}`}>
Siguiente: Personalizar <Icons.ChevronRight size={16} />
</button>
</div>
</div>
)}
{step === 3 && selection.category && (
<div className="animate-fade-in max-w-3xl mx-auto">
<button onClick={prevStep} className="flex items-center gap-2 text-gray-500 hover:text-white mb-8 transition-colors text-sm uppercase tracking-widest"><Icons.ArrowLeft size={16} /> Volver a Planes</button>
<div className="text-center mb-12"><h2 className="text-3xl font-light mb-2">Complementa tu Experiencia</h2><p className="text-gray-400">Agrega servicios adicionales.</p></div>
<div className="space-y-4 mb-12">
{getAvailableAddons().map((addon) => (
<AddonCard key={addon.id} addon={addon} isSelected={selection.addons.includes(addon.id)} onToggle={handleAddonToggle} />
))}
</div>
<div className="bg-neutral-900 p-6 border border-neutral-800 mb-8 flex justify-between items-center">
<span className="text-gray-400 text-sm">Total Acumulado (Estimado)</span>
<span className="text-xl font-bold text-[#D4AF37]">{formatCurrency(calculateTotal().subtotal)}</span>
</div>
<div className="flex justify-center">
<button onClick={nextStep} className="flex items-center gap-3 px-8 py-4 text-sm font-bold uppercase tracking-widest bg-white text-black hover:bg-[#D4AF37] transition-all cursor-pointer">Continuar a Finalizar <Icons.ChevronRight size={16} /></button>
</div>
</div>
)}
{step === 4 && selection.category && selection.tier && (
<div className="animate-fade-in max-w-5xl mx-auto">
{!submitSuccess && <button onClick={prevStep} className="flex items-center gap-2 text-gray-500 hover:text-white mb-8 transition-colors text-sm uppercase tracking-widest"><Icons.ArrowLeft size={16} /> Volver a Personalización</button>}
{!submitSuccess ? (
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 bg-neutral-900 border border-neutral-800 p-8 md:p-12">
<div>
<h3 className="text-2xl font-light text-white mb-6">Datos de Facturación</h3>
<div className="space-y-4">
<div><label className="block text-xs uppercase tracking-widest text-gray-500 mb-2">Nombre Completo</label><input type="text" name="name" className="w-full bg-black border border-neutral-700 p-3 text-white focus:border-[#D4AF37] outline-none" onChange={handleDetailsChange} /></div>
<div className="grid grid-cols-2 gap-4">
<div><label className="block text-xs uppercase tracking-widest text-gray-500 mb-2">Email</label><input type="email" name="email" className="w-full bg-black border border-neutral-700 p-3 text-white focus:border-[#D4AF37] outline-none" onChange={handleDetailsChange} /></div>
<div><label className="block text-xs uppercase tracking-widest text-gray-500 mb-2">Teléfono</label><input type="tel" name="phone" className="w-full bg-black border border-neutral-700 p-3 text-white focus:border-[#D4AF37] outline-none" onChange={handleDetailsChange} /></div>
</div>
<div><label className="block text-xs uppercase tracking-widest text-gray-500 mb-2">Proyecto</label><input type="text" name="projectType" className="w-full bg-black border border-neutral-700 p-3 text-white focus:border-[#D4AF37] outline-none" onChange={handleDetailsChange} /></div>
<div><label className="block text-xs uppercase tracking-widest text-gray-500 mb-2">Ubicación</label><input type="text" name="location" className="w-full bg-black border border-neutral-700 p-3 text-white focus:border-[#D4AF37] outline-none" onChange={handleDetailsChange} /></div>
</div>
</div>
<div className="flex flex-col h-full">
<div className="bg-black p-6 border border-neutral-800 flex-grow">
<h3 className="text-[#D4AF37] text-xs uppercase tracking-widest mb-4 border-b border-white/10 pb-4">Resumen</h3>
<div className="space-y-4 mb-4">
<div className="flex justify-between items-start pb-4 border-b border-white/5">
<div><span className="text-gray-500 text-xs block">Servicio Base</span><span className="text-white text-sm font-bold">{SERVICES[selection.category].title}</span><span className="text-[#D4AF37] text-xs block mt-1 uppercase">{getSelectedTierData().name}</span></div>
<div className="text-right"><span className="text-white text-sm">{formatCurrency(getSelectedTierData().price)}</span></div>
</div>
{selection.addons.length > 0 && (
<div className="pb-4 border-b border-white/5"><span className="text-gray-500 text-xs block mb-2">Adicionales</span><ul className="space-y-2">{getSelectedAddonsData().map(addon => (<li key={addon.id} className="flex justify-between items-center text-xs"><span className="text-gray-300 flex items-center gap-2"><Icons.Plus size={10} className="text-[#D4AF37]" /> {addon.name}</span><span className="text-gray-400">{formatCurrency(addon.price)}</span></li>))}</ul></div>
)}
<div className="py-2"><div className="flex justify-between items-center text-sm"><span className="text-gray-300">Subtotal</span><span className="text-white">{formatCurrency(calculateTotal().subtotal)}</span></div><div className="flex justify-between items-center mt-2 text-xs text-gray-500"><span>IVA (Estimado 19%)</span><span>{formatCurrency(calculateTotal().iva)}</span></div></div>
<div className="flex justify-between items-center pt-4 border-t border-dashed border-white/20"><span className="text-lg font-bold text-white">Total</span><span className="text-2xl font-bold text-[#D4AF37]">{formatCurrency(calculateTotal().total)}</span></div>
</div>
<button onClick={handleSubmitOrder} disabled={isSubmitting || !selection.details.email} className={`w-full flex items-center justify-center gap-2 font-bold py-4 px-4 transition-all uppercase tracking-widest text-sm ${isSubmitting ? 'bg-neutral-800 text-gray-500 cursor-wait' : 'bg-[#D4AF37] hover:bg-[#c4a02f] text-black'}`}>{isSubmitting ? <><Icons.Loader2 className="animate-spin" size={18} /> Procesando...</> : <><Icons.ShoppingCart size={18} /> Finalizar Pedido</>}</button>
</div>
</div>
</div>
) : (
<div className="animate-fade-in">
<div className="no-print flex flex-col md:flex-row justify-between items-center gap-4 mb-8">
<button onClick={() => window.location.reload()} className="flex items-center gap-2 text-gray-500 hover:text-white transition-colors text-sm uppercase tracking-widest"><Icons.ArrowLeft size={16} /> Nueva Cotización</button>
<div className="flex gap-4">
<button onClick={handlePrint} className="flex items-center gap-2 bg-neutral-800 hover:bg-neutral-700 text-white px-4 py-2 rounded-sm text-sm border border-neutral-700"><Icons.Printer size={16} /> <span className="hidden md:inline">Imprimir / PDF</span></button>
<button onClick={handleEmail} className="flex items-center gap-2 bg-neutral-800 hover:bg-neutral-700 text-white px-4 py-2 rounded-sm text-sm border border-neutral-700"><Icons.Mail size={16} /> <span className="hidden md:inline">Email</span></button>
<button onClick={sendWhatsapp} className="flex items-center gap-2 bg-[#25D366] hover:bg-[#20bd5a] text-black font-bold px-4 py-2 rounded-sm text-sm"><Icons.Share2 size={16} /> <span className="hidden md:inline">WhatsApp</span></button>
</div>
</div>
<div id="printable-area" ref={summaryRef} className="bg-white text-black p-8 md:p-12 max-w-4xl mx-auto shadow-2xl relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-2 bg-[#D4AF37]"></div>
<div className="flex justify-between items-end mb-12 border-b-2 border-gray-100 pb-8">
<div><div className="flex items-center gap-2 mb-2"><div className="bg-black text-[#D4AF37] px-2 py-1 font-bold tracking-widest text-xl">CNTXT</div><span className="text-xs uppercase tracking-widest text-gray-500">Arquitectura</span></div><p className="text-gray-400 text-xs mt-1">Medellín, Colombia</p></div>
<div className="text-right"><h2 className="text-2xl font-light text-gray-900 mb-1 uppercase tracking-wide">Orden de Servicio</h2><p className="text-[#D4AF37] font-mono font-bold">{orderId}</p><p className="text-gray-400 text-xs mt-1">{new Date().toLocaleDateString()}</p></div>
</div>
<div className="grid grid-cols-2 gap-12 mb-12">
<div><h4 className="text-xs font-bold uppercase tracking-widest text-gray-400 mb-4 border-b border-gray-100 pb-2">Cliente</h4><p className="font-bold text-lg text-gray-900">{selection.details.name}</p><p className="text-gray-600 text-sm">{selection.details.email}</p><p className="text-gray-600 text-sm">{selection.details.phone}</p></div>
<div><h4 className="text-xs font-bold uppercase tracking-widest text-gray-400 mb-4 border-b border-gray-100 pb-2">Proyecto</h4><p className="font-bold text-gray-900">{selection.details.projectType}</p><p className="text-gray-600 text-sm flex items-center gap-1"><Icons.Map size={12}/> {selection.details.location}</p></div>
</div>
<div className="mb-12">
<table className="w-full text-left">
<thead><tr className="border-b-2 border-gray-100 text-xs uppercase tracking-widest text-gray-400"><th className="pb-4">Descripción</th><th className="pb-4 text-right">Precio</th></tr></thead>
<tbody className="divide-y divide-gray-50">
<tr><td className="py-4"><p className="font-bold text-gray-900">{SERVICES[selection.category].title}</p><p className="text-sm text-[#D4AF37] font-medium">{getSelectedTierData().name}</p></td><td className="py-4 text-right font-mono text-gray-700">{formatCurrency(getSelectedTierData().price)}</td></tr>
{getSelectedAddonsData().map(addon => (<tr key={addon.id}><td className="py-4 pl-4"><p className="font-bold text-gray-800 flex items-center gap-2"><Icons.Plus size={12} className="text-[#D4AF37]"/> {addon.name}</p></td><td className="py-4 text-right font-mono text-gray-700">{formatCurrency(addon.price)}</td></tr>))}
</tbody>
</table>
</div>
<div className="flex justify-end mb-12">
<div className="w-64 space-y-3">
<div className="flex justify-between text-gray-600 text-sm"><span>Subtotal</span><span className="font-mono">{formatCurrency(calculateTotal().subtotal)}</span></div>
<div className="flex justify-between text-gray-600 text-sm pb-3 border-b border-gray-100"><span>IVA (19%)</span><span className="font-mono">{formatCurrency(calculateTotal().iva)}</span></div>
<div className="flex justify-between text-gray-900 text-lg font-bold"><span>Total</span><span className="text-[#D4AF37]">{formatCurrency(calculateTotal().total)}</span></div>
</div>
</div>
<div className="text-center border-t border-gray-100 pt-8"><p className="text-xs text-gray-400 uppercase tracking-widest mb-2">Contexto Arquitectura S.A.S</p><p className="text-[10px] text-gray-300">Valores preliminares sujetos a revisión.</p></div>
</div>
</div>
)}
</div>
)}
</main>
<footer className="no-print mt-12 py-8 border-t border-neutral-900 text-center text-gray-600 text-xs uppercase tracking-widest"><p>© {new Date().getFullYear()} Contexto Arquitectura.</p></footer>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>