'use client'; import { useEffect, useState } from 'react'; import { api } from '@/lib/api-client'; interface PosStore { id: string; name: string; active: boolean } interface PosTerminal { id: string; storeId: string; name: string; bindingCode: string | null; boundAt: string | null; status: string; lastSeenAt: string | null; settings?: { touchNavigationEnabled?: boolean; quickProductVariantIds?: Array }; } interface CatalogOption { variantId: string; productId: string; name: string; sku: string; priceCents: number } export default function PosAdminPage() { const [stores, setStores] = useState([]); const [terminals, setTerminals] = useState([]); const [storeId, setStoreId] = useState(''); const [name, setName] = useState(''); const [loading, setLoading] = useState(true); const [creating, setCreating] = useState(false); const [error, setError] = useState(''); const [newCode, setNewCode] = useState(''); const [copied, setCopied] = useState(''); const [catalogOptions, setCatalogOptions] = useState([]); const [configuring, setConfiguring] = useState(null); const [touchEnabled, setTouchEnabled] = useState(true); const [quickSlots, setQuickSlots] = useState>([null, null, null, null]); const [savingTouch, setSavingTouch] = useState(false); const [touchMessage, setTouchMessage] = useState(''); const load = async () => { setLoading(true); setError(''); try { const [storeData, terminalData, catalogData] = await Promise.all([ api.get<{ stores: PosStore[] }>('/api/pos/admin/stores'), api.get<{ terminals: PosTerminal[] }>('/api/pos/admin/terminals'), api.get<{ items: CatalogOption[] }>('/api/pos/admin/catalog-products'), ]); setStores(storeData.stores); setTerminals(terminalData.terminals); setCatalogOptions(catalogData.items); setStoreId((current) => current || storeData.stores.find((store) => store.active)?.id || ''); } catch (err) { setError(err instanceof Error ? err.message : 'No se pudo cargar la configuración TPV'); } finally { setLoading(false); } }; useEffect(() => { void load(); }, []); const createTerminal = async (event: React.FormEvent) => { event.preventDefault(); setCreating(true); setError(''); setNewCode(''); try { const terminal = await api.post('/api/pos/admin/terminals', { storeId, name: name.trim() }); setName(''); setNewCode(terminal.bindingCode ?? ''); await load(); } catch (err) { setError(err instanceof Error ? err.message : 'No se pudo crear el terminal'); } finally { setCreating(false); } }; const openTouchConfig = (terminal: PosTerminal) => { setConfiguring(terminal); setTouchEnabled(terminal.settings?.touchNavigationEnabled !== false); const configured = terminal.settings?.quickProductVariantIds ?? []; setQuickSlots([0, 1, 2, 3].map((slot) => configured[slot] ?? null)); setTouchMessage(''); }; const saveTouchConfig = async () => { if (!configuring) return; setSavingTouch(true); setTouchMessage(''); try { await api.patch(`/api/pos/admin/terminals/${configuring.id}/touch-config`, { touchNavigationEnabled: touchEnabled, quickProductVariantIds: quickSlots, }); setTouchMessage('Configuración táctil guardada'); await load(); } catch (err) { setTouchMessage(err instanceof Error ? err.message : 'No se pudo guardar'); } finally { setSavingTouch(false); } }; const copyCode = async (code: string) => { await navigator.clipboard.writeText(code); setCopied(code); setTimeout(() => setCopied(''), 2000); }; return (

TPV

Configura dispositivos y obtén el código para vincular cada caja.

1. Vincular terminal

Crea un terminal aquí y escribe su código de 8 caracteres en el navegador del TPV. Esto identifica qué caja física está vendiendo.

2. Abrir sesión de caja

Después de vincular, el cajero indica el efectivo inicial en el propio TPV. La sesión representa el turno diario de caja.

Crear terminal

{newCode &&

Código creado: {newCode}

} {error &&

{error}

}

Terminales

{loading ?

Cargando…

: terminals.length === 0 ?

No hay terminales.

: (
{terminals.map((terminal) => { const store = stores.find((item) => item.id === terminal.storeId); return ; })}
NombreTiendaCódigoEstadoVinculadoInterfaz
{terminal.name}{store?.name ?? terminal.storeId}{terminal.status}{terminal.boundAt ? 'Sí' : 'No'}
)}
{configuring && (

Interfaz táctil · {configuring.name}

Las categorías y subcategorías se administran en Categorías. Aquí activas su navegación en este terminal y eliges cuatro productos rápidos.

{quickSlots.map((variantId, slot) => ( ))}
{touchMessage &&

{touchMessage}

}
)}
); }