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", "defaultServer": "http://localhost:3000",
"defaultPrinter": "192.168.1.254" "defaultPrinterIp": "192.168.1.254",
"defaultPrinterPort": 9100
} }

View File

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

View File

@@ -67,7 +67,7 @@ export async function printReqHandler(
logger.logOperation("printReq", data); logger.logOperation("printReq", data);
return new Promise((res, rej) => { return new Promise((res, rej) => {
console.log("nfc:printReq", data); console.log("nfc:printReq", data);
const port = 9100; const port = data.printerPort;
let finished = false; let finished = false;
const socket = createConnection(port, data.printerURL); 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 { NfcService } from "../services/NfcService";
import { labelReqHandler, printReqHandler } from "./handlers"; import { labelReqHandler, printReqHandler } from "./handlers";
import { logger } from "./LogService"; import { logger } from "./LogService";
import { createConnection } from "net";
function createWindow(): void { function createWindow(): void {
// Create the browser window. // 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(); createWindow();
app.on("activate", function () { app.on("activate", function () {

View File

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

View File

@@ -29,6 +29,9 @@ const api = {
ping: (url: string): Promise<boolean> => { ping: (url: string): Promise<boolean> => {
return ipcRenderer.invoke("ping:url", url); return ipcRenderer.invoke("ping:url", url);
}, },
pingSocket: (args: { ip: string; port: number }): Promise<boolean> => {
return ipcRenderer.invoke("ping:socket", args);
},
removeAllListeners: (): void => { removeAllListeners: (): void => {
ipcRenderer.removeAllListeners("nfc:reader"); ipcRenderer.removeAllListeners("nfc:reader");
ipcRenderer.removeAllListeners("nfc:tag"); 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="logo-text">NFC.INTERFACE.v1</div>
<div class="technical-info">READY // LOCAL_ACCESS</div> <div class="technical-info">READY // LOCAL_ACCESS</div>
</div> </div>
<nav class="nav-section"> <nav class="nav-section">
<button v-if="currentView === 'main'" class="nav-btn" @click="navigateTo('config')"> <button v-if="currentView === 'main'" class="nav-btn" @click="navigateTo('config')">
CONFIG CONFIG
</button> </button>
<button v-else class="nav-btn" @click="navigateTo('main')"> <button v-else class="nav-btn" @click="navigateTo('main')">
EXIT_CONFIG SALIR_CONFIG
</button> </button>
</nav> </nav>
</header> </header>

View File

@@ -1,10 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { getConfig, getDefaultConfig } from "../utils/config";
const printerUrl = ref(localStorage.getItem("printerUrl") || "192.168.1.254"); const config = getConfig();
const serverUrl = ref( const printerUrl = ref(config.printerIp);
localStorage.getItem("serverUrl") || "http://localhost:3000", const printerPort = ref(config.printerPort);
); const serverUrl = ref(config.serverUrl);
const errorMsg = ref(""); const errorMsg = ref("");
const isSuccess = ref(false); const isSuccess = ref(false);
const pingStatus = ref<{ const pingStatus = ref<{
@@ -14,22 +15,12 @@ const pingStatus = ref<{
server: "idle", 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 => { const handleSave = (): void => {
errorMsg.value = ""; errorMsg.value = "";
isSuccess.value = false; isSuccess.value = false;
localStorage.setItem("printerUrl", printerUrl.value); localStorage.setItem("printerIp", printerUrl.value);
localStorage.setItem("printerPort", printerPort.value.toString());
localStorage.setItem("serverUrl", serverUrl.value); localStorage.setItem("serverUrl", serverUrl.value);
isSuccess.value = true; isSuccess.value = true;
@@ -38,15 +29,29 @@ const handleSave = (): void => {
}, 2000); }, 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> => { const performPing = async (type: "printer" | "server"): Promise<void> => {
errorMsg.value = ""; errorMsg.value = "";
isSuccess.value = false; isSuccess.value = false;
const url = type === "printer" ? printerUrl.value : serverUrl.value; const url = type === "printer" ? printerUrl.value : serverUrl.value;
const ip = printerUrl.value;
const port = printerPort.value;
pingStatus.value[type] = "pinging"; pingStatus.value[type] = "pinging";
try { 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"; pingStatus.value[type] = ok ? "success" : "error";
if (!ok) { if (!ok) {
errorMsg.value = `${type === "printer" ? "Printer" : "Server"} is unreachable`; errorMsg.value = `${type === "printer" ? "Printer" : "Server"} is unreachable`;
@@ -85,7 +90,7 @@ const handlePingServer = (): void => {
<div class="config-item"> <div class="config-item">
<div class="item-meta"> <div class="item-meta">
<span class="item-index">01</span> <span class="item-index">01</span>
<label for="printer-url">PRINTER_ADDRESS</label> <label for="printer-url">IP_IMPRESORA</label>
</div> </div>
<div class="input-with-action"> <div class="input-with-action">
<input <input
@@ -110,11 +115,27 @@ const handlePingServer = (): void => {
</div> </div>
</div> </div>
<!-- Server Config --> <!-- Printer Port Config -->
<div class="config-item"> <div class="config-item">
<div class="item-meta"> <div class="item-meta">
<span class="item-index">02</span> <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>
<div class="input-with-action"> <div class="input-with-action">
<input <input
@@ -135,7 +156,7 @@ const handlePingServer = (): void => {
v-if="pingStatus.server === 'success'" v-if="pingStatus.server === 'success'"
class="field-status success" class="field-status success"
> >
REACHABLE CONECTADO
</div> </div>
<div <div
v-if="pingStatus.server === 'error'" v-if="pingStatus.server === 'error'"
@@ -152,7 +173,10 @@ const handlePingServer = (): void => {
<div class="panel-actions"> <div class="panel-actions">
<button class="save-btn" @click="handleSave"> <button class="save-btn" @click="handleSave">
WRITE_TO_MEMORY GUARDAR CAMBIOS
</button>
<button class="reset-btn" @click="handleReset">
RESETEAR CONFIGURACIÓN
</button> </button>
<span v-if="isSuccess" class="write-success">WRITE_OK</span> <span v-if="isSuccess" class="write-success">WRITE_OK</span>
</div> </div>
@@ -290,6 +314,25 @@ input:focus {
opacity: 0.8; 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 { .write-success {
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 10px; font-size: 10px;

View File

@@ -1,22 +1,28 @@
<script setup lang="ts"> <script setup lang="ts">
import { type CodeResponse } from "src/preload/index.d.js"; 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 { ref, onMounted, onUnmounted } from "vue";
import { getConfig } from "../utils/config";
/** /**
* waiting = esperando a leer una tarjeta (igual es mejor ready) * waiting = esperando a leer una tarjeta (igual es mejor ready)
* reading = proceso de lectura * reading = proceso de lectura
* sucess = se ha leido la tarjeta y sigue en el lector * sucess = se ha leido la tarjeta y sigue en el lector
* error * 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 uid = ref<string | null>(null);
const errorMsg = 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"); 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[]>([]); const errors = ref<string[]>([]);
onMounted(async () => { onMounted(async () => {
// Get initial reader name
readerName.value = await window.api.nfc.getReaderName(); readerName.value = await window.api.nfc.getReaderName();
window.api.nfc.onReader((event) => { window.api.nfc.onReader((event) => {
@@ -71,7 +77,6 @@ const mockReadCard = (): void => {
}, 1000); }, 1000);
}; };
const readCard = async (): Promise<void> => { const readCard = async (): Promise<void> => {
// Reset de los errores anteriores // Reset de los errores anteriores
errors.value = []; errors.value = [];
@@ -79,41 +84,106 @@ const readCard = async (): Promise<void> => {
if (readerState.value !== "waiting") return; if (readerState.value !== "waiting") return;
readerState.value = "reading"; 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 // TODO: De momento está hardcodeado porque no se puede leer la tarjeta
const card_id = "019cdd39-fc08-7417-b16d-a78794a24c01"; const card_id = "019cdd39-fc08-7417-b16d-a78794a24c01";
const override = false; 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({ const codePromise = window.api.nfc.labelReq({
serverURL, serverURL,
card_id, card_id,
override, override,
}); });
const res = await tryCatch(codePromise); const res = await codePromise;
console.log("RES IPC:", res); 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 // Error de la llamada -> sin conexion al servidor
if (res.error != undefined) { if (label.error != undefined) {
errors.value = [...errors.value, String(res.error)]; errors.value = [...errors.value, String(label.error)];
delayWaiting();
return;
} }
// Error que viene del servidor // Error que viene del servidor
if (res.data?.error != undefined) { if (label.data?.error != undefined) {
errors.value = [...errors.value, String(res.data.error)]; errors.value = [...errors.value, String(label.data.error)];
delayWaiting(); }
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; return;
} }
const labelData = res.data.data.data; const labelData = currentLabel.value.data;
labelOutputStructured.value = res.data.data; const { printerIp: printerURL, printerPort } = getConfig();
console.log("label data", labelData);
const impPromise = window.api.nfc.printReq({ const impPromise = window.api.nfc.printReq({
printerURL, printerURL,
printerPort,
label: labelData.label, label: labelData.label,
}); });
@@ -125,17 +195,36 @@ const readCard = async (): Promise<void> => {
errorStr = "Error de conexión con la impresora"; errorStr = "Error de conexión con la impresora";
} }
errors.value = [...errors.value, errorStr]; 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 => { const delayWaiting = (): void => {
setTimeout((): void => { setTimeout((): void => {
readerState.value = "waiting"; readerState.value = "waiting";
errorMsg.value = null; errorMsg.value = null;
}, 3000); }, 1000);
}; };
const triggerError = (): void => { const triggerError = (): void => {
@@ -148,6 +237,7 @@ const triggerError = (): void => {
<template> <template>
<div class="main-display"> <div class="main-display">
<div class="flex-row grow"> <div class="flex-row grow">
<!-- Lector -->
<div class="display-container bg-dots"> <div class="display-container bg-dots">
<div class="display-grid"></div> <div class="display-grid"></div>
@@ -170,7 +260,19 @@ const triggerError = (): void => {
class="reading-state" class="reading-state"
> >
<div class="reading-indicator"></div> <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>
<div <div
@@ -190,13 +292,14 @@ const triggerError = (): void => {
class="error-state" class="error-state"
> >
<div class="error-indicator"></div> <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"> <p class="error-desc">
{{ errorMsg || "UNKNOWN_FAILURE" }} {{ errorMsg || "ERROR DESCONOCIDO" }}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<!-- Salida por pantalla de las respuestas del servidor y la impresora --> <!-- Salida por pantalla de las respuestas del servidor y la impresora -->
<div class="display-container bg-dots"> <div class="display-container bg-dots">
<!-- ERRORES --> <!-- ERRORES -->
@@ -205,31 +308,40 @@ const triggerError = (): void => {
<span>Error: {{ error }}</span> <span>Error: {{ error }}</span>
</span> </span>
</div> </div>
<div v-if="labelOutputStructured != undefined" class="flex-col"> <div v-if="currentLabel != undefined" class="flex-col">
<!-- Errores --> <!-- Errores -->
<span <span
v-if="labelOutputStructured.error != undefined" v-if="currentLabel.error != undefined"
class="error" class="error"
> >
{{ JSON.stringify(labelOutputStructured.error) }} {{ JSON.stringify(currentLabel.error) }}
</span> </span>
<!-- Datos --> <!-- Datos -->
<span> Código {{ labelOutputStructured.data.code }} </span> <span> Código {{ currentLabel.data.code }} </span>
<span> <span>
Etiqueta {{ labelOutputStructured.data.label }} Etiqueta {{ currentLabel.data.label }}
</span> </span>
<span v-if="labelOutputStructured.data.reused == true"> <span v-if="currentLabel.data.reused == true">
YA SE HA IMPRIMIDO YA SE HA IMPRESO ANTERIORMENTE, CONFIRMA REIMPRESION
</span> </span>
<span v-if="labelOutputStructured.data.overriden == true"> <span v-if="currentLabel.data.overriden == true">
SOBREESCRITA SE HA SOBREESCRITO EL CODIGO ANTERIOR
</span> </span>
<!-- Caso de error --> <!-- 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> </div>
</div> </div>
<!-- Discrete mock controls for dev testing --> <!-- Discrete mock controls for dev testing -->
<div class="dev-panel"> <div class="dev-panel">
<button <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,
};
};