/* Torre Auro — "Ecosistema en construcción" Cada oficina disponible es un proyecto de licitación abierta. Cualquiera se suma desde su rol → da vida al ecosistema desde los preparativos. */ const PRJ_STORE_KEY = 'ta_proyectos_v1'; function prjLoad() { try { return JSON.parse(localStorage.getItem(PRJ_STORE_KEY)) || {}; } catch (e) { return {}; } } function prjSave(s) { try { localStorage.setItem(PRJ_STORE_KEY, JSON.stringify(s)); } catch (e) {} } function prjHash(str) { let h = 0; for (let i = 0; i < str.length; i++) h = (h * 31 + str.charCodeAt(i)) >>> 0; return h; } const PROJECT_ROLES = [ { id:'comercial', name:'Comercial / Broker', icon:'ti-briefcase', tagline:'Trae al inquilino ideal', contrib:'Promueve el espacio y conecta con la empresa que lo ocupará.', incentive:'Comisión por renta concretada', cupos:3 }, { id:'admin', name:'Administrativo', icon:'ti-file-text', tagline:'Gestión y contrato', contrib:'Coordina papeleo, contrato y enlace con el desarrollador.', incentive:'Honorarios + reputación', cupos:1 }, { id:'obra', name:'Proveedor de obra', icon:'ti-bucket-droplet', tagline:'Pintura, pisos, acondicionamiento', contrib:'Acondiciona la obra gris según el concepto del inquilino.', incentive:'Contrato de la obra', cupos:4 }, { id:'mobiliario', name:'Mobiliario y equipamiento', icon:'ti-armchair', tagline:'Amobla el espacio', contrib:'Provee mobiliario, equipo y soluciones de oficina.', incentive:'Contrato de suministro', cupos:3 }, { id:'interiores', name:'Diseño de interiores', icon:'ti-ruler-2', tagline:'Arquitectura y layout', contrib:'Diseña la distribución y la imagen del espacio.', incentive:'Contrato de diseño', cupos:2 }, { id:'branding', name:'Branding y señalización', icon:'ti-tag', tagline:'Identidad del espacio', contrib:'Señalización, rotulación y presencia de marca.', incentive:'Contrato de imagen', cupos:2 }, { id:'mudanzas', name:'Mudanzas y logística', icon:'ti-truck', tagline:'Traslado e instalación', contrib:'Logística de instalación y mudanza del inquilino.', incentive:'Contrato de servicio', cupos:2 }, { id:'inversionista',name:'Inversionista / Patrocinador',icon:'ti-coin', tagline:'Respalda el proyecto', contrib:'Aporta capital o patrocinio para acelerar el acondicionamiento.', incentive:'Retorno + reputación', cupos:2 }, { id:'marketing', name:'Marketing', icon:'ti-chart-arrows-vertical', tagline:'Campaña y contenido', contrib:'Estrategia de difusión, contenido y campañas digitales.', incentive:'Honorarios + reputación', cupos:2 }, { id:'embajador', name:'Embajador / Promotor', icon:'ti-speakerphone', tagline:'Difunde en sus redes', contrib:'Promociona el inmueble en su comunidad y redes sociales.', incentive:'Puntos + recompensa por referido', cupos:99 }, ]; const PRJ_NAMES = ['MV','RT','AL','GP','JC','DS','EN','LF','BR','OM','CV','TH','NK','PA','SR','Yed','Zam']; function ProjectModal() { const [open, setOpen] = useState(false); const [ctx, setCtx] = useState(null); // { floorId, code } const [view, setView] = useState('overview'); // overview | apply | done const [role, setRole] = useState(null); const [form, setForm] = useState({ nombre:'', contacto:'', propuesta:'' }); const [store, setStore] = useState(prjLoad()); const bodyRef = useRef(null); useEffect(() => { const onOpen = (e) => { setCtx(e.detail); setView('overview'); setRole(null); setForm({ nombre:'', contacto:'', propuesta:'' }); setStore(prjLoad()); setOpen(true); }; window.addEventListener('ta:open-project', onOpen); return () => window.removeEventListener('ta:open-project', onOpen); }, []); useEffect(() => { if (open) document.body.style.overflow = 'hidden'; else document.body.style.overflow = ''; return () => { document.body.style.overflow = ''; }; }, [open]); if (!ctx) return
; const floor = FLOOR_BY_ID[ctx.floorId]; const office = floor?.offices.find(o => o.code === ctx.code); if (!floor || !office) return ; const z = ZONES[floor.zone]; const acc = z.accHex; const ecoName = z.tag; const key = floor.id + '::' + office.code; const userApps = store[key] || []; // deterministic base data const seed = prjHash(key); const baseApplicants = (rid) => prjHash(key + rid) % 4; const appliedRoles = new Set(userApps.map(a => a.role)); const countFor = (rid) => baseApplicants(rid) + userApps.filter(a => a.role === rid).length; const totalApplicants = PROJECT_ROLES.reduce((a, r) => a + countFor(r.id), 0); const rolesWithTeam = PROJECT_ROLES.filter(r => countFor(r.id) > 0).length; // progress / stage const rented = !office.ok; let progress, stage; if (rented) { progress = 100; stage = 'Operando'; } else { const base = 22 + (seed % 26); // 22–47 baseline const boost = Math.min(28, userApps.length * 6); // user participation pushes it progress = Math.min(96, base + boost); stage = progress < 30 ? 'Publicado' : progress < 60 ? 'Licitación abierta' : progress < 85 ? 'Equipo en formación' : 'Acondicionamiento'; } const daysLeft = rented ? 0 : 9 + (seed % 25); // participants (anonymized initials) const participants = []; PROJECT_ROLES.forEach((r, i) => { const n = countFor(r.id); for (let k = 0; k < Math.min(n, 4); k++) { participants.push({ ini: PRJ_NAMES[(seed + i * 7 + k * 3) % PRJ_NAMES.length], role: r.name, rid: r.id }); } }); // supplier proposals (visible, summary only) const PROPOSALS = [ { rid:'obra', who:'Acabados del Golfo', text:'Pintura + piso porcelánico, obra gris a llave', range:'$120K – $180K', eta:'4 semanas' }, { rid:'mobiliario', who:'Oficina Integral MX', text:'Estaciones, sala de juntas y recepción', range:'$90K – $140K', eta:'3 semanas' }, { rid:'interiores', who:'Estudio Litoral', text:'Proyecto ejecutivo + dirección de obra', range:'$60K – $95K', eta:'2 semanas' }, ].filter((_, i) => ((seed >> i) & 1) || i === 0); function applyTo(r) { setRole(r); setView('apply'); } function submit(e) { e.preventDefault(); const next = { ...store, [key]: [...userApps, { role: role.id, nombre: form.nombre, ts: Date.now() }] }; prjSave(next); setStore(next); setView('done'); } return ({rented ? 'Este espacio ya está operando. Su proyecto fue construido por la comunidad del ecosistema.' : 'Este espacio es un proyecto abierto. Cualquier persona puede sumarse desde su rol para que se concrete — de forma transparente, como una licitación colectiva.'}
{role.contrib}
Estás dentro de {office.code} como {role.name}. El equipo de Torre Auro revisará tu propuesta y te contactará. Tu participación ya cuenta en el avance del proyecto.