import React, { useState, useMemo } from 'react'; import { Plus, Minus, ShoppingCart, CreditCard, Banknote, Calculator, User, Receipt, Package, Euro, TrendingUp, Clock, ToggleLeft, ToggleRight, Settings, Zap, Wifi, WifiOff, AlertCircle, CheckCircle, Loader, Eye, EyeOff, Info, Trash2 } from 'lucide-react'; import { Button, Input, Card, Badge, StatsGrid, StatusCard, getStatusColor, Tabs, Modal, Select } from '../../../../components/ui'; import { PageHeader } from '../../../../components/layout'; import { LoadingSpinner } from '../../../../components/shared'; import { formatters } from '../../../../components/ui/Stats/StatsPresets'; import { useIngredients } from '../../../../api/hooks/inventory'; import { useTenantId } from '../../../../hooks/useTenantId'; import { ProductType, ProductCategory, IngredientResponse } from '../../../../api/types/inventory'; import { useToast } from '../../../../hooks/ui/useToast'; import { usePOSConfigurationData, usePOSConfigurationManager } from '../../../../api/hooks/pos'; import { POSConfiguration, POSProviderConfig } from '../../../../api/types/pos'; import { posService } from '../../../../api/services/pos'; interface CartItem { id: string; name: string; price: number; quantity: number; category: string; stock: number; } const POSPage: React.FC = () => { const [cart, setCart] = useState([]); const [selectedCategory, setSelectedCategory] = useState('all'); const [customerInfo, setCustomerInfo] = useState({ name: '', email: '', phone: '', }); const [paymentMethod, setPaymentMethod] = useState<'cash' | 'card' | 'transfer'>('cash'); const [cashReceived, setCashReceived] = useState(''); const [posMode, setPosMode] = useState<'manual' | 'automatic'>('manual'); const [showPOSConfig, setShowPOSConfig] = useState(false); // POS Configuration State const [showAddPosModal, setShowAddPosModal] = useState(false); const [showEditPosModal, setShowEditPosModal] = useState(false); const [selectedPosConfig, setSelectedPosConfig] = useState(null); const [posFormData, setPosFormData] = useState({}); const [testingConnection, setTestingConnection] = useState(null); const [showCredentials, setShowCredentials] = useState>({}); const tenantId = useTenantId(); const { addToast } = useToast(); // POS Configuration hooks const posData = usePOSConfigurationData(tenantId); const posManager = usePOSConfigurationManager(tenantId); // Fetch finished products from API const { data: ingredientsData, isLoading: productsLoading, error: productsError } = useIngredients(tenantId, { // Filter for finished products only category: undefined, // We'll filter client-side for now search: undefined }); // Filter for finished products and convert to POS format const products = useMemo(() => { if (!ingredientsData) return []; return ingredientsData .filter(ingredient => ingredient.product_type === ProductType.FINISHED_PRODUCT) .map(ingredient => ({ id: ingredient.id, name: ingredient.name, price: Number(ingredient.average_cost) || 0, category: ingredient.category.toLowerCase(), stock: Number(ingredient.current_stock) || 0, ingredient: ingredient })) .filter(product => product.stock > 0); // Only show products in stock }, [ingredientsData]); // Generate categories from actual product data const categories = useMemo(() => { const categoryMap = new Map(); categoryMap.set('all', { id: 'all', name: 'Todos' }); products.forEach(product => { if (!categoryMap.has(product.category)) { const categoryName = product.category.charAt(0).toUpperCase() + product.category.slice(1); categoryMap.set(product.category, { id: product.category, name: categoryName }); } }); return Array.from(categoryMap.values()); }, [products]); // POS Providers Configuration const supportedProviders: POSProviderConfig[] = [ { id: 'toast', name: 'Toast POS', logo: '🍞', description: 'Sistema POS líder para restaurantes y panaderías. Muy popular en España.', features: ['Gestión de pedidos', 'Sincronización de inventario', 'Pagos integrados', 'Reportes en tiempo real'], required_fields: [ { field: 'api_key', label: 'API Key', type: 'password', required: true, help_text: 'Obten tu API key desde Toast Dashboard > Settings > Integrations' }, { field: 'restaurant_guid', label: 'Restaurant GUID', type: 'text', required: true, help_text: 'ID único del restaurante en Toast' }, { field: 'location_id', label: 'Location ID', type: 'text', required: true, help_text: 'ID de la ubicación específica' }, { field: 'environment', label: 'Entorno', type: 'select', required: true, options: [ { value: 'sandbox', label: 'Sandbox (Pruebas)' }, { value: 'production', label: 'Producción' } ]}, ], }, { id: 'square', name: 'Square POS', logo: '⬜', description: 'Solución POS completa con tarifas transparentes. Ampliamente utilizada por pequeñas empresas.', features: ['Procesamiento de pagos', 'Gestión de inventario', 'Análisis de ventas', 'Integración con e-commerce'], required_fields: [ { field: 'application_id', label: 'Application ID', type: 'text', required: true, help_text: 'ID de aplicación de Square Developer Dashboard' }, { field: 'access_token', label: 'Access Token', type: 'password', required: true, help_text: 'Token de acceso para la API de Square' }, { field: 'location_id', label: 'Location ID', type: 'text', required: true, help_text: 'ID de la ubicación de Square' }, { field: 'webhook_signature_key', label: 'Webhook Signature Key', type: 'password', required: false, help_text: 'Clave para verificar webhooks (opcional)' }, { field: 'environment', label: 'Entorno', type: 'select', required: true, options: [ { value: 'sandbox', label: 'Sandbox (Pruebas)' }, { value: 'production', label: 'Producción' } ]}, ], }, { id: 'lightspeed', name: 'Lightspeed POS', logo: '⚡', description: 'Sistema POS empresarial con API abierta e integración con múltiples herramientas.', features: ['API REST completa', 'Gestión multi-ubicación', 'Reportes avanzados', 'Integración con contabilidad'], required_fields: [ { field: 'api_key', label: 'API Key', type: 'password', required: true, help_text: 'Clave API de Lightspeed Retail' }, { field: 'api_secret', label: 'API Secret', type: 'password', required: true, help_text: 'Secreto API de Lightspeed Retail' }, { field: 'account_id', label: 'Account ID', type: 'text', required: true, help_text: 'ID de cuenta de Lightspeed' }, { field: 'shop_id', label: 'Shop ID', type: 'text', required: true, help_text: 'ID de la tienda específica' }, { field: 'server_region', label: 'Región del Servidor', type: 'select', required: true, options: [ { value: 'eu', label: 'Europa' }, { value: 'us', label: 'Estados Unidos' }, { value: 'ca', label: 'Canadá' } ]}, ], }, ]; const filteredProducts = useMemo(() => { return products.filter(product => selectedCategory === 'all' || product.category === selectedCategory ); }, [products, selectedCategory]); // Load POS configurations function for refetching after updates const loadPosConfigurations = () => { // This will trigger a refetch of POS configurations // Note: posManager may not have refetch method available console.log('POS configurations updated, consider implementing refetch if needed'); }; // POS Configuration Handlers const handleAddPosConfiguration = () => { setPosFormData({ provider: '', config_name: '', credentials: {}, sync_settings: { auto_sync_enabled: true, sync_interval_minutes: 5, sync_sales: true, sync_inventory: true, sync_customers: true, }, }); setShowAddPosModal(true); }; const handleEditPosConfiguration = (config: POSConfiguration) => { setSelectedPosConfig(config); setPosFormData({ provider: (config as any).provider || (config as any).pos_system || '', config_name: (config as any).config_name || (config as any).provider_name || '', credentials: (config as any).credentials || {}, sync_settings: (config as any).sync_settings || { auto_sync_enabled: true, sync_interval_minutes: 5, sync_sales: true, sync_inventory: true, sync_customers: true, }, }); setShowEditPosModal(true); }; const handleSavePosConfiguration = async () => { try { const provider = supportedProviders.find(p => p.id === posFormData.provider); if (!provider) return; // Validate required fields const missingFields = provider.required_fields .filter(field => field.required && !posFormData.credentials[field.field]) .map(field => field.label); if (missingFields.length > 0) { addToast(`Campos requeridos: ${missingFields.join(', ')}`, { type: 'error' }); return; } if (selectedPosConfig) { // Update existing await posService.updatePOSConfiguration({ tenant_id: tenantId, config_id: selectedPosConfig.id, ...posFormData, }); addToast('Configuración actualizada correctamente', { type: 'success' }); setShowEditPosModal(false); loadPosConfigurations(); } else { // Create new await posService.createPOSConfiguration({ tenant_id: tenantId, ...posFormData, }); addToast('Configuración creada correctamente', { type: 'success' }); setShowAddPosModal(false); loadPosConfigurations(); } } catch (error) { addToast('Error al guardar la configuración', { type: 'error' }); } }; const handleTestPosConnection = async (configId: string) => { try { setTestingConnection(configId); const response = await posService.testPOSConnection({ tenant_id: tenantId, config_id: configId, }); if (response.success) { addToast('Conexión exitosa', { type: 'success' }); } else { addToast(`Error en la conexión: ${response.message || 'Error desconocido'}`, { type: 'error' }); } } catch (error) { addToast('Error al probar la conexión', { type: 'error' }); } finally { setTestingConnection(null); } }; const handleDeletePosConfiguration = async (configId: string) => { if (!window.confirm('¿Estás seguro de que deseas eliminar esta configuración?')) { return; } try { await posService.deletePOSConfiguration({ tenant_id: tenantId, config_id: configId, }); addToast('Configuración eliminada correctamente', { type: 'success' }); loadPosConfigurations(); } catch (error) { addToast('Error al eliminar la configuración', { type: 'error' }); } }; const addToCart = (product: typeof products[0]) => { setCart(prevCart => { const existingItem = prevCart.find(item => item.id === product.id); if (existingItem) { // Check if we have enough stock if (existingItem.quantity >= product.stock) { return prevCart; // Don't add if no stock available } return prevCart.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item ); } else { return [...prevCart, { id: product.id, name: product.name, price: product.price, quantity: 1, category: product.category, stock: product.stock }]; } }); }; const updateQuantity = (id: string, quantity: number) => { if (quantity <= 0) { setCart(prevCart => prevCart.filter(item => item.id !== id)); } else { setCart(prevCart => prevCart.map(item => { if (item.id === id) { // Don't allow quantity to exceed stock const maxQuantity = Math.min(quantity, item.stock); return { ...item, quantity: maxQuantity }; } return item; }) ); } }; const clearCart = () => { setCart([]); }; const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); const taxRate = 0.21; // 21% IVA const tax = subtotal * taxRate; const total = subtotal + tax; const change = cashReceived ? Math.max(0, parseFloat(cashReceived) - total) : 0; const processPayment = () => { if (cart.length === 0) return; // TODO: Integrate with real POS API endpoint console.log('Processing payment:', { cart, customerInfo, paymentMethod, total, cashReceived: paymentMethod === 'cash' ? parseFloat(cashReceived) : undefined, change: paymentMethod === 'cash' ? change : undefined, }); // Clear cart after successful payment setCart([]); setCustomerInfo({ name: '', email: '', phone: '' }); setCashReceived(''); alert('Venta procesada exitosamente'); }; // Calculate stats for the POS dashboard const posStats = useMemo(() => { const totalProducts = products.length; const totalStock = products.reduce((sum, product) => sum + product.stock, 0); const cartValue = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0); const cartItems = cart.reduce((sum, item) => sum + item.quantity, 0); const lowStockProducts = products.filter(product => product.stock <= 5).length; const avgProductPrice = totalProducts > 0 ? products.reduce((sum, product) => sum + product.price, 0) / totalProducts : 0; return { totalProducts, totalStock, cartValue, cartItems, lowStockProducts, avgProductPrice }; }, [products, cart]); const stats = [ { title: 'Productos Disponibles', value: posStats.totalProducts, variant: 'default' as const, icon: Package, }, { title: 'Stock Total', value: posStats.totalStock, variant: 'info' as const, icon: Package, }, { title: 'Artículos en Carrito', value: posStats.cartItems, variant: 'success' as const, icon: ShoppingCart, }, { title: 'Valor del Carrito', value: formatters.currency(posStats.cartValue), variant: 'success' as const, icon: Euro, }, { title: 'Stock Bajo', value: posStats.lowStockProducts, variant: 'warning' as const, icon: Clock, }, { title: 'Precio Promedio', value: formatters.currency(posStats.avgProductPrice), variant: 'info' as const, icon: TrendingUp, }, ]; // Loading and error states if (productsLoading || !tenantId) { return (
); } if (productsError) { return (

Error al cargar productos

{productsError.message || 'Ha ocurrido un error inesperado'}

); } // POS Configuration Form Renderer const renderPosConfigurationForm = () => { const selectedProvider = supportedProviders.find(p => p.id === posFormData.provider); return (
setPosFormData((prev: any) => ({ ...prev, config_name: e.target.value }))} placeholder={`Mi ${selectedProvider.name} ${new Date().getFullYear()}`} />

