feat: Campo model en impresoras y guías de formato MCP
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 36s

- Agregar campo model a interface Printer
- Nuevas tools: get_printing_guide, list_models
- Guías de formato para TM-U220 (matricial) y TM-T88 (térmica)
- list_printers ahora incluye el modelo
This commit is contained in:
2025-11-25 13:31:02 -06:00
parent f20acbd1dd
commit 84249a3565
2 changed files with 166 additions and 0 deletions

View File

@@ -7,6 +7,106 @@ import { getAllPrinters, getSelectedPrinter, getPrinterById } from './printers'
import { buildFromOperations } from './eposBuilder'
import { buildSoapEnvelope, sendToPrinter, parsePrinterResponse } from './printer'
// Guías de impresión por modelo
export const PRINTING_GUIDES: Record<string, {
model: string
type: string
columns: number
columnsDoubleWidth: number
supportedChars: string
rules: string[]
operations: string[]
example: string
}> = {
'TM-U220': {
model: 'Epson TM-U220',
type: 'Matricial (impact)',
columns: 40,
columnsDoubleWidth: 20,
supportedChars: 'ASCII básico (A-Z, a-z, 0-9, símbolos comunes). Evitar Unicode especial.',
rules: [
'Máximo 40 caracteres por línea (20 con width:2)',
'Usar solo caracteres ASCII básicos',
'Evitar caracteres Unicode: ╔═║╚╝ y similares',
'Usar = - _ * para separadores',
'Siempre usar feed entre elementos para legibilidad',
'feed:1 entre items, feed:2 entre secciones',
'feed:4 antes del cut final',
'Los headers con width:2 deben ser cortos (max 20 chars)'
],
operations: [
'{ op: "text", value: "texto" } - Imprime texto',
'{ op: "feed", lines: N } - Avanza N líneas',
'{ op: "cut" } - Corta el papel',
'{ op: "align", align: "left|center|right" } - Alineación',
'{ op: "style", bold: true, underline: true, width: 1-2, height: 1-2 } - Estilos'
],
example: `[
{ "op": "align", "align": "center" },
{ "op": "style", "bold": true, "width": 2, "height": 2 },
{ "op": "text", "value": "TITULO" },
{ "op": "style", "bold": false, "width": 1, "height": 1 },
{ "op": "feed", "lines": 2 },
{ "op": "text", "value": "================================" },
{ "op": "feed", "lines": 1 },
{ "op": "align", "align": "left" },
{ "op": "text", "value": "Contenido aquí" },
{ "op": "feed", "lines": 4 },
{ "op": "cut" }
]`
},
'TM-T88': {
model: 'Epson TM-T88 (series)',
type: 'Térmica',
columns: 48,
columnsDoubleWidth: 24,
supportedChars: 'ASCII extendido, algunos caracteres Unicode soportados',
rules: [
'Máximo 48 caracteres por línea (24 con width:2)',
'Soporta más caracteres que matriciales',
'Impresión más rápida y silenciosa',
'Usar feed para espaciado',
'Soporta imágenes y códigos QR'
],
operations: [
'{ op: "text", value: "texto" } - Imprime texto',
'{ op: "feed", lines: N } - Avanza N líneas',
'{ op: "cut" } - Corta el papel',
'{ op: "align", align: "left|center|right" } - Alineación',
'{ op: "style", bold: true, underline: true, width: 1-2, height: 1-2 } - Estilos',
'{ op: "qrcode", data: "texto", size: 4 } - Código QR',
'{ op: "barcode", data: "123456", type: "ean13" } - Código de barras'
],
example: `[
{ "op": "align", "align": "center" },
{ "op": "style", "bold": true, "width": 2, "height": 2 },
{ "op": "text", "value": "RECIBO" },
{ "op": "style", "bold": false, "width": 1, "height": 1 },
{ "op": "feed", "lines": 1 },
{ "op": "qrcode", "data": "https://example.com", "size": 4 },
{ "op": "feed", "lines": 2 },
{ "op": "cut" }
]`
}
}
// Obtener guía por modelo (búsqueda flexible)
export function getPrintingGuide(model: string): typeof PRINTING_GUIDES[string] | null {
// Búsqueda exacta
if (PRINTING_GUIDES[model]) return PRINTING_GUIDES[model]
// Búsqueda parcial (case-insensitive)
const modelLower = model.toLowerCase()
for (const [key, guide] of Object.entries(PRINTING_GUIDES)) {
if (key.toLowerCase().includes(modelLower) || modelLower.includes(key.toLowerCase())) {
return guide
}
}
// Default a TM-U220 si no se encuentra
return PRINTING_GUIDES['TM-U220']
}
// Definición de las tools MCP
export const MCP_TOOLS = [
{
@@ -147,6 +247,29 @@ REGLAS IMPORTANTES:
},
required: ['templateId']
}
},
{
name: 'printercentral_get_printing_guide',
description: 'Obtiene la guía de formato para un modelo de impresora específico. Incluye límites de caracteres, reglas de formato, operaciones disponibles y ejemplos.',
inputSchema: {
type: 'object' as const,
properties: {
model: {
type: 'string',
description: 'Modelo de impresora (ej: "TM-U220", "TM-T88"). Si no se especifica, retorna todas las guías disponibles.'
}
},
required: [] as string[]
}
},
{
name: 'printercentral_list_models',
description: 'Lista todos los modelos de impresora soportados con sus características básicas.',
inputSchema: {
type: 'object' as const,
properties: {},
required: [] as string[]
}
}
]
@@ -179,6 +302,7 @@ export async function handleToolCall(toolName: string, args: Record<string, any>
name: p.name,
host: p.host,
deviceId: p.deviceId,
model: p.model || 'No especificado',
isDefault: p.isDefault,
isSelected: selected?.id === p.id
}))
@@ -403,6 +527,44 @@ export async function handleToolCall(toolName: string, args: Record<string, any>
}
}
case 'printercentral_get_printing_guide': {
const { model } = args
if (model) {
const guide = getPrintingGuide(model)
return {
content: [{
type: 'text',
text: JSON.stringify(guide, null, 2)
}]
}
} else {
// Retornar todas las guías
return {
content: [{
type: 'text',
text: JSON.stringify(PRINTING_GUIDES, null, 2)
}]
}
}
}
case 'printercentral_list_models': {
const models = Object.entries(PRINTING_GUIDES).map(([key, guide]) => ({
id: key,
model: guide.model,
type: guide.type,
columns: guide.columns,
columnsDoubleWidth: guide.columnsDoubleWidth
}))
return {
content: [{
type: 'text',
text: JSON.stringify(models, null, 2)
}]
}
}
default:
return {
content: [{

View File

@@ -8,6 +8,7 @@ export interface Printer {
name: string
host: string
deviceId: string
model?: string // Modelo de impresora (ej: "TM-U220", "TM-T88VI")
timeout: number
isDefault: boolean
createdAt: string
@@ -94,6 +95,7 @@ export async function createPrinter(data: {
name: string
host: string
deviceId: string
model?: string
timeout?: number
isDefault?: boolean
}): Promise<Printer> {
@@ -105,6 +107,7 @@ export async function createPrinter(data: {
name: data.name,
host: data.host,
deviceId: data.deviceId,
model: data.model,
timeout: data.timeout || 60000,
isDefault: data.isDefault || store.printers.length === 0, // Primera impresora es default
createdAt: now,
@@ -131,6 +134,7 @@ export async function updatePrinter(id: string, data: Partial<{
name: string
host: string
deviceId: string
model: string
timeout: number
isDefault: boolean
}>): Promise<Printer | null> {