Ya genera el codigo e imprime

This commit is contained in:
2026-03-12 16:05:49 +01:00
parent f85c0e3b08
commit ed4866ad28
7 changed files with 194 additions and 97 deletions

View File

@@ -13,7 +13,7 @@
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false", "typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "npm run typecheck:node && npm run typecheck:web", "typecheck": "npm run typecheck:node && npm run typecheck:web",
"start": "electron-vite preview", "start": "electron-vite preview",
"dev": "electron-vite dev", "dev": "electron-vite dev --watch",
"build": "npm run typecheck && electron-vite build", "build": "npm run typecheck && electron-vite build",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir", "build:unpack": "npm run build && electron-builder --dir",

View File

@@ -3,7 +3,7 @@ import { join } from "path";
import { electronApp, optimizer, is } from "@electron-toolkit/utils"; import { electronApp, optimizer, is } from "@electron-toolkit/utils";
import icon from "../../resources/icon.png?asset"; import icon from "../../resources/icon.png?asset";
import { NfcService } from "../services/NfcService"; import { NfcService } from "../services/NfcService";
import net, { createConnection } from "net";
function createWindow(): void { function createWindow(): void {
// Create the browser window. // Create the browser window.
const mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
@@ -60,21 +60,26 @@ app.whenReady().then(() => {
ipcMain.on("ping", () => console.log("pong")); ipcMain.on("ping", () => console.log("pong"));
// HANDLE es bidireccionar ON es unidireccional // HANDLE es bidireccionar ON es unidireccional
ipcMain.handle("nfc:labelReq", async (_event, data: unknown) => { ipcMain.handle("nfc:labelReq", async (_event, data) => {
console.log("nfc:labelReq", data); console.log("nfc:labelReq", data);
try { try {
const response = await fetch("http://localhost:3000/nfc/generate", { const endpoint = "/nfc/generate";
const url = data.serverURL + endpoint;
console.log("fullUrl = ", url);
const response = await fetch(data.serverURL + endpoint, {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ body: JSON.stringify({
card_id: "019cdd39-fc08-7417-b16d-a78794a24c01", card_id: data.card_id,
override: false, override: data.override,
}), }),
}); });
console.log("Codigos:", response); console.log("Response", response.status);
return 200; const body = await response.json();
console.log("Codigos:", body);
return body;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return false; return false;
@@ -82,6 +87,38 @@ app.whenReady().then(() => {
return data; return data;
}); });
ipcMain.handle("nfc:printReq", async (_event, data) => {
console.log("nfc:printReq", data);
const client = new net.Socket();
const port = 9100;
const host = data.printerURL + port;
const socket = createConnection(port, data.printerURL);
socket.setTimeout(10 * 1000);
socket.on("connect", (e) => {
console.log("Conectado!", e);
socket.write(data.label);
socket.end();
});
/*
client.connect(data.printerURL + port, () => {
console.log("Connected to ZSim Printer");
const res = client.write(data.label, (res) => {
console.log("resultado de la impresion", res);
});
console.log("Resultado", res);
});
*/
socket.on("close", () =>
console.log("Print job sent and connection closed."),
);
socket.on("error", (err) =>
console.error("Printer Error:", err.message),
);
return data;
});
ipcMain.handle("ping:url", async (_event, url: string) => { ipcMain.handle("ping:url", async (_event, url: string) => {
try { try {
const response = await fetch(url, { const response = await fetch(url, {

View File

@@ -1,11 +1,42 @@
import { ElectronAPI } from "@electron-toolkit/preload"; import { ElectronAPI } from "@electron-toolkit/preload";
export type CodeResponse = {
error?: string;
data: {
label: string;
code: string;
overriden: boolean;
reused: boolean;
};
};
export type CodeRequest = {
card_id: string; // UUIDv7
override?: boolean;
};
export type PrinterRequest = {
printerURL: string;
label: string;
};
export type PrinterResponse = unknown;
export interface NfcAPI { export interface NfcAPI {
onTag: (callback: (event: { uid: string }) => void) => void; onTag: (callback: (event: { uid: string }) => void) => void;
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>;
removeAllListeners: () => void; removeAllListeners: () => void;
labelReq: (args: {
serverURL: string;
card_id: string;
override?: boolean;
}) => Promise<CodeResponse>;
printReq: (args: {
printerURL: string;
label: string;
}) => Promise<PrinterResponse>;
} }
declare global { declare global {

View File

@@ -1,5 +1,6 @@
import { contextBridge, ipcRenderer } from "electron"; import { contextBridge, ipcRenderer } from "electron";
import { electronAPI } from "@electron-toolkit/preload"; import { electronAPI } from "@electron-toolkit/preload";
import { CodeRequest, PrinterRequest } from "./index.d.js";
// Custom APIs for renderer // Custom APIs for renderer
const api = { const api = {
@@ -13,9 +14,12 @@ const api = {
onError: (callback: (event: { message: string }) => void): void => { onError: (callback: (event: { message: string }) => void): void => {
ipcRenderer.on("nfc:error", (_event, value) => callback(value)); ipcRenderer.on("nfc:error", (_event, value) => callback(value));
}, },
labelReq: (data: unknown): Promise<void> => { labelReq: (data: CodeRequest): Promise<unknown> => {
return ipcRenderer.invoke("nfc:labelReq", data); return ipcRenderer.invoke("nfc:labelReq", data);
}, },
printReq: (data: PrinterRequest): Promise<void> => {
return ipcRenderer.invoke("nfc:printReq", data);
},
ping: (url: string): Promise<boolean> => { ping: (url: string): Promise<boolean> => {
return ipcRenderer.invoke("ping:url", url); return ipcRenderer.invoke("ping:url", url);
}, },

View File

@@ -6,3 +6,27 @@ body {
overflow: auto; overflow: auto;
user-select: none; user-select: none;
} }
.flex-row {
display: flex;
flex-direction: row;
gap: 1em;
}
.flex-col {
display: flex;
flex-direction: column;
gap: 1em;
}
.grow {
flex-grow: 1;
}
.bg-dots {
background-image: radial-gradient(
var(--te-gray-light) 1px,
transparent 1px
);
background-size: 20px 20px;
}

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
const printerUrl = ref(localStorage.getItem("printerUrl") || "192.168.1.100"); const printerUrl = ref(localStorage.getItem("printerUrl") || "192.168.1.254");
const serverUrl = ref( const serverUrl = ref(
localStorage.getItem("serverUrl") || "http://localhost:3000", localStorage.getItem("serverUrl") || "http://localhost:3000",
); );
@@ -29,16 +29,6 @@ const handleSave = (): void => {
errorMsg.value = ""; errorMsg.value = "";
isSuccess.value = false; isSuccess.value = false;
if (!validateIpOrUrl(printerUrl.value)) {
errorMsg.value = "Invalid Printer IP or URL format";
return;
}
if (!validateIpOrUrl(serverUrl.value)) {
errorMsg.value = "Invalid Server IP or URL format";
return;
}
localStorage.setItem("printerUrl", printerUrl.value); localStorage.setItem("printerUrl", printerUrl.value);
localStorage.setItem("serverUrl", serverUrl.value); localStorage.setItem("serverUrl", serverUrl.value);
isSuccess.value = true; isSuccess.value = true;
@@ -54,20 +44,9 @@ const performPing = async (type: "printer" | "server"): Promise<void> => {
const url = type === "printer" ? printerUrl.value : serverUrl.value; const url = type === "printer" ? printerUrl.value : serverUrl.value;
if (!validateIpOrUrl(url)) {
errorMsg.value = `Invalid ${type === "printer" ? "Printer" : "Server"} IP/URL format`;
return;
}
// Ensure URL has protocol for the ping check
let pingUrl = url;
if (!url.startsWith("http://") && !url.startsWith("https://")) {
pingUrl = `http://${url}`;
}
pingStatus.value[type] = "pinging"; pingStatus.value[type] = "pinging";
try { try {
const ok = await window.api.nfc.ping(pingUrl); const ok = await window.api.nfc.ping(url);
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`;

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { type CodeResponse } from "src/preload/index.d.js";
import { ref, onMounted, onUnmounted } from "vue"; import { ref, onMounted, onUnmounted } from "vue";
import axios from "axios";
/** /**
* 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
@@ -10,6 +10,8 @@ import axios from "axios";
const readerState = ref<"waiting" | "reading" | "success" | "error">("waiting"); const readerState = ref<"waiting" | "reading" | "success" | "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 labelOutput = ref<string>("");
const labelOutputStructured = ref<CodeResponse | null>(null);
onMounted(() => { onMounted(() => {
window.api.nfc.onTag((event) => { window.api.nfc.onTag((event) => {
@@ -60,36 +62,40 @@ const mockReadCard = (): void => {
}, 1000); }, 1000);
}; };
type CodeResponse = {
error?: string,
data: {
label: string;
code: string;
overriden: boolean;
reused: boolean;
}
}
type CodeRequest = { const readCard = async (): Promise<void> => {
card_id: string; // UUIDv7 console.log("test readcard");
override?: boolean;
}
const readCard = async (): void => {
console.log("test readcard")
if (readerState.value !== "waiting") return; if (readerState.value !== "waiting") return;
readerState.value = "reading"; readerState.value = "reading";
const serverURL = const serverURL =
localStorage.getItem("serverUrl") || "http://localhost:3000"; localStorage.getItem("serverUrl") || "http://localhost:3000";
const endpoint = "/nfc/generate"; 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;
try { try {
window.api.nfc.labelReq({ card_id, override }); const res: CodeResponse = await window.api.nfc.labelReq({
serverURL,
card_id,
override,
});
console.log("Res IPC", res);
labelOutput.value = res.data.label;
labelOutputStructured.value = res;
console.log("Imprimiendo", res);
const impRes = await window.api.nfc.printReq({
printerURL,
label: res.data.label,
});
console.log("Impreso", impRes);
readerState.value = "waiting";
} catch (e) { } catch (e) {
console.error(e); console.error(e);
readerState.value = "error"; readerState.value = "error";
@@ -102,11 +108,10 @@ const delayWaiting = (): void => {
readerState.value = "waiting"; readerState.value = "waiting";
errorMsg.value = null; errorMsg.value = null;
}, 3000); }, 3000);
} };
const triggerError = (): void => { const triggerError = (): void => {
if (readerState.value !== "waiting") return; if (readerState.value !== "waiting") return;
readerState.value = "error"; readerState.value = "error";
delayWaiting(); delayWaiting();
}; };
@@ -114,52 +119,74 @@ const triggerError = (): void => {
<template> <template>
<div class="main-display"> <div class="main-display">
<div class="display-container"> <div class="flex-row grow">
<div class="display-grid"></div> <div class="display-container bg-dots">
<div class="top-meta"> <div class="display-grid"></div>
<div class="meta-item">
<span class="label">MODULE</span>
<span class="value">NFC_RD_01</span>
</div>
</div>
<div class="status-center"> <div class="top-meta">
<div v-if="readerState === 'waiting'" class="waiting-state"> <div class="meta-item">
<div class="pulse-ring"></div> <span class="label">MODULE</span>
<div class="pulse-inner"></div> <span class="value">NFC_RD_01</span>
<h1 class="status-msg">TAP CARD TO INITIALIZE</h1>
</div>
<div
v-else-if="readerState === 'reading'"
class="reading-state"
>
<div class="reading-indicator"></div>
<h1 class="status-msg reading">PROCESSING_DATA...</h1>
</div>
<div
v-else-if="readerState === 'success'"
class="success-state"
>
<div class="success-indicator"></div>
<h1 class="status-msg success">SESSION_START_OK</h1>
<div class="id-capture">
<span class="id-label">UID_IDENTIFIED:</span>
<span class="id-value">{{ uid }}</span>
</div> </div>
</div> </div>
<div v-else-if="readerState === 'error'" class="error-state"> <div class="status-center">
<div class="error-indicator"></div> <div v-if="readerState === 'waiting'" class="waiting-state">
<h1 class="status-msg error">INTERFACE_ERROR</h1> <div class="pulse-ring"></div>
<p class="error-desc"> <div class="pulse-inner"></div>
{{ errorMsg || "UNKNOWN_FAILURE" }} <h1 class="status-msg">TAP CARD TO INITIALIZE</h1>
</p> </div>
<div
v-else-if="readerState === 'reading'"
class="reading-state"
>
<div class="reading-indicator"></div>
<h1 class="status-msg reading">PROCESSING_DATA...</h1>
</div>
<div
v-else-if="readerState === 'success'"
class="success-state"
>
<div class="success-indicator"></div>
<h1 class="status-msg success">SESSION_START_OK</h1>
<div class="id-capture">
<span class="id-label">UID_IDENTIFIED:</span>
<span class="id-value">{{ uid }}</span>
</div>
</div>
<div
v-else-if="readerState === 'error'"
class="error-state"
>
<div class="error-indicator"></div>
<h1 class="status-msg error">INTERFACE_ERROR</h1>
<p class="error-desc">
{{ errorMsg || "UNKNOWN_FAILURE" }}
</p>
</div>
</div>
</div>
<!-- Salida por pantalla de las respuestas del servidor y la impresora -->
<div class="display-container bg-dots">
<div v-if="labelOutputStructured != undefined" class="flex-col">
<span> Código {{ labelOutputStructured.data.code }} </span>
<span>
Etiqueta {{ labelOutputStructured.data.label }}
</span>
<span v-if="labelOutputStructured.data.reused == true">
YA SE HA IMPRIMIDO
</span>
<span v-if="labelOutputStructured.data.overriden == true">
SOBREESCRITA
</span>
<!-- Caso de error -->
</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
@@ -221,11 +248,6 @@ const triggerError = (): void => {
.display-grid { .display-grid {
position: absolute; position: absolute;
inset: 0; inset: 0;
background-image: radial-gradient(
var(--te-gray-light) 1px,
transparent 1px
);
background-size: 20px 20px;
opacity: 0.5; opacity: 0.5;
pointer-events: none; pointer-events: none;
} }