turnero: integración QZ Tray para impresión silenciosa en kiosko
- Genera par de llaves RSA (cert en config/qz/) - api/qz_sign.php firma los print jobs con SHA512 - assets/js/qz-print.js conecta QZ Tray y envía trabajo HTML - ver_formulario_enviado.php carga QZ cuando ?autoprint=1 - Fallback a window.print() si QZ no está instalado Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
2cfb77b1a6
commit
5dd6ee5040
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* POST /api/qz_sign.php
|
||||||
|
* Firma el request de QZ Tray con la llave privada RSA del servidor.
|
||||||
|
* QZ Tray verifica la firma usando el digital-certificate.txt instalado en el cliente.
|
||||||
|
*/
|
||||||
|
header('Content-Type: text/plain');
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
|
||||||
|
$request = file_get_contents('php://input');
|
||||||
|
if (!$request) { http_response_code(400); exit('missing request'); }
|
||||||
|
|
||||||
|
$keyPath = __DIR__ . '/../config/qz/private-key.pem';
|
||||||
|
$key = openssl_pkey_get_private('file://' . $keyPath);
|
||||||
|
if (!$key) { http_response_code(500); exit('key error'); }
|
||||||
|
|
||||||
|
openssl_sign($request, $signature, $key, 'SHA512');
|
||||||
|
echo base64_encode($signature);
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* qz-print.js — Impresión silenciosa vía QZ Tray
|
||||||
|
* Requiere qz-tray.js cargado antes de este archivo.
|
||||||
|
* Uso: qzPrint({ url, printer })
|
||||||
|
*/
|
||||||
|
window.qzPrint = async function({ printer } = {}) {
|
||||||
|
try {
|
||||||
|
// 1. Conectar
|
||||||
|
if (!qz.websocket.isActive()) {
|
||||||
|
await qz.websocket.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Firma con el servidor
|
||||||
|
qz.security.setSignatureAlgorithm('SHA512');
|
||||||
|
qz.security.setSignaturePromise(function(toSign) {
|
||||||
|
return function(resolve, reject) {
|
||||||
|
fetch('/api/qz_sign.php', { method: 'POST', body: toSign })
|
||||||
|
.then(r => r.text()).then(resolve).catch(reject);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Certificado público
|
||||||
|
qz.security.setCertificatePromise(function(resolve) {
|
||||||
|
fetch('/config/qz/digital-certificate.txt')
|
||||||
|
.then(r => r.text()).then(resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4. Seleccionar impresora
|
||||||
|
var p = printer || await qz.printers.getDefault();
|
||||||
|
console.log('[QZ] Impresora:', p);
|
||||||
|
|
||||||
|
// 5. Imprimir la página actual como HTML
|
||||||
|
var cfg = qz.configs.create(p);
|
||||||
|
var data = [{
|
||||||
|
type : 'pixel',
|
||||||
|
format: 'html',
|
||||||
|
flavor: 'plain',
|
||||||
|
data : document.documentElement.outerHTML,
|
||||||
|
}];
|
||||||
|
|
||||||
|
await qz.print(cfg, data);
|
||||||
|
console.log('[QZ] Trabajo enviado correctamente');
|
||||||
|
} catch(e) {
|
||||||
|
console.warn('[QZ] Error:', e.message || e);
|
||||||
|
// Fallback al diálogo del navegador si QZ no está disponible
|
||||||
|
window.print();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Bloquear acceso web a la llave privada
|
||||||
|
<Files "private-key.pem">
|
||||||
|
Order deny,allow
|
||||||
|
Deny from all
|
||||||
|
</Files>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID+TCCAuGgAwIBAgIULkHXGPyy0w0ieUoN/YOZOO5MPvkwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwgYsxCzAJBgNVBAYTAkNPMRswGQYDVQQIDBJOb3J0ZSBkZSBTYW50YW5kZXIx
|
||||||
|
DzANBgNVBAcMBkN1Y3V0YTEjMCEGA1UECgwaTGFib3JhdG9yaW8gWGltZW5hIENh
|
||||||
|
aWNlZG8xKTAnBgNVBAMMIGVycC5sYWJvcmF0b3Jpb3hpbWVuYWNhaWNlZG8uY29t
|
||||||
|
MB4XDTI2MDcyMTE5NTEzNloXDTM2MDcxODE5NTEzNlowgYsxCzAJBgNVBAYTAkNP
|
||||||
|
MRswGQYDVQQIDBJOb3J0ZSBkZSBTYW50YW5kZXIxDzANBgNVBAcMBkN1Y3V0YTEj
|
||||||
|
MCEGA1UECgwaTGFib3JhdG9yaW8gWGltZW5hIENhaWNlZG8xKTAnBgNVBAMMIGVy
|
||||||
|
cC5sYWJvcmF0b3Jpb3hpbWVuYWNhaWNlZG8uY29tMIIBIjANBgkqhkiG9w0BAQEF
|
||||||
|
AAOCAQ8AMIIBCgKCAQEAjbzIjSUEj6qjwHvg+qNAEk0rAjfezbpyGPGkbH1AFnmI
|
||||||
|
8rz2TZKAN5PWJSvjYS91YEtnfFttEDa0wQpvHOIPhn2l8gAsYNFoDS+i1ilXHBEA
|
||||||
|
OtAB7uVL2TvrZBTQtcab4upI1ehUjdqA9ae51KBEfqOD3k7dc5X8IeskBDMLfI5G
|
||||||
|
cuJejGpx6S8aFur/PfZfu3oApHW02b4GXqSguUHwBJZtGauFQWRrN/6VG3YVKXON
|
||||||
|
g8bDEyFgPCZ2Slp4pHoLK5BIjBkF1t3PUcSq5rRZCxjYzoOcDIfoVV5f4Y+QSspM
|
||||||
|
u3tI+cMAnqwrKma/TBmSSikTMuK9nb+qjVvlQ/kijwIDAQABo1MwUTAdBgNVHQ4E
|
||||||
|
FgQUVnCTxhnONKLUYtxlT4SsIPH5rEgwHwYDVR0jBBgwFoAUVnCTxhnONKLUYtxl
|
||||||
|
T4SsIPH5rEgwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAEL/L
|
||||||
|
PP8b2UQavVLh3r0D3Is0V7JrVTRsT/iB2FwJLhudHAojVBinh7+tvZboEFkJ5trR
|
||||||
|
bNG3XxXDprw76awc/BsR19YXWrjZfraROrVNYEVTW1+Mit2HYRuTiEnVxOX1EENR
|
||||||
|
dAwZrwtFmpy3I2sZNblxQm0cZbDq6g4x/LBTwUXmXQK259Nrv5SEjs8wixeDMDNC
|
||||||
|
wuA9vAamsw+38nqHCouNtE+yPwM7srinFkfilMnIB2d874eqaVBvEKWmUtBCsWaO
|
||||||
|
f5OviyodKH9BtF+ACpgU2E/at03VratR5REIjje52qsWOMY1Wnn8FIYsCWJIPQYu
|
||||||
|
m56yKz4rQLPX/kyeCQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCNvMiNJQSPqqPA
|
||||||
|
e+D6o0ASTSsCN97NunIY8aRsfUAWeYjyvPZNkoA3k9YlK+NhL3VgS2d8W20QNrTB
|
||||||
|
Cm8c4g+GfaXyACxg0WgNL6LWKVccEQA60AHu5UvZO+tkFNC1xpvi6kjV6FSN2oD1
|
||||||
|
p7nUoER+o4PeTt1zlfwh6yQEMwt8jkZy4l6ManHpLxoW6v899l+7egCkdbTZvgZe
|
||||||
|
pKC5QfAElm0Zq4VBZGs3/pUbdhUpc42DxsMTIWA8JnZKWnikegsrkEiMGQXW3c9R
|
||||||
|
xKrmtFkLGNjOg5wMh+hVXl/hj5BKyky7e0j5wwCerCsqZr9MGZJKKRMy4r2dv6qN
|
||||||
|
W+VD+SKPAgMBAAECggEAPqedtfsPrZx+f6ejN9hzicOQCA5/kMzjBBDJoOWrL2Qx
|
||||||
|
PDB45qikwiy5ZLwmav8qMVOT3v6hUyIDvDPrE0cBGvAvK6+U7oWTLAULRAWJStBf
|
||||||
|
HCB4Qk0dPt3Ee/zRmBFANspfQSPPQNe+2xj2Rj5EmQCaWerd7Or3xlymEq8n3Dp2
|
||||||
|
E9pFn4wiEDdjXG69/fqu/LoFpz31E2Mp0q+V4ZdWvMXA4U6akH2/oGWCnjDdhm/O
|
||||||
|
Wy1shQL5oVxdw/yhHhaC/3GZv/MKVbaW7JeBtnt3S5Szktr6A1O2obSJS+KOw9d+
|
||||||
|
Y2rqIMAWXv+kHKzAB2LtfczpfkEDkqQ7jCbu+sjX4QKBgQC+3uwfwM9mlxoCt93E
|
||||||
|
cerMyfrbssTjZjZSsxnHsGz+qmDr8TIyOrwcZnnYs6MNkuTJW6MvgN8hM8oO2arh
|
||||||
|
E3s0oWrbILD42rzVQUeLr7/N/JjUwK7yyEnfZX/hnQdilRsT+rxpXBIA0HfUheih
|
||||||
|
6KkddNoOyvs+wu1eOralO117LwKBgQC+Ge4ch1hicRchwX/Bu+h3cdkLVVgdI08+
|
||||||
|
O7vnSCuhj8Ead7z3C5XjEqJOs75xY+LF4UxPsrqTuCxpWIOupCSnRe+RGMcJdzbR
|
||||||
|
0i/leyL9fWhvHzP13R4T6sqAHqIXKgCfR7Hx99TW3Whc4N2GoqAnIrjqow+H+ic8
|
||||||
|
8Hrr+Ul2oQKBgQC1AyRbWLdYS6RXP5gJXR+X51UIVZlzLtQFyeSBBEfZnCselzdL
|
||||||
|
e3g6VtTnNjVEAjMG4uj3e/gfvMW7H6J2ocsONqbn+TDcUFUUyTvYtWvpJcyqt7Ey
|
||||||
|
fc/RFKkahZkjXNS5NejI4pAQRaPe4L+mDMeVL+Q8czOiaapC2tusB4i38QKBgCYh
|
||||||
|
G1Jbj03HcyVRI2ffYcQ7cJZGWvMVNvq7jnfYUPAJ3miJpbxDdZ/jB+0TPlqN91lL
|
||||||
|
VDwUFDo20ambmGX6BGQMsf1/Y8SxRayWJQc5SI5hjgXj0084N6U1DcLe4hIVWaSZ
|
||||||
|
A8cNt4IVTK58Z9JuYgMXgtGFPUM/2Ijvjygvix2hAoGBALSgjUzho5Jn/vAQza/M
|
||||||
|
k4q24RZ0kBJ1Go+/b3iSOieCCVKgow9224r/frcrFvXWzbNjhKs/HAV1bB9uCrxD
|
||||||
|
/V/1byH0XLYmYIKvhTqpTtBLCYXrv+2m+ox8pHrgPze4KuJy3VdN/V/Dj3CpY0VL
|
||||||
|
G5Uf9TfOiMk4EiZw/pBR1YT9
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -2762,7 +2762,16 @@ const topaz = (() => {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</script>
|
</script>
|
||||||
<?php if ($autoprint): ?>
|
<?php if ($autoprint): ?>
|
||||||
<script>window.addEventListener('load', function() { setTimeout(function() { window.print(); }, 600); });</script>
|
<script src="https://cdn.jsdelivr.net/npm/qz-tray@2.2.4/qz-tray.js"></script>
|
||||||
|
<script src="/assets/js/qz-print.js"></script>
|
||||||
|
<script>
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
if (typeof qz !== 'undefined') { qzPrint(); }
|
||||||
|
else { window.print(); }
|
||||||
|
}, 800);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user