// feedback-vendedora.jsx β FAB + modal de feedback para beta-testers // Monta un botΓ³n flotante en TODAS las pantallas de la vendedora. // Submit: POST directo a Supabase tabla beta_feedback. (function () { const SUPABASE_TABLE = 'beta_feedback'; const MAX_CHARS = 1000; const TIPOS = [ { value: 'bug', label: 'π Bug', placeholder: 'DescribΓ quΓ© pasΓ³ y en quΓ© pantalla ocurriΓ³...' }, { value: 'sugerencia', label: 'π‘ Sugerencia', placeholder: 'Contame quΓ© mejorarΓas o quΓ© te gustarΓa tener...' }, { value: 'duda', label: 'β Duda', placeholder: 'ΒΏQuΓ© no te quedΓ³ claro o no sabΓ©s cΓ³mo hacer?' }, { value: 'elogio', label: 'β€οΈ Elogio', placeholder: 'ΒΏQuΓ© te gustΓ³? Tu feedback positivo nos ayuda a saber quΓ© mantener.' }, ]; const SEVERIDADES = [ { value: 'baja', label: 'Baja' }, { value: 'media', label: 'Media' }, { value: 'alta', label: 'Alta' }, { value: 'critica', label: 'CrΓtica' }, ]; // ββ helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ function getVendedoraAuth() { // 1. Preferimos el objeto completo cacheado por App try { const raw = localStorage.getItem('inova-vend'); if (raw) { const v = JSON.parse(raw); if (v && v.id) return v; } } catch {} return null; } // ββ FeedbackModal βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ function FeedbackModal({ screen, vendedora, onClose }) { const T = window.T; const [tipo, setTipo] = React.useState('bug'); const [severidad, setSeveridad] = React.useState('media'); const [mensaje, setMensaje] = React.useState(''); const [estado, setEstado] = React.useState('idle'); // idle | sending | ok | err | err404 const overlayRef = React.useRef(null); // Focus trap: enfocar el primer input al abrir const firstFocusRef = React.useRef(null); React.useEffect(() => { if (firstFocusRef.current) firstFocusRef.current.focus(); }, []); // Cerrar con ESC React.useEffect(() => { const handler = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, [onClose]); // Bloquear scroll del body mientras estΓ‘ abierto React.useEffect(() => { const prev = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = prev; }; }, []); const currentPlaceholder = TIPOS.find(t => t.value === tipo)?.placeholder || ''; const charsLeft = MAX_CHARS - mensaje.length; const canSubmit = mensaje.trim().length > 0 && estado === 'idle'; const handleSubmit = async () => { if (!canSubmit) return; setEstado('sending'); const vend = vendedora || getVendedoraAuth(); const payload = { vendedora_id: vend?.id || null, vendedora_nombre: vend?.nombre || null, vendedora_cedula: vend?.cedula || null, tipo, severidad: tipo === 'bug' ? severidad : 'baja', mensaje: mensaje.trim().slice(0, MAX_CHARS), pantalla: screen || 'desconocida', url: window.location.pathname + (window.location.hash || ''), user_agent: navigator.userAgent, }; try { const sb = await window.getSupabase(); const { error, status } = await sb .from(SUPABASE_TABLE) .insert([payload]); if (error) { if (status === 404 || (error.code && error.code === '42P01')) { setEstado('err404'); } else { setEstado('err'); } } else { setEstado('ok'); // Cierra el modal a los 2.5 s para que lean el toast setTimeout(() => onClose(), 2500); } } catch (e) { // 404 puede venir tambiΓ©n como network error con status 0 cuando la tabla no existe if (e?.status === 404 || String(e).includes('does not exist') || String(e?.message).includes('does not exist')) { setEstado('err404'); } else { setEstado('err'); } } }; // ββ Overlay click fuera β cerrar βββββββββββββββββββββββββββββββββββββββ const handleOverlayClick = (e) => { if (e.target === overlayRef.current) onClose(); }; return (