Listo para release

This commit is contained in:
2026-03-17 12:40:03 +01:00
parent f076ee1b83
commit af0f283926
10 changed files with 294 additions and 63 deletions

View File

@@ -1,4 +1,5 @@
{
"defultServer": "http://localhost:3000",
"defaultPrinter": "192.168.1.254"
"defaultServer": "http://localhost:3000",
"defaultPrinterIp": "192.168.1.254",
"defaultPrinterPort": 9100
}

View File

@@ -25,6 +25,7 @@ export class LogService {
this.info(`--- Log initialized at ${now.toISOString()} ---`);
console.log(`Log file: ${this.logPath}`);
}
/**
* Garantizar que sea singleton
* @returns

View File

@@ -67,7 +67,7 @@ export async function printReqHandler(
logger.logOperation("printReq", data);
return new Promise((res, rej) => {
console.log("nfc:printReq", data);
const port = 9100;
const port = data.printerPort;
let finished = false;
const socket = createConnection(port, data.printerURL);

View File

@@ -5,6 +5,7 @@ import icon from "../../resources/icon.png?asset";
import { NfcService } from "../services/NfcService";
import { labelReqHandler, printReqHandler } from "./handlers";
import { logger } from "./LogService";
import { createConnection } from "net";
function createWindow(): void {
// Create the browser window.
@@ -87,6 +88,41 @@ app.whenReady().then(() => {
}
});
ipcMain.handle(
"ping:socket",
async (_event, data: { ip: string; port: number }) => {
return new Promise((req, rej) => {
const { ip, port } = data;
let finished = false;
console.log("Pinging,", ip, port);
const socket = createConnection(port, ip);
socket.setTimeout(3 * 1000);
socket.on("connect", (e) => {
console.log("Conectado!", e);
logger.info("Printer connected, sending label...");
socket.end();
finished = true;
req(true);
});
socket.on("timeout", (err) => {
if (finished) return;
finished = true;
socket.destroy();
rej(false);
});
socket.on("error", (err) => {
if (finished) return;
finished = true;
socket.destroy();
rej(false);
});
});
},
);
createWindow();
app.on("activate", function () {

View File

@@ -19,6 +19,7 @@ export type CodeRequest = {
export type PrinterRequest = {
printerURL: string;
printerPort: number;
label: string;
};
@@ -31,10 +32,12 @@ export interface NfcAPI {
onRemoved: (callback: (event: { uid: string }) => void) => void;
onError: (callback: (event: { message: string }) => void) => void;
ping: (url: string) => Promise<boolean>;
pingSocket: (args: { ip: string; port: number }) => Promise<boolean>;
removeAllListeners: () => void;
labelReq: (args: CodeRequest) => Promise<Result<string, CodeResponse>>;
printReq: (args: {
printerURL: string;
printerPort: number;
label: string;
}) => Promise<PrinterResponse>;
}

View File

@@ -29,6 +29,9 @@ const api = {
ping: (url: string): Promise<boolean> => {
return ipcRenderer.invoke("ping:url", url);
},
pingSocket: (args: { ip: string; port: number }): Promise<boolean> => {
return ipcRenderer.invoke("ping:socket", args);
},
removeAllListeners: (): void => {
ipcRenderer.removeAllListeners("nfc:reader");
ipcRenderer.removeAllListeners("nfc:tag");

View File

@@ -18,13 +18,13 @@ const navigateTo = (view: "main" | "config"): void => {
<div class="logo-text">NFC.INTERFACE.v1</div>
<div class="technical-info">READY // LOCAL_ACCESS</div>
</div>
<nav class="nav-section">
<button v-if="currentView === 'main'" class="nav-btn" @click="navigateTo('config')">
CONFIG
</button>
<button v-else class="nav-btn" @click="navigateTo('main')">
EXIT_CONFIG
SALIR_CONFIG
</button>
</nav>
</header>

View File

@@ -1,10 +1,11 @@
<script setup lang="ts">
import { ref } from "vue";
import { getConfig, getDefaultConfig } from "../utils/config";
const printerUrl = ref(localStorage.getItem("printerUrl") || "192.168.1.254");
const serverUrl = ref(
localStorage.getItem("serverUrl") || "http://localhost:3000",
);
const config = getConfig();
const printerUrl = ref(config.printerIp);
const printerPort = ref(config.printerPort);
const serverUrl = ref(config.serverUrl);
const errorMsg = ref("");
const isSuccess = ref(false);
const pingStatus = ref<{
@@ -14,22 +15,12 @@ const pingStatus = ref<{
server: "idle",
});
const validateIpOrUrl = (value: string): boolean => {
if (!value) return false;
// Basic IP regex
const ipRegex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
// Basic URL regex
const urlRegex = /^(http|https):\/\/[^ "]+$/;
return ipRegex.test(value) || urlRegex.test(value);
};
const handleSave = (): void => {
errorMsg.value = "";
isSuccess.value = false;
localStorage.setItem("printerUrl", printerUrl.value);
localStorage.setItem("printerIp", printerUrl.value);
localStorage.setItem("printerPort", printerPort.value.toString());
localStorage.setItem("serverUrl", serverUrl.value);
isSuccess.value = true;
@@ -38,15 +29,29 @@ const handleSave = (): void => {
}, 2000);
};
const handleReset = (): void => {
const defaults = getDefaultConfig();
printerUrl.value = defaults.printerIp;
printerPort.value = defaults.printerPort;
serverUrl.value = defaults.serverUrl;
};
const performPing = async (type: "printer" | "server"): Promise<void> => {
errorMsg.value = "";
isSuccess.value = false;
const url = type === "printer" ? printerUrl.value : serverUrl.value;
const ip = printerUrl.value;
const port = printerPort.value;
pingStatus.value[type] = "pinging";
try {
const ok = await window.api.nfc.ping(url);
const promise =
type == "printer"
? window.api.nfc.pingSocket({ ip, port })
: window.api.nfc.ping(url);
const ok = await promise;
pingStatus.value[type] = ok ? "success" : "error";
if (!ok) {
errorMsg.value = `${type === "printer" ? "Printer" : "Server"} is unreachable`;
@@ -85,7 +90,7 @@ const handlePingServer = (): void => {
<div class="config-item">
<div class="item-meta">
<span class="item-index">01</span>
<label for="printer-url">PRINTER_ADDRESS</label>
<label for="printer-url">IP_IMPRESORA</label>
</div>
<div class="input-with-action">
<input
@@ -110,11 +115,27 @@ const handlePingServer = (): void => {
</div>
</div>
<!-- Server Config -->
<!-- Printer Port Config -->
<div class="config-item">
<div class="item-meta">
<span class="item-index">02</span>
<label for="server-url">GATEWAY_SERVER</label>
<label for="printer-port">PUERTO_IMPRESORA</label>
</div>
<div class="input-with-action">
<input
id="printer-port"
v-model.number="printerPort"
type="number"
placeholder="PORT"
/>
</div>
</div>
<!-- Server Config -->
<div class="config-item">
<div class="item-meta">
<span class="item-index">03</span>
<label for="server-url">URL_SERVIDOR</label>
</div>
<div class="input-with-action">
<input
@@ -135,7 +156,7 @@ const handlePingServer = (): void => {
v-if="pingStatus.server === 'success'"
class="field-status success"
>
REACHABLE
CONECTADO
</div>
<div
v-if="pingStatus.server === 'error'"
@@ -152,7 +173,10 @@ const handlePingServer = (): void => {
<div class="panel-actions">
<button class="save-btn" @click="handleSave">
WRITE_TO_MEMORY
GUARDAR CAMBIOS
</button>
<button class="reset-btn" @click="handleReset">
RESETEAR CONFIGURACIÓN
</button>
<span v-if="isSuccess" class="write-success">WRITE_OK</span>
</div>
@@ -290,6 +314,25 @@ input:focus {
opacity: 0.8;
}
.reset-btn {
appearance: none;
background-color: transparent;
border: 1px solid var(--te-gray);
color: var(--te-gray);
padding: 10px 24px;
font-size: 11px;
font-weight: 700;
}
.reset-btn:hover {
border-color: var(--te-red);
color: var(--te-red);
}
.reset-btn:active {
opacity: 0.6;
}
.write-success {
font-family: var(--font-mono);
font-size: 10px;

View File

@@ -1,22 +1,28 @@
<script setup lang="ts">
import { type CodeResponse } from "src/preload/index.d.js";
import { tryCatch } from "../../../types/Result.js";
import { Result, tryCatch } from "../../../types/Result.js";
import { ref, onMounted, onUnmounted } from "vue";
import { getConfig } from "../utils/config";
/**
* waiting = esperando a leer una tarjeta (igual es mejor ready)
* reading = proceso de lectura
* sucess = se ha leido la tarjeta y sigue en el lector
* error
*/
const readerState = ref<"waiting" | "reading" | "success" | "error">("waiting");
const readerState = ref<
"waiting" | "reading" | "success" | "printing" | "error"
>("waiting");
const uid = ref<string | null>(null);
const errorMsg = ref<string | null>(null);
const labelOutputStructured = ref<CodeResponse | null>(null);
// Mensaje actual, no se reseta hasta la siguiente llamada
const currentLabel = ref<CodeResponse | null>(null);
// Nombre del lector de nfc o "OFFLINE" si no está conectado
const readerName = ref<string>("OFFLINE");
// Lista de erroes para mostrar errores de conexion o parecido.
// De momento está un poco crudo.
const errors = ref<string[]>([]);
onMounted(async () => {
// Get initial reader name
readerName.value = await window.api.nfc.getReaderName();
window.api.nfc.onReader((event) => {
@@ -71,7 +77,6 @@ const mockReadCard = (): void => {
}, 1000);
};
const readCard = async (): Promise<void> => {
// Reset de los errores anteriores
errors.value = [];
@@ -79,41 +84,106 @@ const readCard = async (): Promise<void> => {
if (readerState.value !== "waiting") return;
readerState.value = "reading";
const serverURL =
localStorage.getItem("serverUrl") || "http://localhost:3000";
const printerURL = localStorage.getItem("printerUrl") || "192.168.1.254";
// TODO: De momento está hardcodeado porque no se puede leer la tarjeta
const card_id = "019cdd39-fc08-7417-b16d-a78794a24c01";
const override = false;
const res = await loadLabel({
card_id,
override,
});
if (res.error != undefined) {
return;
}
const labelData = res.data.data;
if (labelData.reused == true) {
// Si la etiqueta ya ha sido impresa,
// se espera confiramcion de volver a imprimir o generar una nueva
return;
}
await printCurrentLabel();
delayWaiting();
};
/**
* Solicita un nuevo código, se puede solicitar sobreescribir el anterior
* No se guarda el resultado.
* No se imprime.
* TODO:
* - Si crece meter estas llamadas en un repositorio
*/
const requestLabel = async (args: {
override: boolean;
card_id: string;
}): Promise<Result<string, CodeResponse>> => {
const { serverUrl: serverURL } = getConfig();
const { override, card_id } = args;
const codePromise = window.api.nfc.labelReq({
serverURL,
card_id,
override,
});
const res = await tryCatch(codePromise);
const res = await codePromise;
console.log("RES IPC:", res);
return res;
};
/**
* Solicita una etiqueta y la carga en `currentLabel`.
* No se imprime.
* @param args
*/
const loadLabel = async (args: {
card_id: string;
override: boolean;
}): Promise<Result<string, CodeResponse>> => {
readerState.value = "reading";
const label = await requestLabel(args);
// Error de la llamada -> sin conexion al servidor
if (res.error != undefined) {
errors.value = [...errors.value, String(res.error)];
delayWaiting();
return;
if (label.error != undefined) {
errors.value = [...errors.value, String(label.error)];
}
// Error que viene del servidor
if (res.data?.error != undefined) {
errors.value = [...errors.value, String(res.data.error)];
delayWaiting();
if (label.data?.error != undefined) {
errors.value = [...errors.value, String(label.data.error)];
}
const labeldata = label.data;
if (labeldata != undefined) currentLabel.value = labeldata;
delayWaiting();
return label;
};
/**
* Imprime la etiqueta que se ha solicitado y esta almacenada
* en currentLabel.
*/
const printCurrentLabel = async (): Promise<void> => {
// Doble check por si acaso
readerState.value = "printing";
if (
currentLabel.value == undefined ||
currentLabel.value.data == undefined
) {
console.error("Error currentLabel", currentLabel);
return;
}
const labelData = res.data.data.data;
labelOutputStructured.value = res.data.data;
console.log("label data", labelData);
const labelData = currentLabel.value.data;
const { printerIp: printerURL, printerPort } = getConfig();
const impPromise = window.api.nfc.printReq({
printerURL,
printerPort,
label: labelData.label,
});
@@ -125,17 +195,36 @@ const readCard = async (): Promise<void> => {
errorStr = "Error de conexión con la impresora";
}
errors.value = [...errors.value, errorStr];
delayWaiting();
}
delayWaiting();
};
const generateNewLabelAndPrint = async (args: {
override?: boolean;
}): Promise<void> => {
// TODO: De momento está hardcodeado porque no se puede leer la tarjeta
const card_id = "019cdd39-fc08-7417-b16d-a78794a24c01";
const override = args.override ?? false;
const labelRes = await loadLabel({
card_id,
override,
});
if (labelRes.error != undefined) {
console.error(labelRes.error);
}
delayWaiting();
await printCurrentLabel();
return;
};
const delayWaiting = (): void => {
setTimeout((): void => {
readerState.value = "waiting";
errorMsg.value = null;
}, 3000);
}, 1000);
};
const triggerError = (): void => {
@@ -148,6 +237,7 @@ const triggerError = (): void => {
<template>
<div class="main-display">
<div class="flex-row grow">
<!-- Lector -->
<div class="display-container bg-dots">
<div class="display-grid"></div>
@@ -170,7 +260,19 @@ const triggerError = (): void => {
class="reading-state"
>
<div class="reading-indicator"></div>
<h1 class="status-msg reading">PROCESSING_DATA...</h1>
<h1 class="status-msg reading">
PROCESANDO TARJETA...
</h1>
</div>
<div
v-else-if="readerState === 'printing'"
class="reading-state"
>
<div class="reading-indicator"></div>
<h1 class="status-msg reading">
IMPRIMIENDO ETIQUETA...
</h1>
</div>
<div
@@ -190,13 +292,14 @@ const triggerError = (): void => {
class="error-state"
>
<div class="error-indicator"></div>
<h1 class="status-msg error">INTERFACE_ERROR</h1>
<h1 class="status-msg error">ERROR DEL LECTOR</h1>
<p class="error-desc">
{{ errorMsg || "UNKNOWN_FAILURE" }}
{{ errorMsg || "ERROR DESCONOCIDO" }}
</p>
</div>
</div>
</div>
<!-- Salida por pantalla de las respuestas del servidor y la impresora -->
<div class="display-container bg-dots">
<!-- ERRORES -->
@@ -205,31 +308,40 @@ const triggerError = (): void => {
<span>Error: {{ error }}</span>
</span>
</div>
<div v-if="labelOutputStructured != undefined" class="flex-col">
<div v-if="currentLabel != undefined" class="flex-col">
<!-- Errores -->
<span
v-if="labelOutputStructured.error != undefined"
v-if="currentLabel.error != undefined"
class="error"
>
{{ JSON.stringify(labelOutputStructured.error) }}
{{ JSON.stringify(currentLabel.error) }}
</span>
<!-- Datos -->
<span> Código {{ labelOutputStructured.data.code }} </span>
<span> Código {{ currentLabel.data.code }} </span>
<span>
Etiqueta {{ labelOutputStructured.data.label }}
Etiqueta {{ currentLabel.data.label }}
</span>
<span v-if="labelOutputStructured.data.reused == true">
YA SE HA IMPRIMIDO
<span v-if="currentLabel.data.reused == true">
YA SE HA IMPRESO ANTERIORMENTE, CONFIRMA REIMPRESION
</span>
<span v-if="labelOutputStructured.data.overriden == true">
SOBREESCRITA
<span v-if="currentLabel.data.overriden == true">
SE HA SOBREESCRITO EL CODIGO ANTERIOR
</span>
<!-- Caso de error -->
</div>
<div v-if="currentLabel?.data.reused == true" class="dev-panel">
<button class="dev-btn" @click="printCurrentLabel">
IMPRIMIR
</button>
<button
class="dev-btn"
@click="generateNewLabelAndPrint({ override: true })"
>
GENERAR NUEVO </button>
</div>
</div>
</div>
<!-- Discrete mock controls for dev testing -->
<div class="dev-panel">
<button

View File

@@ -0,0 +1,32 @@
import baseConfig from "../../../config/base.json";
export interface AppConfig {
serverUrl: string;
printerIp: string;
printerPort: number;
}
export const getConfig = (): AppConfig => {
const serverUrl =
localStorage.getItem("serverUrl") || baseConfig.defaultServer;
const printerIp =
localStorage.getItem("ipPrinter") || baseConfig.defaultPrinterIp;
const printerPortRaw = localStorage.getItem("printerPort");
const printerPort = printerPortRaw
? parseInt(printerPortRaw, 10)
: baseConfig.defaultPrinterPort;
return {
serverUrl,
printerIp,
printerPort,
};
};
export const getDefaultConfig = (): AppConfig => {
return {
serverUrl: baseConfig.defaultServer,
printerIp: baseConfig.defaultPrinterIp,
printerPort: baseConfig.defaultPrinterPort,
};
};