feat: Sistema de gestión de impresoras persistente

- Crear modelo Printer con campos: id, name, host, deviceId, timeout, isDefault
- Almacenamiento persistente en data/printers.json
- APIs CRUD: GET/POST /api/printers, GET/PUT/DELETE /api/printers/:id
- API para seleccionar impresora activa: POST /api/printers/select
- Endpoint de impresión ahora usa la impresora seleccionada o la especificada por printerId
- Composable usePrinters() para el cliente
- UI: Selector de impresora en sidebar, tab Impresoras en mobile
- Componentes: PrintersList, PrintersCard, PrintersForm, PrintersSelector
This commit is contained in:
2025-11-25 00:44:50 -06:00
parent 955584275b
commit e97b2b4d8e
15 changed files with 1006 additions and 6 deletions

178
server/utils/printers.ts Normal file
View File

@@ -0,0 +1,178 @@
// Gestión de impresoras persistentes
import { readFile, writeFile, mkdir } from 'fs/promises'
import { existsSync } from 'fs'
import { join } from 'path'
export interface Printer {
id: string
name: string
host: string
deviceId: string
timeout: number
isDefault: boolean
createdAt: string
updatedAt: string
}
export interface PrintersStore {
printers: Printer[]
selectedPrinterId: string | null
}
// Directorio de datos persistentes
const DATA_DIR = join(process.cwd(), 'data')
const PRINTERS_FILE = join(DATA_DIR, 'printers.json')
// Asegurar que el directorio existe
async function ensureDataDir(): Promise<void> {
if (!existsSync(DATA_DIR)) {
await mkdir(DATA_DIR, { recursive: true })
}
}
// Leer store de impresoras
export async function readPrintersStore(): Promise<PrintersStore> {
await ensureDataDir()
try {
const data = await readFile(PRINTERS_FILE, 'utf-8')
return JSON.parse(data)
} catch {
// Si no existe el archivo, retornar store vacío
return {
printers: [],
selectedPrinterId: null
}
}
}
// Guardar store de impresoras
export async function writePrintersStore(store: PrintersStore): Promise<void> {
await ensureDataDir()
await writeFile(PRINTERS_FILE, JSON.stringify(store, null, 2), 'utf-8')
}
// Generar ID único
function generateId(): string {
return `printer_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
}
// CRUD Operations
export async function getAllPrinters(): Promise<Printer[]> {
const store = await readPrintersStore()
return store.printers
}
export async function getPrinterById(id: string): Promise<Printer | null> {
const store = await readPrintersStore()
return store.printers.find(p => p.id === id) || null
}
export async function getDefaultPrinter(): Promise<Printer | null> {
const store = await readPrintersStore()
return store.printers.find(p => p.isDefault) || store.printers[0] || null
}
export async function getSelectedPrinter(): Promise<Printer | null> {
const store = await readPrintersStore()
if (store.selectedPrinterId) {
const printer = store.printers.find(p => p.id === store.selectedPrinterId)
if (printer) return printer
}
// Fallback a la impresora por defecto
return getDefaultPrinter()
}
export async function setSelectedPrinter(printerId: string | null): Promise<void> {
const store = await readPrintersStore()
store.selectedPrinterId = printerId
await writePrintersStore(store)
}
export async function createPrinter(data: {
name: string
host: string
deviceId: string
timeout?: number
isDefault?: boolean
}): Promise<Printer> {
const store = await readPrintersStore()
const now = new Date().toISOString()
const printer: Printer = {
id: generateId(),
name: data.name,
host: data.host,
deviceId: data.deviceId,
timeout: data.timeout || 60000,
isDefault: data.isDefault || store.printers.length === 0, // Primera impresora es default
createdAt: now,
updatedAt: now
}
// Si esta es default, quitar default de las demás
if (printer.isDefault) {
store.printers.forEach(p => p.isDefault = false)
}
store.printers.push(printer)
// Si es la primera impresora, seleccionarla
if (store.printers.length === 1) {
store.selectedPrinterId = printer.id
}
await writePrintersStore(store)
return printer
}
export async function updatePrinter(id: string, data: Partial<{
name: string
host: string
deviceId: string
timeout: number
isDefault: boolean
}>): Promise<Printer | null> {
const store = await readPrintersStore()
const index = store.printers.findIndex(p => p.id === id)
if (index === -1) return null
// Si se está estableciendo como default, quitar default de las demás
if (data.isDefault) {
store.printers.forEach(p => p.isDefault = false)
}
store.printers[index] = {
...store.printers[index],
...data,
updatedAt: new Date().toISOString()
}
await writePrintersStore(store)
return store.printers[index]
}
export async function deletePrinter(id: string): Promise<boolean> {
const store = await readPrintersStore()
const index = store.printers.findIndex(p => p.id === id)
if (index === -1) return false
const wasDefault = store.printers[index].isDefault
store.printers.splice(index, 1)
// Si era la impresora seleccionada, limpiar selección
if (store.selectedPrinterId === id) {
store.selectedPrinterId = store.printers[0]?.id || null
}
// Si era default y hay otras impresoras, hacer la primera default
if (wasDefault && store.printers.length > 0) {
store.printers[0].isDefault = true
}
await writePrintersStore(store)
return true
}