Credenciales de API

{selectedProvider.required_fields.map(field => (
{field.type === 'password' && ( )}
{field.type === 'select' ? ( setPosFormData(prev => ({ ...prev, credentials: { ...prev.credentials, [field.field]: e.target.value } }))} placeholder={field.placeholder} /> )} {field.help_text && (

{field.help_text}

)}
))}

Configuración de Sincronización

setPosFormData((prev: any) => ({ ...prev, sync_settings: { ...prev.sync_settings, sync_sales: e.target.checked } }))} /> Sincronizar ventas
)}
); }; return (
{/* POS Mode Toggle */}

Modo de Operación

{posMode === 'manual' ? 'Sistema POS manual interno - gestiona las ventas directamente desde la aplicación' : 'Integración automática con sistemas POS externos - sincroniza datos automáticamente' }

Manual Automático
{posMode === 'automatic' && ( )}
{posMode === 'manual' ? ( <> {/* Stats Grid */}
{/* Products Section */}
{/* Categories */}
{categories.map(category => ( ))}
{/* Products Grid */}
{filteredProducts.map(product => { const cartItem = cart.find(item => item.id === product.id); const inCart = !!cartItem; const cartQuantity = cartItem?.quantity || 0; const remainingStock = product.stock - cartQuantity; const getStockStatusConfig = () => { if (remainingStock <= 0) { return { color: getStatusColor('cancelled'), text: 'Sin Stock', icon: Package, isCritical: true, isHighlight: false }; } else if (remainingStock <= 5) { return { color: getStatusColor('pending'), text: `${remainingStock} disponibles`, icon: Package, isCritical: false, isHighlight: true }; } else { return { color: getStatusColor('completed'), text: `${remainingStock} disponibles`, icon: Package, isCritical: false, isHighlight: false }; } }; return ( addToCart(product) } ]} /> ); })}
{/* Empty State */} {filteredProducts.length === 0 && (

No hay productos disponibles

{selectedCategory === 'all' ? 'No hay productos en stock en este momento' : `No hay productos en la categoría "${categories.find(c => c.id === selectedCategory)?.name}"` }

)}
{/* Cart and Checkout Section */}
{/* Cart */}

