/* ═══════════════════════════════════════════════ SALA VIEW — Product Registration (Mobile-first) ═══════════════════════════════════════════════ */ const SalaView = ({ user }) => { const [searchTerm, setSearchTerm] = React.useState(''); const [cantidad, setCantidad] = React.useState(''); const [fechaVenc, setFechaVenc] = React.useState(''); const [nota, setNota] = React.useState(''); const [tipo, setTipo] = React.useState('vencido'); const [estadoDanado, setEstadoDanado] = React.useState(''); const [filtroEstado, setFiltroEstado] = React.useState('todos'); const [showForm, setShowForm] = React.useState(false); const [editingId, setEditingId] = React.useState(null); const [searchResults, setSearchResults] = React.useState([]); const [selectedProduct, setSelectedProduct] = React.useState(null); const [saving, setSaving] = React.useState(false); const [searchLoading, setSearchLoading] = React.useState(false); const searchTimerRef = React.useRef(null); const sucId = user.sucursal || 'SM'; const sucUbicacion = window.MOCK.getPreferredUbicacionForSala ? window.MOCK.getPreferredUbicacionForSala(sucId) : sucId; const resolveUbicacionCode = (value) => { if (window.MOCK.resolveSalaDisplayCode) return window.MOCK.resolveSalaDisplayCode(value); return String(value || '').trim().toUpperCase() || sucUbicacion; }; const draftKey = React.useMemo(() => `vencidos_sala_draft_${user.id || 'anon'}_${sucId}`, [user.id, sucId]); const sortByArrival = React.useCallback((items) => { return [...items].sort((a, b) => (b.id || 0) - (a.id || 0)); }, []); const isMySucursalProduct = React.useCallback((product) => { if (window.MOCK.productMatchesSucursal) { return window.MOCK.productMatchesSucursal(product, sucId); } return (product && product.sucursal) === sucId; }, [sucId]); const [localProducts, setLocalProducts] = React.useState( sortByArrival(window.MOCK.MOCK_PRODUCTS.filter(isMySucursalProduct)) ); React.useEffect(() => { const handler = () => { setLocalProducts(sortByArrival(window.MOCK.MOCK_PRODUCTS.filter(isMySucursalProduct))); }; window.addEventListener('vencidos_data_changed', handler); return () => window.removeEventListener('vencidos_data_changed', handler); }, [isMySucursalProduct, sortByArrival]); React.useEffect(() => { try { const raw = localStorage.getItem(draftKey); if (!raw) return; const draft = JSON.parse(raw); if (!draft || typeof draft !== 'object') return; setSearchTerm(draft.searchTerm || ''); setCantidad(draft.cantidad || ''); setFechaVenc(draft.fechaVenc || ''); setNota(draft.nota || ''); setTipo(draft.tipo || 'vencido'); setEstadoDanado(draft.estadoDanado || ''); setShowForm(Boolean(draft.showForm)); if (draft.selectedProduct && typeof draft.selectedProduct === 'object') { setSelectedProduct(draft.selectedProduct); } } catch {} }, [draftKey]); React.useEffect(() => { try { const draft = { searchTerm, cantidad, fechaVenc, nota, tipo, estadoDanado, showForm, selectedProduct, }; localStorage.setItem(draftKey, JSON.stringify(draft)); } catch {} }, [draftKey, searchTerm, cantidad, fechaVenc, nota, tipo, estadoDanado, showForm, selectedProduct]); const filteredProducts = filtroEstado === 'todos' ? localProducts : localProducts.filter(p => p.estado === filtroEstado); const counts = React.useMemo(() => { const c = {}; window.MOCK.ESTADOS.forEach(e => { c[e.id] = 0; }); localProducts.forEach(p => { if (c[p.estado] !== undefined) c[p.estado]++; }); return c; }, [localProducts]); const handleSearch = (term) => { setSearchTerm(term); setSelectedProduct(null); if (searchTimerRef.current) clearTimeout(searchTimerRef.current); if (term.length < 2) { setSearchResults([]); return; } setSearchLoading(true); searchTimerRef.current = setTimeout(async () => { try { const res = await fetch(`${window.MOCK.getApiBase()}/catalog/search?q=${encodeURIComponent(term)}&sucursal=${sucUbicacion}&limit=8`); const data = await res.json(); setSearchResults(data); } catch { setSearchResults([]); } setSearchLoading(false); }, 300); }; const selectProduct = (prod) => { setSelectedProduct(prod); setSearchTerm(prod.nombre); setSearchResults([]); }; const handleAdd = async () => { const editingProduct = editingId ? window.MOCK.MOCK_PRODUCTS.find(p => p.id === editingId) : null; if (!selectedProduct && !editingProduct) return; if (!cantidad || (tipo === 'vencido' && !fechaVenc)) return; if (tipo === 'dañado' && !estadoDanado) return; setSaving(true); try { const now = new Date(); const baseProduct = selectedProduct || { nombre: editingProduct.nombre, barcode: editingProduct.codigo || '', proveedor: editingProduct.proveedor || '', categoria: editingProduct.cat1 || '', abc: editingProduct.abc || '', abcSucursal: editingProduct.abcSucursal || '', stock: editingProduct.stock || 0, sales30d: editingProduct.sales || 0, coverageDays: editingProduct.coverageDays || 0, cobertura: editingProduct.cobertura || '', }; const cantidadNum = parseInt(cantidad, 10); const stockDisponible = Number(baseProduct.stock || 0); if (!Number.isFinite(cantidadNum) || cantidadNum <= 0) { alert('La cantidad debe ser mayor a 0'); return; } if (cantidadNum > stockDisponible) { alert(`No puedes registrar más de ${stockDisponible} u (stock disponible).`); return; } const dias = tipo === 'vencido' ? window.MOCK.calcDiasRestantes(fechaVenc) : null; const estadoVenc = window.MOCK.calcEstadoVencimiento(dias); const provNombre = baseProduct.proveedor || window.MOCK.PROVEEDORES[0].name; const provCambio = window.MOCK.PROVEEDORES.find(p => p.name === provNombre)?.cambio ?? false; const accion = tipo === 'vencido' ? window.MOCK.calcAccionVencido(provNombre, provCambio, estadoVenc, !fechaVenc) : window.MOCK.calcAccionDanado(estadoDanado, provCambio, ''); const payload = { codigo: baseProduct.barcode || '', nombre: baseProduct.nombre, proveedor: provNombre, proveedorCambio: provCambio, sucursal: sucUbicacion, sucursalName: sucUbicacion, tipo, cantidad: cantidadNum, fechaVencimiento: tipo === 'vencido' ? fechaVenc : '', estadoDanado: tipo === 'dañado' ? estadoDanado : '', estado: tipo === 'vencido' ? window.MOCK.calcEstadoSOS(fechaVenc, provCambio) : (editingProduct?.estado || 'proximo_vencer'), accion, reportadoPor: user.name, reportadoPorId: user.id, fechaRegistro: editingProduct?.fechaRegistro || now.toISOString().split('T')[0], nota, ...(window.MOCK.parseCategoryPath ? window.MOCK.parseCategoryPath(baseProduct.categoria) : {}), abc: baseProduct.abc || '', abcSucursal: baseProduct.abcSucursal || '', stock: baseProduct.stock || 0, sales: baseProduct.sales30d || 0, coverageDays: baseProduct.coverageDays || 0, cobertura: baseProduct.cobertura || '', }; if (editingProduct) { const updated = await window.MOCK.updateProduct(editingId, payload, user); setLocalProducts(prev => sortByArrival(prev.map(p => p.id === editingId ? updated : p))); window.MOCK.addAuditEntry('Editó producto', `${updated.nombre} en ${resolveUbicacionCode(updated.sucursalName || updated.sucursal)}`, user.name, user.role, user.avatar); } else { const created = await window.MOCK.addProduct(payload); if (isMySucursalProduct(created)) { setLocalProducts(prev => sortByArrival([created, ...prev])); } window.MOCK.addAuditEntry('Registró producto', `${created.nombre} en ${resolveUbicacionCode(created.sucursalName || created.sucursal)}`, user.name, user.role, user.avatar); } resetForm(); } catch (e) { alert((editingId ? 'Error al editar: ' : 'Error al registrar: ') + e.message); } finally { setSaving(false); } }; const resetForm = () => { setSearchTerm(''); setCantidad(''); setFechaVenc(''); setNota(''); setSelectedProduct(null); setSearchResults([]); setTipo('vencido'); setEstadoDanado(''); setShowForm(false); setEditingId(null); try { localStorage.removeItem(draftKey); } catch {} }; const handleEdit = (prod) => { setEditingId(prod.id); setShowForm(true); setTipo(prod.tipo || 'vencido'); setCantidad(String(prod.cantidad || 1)); setFechaVenc(prod.fechaVencimiento || ''); setEstadoDanado(prod.estadoDanado || ''); setNota(prod.nota || ''); setSearchTerm(prod.nombre || ''); setSearchResults([]); setSelectedProduct({ id: prod.id, nombre: prod.nombre, barcode: prod.codigo || '', proveedor: prod.proveedor || '', categoria: prod.cat1 || '', abc: prod.abc || '', abcSucursal: prod.abcSucursal || '', stock: prod.stock || 0, sales30d: prod.sales || 0, coverageDays: prod.coverageDays || 0, cobertura: prod.cobertura || '', }); window.scrollTo({ top: 0, behavior: 'smooth' }); }; const getDaysColor = (dias) => { if (dias < 0) return 'var(--status-danger)'; if (dias < 30) return 'var(--status-danger)'; if (dias < 90) return 'var(--status-warning)'; return 'var(--status-success)'; }; const s = { container: { padding: '24px', maxWidth: 800, margin: '0 auto', paddingBottom: 100 }, statsRow: { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 12, marginBottom: 24, }, stat: (color, bgColor) => ({ padding: '16px', borderRadius: 'var(--radius-lg)', background: bgColor, border: '1px solid var(--border-subtle)', }), statNum: (color) => ({ fontSize: 28, fontWeight: 800, color, letterSpacing: '-0.03em', lineHeight: 1, }), statLabel: { fontSize: 12, color: 'var(--text-tertiary)', marginTop: 4, fontWeight: 500 }, addBtn: { width: '100%', padding: '14px', borderRadius: 'var(--radius-md)', fontSize: 15, fontWeight: 700, marginBottom: 20, }, form: { background: 'var(--card-bg)', border: '1px solid var(--border-color)', borderRadius: 'var(--radius-lg)', padding: 20, marginBottom: 20, animation: 'slideUp 0.3s cubic-bezier(0.16,1,0.3,1) both', }, formTitle: { fontSize: 16, fontWeight: 700, marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center', }, fieldRow: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 12 }, field: { marginBottom: 12 }, fieldLabel: { display: 'block', fontSize: 12, fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 6, }, searchDropdown: { position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 50, background: 'var(--bg-secondary)', border: '1px solid var(--border-color)', borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-lg)', overflow: 'hidden', marginTop: 4, }, searchItem: { padding: '10px 14px', cursor: 'pointer', fontSize: 13, borderBottom: '1px solid var(--border-subtle)', transition: 'background var(--transition-fast)', }, searchCode: { fontSize: 11, color: 'var(--text-tertiary)', marginTop: 2 }, stockWarn: { marginTop: 6, fontSize: 11, color: 'var(--status-danger)', fontWeight: 600, }, tipoTabs: { display: 'flex', gap: 4, padding: 3, borderRadius: 'var(--radius-md)', background: 'var(--bg-tertiary)', marginBottom: 12, }, tipoTab: (active) => ({ flex: 1, padding: '8px', textAlign: 'center', borderRadius: 'var(--radius-sm)', fontSize: 13, fontWeight: 600, cursor: 'pointer', border: 'none', background: active ? 'var(--bg-secondary)' : 'transparent', color: active ? 'var(--text-primary)' : 'var(--text-tertiary)', boxShadow: active ? 'var(--shadow-xs)' : 'none', fontFamily: 'var(--font-body)', transition: 'all var(--transition-fast)', }), filterRow: { display: 'flex', gap: 6, marginBottom: 16, flexWrap: 'wrap', }, filterChip: (active) => ({ padding: '6px 14px', borderRadius: 'var(--radius-full)', fontSize: 12, fontWeight: 600, cursor: 'pointer', border: 'none', background: active ? 'var(--accent)' : 'var(--bg-tertiary)', color: active ? 'white' : 'var(--text-secondary)', fontFamily: 'var(--font-body)', transition: 'all var(--transition-fast)', }), listHeader: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, fontSize: 13, color: 'var(--text-tertiary)', fontWeight: 600, }, productCard: { background: 'var(--card-bg)', border: '1px solid var(--border-color)', borderRadius: 'var(--radius-md)', padding: '14px 16px', marginBottom: 8, transition: 'all var(--transition-fast)', animation: 'slideUp 0.35s cubic-bezier(0.16,1,0.3,1) both', }, prodTop: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12, }, prodName: { fontSize: 14, fontWeight: 700, lineHeight: 1.3, flex: 1 }, prodMeta: { fontSize: 12, color: 'var(--text-tertiary)', marginTop: 4, display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap', }, prodActions: { display: 'flex', gap: 8, marginTop: 10, justifyContent: 'flex-end', }, daysBadge: (color) => ({ padding: '2px 8px', borderRadius: 'var(--radius-full)', fontSize: 11, fontWeight: 700, color: 'white', background: color, whiteSpace: 'nowrap', }), qtyBadge: { padding: '2px 8px', borderRadius: 'var(--radius-full)', fontSize: 11, fontWeight: 600, background: 'var(--bg-tertiary)', color: 'var(--text-secondary)', }, }; return (
Consulta la bitácora de tus acciones y registros en esta sucursal