qrdnicapacitor 1.0.8 → 1.0.9

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/Package.swift CHANGED
@@ -11,7 +11,7 @@ let package = Package(
11
11
  ],
12
12
  dependencies: [
13
13
  .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0"),
14
- .package(url: "https://github.com/diegocidm4/iQRDNI.git", from: "1.0.3"),
14
+ .package(url: "https://github.com/diegocidm4/iQRDNI.git", from: "1.0.4"),
15
15
  .package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", exact: "1.6.0")
16
16
  ],
17
17
  targets: [
@@ -53,7 +53,7 @@ dependencies {
53
53
  //implementation(name: 'qrdnidroid-release', ext: 'aar')
54
54
  implementation files('libs/jpeg2000.jar')
55
55
  implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
56
- implementation ('me.dm7.barcodescanner:zxing:1.9.8') {
56
+ implementation ('com.journeyapps:zxing-android-embedded:4.3.0'){
57
57
  exclude group: 'com.android.support'
58
58
  }
59
59
  implementation project(':capacitor-android')
Binary file
@@ -7,78 +7,165 @@ import android.util.Log;
7
7
 
8
8
  import androidx.appcompat.app.AppCompatActivity;
9
9
 
10
+ import com.google.zxing.BarcodeFormat;
11
+ import com.google.zxing.DecodeHintType;
10
12
  import com.google.zxing.Result;
13
+ import com.google.zxing.ResultMetadataType;
14
+ import com.google.zxing.ResultPoint;
15
+ import com.journeyapps.barcodescanner.BarcodeCallback;
16
+ import com.journeyapps.barcodescanner.BarcodeResult;
17
+ import com.journeyapps.barcodescanner.BarcodeView;
18
+ import com.journeyapps.barcodescanner.DefaultDecoderFactory;
11
19
 
12
- import me.dm7.barcodescanner.zxing.ZXingScannerView;
20
+ import java.io.ByteArrayOutputStream;
21
+ import java.nio.charset.StandardCharsets;
22
+ import java.util.Collections;
23
+ import java.util.HashMap;
24
+ import java.util.List;
25
+ import java.util.Map;
13
26
 
14
- public class QrCodeScanner extends AppCompatActivity implements ZXingScannerView.ResultHandler {
27
+
28
+
29
+ public class QrCodeScanner extends AppCompatActivity {
15
30
  public static final String KEY_QR_CODE = "qr_code";
16
- private ZXingScannerView mScannerView;
31
+ private BarcodeView mBarcodeView;
17
32
  private Boolean returnString = true;
33
+ private boolean handled = false; // evita procesar el mismo QR dos veces
34
+
18
35
  @Override
19
36
  public void onCreate(Bundle state) {
20
37
  super.onCreate(state);
21
- // Programmatically initialize the scanner view
22
- mScannerView = new ZXingScannerView(this);
23
- // Set the scanner view as the content view
24
- if(getIntent().hasExtra("returnString")) {
38
+
39
+ if (getIntent().hasExtra("returnString")) {
25
40
  returnString = (Boolean) getIntent().getExtras().get("returnString");
26
41
  }
27
42
 
28
- setContentView(mScannerView);
43
+ mBarcodeView = new BarcodeView(this);
44
+
45
+ // CONFIGURACIÓN CLAVE: forzar ISO-8859-1 para que los segmentos byte
46
+ // se decodifiquen 1:1 y getText() reconstruya el payload sin pérdida
47
+ Map<DecodeHintType, Object> hints = new HashMap<>();
48
+ hints.put(DecodeHintType.CHARACTER_SET, "ISO-8859-1");
49
+ hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
50
+
51
+ mBarcodeView.setDecoderFactory(new DefaultDecoderFactory(
52
+ Collections.singletonList(BarcodeFormat.QR_CODE), // solo QR
53
+ hints,
54
+ "ISO-8859-1",
55
+ 0)); // 0 = scan normal
56
+
57
+ mBarcodeView.setFramingRectSize(new com.journeyapps.barcodescanner.Size(1200, 1200));
58
+
59
+ setContentView(mBarcodeView);
60
+ mBarcodeView.decodeContinuous(callback);
29
61
  }
30
62
 
31
63
  @Override
32
64
  public void onResume() {
33
65
  super.onResume();
34
- // Register ourselves as a handler for scan results.
35
- mScannerView.setResultHandler(this);
36
- // Start camera on resume
37
- mScannerView.startCamera();
66
+ mBarcodeView.resume();
38
67
  }
39
68
 
40
69
  @Override
41
70
  public void onPause() {
42
71
  super.onPause();
43
- // Stop camera on pause
44
- mScannerView.stopCamera();
72
+ mBarcodeView.pause();
45
73
  }
74
+ private final BarcodeCallback callback = new BarcodeCallback() {
75
+ @Override
76
+ public void barcodeResult(BarcodeResult barcodeResult) {
77
+ handleResult(barcodeResult.getResult()); // Result original de ZXing
78
+ }
46
79
 
47
- @Override
48
- public void handleResult(Result rawResult) {
49
- if(returnString)
50
- {
80
+ @Override
81
+ public void possibleResultPoints(List<ResultPoint> resultPoints) {
82
+ // No necesario
83
+ }
84
+ };
85
+
86
+ private void handleResult(Result rawResult) {
87
+ if (handled) return;
88
+
89
+ if (returnString) {
90
+ handled = true;
51
91
  Intent intent = new Intent();
52
- intent.putExtra(KEY_QR_CODE, rawResult.getText());
92
+ intent.putExtra(Constantes.KEY_QR_CODE, normalizeText(rawResult.getText()));
53
93
  setResult(RESULT_OK, intent);
54
94
  finish();
55
- }
56
- else {
95
+ } else {
57
96
  Log.i("QrCodeScanner", "QR detectado");
58
- // 1. OBTENER BYTES CRUDOS (CRÍTICO PARA DNI 4.0)
59
- // No uses getText() para datos binarios
60
- byte[] rawBytes = rawResult.getRawBytes();
61
97
 
62
- if (rawBytes != null && rawBytes.length > 0) {
98
+ byte[] rawBytes = extractPayload(rawResult);
63
99
 
64
- // 2. CONVERTIR A BASE64
65
- // Como tu fragmento 'DatosMiDnie' espera un String y hace un Base64.decode,
66
- // aquí debemos hacer el Base64.encode.
100
+ if (rawBytes != null && rawBytes.length > 0) {
101
+ handled = true;
67
102
  String base64Result = Base64.encodeToString(rawBytes, Base64.NO_WRAP);
68
-
69
- // Debug: Ver longitud
70
103
  Log.d("QrCodeScanner", "Bytes leídos: " + rawBytes.length);
71
104
 
72
- // 3. RETORNAR EL RESULTADO
73
105
  Intent intent = new Intent();
74
- intent.putExtra(KEY_QR_CODE, base64Result);
106
+ intent.putExtra(Constantes.KEY_QR_CODE, base64Result);
75
107
  setResult(RESULT_OK, intent);
76
108
  finish();
77
109
  } else {
78
- // Si falla la lectura de bytes, reanudamos la cámara
79
- mScannerView.resumeCameraPreview(this);
110
+ // En modo continuo no hace falta reanudar nada:
111
+ // el escáner sigue intentándolo solo
112
+ Log.w("QrCodeScanner", "Payload vacío, reintentando...");
113
+ }
114
+ }
115
+ }
116
+
117
+ private byte[] extractPayload(Result result) {
118
+ // 1. Reconstrucción vía texto (con ISO-8859-1 forzado es determinista)
119
+ String text = result.getText();
120
+ if (text != null && !text.isEmpty()) {
121
+ byte[] full = text.getBytes(StandardCharsets.ISO_8859_1);
122
+ if (full.length > 2 && (full[0] & 0xFF) == 0xDC && (full[1] & 0xFF) == 0x03) {
123
+ Log.d("QrCodeScanner", "Payload vía getText (completo)");
124
+ return full;
125
+ }
126
+ }
127
+
128
+ // 2. Fallback: byte segments
129
+ Map<ResultMetadataType, Object> meta = result.getResultMetadata();
130
+ if (meta != null && meta.containsKey(ResultMetadataType.BYTE_SEGMENTS)) {
131
+ List<byte[]> segments = (List<byte[]>) meta.get(ResultMetadataType.BYTE_SEGMENTS);
132
+ if (segments != null && !segments.isEmpty()) {
133
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
134
+ for (byte[] seg : segments) {
135
+ bos.write(seg, 0, seg.length);
136
+ }
137
+ byte[] payload = bos.toByteArray();
138
+ if (payload.length > 0) {
139
+ Log.d("QrCodeScanner", "Payload vía BYTE_SEGMENTS (fallback)");
140
+ return payload;
141
+ }
80
142
  }
81
143
  }
144
+ return result.getRawBytes(); // último recurso
82
145
  }
83
146
 
147
+ /**
148
+ * El decoder está forzado a ISO-8859-1 (necesario para QRs binarios del DNI).
149
+ * Para QRs de texto, eso convierte el UTF-8 real en mojibake (ñ, á...).
150
+ * Como ISO-8859-1 es 1:1 con los bytes, podemos recuperar los bytes originales
151
+ * y re-decodificarlos como UTF-8 si son UTF-8 válido.
152
+ */
153
+ private String normalizeText(String text) {
154
+ if (text == null || text.isEmpty()) return text;
155
+ byte[] raw = text.getBytes(StandardCharsets.ISO_8859_1);
156
+ if (isValidUtf8(raw)) {
157
+ return new String(raw, StandardCharsets.UTF_8);
158
+ }
159
+ return text; // ASCII puro o Latin-1 real: se queda como está
160
+ }
161
+
162
+ private boolean isValidUtf8(byte[] bytes) {
163
+ java.nio.charset.CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
164
+ try {
165
+ decoder.decode(java.nio.ByteBuffer.wrap(bytes));
166
+ return true;
167
+ } catch (java.nio.charset.CharacterCodingException e) {
168
+ return false;
169
+ }
170
+ }
84
171
  }
@@ -0,0 +1,102 @@
1
+ //
2
+ // QRSegmentDecoder.swift
3
+ // TramiteFoto
4
+ //
5
+ // Created by CQeSolutions on 07/06/2026.
6
+ // Copyright © 2026 Diego. All rights reserved.
7
+ //
8
+
9
+
10
+ // QRSegmentDecoder.swift
11
+ // Reensambla el payload lógico de un QR a partir del stream de bits
12
+ // del errorCorrectedPayload, segmento a segmento (byte, alfanumérico,
13
+ // numérico), igual que hacen los decodificadores completos.
14
+
15
+ import Foundation
16
+
17
+ enum QRSegmentDecoder {
18
+
19
+ static func reassemble(payload: Data, version: Int) -> Data? {
20
+ let bytes = [UInt8](payload)
21
+ var bitPos = 0
22
+ let totalBits = bytes.count * 8
23
+
24
+ func readBits(_ n: Int) -> Int? {
25
+ guard n > 0, bitPos + n <= totalBits else { return nil }
26
+ var value = 0
27
+ for _ in 0..<n {
28
+ let byteIndex = bitPos >> 3
29
+ let bitIndex = 7 - (bitPos & 7)
30
+ value = (value << 1) | Int((bytes[byteIndex] >> UInt8(bitIndex)) & 1)
31
+ bitPos += 1
32
+ }
33
+ return value
34
+ }
35
+
36
+ func countBits(forMode mode: Int) -> Int? {
37
+ switch mode {
38
+ case 0b0001: return version <= 9 ? 10 : (version <= 26 ? 12 : 14) // numérico
39
+ case 0b0010: return version <= 9 ? 9 : (version <= 26 ? 11 : 13) // alfanumérico
40
+ case 0b0100: return version <= 9 ? 8 : 16 // byte
41
+ default: return nil
42
+ }
43
+ }
44
+
45
+ let alphaTable = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".utf8)
46
+ var out = [UInt8]()
47
+
48
+ while bitPos + 4 <= totalBits {
49
+ guard let mode = readBits(4) else { break }
50
+ if mode == 0b0000 { break } // terminador
51
+
52
+ if mode == 0b0111 { // ECI: saltar el designador (1, 2 o 3 bytes)
53
+ guard let first = readBits(8) else { return nil }
54
+ if first & 0x80 != 0 {
55
+ if first & 0x40 == 0 { _ = readBits(8) } else { _ = readBits(16) }
56
+ }
57
+ continue
58
+ }
59
+
60
+ guard let cBits = countBits(forMode: mode),
61
+ let count = readBits(cBits) else { return nil }
62
+
63
+ switch mode {
64
+ case 0b0100: // BYTE: count bytes tal cual
65
+ for _ in 0..<count {
66
+ guard let b = readBits(8) else { return nil }
67
+ out.append(UInt8(b))
68
+ }
69
+ case 0b0010: // ALFANUMÉRICO: 11 bits por par de caracteres
70
+ var remaining = count
71
+ while remaining >= 2 {
72
+ guard let v = readBits(11), v < 45 * 45 else { return nil }
73
+ out.append(alphaTable[v / 45])
74
+ out.append(alphaTable[v % 45])
75
+ remaining -= 2
76
+ }
77
+ if remaining == 1 {
78
+ guard let v = readBits(6), v < 45 else { return nil }
79
+ out.append(alphaTable[v])
80
+ }
81
+ case 0b0001: // NUMÉRICO: 10 bits por trío de dígitos
82
+ var remaining = count
83
+ while remaining >= 3 {
84
+ guard let v = readBits(10) else { return nil }
85
+ out.append(contentsOf: String(format: "%03d", v).utf8)
86
+ remaining -= 3
87
+ }
88
+ if remaining == 2 {
89
+ guard let v = readBits(7) else { return nil }
90
+ out.append(contentsOf: String(format: "%02d", v).utf8)
91
+ } else if remaining == 1 {
92
+ guard let v = readBits(4) else { return nil }
93
+ out.append(contentsOf: String(v).utf8)
94
+ }
95
+ default:
96
+ return nil // modo no soportado (kanji...): abortar al fallback
97
+ }
98
+ }
99
+
100
+ return out.isEmpty ? nil : Data(out)
101
+ }
102
+ }
@@ -8,65 +8,63 @@
8
8
  import AVFoundation
9
9
  import UIKit
10
10
 
11
+ import AVFoundation
12
+ import UIKit
13
+
11
14
  class ScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
12
15
  var captureSession: AVCaptureSession!
13
16
  var previewLayer: AVCaptureVideoPreviewLayer!
14
17
  var returnString: Bool = true
15
-
16
-
18
+
17
19
  public func inicializa(returnString: Bool) {
18
20
  self.returnString = returnString
19
21
  }
20
-
22
+
21
23
  override func viewDidLoad() {
22
24
  super.viewDidLoad()
23
- view.backgroundColor = .black
24
25
 
25
- // Creamos la sesión
26
+ view.backgroundColor = UIColor.black
26
27
  captureSession = AVCaptureSession()
27
28
 
28
- // Ejecutamos la configuración pesada en segundo plano
29
- DispatchQueue.global(qos: .userInitiated).async { [weak self] in
30
- guard let self = self else { return }
31
-
32
- guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
33
- let videoInput: AVCaptureDeviceInput
34
-
35
- do {
36
- videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
37
- } catch {
38
- return
39
- }
29
+ guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
30
+ let videoInput: AVCaptureDeviceInput
40
31
 
41
- if (self.captureSession.canAddInput(videoInput)) {
42
- self.captureSession.addInput(videoInput)
43
- }
32
+ do {
33
+ videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
34
+ } catch {
35
+ return
36
+ }
44
37
 
45
- let metadataOutput = AVCaptureMetadataOutput()
38
+ if captureSession.canAddInput(videoInput) {
39
+ captureSession.addInput(videoInput)
40
+ } else {
41
+ failed()
42
+ return
43
+ }
46
44
 
47
- if (self.captureSession.canAddOutput(metadataOutput)) {
48
- self.captureSession.addOutput(metadataOutput)
49
- metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
50
- metadataOutput.metadataObjectTypes = [.qr]
51
- }
45
+ let metadataOutput = AVCaptureMetadataOutput()
52
46
 
53
- // La UI sí se actualiza en el Main Thread
54
- DispatchQueue.main.async {
55
- self.previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession)
56
- self.previewLayer.frame = self.view.layer.bounds
57
- self.previewLayer.videoGravity = .resizeAspectFill
58
- self.view.layer.addSublayer(self.previewLayer)
59
-
60
- // Iniciamos la sesión fuera del hilo principal de nuevo
61
- DispatchQueue.global(qos: .userInitiated).async {
62
- self.captureSession.startRunning()
63
- }
64
- }
47
+ if captureSession.canAddOutput(metadataOutput) {
48
+ captureSession.addOutput(metadataOutput)
49
+ metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
50
+ metadataOutput.metadataObjectTypes = [.qr]
51
+ } else {
52
+ failed()
53
+ return
65
54
  }
55
+
56
+ previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
57
+ previewLayer.frame = view.layer.bounds
58
+ previewLayer.videoGravity = .resizeAspectFill
59
+ view.layer.addSublayer(previewLayer)
60
+
61
+ captureSession.startRunning()
66
62
  }
67
-
63
+
68
64
  func failed() {
69
- let ac = UIAlertController(title: "Scanning not supported", message: "Your device does not support scanning a code from an item. Please use a device with a camera.", preferredStyle: .alert)
65
+ let ac = UIAlertController(title: "Scanning not supported",
66
+ message: "Your device does not support scanning a code from an item. Please use a device with a camera.",
67
+ preferredStyle: .alert)
70
68
  ac.addAction(UIAlertAction(title: "OK", style: .default))
71
69
  present(ac, animated: true)
72
70
  captureSession = nil
@@ -74,90 +72,64 @@ class ScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDel
74
72
 
75
73
  override func viewWillAppear(_ animated: Bool) {
76
74
  super.viewWillAppear(animated)
77
-
78
- if (captureSession?.isRunning == false) {
75
+ if captureSession?.isRunning == false {
79
76
  captureSession.startRunning()
80
77
  }
81
78
  }
82
79
 
83
80
  override func viewWillDisappear(_ animated: Bool) {
84
81
  super.viewWillDisappear(animated)
85
-
86
- if (captureSession?.isRunning == true) {
82
+ if captureSession?.isRunning == true {
87
83
  captureSession.stopRunning()
88
84
  }
89
85
  }
90
- /*
91
- func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
92
- captureSession.stopRunning()
93
-
94
- var parametros: [String: Any] = [String: Any] ()
95
- if(self.returnString)
96
- {
97
- if let metadataObject = metadataObjects.first {
98
- guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
99
- guard let stringValue = readableObject.stringValue else { return }
100
- AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
101
- parametros["qrcode"] = stringValue
86
+
87
+ func metadataOutput(_ output: AVCaptureMetadataOutput,
88
+ didOutput metadataObjects: [AVMetadataObject],
89
+ from connection: AVCaptureConnection) {
90
+
91
+ guard let readableObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject else { return }
92
+
93
+ var parametros: [String: Any] = [:]
94
+
95
+ if returnString {
96
+ guard let stringValue = readableObject.stringValue else { return }
97
+ captureSession.stopRunning()
98
+ AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
99
+ parametros["qrcode"] = stringValue
100
+ } else {
101
+ // Le pasamos el OBJETO completo, no el payload:
102
+ // dentro decide entre stringValue (vía buena) y descriptor (fallback)
103
+ guard let payload = extractPayload(from: readableObject) else {
104
+ // Lectura inservible: no paramos la sesión, seguimos escaneando
105
+ return
102
106
  }
107
+ captureSession.stopRunning()
108
+ AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
109
+ parametros["qrcodeData"] = payload
103
110
  }
104
- else {
105
- if let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject {
106
-
107
- // IMPORTANTE: Accedemos al descriptor, no al stringValue.
108
- // El descriptor contiene los bytes crudos (RAW) del QR.
109
- if let descriptor = metadataObject.descriptor as? CIQRCodeDescriptor {
110
-
111
- let rawPayload = descriptor.errorCorrectedPayload
112
- //print("Datos brutos: \(rawPayload.toPrettyHexString())")
113
- parametros["qrcodeData"] = rawPayload
114
- }
115
- }
116
- }
117
-
118
- NotificationCenter.default.post(name: NSNotification.Name(rawValue: "lecturaQR"), object: nil, userInfo: parametros)
111
+
112
+ NotificationCenter.default.post(name: NSNotification.Name(rawValue: "lecturaQR"),
113
+ object: nil,
114
+ userInfo: parametros)
119
115
  }
120
- */
121
-
122
- func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
123
-
124
- // 1. Detenemos la sesión inmediatamente para evitar múltiples lecturas
125
- // startRunning/stopRunning siempre fuera del hilo principal si es posible
126
- DispatchQueue.global(qos: .userInitiated).async {
127
- if self.captureSession.isRunning {
128
- self.captureSession.stopRunning()
129
- }
130
- }
131
116
 
132
- // 2. Procesamos los datos
133
- guard let metadataObject = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
134
- let descriptor = metadataObject.descriptor as? CIQRCodeDescriptor else {
135
- // Si falla, reiniciamos sesión
136
- DispatchQueue.global(qos: .userInitiated).async { self.captureSession.startRunning() }
137
- return
138
- }
117
+ func extractPayload(from object: AVMetadataMachineReadableCodeObject) -> Data? {
118
+ guard let descriptor = object.descriptor as? CIQRCodeDescriptor else { return nil }
139
119
 
140
- let rawPayload = descriptor.errorCorrectedPayload
141
- let base64String = rawPayload.base64EncodedString()
142
-
143
- // Feedback táctico
144
- AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
145
-
146
- // 3. TODO el proceso pesado de validación y cierre debe ir fuera del hilo UI
147
- // pero el 'dismiss' debe volver al Main para no congelar la pantalla
148
- DispatchQueue.main.async {
149
- let parametros: [String: Any] = ["qrcode": base64String]
150
-
151
- // Notificamos al Plugin
152
- NotificationCenter.default.post(
153
- name: NSNotification.Name(rawValue: "lecturaQR"),
154
- object: nil,
155
- userInfo: parametros
156
- )
157
-
158
- // Cerramos la cámara
159
- self.dismiss(animated: true, completion: nil)
160
- }
120
+ let raw = descriptor.errorCorrectedPayload
121
+ let version = descriptor.symbolVersion
122
+
123
+ // 1. Reensamblado de segmentos a nivel de bits (la vía correcta)
124
+ if let reassembled = QRSegmentDecoder.reassemble(payload: raw, version: version),
125
+ reassembled.count > 2,
126
+ reassembled[reassembled.startIndex] == 0xDC,
127
+ reassembled[reassembled.startIndex + 1] == 0x03 {
128
+ return reassembled
129
+ }
130
+
131
+ // 2. Fallback: payload crudo; alignToMagicByte del parser lo intentará
132
+ return raw
161
133
  }
162
134
 
163
135
  override var prefersStatusBarHidden: Bool {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qrdnicapacitor",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Lectura y validación de códigos QR generados por la app MiDNI",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",