/home/crealab/cntxt.brainware.com.co/cotizador-campestre-v5.html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Configurador Vivienda Campestre - CNTXT®</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<style>
/* Tipografía Premium */
@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700&display=swap');
body { margin: 0; background-color: #000; color: #fff; font-family: 'Manrope', 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.6s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
/* Scrollbar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: #050505; }
::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #D4AF37; }
/* Checkbox */
.custom-checkbox:checked + div { border-color: #D4AF37; background-color: #0a0a0a; }
.custom-checkbox:checked + div .check-icon { opacity: 1; transform: scale(1); }
.custom-checkbox:checked + div h3 { color: #fff; }
.custom-checkbox:checked + div .price-tag { color: #D4AF37; }
/* ESTILOS DEL DOCUMENTO (COTIZACIÓN - OCULTO) */
.quotation-sheet {
background-color: #ffffff;
color: #000000;
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 50px;
display: none;
}
</style>
</head>
<body>
<div id="root"><div style="display:flex;height:100vh;align-items:center;justify-content:center;color:#333;">Cargando experiencia CNTXT®...</div></div>
<script type="text/babel">
const { useState, useEffect, useRef } = React;
// --- LUCIDE REACT POLYFILL (Fix for Black Screen) ---
const lucideReact = Object.keys(lucide.icons).reduce((acc, name) => {
acc[name] = ({ color = 'currentColor', size = 24, strokeWidth = 2, children, ...props }) => {
return React.createElement('svg', {
xmlns: "http://www.w3.org/2000/svg",
width: size,
height: size,
viewBox: "0 0 24 24",
fill: "none",
stroke: color,
strokeWidth: strokeWidth,
strokeLinecap: "round",
strokeLinejoin: "round",
...props
}, [...lucide.icons[name].map(([tag, attrs], i) =>
React.createElement(tag, { ...attrs, key: i })
), children]);
};
return acc;
}, {});
const { Check, ChevronRight, ArrowLeft, Plus, Minus, Download, Loader2, Copy } = lucideReact;
// --- URL DE WEBHOOK (N8n) ---
const WEBHOOK_URL = "https://n8nflujos-n8n.0fdovo.easypanel.host/webhook-test/cotizacion";
// ...
// (Inside App component - we need to target the handleSubmit function specifically)
// --- ICONOS CUSTOM (SVG inline para los que no están en Lucide básico o personalizados) ---
const Icons = {
Cube: (p)=><svg {...p} 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.12 6.4-6.05-4.06a2 2 0 0 0-2.17-.05L2.9 8.12a2 2 0 0 0-.9 1.7v9.58a2 2 0 0 0 1.12 1.83l12.9 4.43a2 2 0 0 0 1.37 0l5.5-2.2a2 2 0 0 0 1.1-1.83V8.12a2 2 0 0 0-.87-1.73ZM12 3v18.75"/></svg>,
Home: (p)=><svg {...p} 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>,
Video: (p)=><svg {...p} 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="m22 8-6 4 6 4V8Z"/><rect width="14" height="12" x="2" y="6" rx="2" ry="2"/></svg>,
Eye: (p)=><svg {...p} 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="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg>,
Layers: (p)=><svg {...p} 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>,
Globe: (p)=><svg {...p} 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="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>,
Zap: (p)=><svg {...p} 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>
};
// --- SERVICIOS ---
const BASE_PLAN = {
id: 'esencia_exterior',
name: 'ESENCIA EXTERIOR',
tagline: 'Visualización Arquitectónica Integral',
price: 3200000,
description: 'Paquete fundamental para entender volumetría y materialidad. Incluye modelado y visualización completa del edificio desde el exterior.',
features: [
'V0.1 Modelado arquitectónico sobre planos',
'V0.3 Modelo 3D de edificios exteriores',
'V1.2 Pack ilimitado imágenes exteriores',
'V4.1 Planimetría comercial ambientada'
]
};
const ADDON_CATEGORIES = [
{
name: "Interiorismo",
items: [
{ id: 'v0_4', code: 'V0.4', title: 'Modelo Ambientación Interior', price: 1200000, desc: 'Modelado de detalles especiales: Zócalos, cielos, muebles fijos.', icon: <Icons.Home/> },
{ id: 'v1_3', code: 'V1.3', title: 'Pack Renders Interiores', price: 800000, desc: 'Pack ilimitado de imágenes de interiores con iluminación configurada.', icon: <Icons.Home/> },
]
},
{
name: "Video & Animación",
items: [
{ id: 'v2_3', code: 'V2.3', title: 'Clip Comercial (40 seg)', price: 1200000, desc: 'Video cinematográfico versión Trailer para redes (720p).', icon: <Icons.Video/> },
{ id: 'v2_4', code: 'V2.4', title: 'Clip Emocional (1:20 min)', price: 2200000, desc: 'Video versión Trailer extendido cinematográfico (720p).', icon: <Icons.Video/> },
{ id: 'v2_1', code: 'V2.1', title: 'Video Comercial (2:30 min)', price: 4500000, desc: 'Video 2K con guion y dirección creativa completa.', icon: <Icons.Video/> },
{ id: 'v2_2', code: 'V2.2', title: 'Video Recorrido Espacial', price: 1800000, desc: 'Recorrido de entendimiento espacial en primera persona.', icon: <Icons.Video/> },
{ id: 'v2_5', code: 'V2.5', title: 'Escalado 8K (Upgrade)', price: 500000, desc: 'Escalado de videos hasta resolución 8K mediante IA.', icon: <Icons.Zap/> },
]
},
{
name: "Interactivo & Inmersivo",
items: [
{ id: 'v3_1', code: 'V3.1', title: 'Recorridos 360°', price: 950000, desc: '4 imágenes 360° alojadas en Kuula. Experiencia interactiva.', icon: <Icons.Eye/> },
{ id: 'v3_2', code: 'V3.2', title: 'Recorrido en Tiempo Real', price: 1500000, desc: 'Servicio en vivo (2 horas) para eventos o lanzamientos.', icon: <Icons.Eye/> },
{ id: 'v0_5', code: 'V0.5', title: 'Gaussian Splatting', price: 1500000, desc: 'Modelo implantado realista del entorno para plataformas.', icon: <Icons.Layers/> },
]
},
{
name: "Web & Comercial",
items: [
{ id: 'v5_1', code: 'V5.1', title: 'Web Inmobiliaria Basic', price: 2500000, desc: 'Diseño y montaje web con dominio propio.', icon: <Icons.Globe/> },
{ id: 'v5_4', code: 'V5.4', title: 'Landing Page Lanzamiento', price: 1800000, desc: 'Plataforma web para lanzamiento comercial (Wix).', icon: <Icons.Globe/> },
{ id: 'v5_3', code: 'V5.3', title: 'Web Lite (Web3D)', price: 3500000, desc: 'Plataforma web interactiva con integración 3D (Hasta 100 Und).', icon: <Icons.Globe/> },
{ id: 'v5_5', code: 'V5.5', title: 'Ficha de Lote Web', price: 1200000, desc: 'Link de interfaz con visita a lote y video drone.', icon: <Icons.Globe/> },
{ id: 'v5_2', code: 'V5.2', title: 'Brochure Digital/Print', price: 850000, desc: 'Diagramación PDF para envío virtual e impresión.', icon: <Icons.Layers/> },
]
}
];
const ALL_ADDONS = ADDON_CATEGORIES.flatMap(cat => cat.items);
// --- UTILIDADES ---
const formatCurrency = (val) => new Intl.NumberFormat('es-CO', { style: 'currency', currency: 'COP', minimumFractionDigits: 0 }).format(val);
const generateOrderId = () => `VC-${Math.floor(10000 + Math.random() * 90000)}-${new Date().getFullYear().toString().substr(-2)}`;
// --- COMPONENTES ---
const ProgressBar = ({ step, totalSteps }) => (
<div className="w-full h-1 bg-gray-900 sticky top-0 z-50">
<div className="h-full bg-[#D4AF37] transition-all duration-700 ease-out" style={{ width: `${(step / totalSteps) * 100}%` }}></div>
</div>
);
const BasePlanCard = ({ plan }) => (
<div className="bg-neutral-900 border border-neutral-800 p-6 md:p-8 flex flex-col md:flex-row gap-8 items-start animate-fade-in relative overflow-hidden">
<div className="flex-grow z-10">
<div className="flex items-center gap-3 mb-3">
<Icons.Cube className="text-[#D4AF37] w-5 h-5" />
<h4 className="text-[#D4AF37] text-xs font-bold uppercase tracking-[0.2em]">{plan.tagline}</h4>
</div>
<h2 className="text-4xl md:text-5xl font-light text-white mb-6">{plan.name}</h2>
<p className="text-gray-400 font-light max-w-xl text-lg mb-8 leading-relaxed">{plan.description}</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
{plan.features.map((f, i) => (
<div key={i} className="flex gap-3 items-center text-sm text-gray-300">
<span className="w-1.5 h-1.5 bg-[#D4AF37] rounded-full"></span>
{f}
</div>
))}
</div>
</div>
<div className="flex flex-col items-start md:items-end justify-between self-stretch md:min-w-[280px] bg-black/20 p-6 rounded-lg border border-white/5">
<div className="text-right">
<span className="text-gray-500 text-xs uppercase tracking-widest block mb-1">Inversión Base</span>
<span className="text-3xl font-light text-[#D4AF37]">{formatCurrency(plan.price)}</span>
</div>
<div className="mt-8 text-xs text-gray-500 text-right max-w-[200px]">
*Incluye todos los entregables esenciales para comercialización.
</div>
</div>
</div>
);
const AddonGroup = ({ category, selectedAddons, onToggle }) => (
<div className="animate-fade-in mb-12">
<h3 className="text-xl font-light text-white mb-6 flex items-center gap-3 border-b border-white/10 pb-4">
<span className="text-[#D4AF37]">{category.name}</span>
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{category.items.map(addon => {
const isSelected = selectedAddons.includes(addon.id);
return (
<label key={addon.id} className="cursor-pointer group relative">
<input type="checkbox" className="hidden custom-checkbox" checked={isSelected} onChange={() => onToggle(addon.id)} />
<div className={`h-full bg-neutral-900 border border-neutral-800 p-5 hover:border-neutral-600 transition-all duration-300 flex flex-col relative overflow-hidden group-hover:transform group-hover:scale-[1.01] ${isSelected ? 'border-[#D4AF37] bg-black ring-1 ring-[#D4AF37]/20' : ''}`}>
<div className="flex justify-between items-start mb-4">
<div className={`p-2 rounded bg-black border border-neutral-800 text-gray-400 group-hover:text-[#D4AF37] transition-colors ${isSelected ? 'text-[#D4AF37] border-[#D4AF37]' : ''}`}>
{addon.icon}
</div>
<div className={`check-icon w-5 h-5 bg-[#D4AF37] text-black flex items-center justify-center opacity-0 transition-all duration-300 rounded-full transform scale-50`}>
<Check size={12} strokeWidth={4} />
</div>
</div>
<div className="mb-4 flex-grow">
<div className="flex items-center gap-2 mb-1">
<span className="text-[10px] font-bold text-gray-600 bg-gray-900 px-1.5 py-0.5 rounded border border-gray-800">{addon.code}</span>
</div>
<h3 className="font-medium text-gray-200 text-sm mb-2 group-hover:text-white transition-colors">{addon.title}</h3>
<p className="text-xs text-gray-500 font-light leading-relaxed">{addon.desc}</p>
</div>
<div className="mt-auto pt-4 border-t border-white/5 flex justify-between items-center">
<span className={`price-tag text-sm font-light text-gray-400 transition-colors`}>{formatCurrency(addon.price)}</span>
<span className={`text-[10px] uppercase tracking-wider font-bold ${isSelected ? 'text-[#D4AF37]' : 'text-gray-600 group-hover:text-gray-400'}`}>
{isSelected ? 'Agregado' : 'Agregar +'}
</span>
</div>
</div>
</label>
)
})}
</div>
</div>
);
// --- MAIN APP ---
const App = () => {
const [step, setStep] = useState(1);
const [selection, setSelection] = useState({
plan: BASE_PLAN.id,
addons: [],
details: { name: '', email: '', phone: '', project: '', location: '', comments: '' }
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitSuccess, setSubmitSuccess] = useState(false);
const [generatedId, setGeneratedId] = useState('');
// Totales
const totals = (() => {
const planPrice = BASE_PLAN.price;
const addonsPrice = selection.addons.reduce((sum, id) => {
const item = ALL_ADDONS.find(a => a.id === id);
return sum + (item ? item.price : 0);
}, 0);
const subtotal = planPrice + addonsPrice;
const iva = subtotal * 0.19;
const total = subtotal + iva;
return { subtotal, iva, total };
})();
// Handlers
const toggleAddon = (id) => setSelection(prev => ({
...prev,
addons: prev.addons.includes(id) ? prev.addons.filter(x => x !== id) : [...prev.addons, id]
}));
const handleDetailsChange = (e) => setSelection(prev => ({ ...prev, details: { ...prev.details, [e.target.name]: e.target.value } }));
const nextStep = () => setStep(prev => prev + 1);
const prevStep = () => { setStep(prev => prev - 1); setSubmitSuccess(false); };
// GENERAR PDF (Cotización)
const generatePDF = () => {
const element = document.getElementById('quotation-print');
const clone = element.cloneNode(true);
// Remove class to avoid inheriting 'display: none' or other conflicting styles
clone.classList.remove('quotation-sheet');
// Apply explicit styles for PDF generation
Object.assign(clone.style, {
display: 'block',
position: 'absolute',
top: '0',
left: '0',
width: '800px',
padding: '50px',
backgroundColor: '#ffffff',
color: '#000000',
zIndex: '-9999',
fontFamily: 'Arial, sans-serif' // Ensure safe font
});
document.body.appendChild(clone);
// Debug log to verify visibility
console.log('PDF Clone Height:', clone.offsetHeight);
const opt = {
margin: 0,
filename: `Cotizacion_CNTXT_${generatedId || 'Draft'}.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'mm', format: 'letter', orientation: 'portrait' }
};
html2pdf().set(opt).from(clone).save().then(() => {
document.body.removeChild(clone);
});
};
const handleSubmit = async () => {
if (!selection.details.name || !selection.details.email) {
alert("Por favor completa Nombre y Email obligatoriamente.");
return;
}
setIsSubmitting(true);
const newId = generateOrderId();
setGeneratedId(newId);
const itemsList = [
{ tipo: 'Plan Base', nombre: BASE_PLAN.name, precio: BASE_PLAN.price, sku: 'BASE-01' },
...selection.addons.map(id => {
const item = ALL_ADDONS.find(a => a.id === id);
return { tipo: 'Adicional', nombre: item.title, precio: item.price, sku: item.code };
})
];
const payload = {
order_id: newId,
cliente: selection.details,
items: itemsList,
totales: totals,
fecha: new Date().toISOString()
};
try {
const res = await fetch(WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error(`Error ${res.status}: ${res.statusText}`);
// Simulamos delay mínimo para UX
await new Promise(r => setTimeout(r, 2000));
// Disparamos descarga de PDF automática
generatePDF();
setSubmitSuccess(true);
setStep(3); // Ir a pantalla final
} catch (e) {
console.error(e);
alert("Error al procesar la solicitud.");
} finally {
setIsSubmitting(false);
}
};
return (
<div className="min-h-screen bg-black text-white pb-20">
{/* HIDDEN PDF TEMPLATE */}
<div id="quotation-print" className="quotation-sheet">
<div style={{borderBottom:'2px solid #D4AF37', paddingBottom:'20px', marginBottom:'30px', display:'flex', justifyContent:'space-between', alignItems:'center'}}>
<h1 style={{fontSize:'24px', fontWeight:'bold', color:'#000'}}>CNTXT®</h1>
<div style={{textAlign:'right'}}>
<div style={{fontSize:'12px', color:'#666'}}>COTIZACIÓN</div>
<div style={{fontSize:'16px', fontWeight:'bold', color:'#D4AF37'}}>{generatedId || 'PRE-VIEW'}</div>
</div>
</div>
<div style={{marginBottom:'30px'}}>
<h3 style={{fontSize:'14px', fontWeight:'bold', marginBottom:'10px', color:'#000'}}>Cliente</h3>
<div style={{fontSize:'12px', color:'#333'}}>
<p><strong>Nombre:</strong> {selection.details.name}</p>
<p><strong>Email:</strong> {selection.details.email}</p>
<p><strong>Proyecto:</strong> {selection.details.project}</p>
</div>
</div>
<table style={{width:'100%', borderCollapse:'collapse', marginBottom:'30px'}}>
<thead>
<tr style={{backgroundColor:'#f5f5f5', borderBottom:'1px solid #ddd'}}>
<th style={{textAlign:'left', padding:'10px', fontSize:'12px', color:'#000'}}>ÍTEM / SERVICIO</th>
<th style={{textAlign:'right', padding:'10px', fontSize:'12px', color:'#000'}}>VALOR</th>
</tr>
</thead>
<tbody>
<tr style={{borderBottom:'1px solid #eee'}}>
<td style={{padding:'10px', fontSize:'11px'}}>
<strong>{BASE_PLAN.name}</strong><br/>
<span style={{color:'#666'}}>{BASE_PLAN.tagline}</span>
</td>
<td style={{textAlign:'right', padding:'10px', fontSize:'11px'}}>{formatCurrency(BASE_PLAN.price)}</td>
</tr>
{selection.addons.map(id => {
const item = ALL_ADDONS.find(a => a.id === id);
return (
<tr key={id} style={{borderBottom:'1px solid #eee'}}>
<td style={{padding:'10px', fontSize:'11px'}}>
<strong>{item.title}</strong><br/>
<span style={{color:'#666'}}>Adicional ({item.code})</span>
</td>
<td style={{textAlign:'right', padding:'10px', fontSize:'11px'}}>{formatCurrency(item.price)}</td>
</tr>
);
})}
</tbody>
<tfoot>
<tr>
<td style={{padding:'10px', textAlign:'right', fontSize:'11px'}}><strong>Subtotal:</strong></td>
<td style={{padding:'10px', textAlign:'right', fontSize:'11px'}}>{formatCurrency(totals.subtotal)}</td>
</tr>
<tr>
<td style={{padding:'10px', textAlign:'right', fontSize:'11px'}}><strong>IVA (19%):</strong></td>
<td style={{padding:'10px', textAlign:'right', fontSize:'11px'}}>{formatCurrency(totals.iva)}</td>
</tr>
<tr style={{backgroundColor:'#000', color:'#fff'}}>
<td style={{padding:'10px', textAlign:'right', fontSize:'12px', color:'#fff'}}><strong>TOTAL FINAL:</strong></td>
<td style={{padding:'10px', textAlign:'right', fontSize:'12px', fontWeight:'bold', color:'#D4AF37'}}>{formatCurrency(totals.total)}</td>
</tr>
</tfoot>
</table>
<div style={{fontSize:'10px', color:'#999', textAlign:'center', marginTop:'50px'}}>
CNTXT Arquitectura & Visualización | www.contexto.com.co | Medellín, Colombia
</div>
</div>
{/* HEADER */}
<div className="flex items-center justify-between px-6 py-6 bg-black border-b border-white/10 sticky top-0 z-40 backdrop-blur-md bg-black/90">
<div className="flex flex-col items-center justify-center border border-[#D4AF37] px-4 py-2 rounded-full">
<span className="text-[10px] text-[#D4AF37] tracking-[0.2em] uppercase">Configurador</span>
<h1 className="text-xl font-bold text-white tracking-widest leading-none mt-0.5">VIVIENDA</h1>
</div>
<div className="flex items-center gap-6">
<div className="text-right hidden md:block">
<div className="text-[10px] text-gray-500 uppercase tracking-widest">Presupuesto Estimado</div>
<div className="text-xl font-light text-[#D4AF37]">{formatCurrency(totals.total)}</div>
</div>
</div>
</div>
{!submitSuccess && <ProgressBar step={step} totalSteps={3} />}
<main className="max-w-6xl mx-auto px-4 py-8 md:py-12">
{/* VIEW 1: SELECCION */}
{step === 1 && (
<div className="animate-fade-in">
<div className="mb-16">
<BasePlanCard plan={BASE_PLAN} />
</div>
<div className="text-center mb-12">
<h2 className="text-3xl font-light text-white mb-2">Personaliza tu Alcance</h2>
<p className="text-gray-500 font-light max-w-2xl mx-auto">Selecciona los módulos adicionales según la etapa y necesidad de tu proyecto.</p>
</div>
{ADDON_CATEGORIES.map((cat, i) => (
<AddonGroup key={i} category={cat} selectedAddons={selection.addons} onToggle={toggleAddon} />
))}
<div className="fixed bottom-0 left-0 w-full bg-neutral-900 border-t border-white/10 p-4 z-50 flex justify-center md:hidden">
<button onClick={nextStep} className="bg-[#D4AF37] text-black font-bold uppercase tracking-widest text-xs px-8 py-4 w-full rounded-sm flex items-center justify-center gap-2">
Continuar ({formatCurrency(totals.total)}) <ChevronRight size={16} />
</button>
</div>
<div className="flex justify-center mt-12 mb-20">
<button onClick={nextStep} className="hidden md:flex bg-[#D4AF37] hover:bg-white transition-colors text-black font-bold uppercase tracking-widest text-xs px-12 py-5 rounded-sm items-center gap-3">
Continuar con Datos <ChevronRight size={16} />
</button>
</div>
</div>
)}
{/* VIEW 2: DATOS */}
{step === 2 && (
<div className="animate-fade-in max-w-4xl mx-auto">
<button onClick={prevStep} className="flex items-center gap-2 text-gray-500 hover:text-white mb-8 text-xs uppercase tracking-widest transition-colors">
<ArrowLeft size={16} /> Volver a configuración
</button>
<div className="grid grid-cols-1 lg:grid-cols-5 gap-8 lg:gap-12">
{/* Form */}
<div className="lg:col-span-3">
<h2 className="text-2xl font-light text-white mb-6">Información del Proyecto</h2>
<div className="space-y-5">
<div>
<label className="text-[10px] uppercase text-gray-500 mb-2 block tracking-widest">Nombre Completo *</label>
<input type="text" name="name" value={selection.details.name} onChange={handleDetailsChange} className="w-full bg-neutral-900 border border-neutral-800 p-4 text-white focus:border-[#D4AF37] outline-none transition-colors rounded-sm" placeholder="Tu nombre o empresa" />
</div>
<div>
<label className="text-[10px] uppercase text-gray-500 mb-2 block tracking-widest">Email Corporativo *</label>
<input type="email" name="email" value={selection.details.email} onChange={handleDetailsChange} className="w-full bg-neutral-900 border border-neutral-800 p-4 text-white focus:border-[#D4AF37] outline-none transition-colors rounded-sm" placeholder="email@empresa.com" />
</div>
<div className="grid grid-cols-2 gap-5">
<div>
<label className="text-[10px] uppercase text-gray-500 mb-2 block tracking-widest">Teléfono</label>
<input type="tel" name="phone" value={selection.details.phone} onChange={handleDetailsChange} className="w-full bg-neutral-900 border border-neutral-800 p-4 text-white focus:border-[#D4AF37] outline-none transition-colors rounded-sm" />
</div>
<div>
<label className="text-[10px] uppercase text-gray-500 mb-2 block tracking-widest">Ubicación</label>
<input type="text" name="location" value={selection.details.location} onChange={handleDetailsChange} className="w-full bg-neutral-900 border border-neutral-800 p-4 text-white focus:border-[#D4AF37] outline-none transition-colors rounded-sm" placeholder="Ciudad / País" />
</div>
</div>
<div>
<label className="text-[10px] uppercase text-gray-500 mb-2 block tracking-widest">Nombre del Proyecto</label>
<input type="text" name="project" value={selection.details.project} onChange={handleDetailsChange} className="w-full bg-neutral-900 border border-neutral-800 p-4 text-white focus:border-[#D4AF37] outline-none transition-colors rounded-sm" placeholder="Ej: Casa en el Bosque" />
</div>
<div>
<label className="text-[10px] uppercase text-gray-500 mb-2 block tracking-widest">Detalles Adicionales</label>
<textarea name="comments" value={selection.details.comments} onChange={handleDetailsChange} rows={4} className="w-full bg-neutral-900 border border-neutral-800 p-4 text-white focus:border-[#D4AF37] outline-none transition-colors rounded-sm" placeholder="Cuéntanos brevemente sobre los tiempos o requerimientos especiales..."></textarea>
</div>
</div>
</div>
{/* Resumen Sidebar */}
<div className="lg:col-span-2">
<div className="bg-neutral-900 border border-neutral-800 p-6 sticky top-24">
<h3 className="text-[#D4AF37] text-[10px] uppercase tracking-[0.2em] mb-6 border-b border-white/5 pb-4">Resumen de Inversión</h3>
<div className="space-y-4 mb-6">
<div className="flex justify-between items-start">
<div>
<span className="text-white text-sm font-bold block">{BASE_PLAN.name}</span>
<span className="text-gray-500 text-xs">Plan Base</span>
</div>
<span className="text-gray-300 text-sm">{formatCurrency(BASE_PLAN.price)}</span>
</div>
{selection.addons.length > 0 && (
<div className="border-t border-white/5 pt-4 mt-4">
<span className="text-gray-500 text-[10px] uppercase tracking-widest block mb-3">Adicionales Seleccionados</span>
<ul className="space-y-2">
{selection.addons.map(id => {
const item = ALL_ADDONS.find(a => a.id === id);
return (
<li key={id} className="flex justify-between text-xs">
<span className="text-gray-400">{item.title}</span>
<span className="text-gray-500">{formatCurrency(item.price)}</span>
</li>
)
})}
</ul>
</div>
)}
</div>
<div className="border-t border-white/10 pt-4 mt-auto">
<div className="flex justify-between items-center mb-2">
<span className="text-gray-400 text-xs">Subtotal</span>
<span className="text-gray-300 text-sm">{formatCurrency(totals.subtotal)}</span>
</div>
<div className="flex justify-between items-center mb-6">
<span className="text-gray-400 text-xs">IVA (19%)</span>
<span className="text-gray-300 text-sm">{formatCurrency(totals.iva)}</span>
</div>
<div className="flex justify-between items-center text-xl font-light text-white mb-8 pt-4 border-t border-white/10">
<span>Total</span>
<span className="text-[#D4AF37]">{formatCurrency(totals.total)}</span>
</div>
<button onClick={handleSubmit} disabled={isSubmitting} className={`w-full flex items-center justify-center gap-2 font-bold py-4 px-4 uppercase tracking-widest text-xs transition-all rounded-sm ${isSubmitting ? 'bg-neutral-800 text-gray-500 cursor-not-allowed' : 'bg-[#D4AF37] hover:bg-white text-black'}`}>
{isSubmitting ? <><Loader2 className="animate-spin" size={16} /> Procesando...</> : 'Confirmar Cotización'}
</button>
<p className="text-center text-[10px] text-gray-600 mt-4">Al confirmar, aceptas recibir la cotización formal en tu correo.</p>
</div>
</div>
</div>
</div>
</div>
)}
{/* VIEW 3: EXITO */}
{step === 3 && (
<div className="animate-fade-in flex flex-col items-center justify-center text-center py-12">
<div className="w-24 h-24 rounded-full border border-[#D4AF37] text-[#D4AF37] flex items-center justify-center mb-8 relative">
<div className="absolute inset-0 bg-[#D4AF37] opacity-10 rounded-full animate-pulse"></div>
<Check size={40} />
</div>
<h1 className="text-4xl md:text-6xl font-thin text-white mb-6">Solicitud Recibida</h1>
<p className="text-gray-400 text-lg max-w-xl mx-auto mb-8 leading-relaxed">
Gracias <strong>{selection.details.name}</strong>. Hemos generado tu cotización <span className="text-[#D4AF37]">#{generatedId}</span> exitosamente.
</p>
<div className="bg-neutral-900 border border-neutral-800 p-6 rounded-lg max-w-md w-full mb-12">
<div className="flex items-center gap-4 mb-4">
<div className="p-3 bg-black rounded text-[#D4AF37]"><Download size={20}/></div>
<div className="text-left">
<h4 className="text-white text-sm font-bold">Descarga Automática</h4>
<p className="text-xs text-gray-500">Tu PDF se está descargando...</p>
</div>
</div>
<div className="text-xs text-gray-500 border-t border-auto pt-4 text-left">
También hemos enviado una copia a <span className="text-white">{selection.details.email}</span>.
</div>
</div>
<button onClick={() => window.location.reload()} className="text-gray-500 hover:text-white uppercase tracking-widest text-xs flex items-center gap-2 transition-colors">
<Plus size={14} /> Configurar nuevo proyecto
</button>
</div>
)}
</main>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>