feat(pos/facturapi): finalize Horux-to-Facturapi migration

- Normalize Facturapi key/org_id resolution (supports both cfdi_ prefixed
  tenant_config keys and short names used by invoicing_bp).
- Add CSD upload end-to-end (backend + frontend).
- Add helper scripts: setup_facturapi_orgs.py and check_facturapi_tenants.py.
- Add 20 unit tests with mocks (pos/tests/test_facturapi_service.py).
- Add CI workflow for lint + console tests on Python 3.11/3.13.
- Add pyproject.toml and requirements-dev.txt with ruff/pytest config.
- Update FASES_IMPLEMENTADAS.md with FASE 8 documentation.

Tests: 81 passing (61 console + 20 Facturapi).
This commit is contained in:
2026-06-15 04:58:42 +00:00
parent 6aff32f93b
commit d67887284d
15 changed files with 1559 additions and 481 deletions

View File

@@ -329,6 +329,73 @@ const Invoicing = (() => {
}
}
function resetCsdForm() {
document.getElementById('csd-form').reset();
document.getElementById('csd-cer-label').textContent = 'Subir certificado .cer';
document.getElementById('csd-key-label').textContent = 'Subir llave privada .key';
}
function updateFileLabels() {
const cer = document.getElementById('csd-cer');
const key = document.getElementById('csd-key');
if (cer && cer.files.length) {
document.getElementById('csd-cer-label').textContent = cer.files[0].name;
}
if (key && key.files.length) {
document.getElementById('csd-key-label').textContent = key.files[0].name;
}
}
async function uploadCsd(btn) {
if (!btn) return;
const cer = document.getElementById('csd-cer');
const key = document.getElementById('csd-key');
const password = document.getElementById('contrasena-csd').value.trim();
if (!cer || !cer.files.length || !key || !key.files.length) {
alert('Selecciona el archivo .cer y .key');
return;
}
if (!password) {
alert('Escribe la contraseña del CSD');
return;
}
const formData = new FormData();
formData.append('certificate', cer.files[0]);
formData.append('private_key', key.files[0]);
formData.append('password', password);
btn.disabled = true;
const originalText = btn.innerHTML;
btn.textContent = 'Subiendo...';
try {
const res = await fetch(`${API}/facturapi/csd`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token()}` },
body: formData,
});
const data = await res.json().catch(() => ({ error: res.statusText }));
if (!res.ok) throw new Error(data.error || 'Upload failed');
alert('CSD actualizado correctamente');
resetCsdForm();
loadFacturapiStatus();
} catch (e) {
alert('Error al subir CSD: ' + e.message);
} finally {
btn.disabled = false;
btn.innerHTML = originalText;
}
}
// Wire file input change listeners
setTimeout(() => {
const cer = document.getElementById('csd-cer');
const key = document.getElementById('csd-key');
if (cer) cer.addEventListener('change', updateFileLabels);
if (key) key.addEventListener('change', updateFileLabels);
}, 0);
// ---- Detail modal (uses modalDetalleOverlay) ----
async function showDetail(cfdiId) {
const overlay = document.getElementById('modalDetalleOverlay');
@@ -612,6 +679,7 @@ const Invoicing = (() => {
showDetail, showCancelModal, confirmCancel, processQueue,
showNewInvoiceModal, closeNewInvoiceModal, submitNewInvoice, notaCreditoPlaceholder,
openGlobalInvoiceModal, previewGlobalInvoice, generateGlobalInvoice, setupFacturapi,
uploadCsd, resetCsdForm,
};
// Register Cmd+K items
if (typeof registerCmdKItem === "function") {