/** * Scanner Screen * * Dedicated screen for barcode/QR scanning with product lookup */ import { useState, useCallback } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, ActivityIndicator, ScrollView, Alert, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { BarcodeScanner } from '@/components/BarcodeScanner'; import { useBarcode, BARCODE_PRESETS } from '@/hooks/useBarcode'; import { productsApi } from '@/services/api'; import { Product, ScannedBarcode, ProductBarcode } from '@/types'; type ScanMode = 'product' | 'qr' | 'all'; interface ScannedProduct { barcode: string; product: Product | null; scannedAt: number; error?: string; } export default function ScannerScreen() { const [mode, setMode] = useState('product'); const [isScanning, setIsScanning] = useState(false); const [isLoading, setIsLoading] = useState(false); const [scannedProducts, setScannedProducts] = useState([]); const [selectedProduct, setSelectedProduct] = useState(null); const handleProductScan = useCallback(async (barcode: ProductBarcode) => { if (!barcode.isValid) { Alert.alert('Código Inválido', `El código ${barcode.code} no es válido.`); return; } setIsLoading(true); try { // Search product by barcode const response = await productsApi.list({ search: barcode.code, limit: 1 }); const product = response.data?.[0] || null; const scannedProduct: ScannedProduct = { barcode: barcode.code, product, scannedAt: Date.now(), error: product ? undefined : 'Producto no encontrado', }; setScannedProducts((prev) => [scannedProduct, ...prev.slice(0, 19)]); setSelectedProduct(scannedProduct); if (!product) { Alert.alert( 'Producto No Encontrado', `No se encontró un producto con el código ${barcode.code}.`, [ { text: 'Continuar Escaneando', style: 'cancel' }, { text: 'Crear Producto', onPress: () => handleCreateProduct(barcode.code), }, ] ); } } catch (error) { console.error('Error searching product:', error); const scannedProduct: ScannedProduct = { barcode: barcode.code, product: null, scannedAt: Date.now(), error: 'Error al buscar producto', }; setScannedProducts((prev) => [scannedProduct, ...prev.slice(0, 19)]); } finally { setIsLoading(false); } }, []); const handleQRScan = useCallback((data: any) => { Alert.alert('QR Escaneado', JSON.stringify(data, null, 2)); }, []); const handleCreateProduct = (barcode: string) => { Alert.alert('Crear Producto', `Crear producto con código: ${barcode}\n\nFuncionalidad próximamente.`); }; const handleAddToInventory = (product: Product) => { Alert.alert( 'Agregar a Inventario', `¿Agregar ${product.name} al inventario?`, [ { text: 'Cancelar', style: 'cancel' }, { text: 'Agregar', onPress: () => { Alert.alert('Agregado', `${product.name} agregado al inventario.`); }, }, ] ); }; const handleAddToOrder = (product: Product) => { Alert.alert( 'Agregar a Pedido', `¿Agregar ${product.name} al pedido actual?`, [ { text: 'Cancelar', style: 'cancel' }, { text: 'Agregar', onPress: () => { Alert.alert('Agregado', `${product.name} agregado al pedido.`); }, }, ] ); }; const formatCurrency = (amount: number) => { return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN', }).format(amount); }; const formatTime = (timestamp: number) => { return new Date(timestamp).toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit', }); }; if (isScanning) { return ( setIsScanning(false)} /> ); } return ( {/* Header */} Escáner {scannedProducts.length} productos escaneados {/* Mode Selector */} {(['product', 'qr', 'all'] as ScanMode[]).map((m) => ( setMode(m)} > {m === 'product' ? 'Productos' : m === 'qr' ? 'QR' : 'Todos'} ))} {/* Scan Button */} setIsScanning(true)} > Iniciar Escaneo {/* Loading Indicator */} {isLoading && ( Buscando producto... )} {/* Selected Product Details */} {selectedProduct && selectedProduct.product && ( {selectedProduct.product.code} {selectedProduct.product.name} {selectedProduct.product.categoryName && ( {selectedProduct.product.categoryName} )} {formatCurrency(selectedProduct.product.unitPrice)} Precio handleAddToInventory(selectedProduct.product!)} > Inventario handleAddToOrder(selectedProduct.product!)} > Agregar )} {/* Scan History */} Historial de Escaneos {scannedProducts.length === 0 ? ( No hay productos escaneados ) : ( scannedProducts.map((item, index) => ( setSelectedProduct(item)} > {item.barcode} {item.product?.name || item.error || 'Producto no encontrado'} {formatTime(item.scannedAt)} )) )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f8fafc', }, header: { padding: 20, paddingTop: 60, backgroundColor: '#1e40af', }, headerTitle: { fontSize: 28, fontWeight: '700', color: '#ffffff', }, headerSubtitle: { fontSize: 14, color: '#93c5fd', marginTop: 4, }, modeSelector: { flexDirection: 'row', padding: 16, gap: 8, }, modeButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 12, paddingHorizontal: 16, borderRadius: 10, backgroundColor: '#ffffff', gap: 6, }, modeButtonActive: { backgroundColor: '#1e40af', }, modeText: { fontSize: 14, fontWeight: '500', color: '#64748b', }, modeTextActive: { color: '#ffffff', }, scanButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginHorizontal: 16, paddingVertical: 20, borderRadius: 12, backgroundColor: '#1e40af', gap: 12, }, scanButtonText: { fontSize: 18, fontWeight: '600', color: '#ffffff', }, loadingOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(255, 255, 255, 0.9)', justifyContent: 'center', alignItems: 'center', zIndex: 100, }, loadingText: { fontSize: 16, color: '#64748b', marginTop: 12, }, productDetails: { margin: 16, backgroundColor: '#ffffff', borderRadius: 12, padding: 16, }, productHeader: { flexDirection: 'row', alignItems: 'center', }, productIcon: { width: 56, height: 56, borderRadius: 12, backgroundColor: '#eff6ff', justifyContent: 'center', alignItems: 'center', }, productInfo: { flex: 1, marginLeft: 12, }, productCode: { fontSize: 12, color: '#64748b', fontWeight: '500', }, productName: { fontSize: 16, fontWeight: '600', color: '#1f2937', marginTop: 2, }, productCategory: { fontSize: 13, color: '#9ca3af', marginTop: 2, }, productPrice: { alignItems: 'flex-end', }, priceAmount: { fontSize: 18, fontWeight: '700', color: '#1e40af', }, priceLabel: { fontSize: 12, color: '#9ca3af', }, productActions: { flexDirection: 'row', gap: 12, marginTop: 16, paddingTop: 16, borderTopWidth: 1, borderTopColor: '#f1f5f9', }, actionButton: { flex: 1, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 12, borderRadius: 8, backgroundColor: '#eff6ff', gap: 6, }, actionButtonPrimary: { backgroundColor: '#1e40af', }, actionButtonText: { fontSize: 14, fontWeight: '500', color: '#1e40af', }, actionButtonTextPrimary: { color: '#ffffff', }, historySection: { flex: 1, marginTop: 16, }, historyTitle: { fontSize: 16, fontWeight: '600', color: '#1f2937', paddingHorizontal: 16, marginBottom: 12, }, historyList: { flex: 1, }, emptyHistory: { alignItems: 'center', paddingVertical: 48, }, emptyHistoryText: { fontSize: 14, color: '#9ca3af', marginTop: 12, }, historyItem: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#ffffff', marginHorizontal: 16, marginBottom: 8, padding: 12, borderRadius: 10, }, historyItemSelected: { backgroundColor: '#eff6ff', borderWidth: 1, borderColor: '#1e40af', }, historyItemIcon: { marginRight: 12, }, historyItemContent: { flex: 1, }, historyItemCode: { fontSize: 14, fontWeight: '600', color: '#1f2937', }, historyItemName: { fontSize: 13, color: '#64748b', marginTop: 2, }, historyItemTime: { fontSize: 12, color: '#9ca3af', }, });