feat(verifactu): persist certificate token in localStorage

- Add saveVeriFactuToken / getVeriFactuToken / clearVeriFactuToken helpers in verifactu.js
- Save token to localStorage (key: verifactu_cert_token) after successful registration
- Show active token status on SettingsPage load (first 8 chars + ellipsis)
- Add "Borrar token" button that appears only when a token is stored

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
admin 2026-05-28 15:58:03 +02:00
parent 81f40f7d7f
commit 7a06bd8963
2 changed files with 44 additions and 3 deletions

View File

@ -2,7 +2,7 @@ import { isDarkTheme, toggleTheme } from '../services/theme.js';
import { icons } from '../services/icons.js'; import { icons } from '../services/icons.js';
import { apiGet, apiPut } from '../services/apiClient.js'; import { apiGet, apiPut } from '../services/apiClient.js';
import { showToast } from '../services/toast.js'; import { showToast } from '../services/toast.js';
import { encryptVeriFactuPassword, formatVeriFactuError, getVeriFactuFormats, getVeriFactuHealth, registerVeriFactuCertificate } from '../services/verifactu.js'; import { encryptVeriFactuPassword, formatVeriFactuError, getVeriFactuFormats, getVeriFactuHealth, registerVeriFactuCertificate, saveVeriFactuToken, getVeriFactuToken, clearVeriFactuToken } from '../services/verifactu.js';
function decodeTokenPayload(token) { function decodeTokenPayload(token) {
try { try {
@ -155,6 +155,7 @@ export function renderSettingsPage() {
<div style="display:flex;gap:0.75rem;justify-content:flex-end;flex-wrap:wrap"> <div style="display:flex;gap:0.75rem;justify-content:flex-end;flex-wrap:wrap">
<button type="button" class="btn-cancel-compact" id="verifactu-health-btn">Comprobar API</button> <button type="button" class="btn-cancel-compact" id="verifactu-health-btn">Comprobar API</button>
<button type="button" class="btn-cancel-compact" id="verifactu-formats-btn">Ver formatos</button> <button type="button" class="btn-cancel-compact" id="verifactu-formats-btn">Ver formatos</button>
<button type="button" class="btn-cancel-compact" id="verifactu-clear-token-btn" style="display:none">Borrar token</button>
<button type="button" class="btn-submit-compact" id="verifactu-register-btn">Registrar certificado</button> <button type="button" class="btn-submit-compact" id="verifactu-register-btn">Registrar certificado</button>
</div> </div>
<p id="verifactu-status" style="font-size:0.8rem;color:var(--text-secondary);min-height:1.2em"></p> <p id="verifactu-status" style="font-size:0.8rem;color:var(--text-secondary);min-height:1.2em"></p>
@ -202,6 +203,26 @@ export function renderSettingsPage() {
const verifactuCertName = container.querySelector('#verifactu-cert-name'); const verifactuCertName = container.querySelector('#verifactu-cert-name');
const verifactuCertFile = container.querySelector('#verifactu-cert-file'); const verifactuCertFile = container.querySelector('#verifactu-cert-file');
const verifactuCertPassword = container.querySelector('#verifactu-cert-password'); const verifactuCertPassword = container.querySelector('#verifactu-cert-password');
const clearTokenBtn = container.querySelector('#verifactu-clear-token-btn');
const renderVeriFactuTokenStatus = () => {
const token = getVeriFactuToken();
if (token) {
verifactuStatus.textContent = `Token activo: ${token.slice(0, 8)}...`;
clearTokenBtn.style.display = '';
} else {
clearTokenBtn.style.display = 'none';
}
};
renderVeriFactuTokenStatus();
clearTokenBtn.addEventListener('click', () => {
clearVeriFactuToken();
verifactuStatus.textContent = 'Token eliminado.';
clearTokenBtn.style.display = 'none';
showToast('Token de VeriFactu eliminado', 'success');
});
verifactuCertFile.addEventListener('change', () => { verifactuCertFile.addEventListener('change', () => {
const file = verifactuCertFile.files?.[0]; const file = verifactuCertFile.files?.[0];
@ -333,8 +354,12 @@ export function renderSettingsPage() {
return; return;
} }
const token = result.token ? ` Token: ${result.token}` : ''; if (result.token) {
verifactuStatus.textContent = `Certificado registrado.${token}`; saveVeriFactuToken(result.token);
renderVeriFactuTokenStatus();
}
const tokenHint = result.token ? ` Token: ${result.token.slice(0, 8)}...` : '';
verifactuStatus.textContent = `Certificado registrado.${tokenHint}`;
showToast('Certificado registrado en VeriFactu', 'success'); showToast('Certificado registrado en VeriFactu', 'success');
} catch (error) { } catch (error) {
const errorMessage = formatVeriFactuError(error); const errorMessage = formatVeriFactuError(error);

View File

@ -3,6 +3,22 @@ import { apiGet, apiPost } from './apiClient.js';
let cachedPublicKeyPem = null; let cachedPublicKeyPem = null;
const VERIFACTU_TOKEN_KEY = 'verifactu_cert_token';
export function saveVeriFactuToken(token) {
if (token) {
localStorage.setItem(VERIFACTU_TOKEN_KEY, token);
}
}
export function getVeriFactuToken() {
return localStorage.getItem(VERIFACTU_TOKEN_KEY) || null;
}
export function clearVeriFactuToken() {
localStorage.removeItem(VERIFACTU_TOKEN_KEY);
}
const VERIFACTU_ERROR_MESSAGES = { const VERIFACTU_ERROR_MESSAGES = {
missing_fields: 'Faltan campos obligatorios para registrar el certificado.', missing_fields: 'Faltan campos obligatorios para registrar el certificado.',
invalid_json: 'La peticion enviada a VeriFactu no tiene un JSON valido.', invalid_json: 'La peticion enviada a VeriFactu no tiene un JSON valido.',