import { useState, useEffect } from 'react'; import { useCartStore } from '../store/cartStore'; import { productService } from '../services/productService'; import type { Product } from '../types/Product'; import ProductSearch from '../components/POS/ProductSearch'; import { Trash2, ShoppingCart, CreditCard, User, Box, Layers } from 'lucide-react'; import { useToast } from '../context/use-toast'; import PaymentModal, { type Payment } from '../components/PaymentModal'; import { orderService } from '../services/orderService'; import type { CreateOrderRequest } from '../types/Order'; import AdEditorModal from '../components/POS/AdEditorModal'; // Importamos el componente de búsqueda de clientes para el modal (Asumiremos que existe o usamos un simple prompt por ahora para no extender demasiado, idealmente ClientSearchModal) // import ClientSearchModal from '../components/POS/ClientSearchModal'; export default function UniversalPosPage() { const { showToast } = useToast(); const { items, addItem, removeItem, clearCart, getTotal, clientId, clientName, setClient, sellerId, setSeller } = useCartStore(); const [catalog, setCatalog] = useState([]); const [isProcessing, setIsProcessing] = useState(false); const [showPayment, setShowPayment] = useState(false); // Estados de Modales const [showAdEditor, setShowAdEditor] = useState(false); const [selectedAdProduct, setSelectedAdProduct] = useState(null); // Estado de carga para agregar combos (puede tardar un poco en traer los hijos) const [addingProduct, setAddingProduct] = useState(false); useEffect(() => { productService.getAll().then(setCatalog).catch(console.error); const userStr = localStorage.getItem('user'); if (userStr) { try { const user = JSON.parse(userStr); if (user.id) setSeller(user.id); } catch { /* ... */ } } }, [setSeller]); // Manejador de Teclado Global del POS useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // F10: Cobrar if (e.key === 'F10') { e.preventDefault(); handleCheckout(); } // F7: Cambiar Cliente (Antes F9) if (e.key === 'F7') { e.preventDefault(); handleChangeClient(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [items, clientId]); // Dependencias para que handleCheckout tenga el estado fresco const handleChangeClient = () => { // Aquí abriríamos el ClientSearchModal. // Para no bloquear, simulamos un cambio rápido o un prompt simple si no hay modal aún. // En producción: setShowClientModal(true); const id = prompt("Ingrese ID de Cliente (Simulación F7):", "1003"); if (id) { // Buscar nombre real en API... setClient(parseInt(id), "Cliente #" + id); } }; const handleProductSelect = async (product: Product) => { setAddingProduct(true); try { // 1. AVISOS CLASIFICADOS if (product.typeCode === 'CLASSIFIED_AD') { if (!clientId) setClient(1005, "Consumidor Final (Default)"); setSelectedAdProduct(product); setShowAdEditor(true); return; } // 2. COMBOS (BUNDLES) - Lógica de Visualización if (product.typeCode === 'BUNDLE') { // Traemos los componentes para mostrarlos en el ticket const components = await productService.getBundleComponents(product.id); const subItemsNames = components.map(c => `${c.quantity}x ${c.childProduct?.name || 'Item'}` ); addItem(product, 1, { subItems: subItemsNames }); showToast(`Combo agregado con ${components.length} ítems`, 'success'); return; } // 3. PRODUCTO ESTÁNDAR addItem(product, 1); showToast(`${product.name} agregado`, 'success'); } catch (error) { console.error(error); showToast("Error al agregar producto", "error"); } finally { setAddingProduct(false); } }; const handleAdConfirmed = (listingId: number, price: number, description: string) => { if (selectedAdProduct) { addItem( { ...selectedAdProduct, basePrice: price }, 1, { relatedEntity: { id: listingId, type: 'Listing', extraInfo: description } } ); showToast('Aviso agregado al carrito', 'success'); } }; const handleCheckout = () => { if (items.length === 0) return showToast("El carrito está vacío", "error"); if (!clientId) setClient(1005, "Consumidor Final"); setShowPayment(true); }; const finalizeOrder = async (_payments: Payment[], isCreditSale: boolean) => { setIsProcessing(true); try { const isDirectPayment = !isCreditSale; const payload: CreateOrderRequest = { clientId: clientId || 1005, sellerId: sellerId || 2, isDirectPayment: isDirectPayment, notes: "Venta de Mostrador (Universal POS)", items: items.map(i => ({ productId: i.productId, quantity: i.quantity, relatedEntityId: i.relatedEntityId, relatedEntityType: i.relatedEntityType })) }; const result = await orderService.createOrder(payload); showToast(`Orden ${result.orderNumber} generada con éxito`, 'success'); clearCart(); setShowPayment(false); } catch (error: any) { console.error(error); const msg = error.response?.data?.message || error.message || "Error al procesar la venta"; showToast(msg, "error"); } finally { setIsProcessing(false); } }; return (
{/* SECCIÓN IZQUIERDA */}

Nueva Venta

Accesos Rápidos

{catalog.filter(p => p.typeCode === 'PHYSICAL' || p.typeCode === 'BUNDLE').slice(0, 9).map(p => ( ))}
{/* SECCIÓN DERECHA: CARRITO */}
{/* Header Carrito */}
Orden Actual
$ {getTotal().toLocaleString()}
{/* Lista de Items */}
{items.length === 0 ? (
Carrito Vacío
) : ( items.map(item => (
{/* Cabecera del Item */}
{item.productName}
{item.quantity} x ${item.unitPrice.toLocaleString()}
${item.subTotal.toLocaleString()}
{/* VISUALIZACIÓN DE COMPONENTES DEL COMBO */} {item.subItems && item.subItems.length > 0 && (
Incluye:
    {item.subItems.map((sub, idx) => (
  • {sub}
  • ))}
)}
)) )}
{/* Cliente y Acciones */}
Cliente
{clientName || "Consumidor Final"}
F7 Cambiar
{/* --- MODALES --- */} {showPayment && ( setShowPayment(false)} /> )} {showAdEditor && ( setShowAdEditor(false)} onConfirm={handleAdConfirmed} clientId={clientId || 1005} /> )}
); }