// ============ PRISMA · PANEL CLIENTE — core ============ const { useState, useEffect, useRef, useMemo } = React; // ---- helpers ---- function hexA(hex, a) { const n = parseInt(hex.slice(1), 16); const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255; return `rgba(${r},${g},${b},${a})`; } function useMeasure() { const ref = useRef(null); const [w, setW] = useState(0); useEffect(() => { if (!ref.current) return; const ro = new ResizeObserver((e) => { const cw = e[0].contentRect.width; if (cw > 0) setW(cw); }); ro.observe(ref.current); return () => ro.disconnect(); }, []); return [ref, w]; } // ---- accent palette ---- const CL_ACCENT = { magenta: '#E8175D', azul: '#3B9EE0', indigo: '#5059A8', teal: '#0D9E8A', ok: '#22C55E', warn: '#F59E0B', bad: '#EF4444', }; const acHex = (k) => CL_ACCENT[k] || '#5059A8'; // ---- status: DB values (available/reserved/sold) <-> display (disponible/reservado/vendido) ---- const DB_TO_DISPLAY = { available: 'disponible', reserved: 'reservado', sold: 'vendido' }; const DISPLAY_TO_DB = { disponible: 'available', reservado: 'reserved', vendido: 'sold' }; const CL_STATUS = { disponible: { label: 'Disponible', color: '#22C55E' }, reservado: { label: 'Reservado', color: '#F59E0B' }, vendido: { label: 'Vendido', color: '#EF4444' }, }; const CL_STATUS_CYCLE = ['disponible', 'reservado', 'vendido']; function clCountStatuses(rows) { const c = { disponible: 0, reservado: 0, vendido: 0 }; rows.forEach(r => r.floors.forEach(f => { c[f.status]++; })); return c; } // ---- periodos para métricas ---- const CL_PERIODS = [ { id: 'hoy', label: 'Hoy', days: 0 }, { id: '3d', label: '3 días', days: 3 }, { id: '7d', label: '7 días', days: 7 }, { id: '1m', label: '1 mes', days: 30 }, { id: '3m', label: '3 meses', days: 90 }, { id: '6m', label: '6 meses', days: 180 }, ]; function clRangeStart(periodId) { const p = CL_PERIODS.find(x => x.id === periodId); if (!p || p.days === 0) { const d = new Date(); d.setHours(0, 0, 0, 0); return d.toISOString(); } return new Date(Date.now() - p.days * 86400000).toISOString(); } // evento tipo → label legible const CL_TIPO_LABEL = { vista_seccion: 'Vista de sección', click_unidad: 'Vista de unidad', click_whatsapp: 'Click WhatsApp', click_pdf: 'Descarga de PDF', click_360: 'Recorrido 360°', click_piso: 'Vista de piso', click_qr: 'Escaneo QR', }; const CL_TIPO_KIND = { vista_seccion: 'view', click_unidad: 'view', click_whatsapp: 'whatsapp', click_pdf: 'pdf', click_360: 'tour', click_piso: 'view', click_qr: 'pdf', }; Object.assign(window, { hexA, useMeasure, acHex, CL_ACCENT, CL_STATUS, CL_STATUS_CYCLE, DB_TO_DISPLAY, DISPLAY_TO_DB, clCountStatuses, CL_PERIODS, clRangeStart, CL_TIPO_LABEL, CL_TIPO_KIND, }); // ============ icons ============ const Ic = ({ d, size = 18, sw = 1.6, fill = 'none', children, ...p }) => ( {d ? : children} ); const CiDashboard = (p) => ; const CiAvail = (p) => ; const CiChart = (p) => ; const CiLogout = (p) => ; const CiChevD = (p) => ; const CiCheck = (p) => ; const CiEye = (p) => ; const CiHome = (p) => ; const CiChat = (p) => ; const CiTour = (p) => ; const CiPdf = (p) => ; const CiLock = (p) => ; const CiMail = (p) => ; const CiInfo = (p) => ; const CiWarn = (p) => ; const CiBuilding = (p) => ; const CiSpinner = ({ size = 20 }) => (
); Object.assign(window, { CiDashboard, CiAvail, CiChart, CiLogout, CiChevD, CiCheck, CiEye, CiHome, CiChat, CiTour, CiPdf, CiLock, CiMail, CiInfo, CiWarn, CiBuilding, CiSpinner, }); // ============ shared UI components ============ const CiCard = ({ children, style, pad = 18, ...p }) => (
{children}
); const CiStatCard = ({ label, value, accent, sub, big }) => { const c = acHex(accent); return (
{label}
14 ? 12 : String(value).length > 10 ? 14 : String(value).length > 6 ? 18 : 26, letterSpacing: '-0.01em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', }}>{value}
{sub &&
{sub}
}
); }; const CiKicker = ({ children, style }) => (
{children}
); const CiSummaryPill = ({ accentKey, children }) => { const c = acHex(accentKey); return ( {children} ); }; // ---- SeriesBars ---- const CiSeriesBars = ({ data, color, height = 232, valueLabel = 'visitas', everyLabel = 1 }) => { const [ref, w] = useMeasure(); const [hover, setHover] = useState(null); const padL = 30, padR = 10, padT = 14, padB = 26; const plotW = Math.max(10, w - padL - padR); const plotH = height - padT - padB; const max = Math.max(1, ...data.map(d => d.value)); const niceMax = Math.max(4, Math.ceil(max / 4) * 4); const steps = 4; const catW = plotW / data.length; const barW = Math.min(38, catW * (data.length > 16 ? 0.62 : 0.52)); const y = (v) => padT + plotH - (v / niceMax) * plotH; return (
{w === 0 ?
: null} {w > 0 && setHover(null)}> {Array.from({ length: steps + 1 }).map((_, i) => { const v = (niceMax / steps) * i; return ( {Math.round(v)} ); })} {data.map((d, ci) => { const bx = padL + ci * catW + (catW - barW) / 2; const by = y(d.value); const bh = padT + plotH - by; const isHover = hover && hover.ci === ci; return ( setHover({ ci, x: bx + barW / 2, y: by, value: d.value, name: d.label })} /> {ci % everyLabel === 0 && {d.label}} ); })} } {w > 0 && hover && }
); }; const CiChartTip = ({ x, y, title, value, cw }) => { const left = Math.min(Math.max(x, 56), cw - 56); return (
{title}
{value}
); }; Object.assign(window, { CiCard, CiStatCard, CiKicker, CiSummaryPill, CiSeriesBars, CiChartTip });