Carrito ({cart.length})

{cart.length > 0 && ( )}
{cart.length === 0 ? (

Carrito vacío

) : ( cart.map(item => { const product = products.find(p => p.id === item.id); const maxQuantity = product?.stock || item.stock; return (

{item.name}

€{item.price.toFixed(2)} c/u

Stock: {maxQuantity}

{item.quantity}

€{(item.price * item.quantity).toFixed(2)}

); }) )}
{cart.length > 0 && (
Subtotal: €{subtotal.toFixed(2)}
IVA (21%): €{tax.toFixed(2)}
Total: €{total.toFixed(2)}
)}
{/* Customer Info */}

Cliente (Opcional)

setCustomerInfo(prev => ({ ...prev, name: e.target.value }))} /> setCustomerInfo(prev => ({ ...prev, email: e.target.value }))} /> setCustomerInfo(prev => ({ ...prev, phone: e.target.value }))} />
{/* Payment */}

Método de Pago

{paymentMethod === 'cash' && (
setCashReceived(e.target.value)} /> {cashReceived && parseFloat(cashReceived) >= total && (

Cambio: €{change.toFixed(2)}

)}
)}
) : ( /* Automatic POS Integration Section */

Sistemas POS Integrados

Gestiona tus sistemas POS externos y configuraciones de integración

{posData.isLoading ? (
) : posData.configurations.length === 0 ? (

No hay sistemas POS configurados

Configura tu primer sistema POS para comenzar a sincronizar datos de ventas.

) : (
{posData.configurations.map(config => { const provider = posData.supportedSystems.find(p => p.id === config.pos_system); return (
📊

{config.provider_name}

{provider?.name || config.pos_system}

{config.is_connected ? ( ) : ( )} {config.is_active ? 'Activo' : 'Inactivo'}
Estado de conexión:
{config.is_active ? ( <> Conectado ) : ( <> Desconectado )}
{config.last_sync_at && (
Última sincronización: {new Date(config.last_sync_at).toLocaleString('es-ES')}
)}
); })}
)}
)} {/* POS Configuration Modals */} {/* Add Configuration Modal */} setShowAddPosModal(false)} title="Agregar Sistema POS" size="lg" >
{renderPosConfigurationForm()}
{/* Edit Configuration Modal */} setShowEditPosModal(false)} title="Editar Sistema POS" size="lg" >
{renderPosConfigurationForm()}
); }; export default POSPage;