@tecsom/apipos-js 0.0.0-stage → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/apipos.js ADDED
@@ -0,0 +1,291 @@
1
+ /*
2
+ * Cliente de Apipos, el agente local de impresión que corre en la caja
3
+ * (por defecto en http://127.0.0.1:50432). Envuelve los endpoints de
4
+ * APIPOS_API.md y crea las impresoras (`ApiPosPrinter`) que mandan los trabajos.
5
+ *
6
+ * La librería no sabe nada del POS que la usa: lo que cada app quiera hacerle al
7
+ * texto antes de imprimir (limpieza, quitar acentos) entra por el hook
8
+ * `transformContent`.
9
+ *
10
+ * Handshake de versión: cada respuesta trae `X-Apipos-Version`; si no coincide
11
+ * con la versión de la librería, avisa UNA vez por versión de Apipos: por
12
+ * defecto con `console.warn`, una vez por página; con `onVersionMismatch`, una
13
+ * vez por cliente.
14
+ */
15
+ import { base64ToBlob, isRecord, isUnknownArray, request as sendRequest, unexpectedResponse } from './http.js';
16
+ import { ApiPosPrinter } from './printer.js';
17
+ import { VERSION } from './version.js';
18
+ import { describeVersions } from './version-check.js';
19
+ const DEFAULT_BASE_URL = 'http://127.0.0.1:50432';
20
+ // El `fetch` global se busca en CADA petición, como en v1 (que llamaba a `fetch`
21
+ // directo): si alguien lo reemplaza después de crear el cliente (dobles de
22
+ // prueba, instrumentación), se usa el vigente. Se invoca como método de
23
+ // `globalThis`, así el navegador no lanza "Illegal invocation".
24
+ const globalFetch = (url, init) => globalThis.fetch(url, init);
25
+ // Default de `transformContent`: copia superficial. Quien imprime puede reusar su
26
+ // arreglo (otra copia, el reintento sin logo) y la librería nunca lo muta.
27
+ const copyContent = content => content.slice();
28
+ // Default de `onVersionMismatch`: el aviso a la consola del navegador. Busca
29
+ // `console.warn` al avisar (no al crear el cliente), así se puede interceptar.
30
+ const warnInConsole = (info) => {
31
+ console.warn(info.message);
32
+ };
33
+ // Versiones de Apipos (`String(info.backend)`; `'null'` = no la reporta) ya
34
+ // avisadas con el `console.warn` por defecto. Vive en el MÓDULO, no en cada
35
+ // cliente: una app puede crear un `new Apipos()` por impresión y el aviso debe
36
+ // salir una vez por página, no en cada ticket.
37
+ const warnedInConsole = new Set();
38
+ function isVersionStatus(value) {
39
+ return value === 'equal' || value === 'client_older' || value === 'client_newer' || value === 'unknown';
40
+ }
41
+ function isStringOrNull(value) {
42
+ return value === null || typeof value === 'string';
43
+ }
44
+ function isServerClientStatus(value) {
45
+ return (isRecord(value) &&
46
+ isStringOrNull(value.name) &&
47
+ isStringOrNull(value.version) &&
48
+ typeof value.server === 'string' &&
49
+ isVersionStatus(value.status) &&
50
+ typeof value.message === 'string');
51
+ }
52
+ // `version` y `client` no existen en Apipos anteriores a 1.0.6: su ausencia no
53
+ // es un error (justo con esos backends importa que checkHealth siga funcionando).
54
+ function isHealthData(value) {
55
+ return (isRecord(value) &&
56
+ typeof value.status === 'string' &&
57
+ typeof value.os === 'string' &&
58
+ typeof value.platform === 'string' &&
59
+ typeof value.backend === 'string' &&
60
+ typeof value.port === 'number' &&
61
+ (value.version === undefined || typeof value.version === 'string') &&
62
+ (value.client === undefined || value.client === null || isServerClientStatus(value.client)));
63
+ }
64
+ function isStringArray(value) {
65
+ return isUnknownArray(value) && value.every(item => typeof item === 'string');
66
+ }
67
+ function isFontFormat(value) {
68
+ return value === 'ttf' || value === 'otf' || value === 'ttc' || value === 'otc';
69
+ }
70
+ function isFontFace(value) {
71
+ return (isRecord(value) &&
72
+ typeof value.style === 'string' &&
73
+ typeof value.bold === 'boolean' &&
74
+ typeof value.italic === 'boolean' &&
75
+ typeof value.weight === 'number' &&
76
+ typeof value.full_name === 'string' &&
77
+ isStringOrNull(value.postscript_name) &&
78
+ typeof value.path === 'string' &&
79
+ typeof value.index === 'number' &&
80
+ isFontFormat(value.format));
81
+ }
82
+ function isFontFamily(value) {
83
+ return (isRecord(value) &&
84
+ typeof value.family === 'string' &&
85
+ typeof value.monospace === 'boolean' &&
86
+ isUnknownArray(value.faces) &&
87
+ value.faces.every(isFontFace));
88
+ }
89
+ /** Forma de `data` de GET /fonts (APIPOS_API.md, "Fuentes del sistema"). */
90
+ function isFontCatalogData(value) {
91
+ return (isRecord(value) &&
92
+ isUnknownArray(value.families) &&
93
+ value.families.every(isFontFamily) &&
94
+ typeof value.count === 'number' &&
95
+ isStringArray(value.builtin) &&
96
+ isStringArray(value.dirs) &&
97
+ typeof value.scanned_at === 'string');
98
+ }
99
+ function isRenderFormat(value) {
100
+ return value === 'pdf' || value === 'png';
101
+ }
102
+ function isFontSource(value) {
103
+ return value === 'system' || value === 'builtin' || value === 'fallback';
104
+ }
105
+ function isRenderedFont(value) {
106
+ return (isRecord(value) &&
107
+ typeof value.family === 'string' &&
108
+ typeof value.resolved === 'string' &&
109
+ isFontSource(value.source) &&
110
+ typeof value.bold === 'boolean' &&
111
+ typeof value.italic === 'boolean');
112
+ }
113
+ function isPageSizeMm(value) {
114
+ return isRecord(value) && typeof value.width === 'number' && typeof value.height === 'number';
115
+ }
116
+ /** Forma de `data` de POST /render (APIPOS_API.md, "Vista previa"). `dpi` es `null` con `pdf`. */
117
+ function isRenderData(value) {
118
+ return (isRecord(value) &&
119
+ isRenderFormat(value.format) &&
120
+ (value.dpi === null || typeof value.dpi === 'number') &&
121
+ typeof value.base64 === 'string' &&
122
+ isPageSizeMm(value.page_mm) &&
123
+ typeof value.pages === 'number' &&
124
+ isUnknownArray(value.fonts) &&
125
+ value.fonts.every(isRenderedFont) &&
126
+ isStringArray(value.warnings));
127
+ }
128
+ // Mime del `blob` de una vista previa según el formato que devolvió Apipos.
129
+ const RENDER_MIME = { pdf: 'application/pdf', png: 'image/png' };
130
+ export class Apipos {
131
+ #baseUrl;
132
+ #fetch;
133
+ #transformContent;
134
+ #onVersionMismatch;
135
+ // Con `onVersionMismatch` inyectado, las versiones ya avisadas son de ESTE
136
+ // cliente: quien inyecta el hook controla su propia política.
137
+ #warnedBackends = new Set();
138
+ constructor(options = {}) {
139
+ this.#baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
140
+ this.#fetch = options.fetch ?? globalFetch;
141
+ this.#transformContent = options.transformContent ?? copyContent;
142
+ this.#onVersionMismatch = options.onVersionMismatch ?? null;
143
+ }
144
+ /** URL base de Apipos (default `http://127.0.0.1:50432`). */
145
+ get baseUrl() {
146
+ return this.#baseUrl;
147
+ }
148
+ /** Alias de `baseUrl` (compatibilidad con v1). */
149
+ get apiUrl() {
150
+ return this.#baseUrl;
151
+ }
152
+ /** Compatibilidad con v1: cambia la URL base para las siguientes peticiones. */
153
+ set apiUrl(url) {
154
+ this.#baseUrl = url;
155
+ }
156
+ /**
157
+ * Verifica que el servicio de impresión esté disponible.
158
+ * GET /health → { data: { status, version, os, platform, backend, port, client }, message, status }
159
+ *
160
+ * Devuelve `{ ok, data, version }`: `ok` es `true` si el servicio responde
161
+ * "ok" y `version` = `describeVersions(VERSION, data.version)`
162
+ * (`{ client, backend, status, message }`).
163
+ *
164
+ * `data.version` pasa por el MISMO aviso deduplicado que el header: si el
165
+ * header ya avisó esa versión no se repite, y si la respuesta no trajo
166
+ * `headers` (dobles de `fetch`) el aviso sale de aquí.
167
+ *
168
+ * Nunca lanza: ante cualquier error devuelve `{ ok: false, data: null, version: null }`.
169
+ */
170
+ async checkHealth() {
171
+ try {
172
+ const data = await this.request('GET', '/health');
173
+ if (!isHealthData(data))
174
+ throw unexpectedResponse('/health');
175
+ const version = describeVersions(VERSION, data.version ?? null);
176
+ this.#notifyVersion(version);
177
+ return { ok: data.status === 'ok', data, version };
178
+ }
179
+ catch (e) {
180
+ console.error('Error checking Apipos health:', e);
181
+ return { ok: false, data: null, version: null };
182
+ }
183
+ }
184
+ /** GET /printers → nombres exactos de las impresoras instaladas; `[]` si falla. */
185
+ async listPrinters() {
186
+ try {
187
+ const data = await this.request('GET', '/printers');
188
+ if (!isStringArray(data))
189
+ throw unexpectedResponse('/printers');
190
+ return data;
191
+ }
192
+ catch (e) {
193
+ console.error('Error fetching printers:', e);
194
+ return [];
195
+ }
196
+ }
197
+ /**
198
+ * Fuentes instaladas en la máquina de Apipos, para elegir `settings.font.family`
199
+ * o `font_family` del renderizador PDF.
200
+ * GET /fonts (con `refresh: true` → GET /fonts?refresh=1, que vuelve a
201
+ * escanear; útil tras instalar una fuente).
202
+ *
203
+ * Devuelve `{ families, builtin, count, dirs, scannedAt }`: `builtin`
204
+ * (`sans-serif`, `serif`, `monospace`) existe siempre, aunque la máquina no
205
+ * tenga fuentes. A diferencia de `listPrinters`, LANZA ante cualquier error
206
+ * (`ApiposError`, sin conexión o `Respuesta inesperada de Apipos en /fonts`).
207
+ */
208
+ async listFonts({ refresh = false } = {}) {
209
+ const data = await this.request('GET', '/fonts', refresh ? { query: { refresh: true } } : {});
210
+ if (!isFontCatalogData(data))
211
+ throw unexpectedResponse('/fonts');
212
+ return {
213
+ families: data.families,
214
+ builtin: data.builtin,
215
+ count: data.count,
216
+ dirs: data.dirs,
217
+ scannedAt: data.scanned_at
218
+ };
219
+ }
220
+ /**
221
+ * Vista previa REAL de un ticket: Apipos lo maqueta con el mismo renderizador
222
+ * PDF que imprime (`settings.renderer: 'pdf'`), sin validar ni tocar ninguna
223
+ * impresora. POST /render con `{ settings, content, format, dpi }`.
224
+ *
225
+ * `content` pasa por `transformContent`, igual que al imprimir: lo que se ve
226
+ * es lo que saldría en papel. `dpi` solo aplica con `png` (72–600).
227
+ *
228
+ * Devuelve `{ format, dpi, base64, pageMm, pages, fonts, warnings, blob }`;
229
+ * `blob` es un `Blob` `application/pdf` o `image/png` listo para
230
+ * `URL.createObjectURL`, y `dpi` es `null` con `pdf`. Lanza ante cualquier error.
231
+ */
232
+ async renderTicket(content, { settings = {}, format = 'pdf', dpi = 203 } = {}) {
233
+ const data = await this.request('POST', '/render', {
234
+ body: { settings, content: this.applyTransform(content), format, dpi }
235
+ });
236
+ if (!isRenderData(data))
237
+ throw unexpectedResponse('/render');
238
+ return {
239
+ format: data.format,
240
+ dpi: data.dpi,
241
+ base64: data.base64,
242
+ pageMm: data.page_mm,
243
+ pages: data.pages,
244
+ fonts: data.fonts,
245
+ warnings: data.warnings,
246
+ blob: base64ToBlob(data.base64, RENDER_MIME[data.format])
247
+ };
248
+ }
249
+ /**
250
+ * Impresora con nombre exacto (de `listPrinters()`) y sus `settings`
251
+ * (`paper_size` numérico obligatorio). Sin nombre devuelve `null` y lo
252
+ * reporta en consola, como v1.
253
+ */
254
+ createPrinter(name, settings = {}) {
255
+ if (!name) {
256
+ console.error('Printer name is required');
257
+ return null;
258
+ }
259
+ return new ApiPosPrinter(name, settings, this);
260
+ }
261
+ /**
262
+ * Acceso a la red para `ApiPosPrinter` (y para endpoints que la librería aún
263
+ * no envuelve): devuelve el `data` del sobre o lanza `ApiposError`.
264
+ */
265
+ request(method, path, options) {
266
+ return sendRequest({ baseUrl: this.#baseUrl, fetch: this.#fetch, onVersion: info => this.#notifyVersion(info) }, method, path, options);
267
+ }
268
+ /** Pasa el `content` de un trabajo por `transformContent`. */
269
+ applyTransform(content) {
270
+ return this.#transformContent(content);
271
+ }
272
+ // Aviso de versión: solo si difieren y una vez por versión de Apipos (por
273
+ // página con el aviso por defecto; por cliente con `onVersionMismatch`). Es
274
+ // informativo: si `onVersionMismatch` lanza, se reporta y la petición (la
275
+ // impresión) sigue su curso.
276
+ #notifyVersion(info) {
277
+ if (info.status === 'equal')
278
+ return;
279
+ const key = String(info.backend);
280
+ const warned = this.#onVersionMismatch ? this.#warnedBackends : warnedInConsole;
281
+ if (warned.has(key))
282
+ return;
283
+ warned.add(key);
284
+ try {
285
+ (this.#onVersionMismatch ?? warnInConsole)(info);
286
+ }
287
+ catch (e) {
288
+ console.error('Error in Apipos onVersionMismatch:', e);
289
+ }
290
+ }
291
+ }
package/dist/http.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { HttpMethod, RequestContext, RequestOptions } from './types.js';
2
+ export declare const CLIENT_HEADER = "X-Apipos-Client";
3
+ export declare const VERSION_HEADER = "X-Apipos-Version";
4
+ export declare const CONNECT_ERROR: (baseUrl: string) => string;
5
+ /**
6
+ * Error de negocio que respondió Apipos (`status: 'error'` o HTTP no-2xx).
7
+ * `message` es el del backend (p. ej. `Printer 'X' not found.`) y `data` lo que
8
+ * traiga el sobre (p. ej. `{ available: [...] }` con las impresoras válidas).
9
+ * Sigue siendo `instanceof Error`.
10
+ */
11
+ export declare class ApiposError extends Error {
12
+ data: unknown;
13
+ constructor(message: string, data?: unknown);
14
+ }
15
+ /** Objeto JSON (no `null`). Los arreglos también pasan: se leen sus claves como `undefined`. */
16
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
17
+ /** `Array.isArray` sin dejar escapar elementos sin tipo. */
18
+ export declare function isUnknownArray(value: unknown): value is readonly unknown[];
19
+ /** Error para cuando Apipos respondió éxito pero con una forma que no es la del contrato. */
20
+ export declare function unexpectedResponse(path: string): Error;
21
+ /**
22
+ * base64 (puro o data URI `data:<mime>;base64,...`) → `Blob` del `mime` dado
23
+ * (el del data URI se descarta). Usa `atob`, que existe en el navegador y en
24
+ * Node: así lo que devuelve `POST /render` se puede abrir, descargar o mostrar
25
+ * con `URL.createObjectURL(blob)`. Un base64 inválido lanza el error de `atob`.
26
+ */
27
+ export declare function base64ToBlob(base64: string, mime: string): Blob;
28
+ /**
29
+ * Único punto de red de la librería. Manda `X-Apipos-Client: apipos-js/<VERSION>`,
30
+ * evalúa `X-Apipos-Version` (handshake: `ctx.onVersion`), interpreta el sobre
31
+ * `{ status, data, message }` y devuelve `data` (o `null`), o lanza
32
+ * `ApiposError(message)` con `error.data` (p. ej. `available`).
33
+ *
34
+ * @param path p. ej. `'/print'`
35
+ */
36
+ export declare function request(ctx: RequestContext, method: HttpMethod, path: string, { body, query }?: RequestOptions): Promise<unknown>;
package/dist/http.js ADDED
@@ -0,0 +1,113 @@
1
+ /*
2
+ * Único punto de red de la librería: todo lo que habla con Apipos pasa por
3
+ * `request()`. Aquí viven el header del cliente, el handshake de versión, el
4
+ * manejo del sobre `{ status, data, message }` y los mensajes de error que ve
5
+ * el usuario del POS.
6
+ */
7
+ import { CLIENT_NAME, VERSION } from './version.js';
8
+ import { describeVersions } from './version-check.js';
9
+ export const CLIENT_HEADER = 'X-Apipos-Client';
10
+ export const VERSION_HEADER = 'X-Apipos-Version';
11
+ export const CONNECT_ERROR = (baseUrl) => `No se pudo conectar con Apipos (${baseUrl}). ¿Está abierta la app de bandeja?`;
12
+ /**
13
+ * Error de negocio que respondió Apipos (`status: 'error'` o HTTP no-2xx).
14
+ * `message` es el del backend (p. ej. `Printer 'X' not found.`) y `data` lo que
15
+ * traiga el sobre (p. ej. `{ available: [...] }` con las impresoras válidas).
16
+ * Sigue siendo `instanceof Error`.
17
+ */
18
+ export class ApiposError extends Error {
19
+ data;
20
+ constructor(message, data = null) {
21
+ super(message);
22
+ this.name = 'ApiposError';
23
+ this.data = data;
24
+ }
25
+ }
26
+ /** Objeto JSON (no `null`). Los arreglos también pasan: se leen sus claves como `undefined`. */
27
+ export function isRecord(value) {
28
+ return typeof value === 'object' && value !== null;
29
+ }
30
+ /** `Array.isArray` sin dejar escapar elementos sin tipo. */
31
+ export function isUnknownArray(value) {
32
+ return Array.isArray(value);
33
+ }
34
+ /** Error para cuando Apipos respondió éxito pero con una forma que no es la del contrato. */
35
+ export function unexpectedResponse(path) {
36
+ return new Error(`Respuesta inesperada de Apipos en ${path}`);
37
+ }
38
+ // Prefijo de un data URI en base64 (`data:image/png;base64,`), con o sin mime.
39
+ const DATA_URI_PREFIX = /^data:[^,]*;base64,/i;
40
+ /**
41
+ * base64 (puro o data URI `data:<mime>;base64,...`) → `Blob` del `mime` dado
42
+ * (el del data URI se descarta). Usa `atob`, que existe en el navegador y en
43
+ * Node: así lo que devuelve `POST /render` se puede abrir, descargar o mostrar
44
+ * con `URL.createObjectURL(blob)`. Un base64 inválido lanza el error de `atob`.
45
+ */
46
+ export function base64ToBlob(base64, mime) {
47
+ const binary = atob(base64.replace(DATA_URI_PREFIX, ''));
48
+ const bytes = new Uint8Array(binary.length);
49
+ for (let index = 0; index < binary.length; index++) {
50
+ bytes[index] = binary.charCodeAt(index);
51
+ }
52
+ return new Blob([bytes], { type: mime });
53
+ }
54
+ function queryString(query) {
55
+ if (!query)
56
+ return '';
57
+ const params = new URLSearchParams();
58
+ for (const [key, value] of Object.entries(query)) {
59
+ if (value === null || value === undefined)
60
+ continue;
61
+ params.append(key, typeof value === 'boolean' ? (value ? '1' : '0') : String(value));
62
+ }
63
+ const text = params.toString();
64
+ return text ? `?${text}` : '';
65
+ }
66
+ /**
67
+ * Único punto de red de la librería. Manda `X-Apipos-Client: apipos-js/<VERSION>`,
68
+ * evalúa `X-Apipos-Version` (handshake: `ctx.onVersion`), interpreta el sobre
69
+ * `{ status, data, message }` y devuelve `data` (o `null`), o lanza
70
+ * `ApiposError(message)` con `error.data` (p. ej. `available`).
71
+ *
72
+ * @param path p. ej. `'/print'`
73
+ */
74
+ export async function request(ctx, method, path, { body, query } = {}) {
75
+ const headers = { [CLIENT_HEADER]: `${CLIENT_NAME}/${VERSION}` };
76
+ const init = { method, headers };
77
+ if (body !== undefined) {
78
+ headers['Content-Type'] = 'application/json';
79
+ init.body = JSON.stringify(body);
80
+ }
81
+ // Se llama suelta (no como `ctx.fetch(...)`): un `fetch` nativo inyectado tal
82
+ // cual (`{ fetch: window.fetch }`) falla con "Illegal invocation" si recibe
83
+ // otro objeto como `this`.
84
+ const send = ctx.fetch;
85
+ let response;
86
+ try {
87
+ response = await send(ctx.baseUrl + path + queryString(query), init);
88
+ }
89
+ catch {
90
+ throw new Error(CONNECT_ERROR(ctx.baseUrl));
91
+ }
92
+ let payload = null;
93
+ try {
94
+ payload = await response.json();
95
+ }
96
+ catch {
97
+ // respuesta sin cuerpo JSON
98
+ }
99
+ // Handshake: TODA respuesta de Apipos ≥ 1.0.6 trae `X-Apipos-Version` (también
100
+ // las de error, por eso va antes de lanzar). Sin el header → `unknown`: es un
101
+ // Apipos anterior. Si la respuesta no trae `headers` (dobles de `fetch` de
102
+ // pruebas) no hay nada que comparar y no se evalúa.
103
+ if (response.headers) {
104
+ ctx.onVersion?.(describeVersions(VERSION, response.headers.get(VERSION_HEADER)));
105
+ }
106
+ const envelope = isRecord(payload) ? payload : null;
107
+ const data = envelope?.data ?? null;
108
+ if (!response.ok || envelope?.status === 'error') {
109
+ const message = envelope?.message;
110
+ throw new ApiposError((typeof message === 'string' && message) || response.statusText || 'Error de impresión', data);
111
+ }
112
+ return data;
113
+ }
@@ -0,0 +1,8 @@
1
+ export { Apipos } from './apipos.js';
2
+ export { ApiPosPrinter } from './printer.js';
3
+ export { VERSION, CLIENT_NAME } from './version.js';
4
+ export { parseVersion, compareVersions, describeVersions } from './version-check.js';
5
+ export { ApiposError, base64ToBlob } from './http.js';
6
+ export { stripAccents, stripAccentsFromContent } from './strip-accents.js';
7
+ export { ticketColumns, TICKET_PREVIEW_CSS, renderTicketHtml, printTicketHtml, renderTicketPreviewPng } from './ticket-preview.js';
8
+ export type * from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ // API pública de @tecsom/apipos-js. Lo que no se exporta aquí (ni en los
2
+ // subpaths de package.json) es interno y puede cambiar sin aviso.
3
+ export { Apipos } from './apipos.js';
4
+ export { ApiPosPrinter } from './printer.js';
5
+ export { VERSION, CLIENT_NAME } from './version.js';
6
+ export { parseVersion, compareVersions, describeVersions } from './version-check.js';
7
+ export { ApiposError, base64ToBlob } from './http.js';
8
+ export { stripAccents, stripAccentsFromContent } from './strip-accents.js';
9
+ export { ticketColumns, TICKET_PREVIEW_CSS, renderTicketHtml, printTicketHtml, renderTicketPreviewPng } from './ticket-preview.js';
@@ -0,0 +1,61 @@
1
+ import type { Apipos } from './apipos.js';
2
+ import type { OpenDrawerResult, PrinterRenderOptions, PrintOptions, PrintPdfOptions, PrintResult, PrintSettings, RenderResult, TicketContent } from './types.js';
3
+ export declare class ApiPosPrinter {
4
+ #private;
5
+ settings: PrintSettings;
6
+ /**
7
+ * Valida `settings.paper_size` (number, obligatorio): lanza
8
+ * `Missing required setting: paper_size` o
9
+ * `Invalid type for setting paper_size: expected number, got <tipo>`.
10
+ */
11
+ constructor(name: string, settings: PrintSettings | undefined, apipos: Apipos);
12
+ validateSettings(): void;
13
+ get name(): string;
14
+ get apiUrl(): string;
15
+ /**
16
+ * Trabajo RAW: `content` es la lista de elementos del ticket.
17
+ *
18
+ * Lo que haya que hacerle al texto (p. ej. una limpieza base o un
19
+ * interruptor por dispositivo "sin acentos ni ñ") lo aplica el
20
+ * `transformContent` del cliente en CADA trabajo, no al crear la impresora:
21
+ * así un cambio de ajustes vale desde el siguiente ticket sin recargar.
22
+ *
23
+ * SIN `options` (o con `{}`) el payload es el de v1, byte a byte:
24
+ * `{ printer, type: 'RAW', settings: this.settings, content }`.
25
+ *
26
+ * Con `options` se agregan claves SOLO dentro de `settings` (nunca al nivel
27
+ * raíz) y `this.settings` no se modifica:
28
+ * renderer → renderer ('escpos' | 'pdf'; con 'pdf' Apipos maqueta el ticket
29
+ * como PDF con fuentes del sistema y lo imprime por la ruta PDF)
30
+ * pdfMode → pdf_mode ; font → font ({ family, size, line_height })
31
+ * marginsMm → margins_mm ; widthMm → width_mm ; maxHeightMm → max_height_mm
32
+ * cut → cut ; settings → se mezcla al final y gana (claves nuevas del backend)
33
+ * Las opciones `undefined` no viajan.
34
+ */
35
+ print(content: TicketContent, options?: PrintOptions): Promise<PrintResult | null>;
36
+ /**
37
+ * Abre el cajón de dinero conectado a ESTA impresora, sin imprimir nada.
38
+ * POST /open-withdrawer con `{ printer }` → `{ printer }`. Lanza
39
+ * `ApiposError` si Apipos responde error (p. ej. `Printer 'X' not found.`).
40
+ */
41
+ openDrawer(): Promise<OpenDrawerResult>;
42
+ /**
43
+ * Vista previa con los settings de ESTA impresora y las mismas `options` que
44
+ * `print()`, con el mismo mapeo a `settings`:
45
+ * `apipos.renderTicket(content, { settings: { ...this.settings, ...mapeo(options) }, format, dpi })`.
46
+ * No imprime ni valida la impresora; `this.settings` no se modifica. Default
47
+ * `png` a 203 dpi (la resolución de una tiquetera: 72 mm → 576 px de ancho).
48
+ */
49
+ render(content: TicketContent, { format, dpi, ...options }?: PrinterRenderOptions): Promise<RenderResult>;
50
+ /**
51
+ * Trabajo PDF: en el navegador el PDF es un Blob; se envía en base64.
52
+ * Opciones:
53
+ * pdfMode: 'driver' (el driver de la impresora renderiza el PDF — el modo
54
+ * correcto para etiquetas, ej. AiYin) | 'raw' (rasteriza a ESC/POS, para
55
+ * tiqueteras de recibos). Default del servidor: 'driver' si se mandan
56
+ * labelSettings, 'raw' si no.
57
+ * labelSettings: { width, height, unit: 'mm'|'inch' } — tamaño de la
58
+ * etiqueta; la página del PDF debe medir lo mismo.
59
+ */
60
+ printPDF(blob: Blob, { pdfMode, labelSettings }?: PrintPdfOptions): Promise<PrintResult | null>;
61
+ }