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'; import ClientCreateModal from '../components/POS/ClientCreateModal'; import ClientSearchModal from '../components/POS/ClientSearchModal'; import { AnimatePresence } from 'framer-motion'; 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); const [showCreateClient, setShowCreateClient] = useState(false); const [showClientSearch, setShowClientSearch] = useState(false); // 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 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 handleChangeClient = () => { setShowClientSearch(true); }; // Callback cuando seleccionan del buscador const handleClientSelected = (client: { id: number; name: string }) => { setClient(client.id, client.name); // showClientSearch se cierra automáticamente por el componente, o lo forzamos aquí si es necesario // El componente ClientSearchModal llama a onClose internamente después de onSelect }; // Callback cuando crean uno nuevo const handleClientCreated = (client: { id: number; name: string }) => { setClient(client.id, client.name); setShowCreateClient(false); }; // Función puente: Del Buscador -> Al Creador const switchToCreate = () => { setShowClientSearch(false); setTimeout(() => setShowCreateClient(true), 100); // Pequeño delay para transición suave }; 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} productId={selectedAdProduct?.id || 0} /> )} {/* MODAL DE BÚSQUEDA DE CLIENTE (F7) */} {showClientSearch && ( setShowClientSearch(false)} onSelect={handleClientSelected} onCreateNew={switchToCreate} /> )} {/* MODAL DE ALTA RÁPIDA */} {showCreateClient && ( setShowCreateClient(false)} onSuccess={handleClientCreated} /> )}
); }