From e97b2b4d8ee8737e19aadb9cc21d4340b149a326 Mon Sep 17 00:00:00 2001 From: josedario87 Date: Tue, 25 Nov 2025 00:44:50 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20Sistema=20de=20gesti=C3=B3n=20de=20impr?= =?UTF-8?q?esoras=20persistente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- app/components/layout/MobileNavigation.vue | 5 + app/components/printers/Card.vue | 89 +++++++++ app/components/printers/Form.vue | 114 ++++++++++++ app/components/printers/List.vue | 132 ++++++++++++++ app/components/printers/Selector.vue | 36 ++++ app/composables/usePrinters.ts | 203 +++++++++++++++++++++ app/pages/index.vue | 15 ++ server/api/print/index.post.ts | 30 ++- server/api/printers/[id].delete.ts | 34 ++++ server/api/printers/[id].get.ts | 34 ++++ server/api/printers/[id].put.ts | 50 +++++ server/api/printers/index.get.ts | 20 ++ server/api/printers/index.post.ts | 41 +++++ server/api/printers/select.post.ts | 31 ++++ server/utils/printers.ts | 178 ++++++++++++++++++ 15 files changed, 1006 insertions(+), 6 deletions(-) create mode 100644 app/components/printers/Card.vue create mode 100644 app/components/printers/Form.vue create mode 100644 app/components/printers/List.vue create mode 100644 app/components/printers/Selector.vue create mode 100644 app/composables/usePrinters.ts create mode 100644 server/api/printers/[id].delete.ts create mode 100644 server/api/printers/[id].get.ts create mode 100644 server/api/printers/[id].put.ts create mode 100644 server/api/printers/index.get.ts create mode 100644 server/api/printers/index.post.ts create mode 100644 server/api/printers/select.post.ts create mode 100644 server/utils/printers.ts diff --git a/app/components/layout/MobileNavigation.vue b/app/components/layout/MobileNavigation.vue index e555941..dc9a7e2 100644 --- a/app/components/layout/MobileNavigation.vue +++ b/app/components/layout/MobileNavigation.vue @@ -18,6 +18,11 @@ const tabs = computed(() => [ label: 'Templates', value: 'templates', icon: 'i-heroicons-document-duplicate' + }, + { + label: 'Impresoras', + value: 'printers', + icon: 'i-heroicons-printer' } ]) diff --git a/app/components/printers/Card.vue b/app/components/printers/Card.vue new file mode 100644 index 0000000..a160b5d --- /dev/null +++ b/app/components/printers/Card.vue @@ -0,0 +1,89 @@ + + + diff --git a/app/components/printers/Form.vue b/app/components/printers/Form.vue new file mode 100644 index 0000000..c03808b --- /dev/null +++ b/app/components/printers/Form.vue @@ -0,0 +1,114 @@ + + + diff --git a/app/components/printers/List.vue b/app/components/printers/List.vue new file mode 100644 index 0000000..d0bb170 --- /dev/null +++ b/app/components/printers/List.vue @@ -0,0 +1,132 @@ + + + diff --git a/app/components/printers/Selector.vue b/app/components/printers/Selector.vue new file mode 100644 index 0000000..07de353 --- /dev/null +++ b/app/components/printers/Selector.vue @@ -0,0 +1,36 @@ + + + diff --git a/app/composables/usePrinters.ts b/app/composables/usePrinters.ts new file mode 100644 index 0000000..7900180 --- /dev/null +++ b/app/composables/usePrinters.ts @@ -0,0 +1,203 @@ +// Composable para gestión de impresoras +export interface Printer { + id: string + name: string + host: string + deviceId: string + timeout: number + isDefault: boolean + createdAt: string + updatedAt: string +} + +const printers = ref([]) +const selectedPrinterId = ref(null) +const loading = ref(false) +const error = ref(null) + +export function usePrinters() { + const selectedPrinter = computed(() => + printers.value.find(p => p.id === selectedPrinterId.value) || null + ) + + const defaultPrinter = computed(() => + printers.value.find(p => p.isDefault) || printers.value[0] || null + ) + + async function fetchPrinters() { + loading.value = true + error.value = null + + try { + const response = await $fetch<{ + ok: boolean + printers: Printer[] + selectedPrinterId: string | null + error?: string + }>('/api/printers') + + if (response.ok) { + printers.value = response.printers + selectedPrinterId.value = response.selectedPrinterId + } else { + error.value = response.error || 'Error al cargar impresoras' + } + } catch (err: any) { + error.value = err.message + } finally { + loading.value = false + } + } + + async function createPrinter(data: { + name: string + host: string + deviceId: string + timeout?: number + isDefault?: boolean + }) { + loading.value = true + error.value = null + + try { + const response = await $fetch<{ + ok: boolean + printer?: Printer + error?: string + }>('/api/printers', { + method: 'POST', + body: data + }) + + if (response.ok && response.printer) { + printers.value.push(response.printer) + // Si es la primera impresora, seleccionarla + if (printers.value.length === 1) { + selectedPrinterId.value = response.printer.id + } + return response.printer + } else { + error.value = response.error || 'Error al crear impresora' + return null + } + } catch (err: any) { + error.value = err.message + return null + } finally { + loading.value = false + } + } + + async function updatePrinter(id: string, data: Partial<{ + name: string + host: string + deviceId: string + timeout: number + isDefault: boolean + }>) { + loading.value = true + error.value = null + + try { + const response = await $fetch<{ + ok: boolean + printer?: Printer + error?: string + }>(`/api/printers/${id}`, { + method: 'PUT', + body: data + }) + + if (response.ok && response.printer) { + const index = printers.value.findIndex(p => p.id === id) + if (index !== -1) { + // Si se estableció como default, quitar default de las demás + if (data.isDefault) { + printers.value.forEach(p => p.isDefault = false) + } + printers.value[index] = response.printer + } + return response.printer + } else { + error.value = response.error || 'Error al actualizar impresora' + return null + } + } catch (err: any) { + error.value = err.message + return null + } finally { + loading.value = false + } + } + + async function deletePrinter(id: string) { + loading.value = true + error.value = null + + try { + const response = await $fetch<{ + ok: boolean + error?: string + }>(`/api/printers/${id}`, { + method: 'DELETE' + }) + + if (response.ok) { + const index = printers.value.findIndex(p => p.id === id) + if (index !== -1) { + printers.value.splice(index, 1) + } + // Si era la seleccionada, seleccionar otra + if (selectedPrinterId.value === id) { + selectedPrinterId.value = printers.value[0]?.id || null + } + return true + } else { + error.value = response.error || 'Error al eliminar impresora' + return false + } + } catch (err: any) { + error.value = err.message + return false + } finally { + loading.value = false + } + } + + async function selectPrinter(printerId: string | null) { + try { + const response = await $fetch<{ + ok: boolean + error?: string + }>('/api/printers/select', { + method: 'POST', + body: { printerId } + }) + + if (response.ok) { + selectedPrinterId.value = printerId + return true + } else { + error.value = response.error || 'Error al seleccionar impresora' + return false + } + } catch (err: any) { + error.value = err.message + return false + } + } + + return { + printers, + selectedPrinterId, + selectedPrinter, + defaultPrinter, + loading, + error, + fetchPrinters, + createPrinter, + updatePrinter, + deletePrinter, + selectPrinter + } +} diff --git a/app/pages/index.vue b/app/pages/index.vue index acb6181..9757584 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -2,6 +2,12 @@ const activeTab = ref('constructor') const isDesktop = useMediaQuery('(min-width: 768px)') const queue = usePrintQueue() +const printers = usePrinters() + +// Cargar impresoras al iniciar +onMounted(() => { + printers.fetchPrinters() +})