iteracion 2

This commit is contained in:
Nachou-u
2026-09-07 18:44:32 -03:00
parent a103a47ae6
commit 09a3f6fb37
93 changed files with 7394 additions and 349 deletions
+124 -6
View File
@@ -3,12 +3,30 @@
{% block content %}
<div class="form-container" style="max-width: 800px; margin: 0 auto;">
<div class="data-card" style="padding: 2.5rem; background: white; border-radius: 8px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);">
<h2 style="color: var(--efintel-petroleum); margin-bottom: 2rem; border-bottom: 2px solid var(--efintel-light); padding-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem;">
<i data-lucide="{% if form.instance.pk %}edit-3{% else %}plus-circle{% endif %}"></i>
{% if form.instance.pk %}Editar Proveedor{% else %}Nuevo Proveedor{% endif %}
<!-- Banner Requerimiento 6: Plan de mitigación de errores para registro de proveedores -->
<div id="draft-alert-banner" style="display:none; margin-bottom:1.5rem; background:#eff6ff; border:1px solid #bfdbfe; border-left:4px solid #2563eb; padding:1rem; border-radius:6px;">
<div style="display:flex; justify-content:space-between; align-items:center;">
<div>
<strong style="color:#1e40af; font-size:0.9rem;">Borrador Detectado en Almacenamiento Local (Autosave Mitigación)</strong>
<p style="margin:0.25rem 0 0 0; color:#1e3a8a; font-size:0.8rem;">Se detectaron datos no guardados de una sesión anterior. ¿Desea restaurarlos?</p>
</div>
<div style="display:flex; gap:0.5rem;">
<button type="button" onclick="restoreVendorDraft()" class="btn-primary" style="padding:0.3rem 0.8rem; font-size:0.8rem; background:#2563eb;">Restaurar</button>
<button type="button" onclick="discardVendorDraft()" class="btn-secondary" style="padding:0.3rem 0.8rem; font-size:0.8rem;">Descartar</button>
</div>
</div>
</div>
<h2 style="color: var(--efintel-petroleum); margin-bottom: 2rem; border-bottom: 2px solid var(--efintel-light); padding-bottom: 1rem; display: flex; align-items: center; justify-content:space-between;">
<span style="display:flex; align-items:center; gap:0.5rem;">
<i data-lucide="{% if form.instance.pk %}edit-3{% else %}plus-circle{% endif %}"></i>
{% if form.instance.pk %}Editar Proveedor{% else %}Nuevo Proveedor (Registro Seguro){% endif %}
</span>
<span class="badge badge-emerald" style="font-size:0.75rem;"><i data-lucide="shield-check" style="width:12px;"></i> Autosave / Anti-Pérdida Activo</span>
</h2>
<form method="post">
<form method="post" id="vendor-form">
{% csrf_token %}
<h3 style="margin-bottom: 1rem; color: var(--efintel-dark); font-size: 1rem;">Información General</h3>
@@ -16,7 +34,13 @@
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 1.5rem; margin-bottom: 1.5rem;">
<div class="form-group">
<label for="{{ form.rut.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">RUT Proveedor</label>
{{ form.rut }}
<div style="display:flex; gap:0.3rem;">
{{ form.rut }}
<button type="button" onclick="consultarSIIVendor()" class="btn-outline" style="padding:0.4rem 0.7rem; font-size:0.75rem; white-space:nowrap; background:#f0f9ff; border-color:#7dd3fc; color:#0369a1;" title="Validar y autocompletar con la API del SII">
<i data-lucide="search" style="width:12px;"></i> Consultar SII
</button>
</div>
<span id="sii-status-text" style="font-size:0.7rem; color:var(--efintel-gray); margin-top:0.25rem; display:block;"></span>
{% if form.rut.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.rut.errors }}</div>{% endif %}
</div>
@@ -70,7 +94,7 @@
</div>
<div style="display: flex; gap: 1rem; padding-top: 1.5rem; border-top: 1px solid var(--efintel-light);">
<button type="submit" class="btn-primary" style="padding: 0.6rem 2rem; font-size: 0.9rem;">Guardar Proveedor</button>
<button type="submit" class="btn-primary" style="padding: 0.6rem 2rem; font-size: 0.9rem; background:var(--efintel-blue);">Guardar Proveedor</button>
<a href="{% url 'gestion_panel' %}" class="btn-secondary" style="padding: 0.6rem 2rem; font-size: 0.9rem; text-decoration: none;">Cancelar</a>
</div>
</form>
@@ -81,5 +105,99 @@
document.querySelectorAll('input, select, textarea').forEach(el => {
el.classList.add('form-control');
});
// Requerimiento 6: Autosave en localStorage para mitigación de errores de formulario
const DRAFT_KEY = 'vendor_form_draft_{{ form.instance.pk|default:"new" }}';
const vendorForm = document.getElementById('vendor-form');
const draftBanner = document.getElementById('draft-alert-banner');
function saveDraft() {
const formData = {};
const elements = vendorForm.querySelectorAll('input, select, textarea');
elements.forEach(el => {
if (el.name && el.name !== 'csrfmiddlewaretoken') {
formData[el.name] = el.value;
}
});
localStorage.setItem(DRAFT_KEY, JSON.stringify(formData));
}
function checkForDraft() {
const saved = localStorage.getItem(DRAFT_KEY);
if (saved) {
try {
const data = JSON.parse(saved);
// Verify if saved data has actual input content
const hasValues = Object.values(data).some(v => v && v.trim() !== '');
if (hasValues && draftBanner) {
draftBanner.style.display = 'block';
}
} catch(e){}
}
}
function restoreVendorDraft() {
const saved = localStorage.getItem(DRAFT_KEY);
if (saved) {
const data = JSON.parse(saved);
Object.keys(data).forEach(key => {
const el = vendorForm.querySelector(`[name="${key}"]`);
if (el) el.value = data[key];
});
if (draftBanner) draftBanner.style.display = 'none';
}
}
function discardVendorDraft() {
localStorage.removeItem(DRAFT_KEY);
if (draftBanner) draftBanner.style.display = 'none';
}
vendorForm.querySelectorAll('input, select, textarea').forEach(el => {
el.addEventListener('input', saveDraft);
el.addEventListener('change', saveDraft);
});
vendorForm.addEventListener('submit', function() {
localStorage.removeItem(DRAFT_KEY);
});
// Check draft on load
checkForDraft();
// Requerimiento 4: Consulta API SII en vivo para autocompletar RUT y Razón Social
function consultarSIIVendor() {
const rutInput = document.getElementById('{{ form.rut.id_for_label }}');
const statusText = document.getElementById('sii-status-text');
if (!rutInput || !rutInput.value.trim()) {
alert('Por favor ingrese un RUT para consultar en el SII.');
return;
}
statusText.textContent = 'Consultando API SII...';
statusText.style.color = '#0284c7';
fetch(`/api/sii/taxpayer-info/?rut=${encodeURIComponent(rutInput.value.trim())}`)
.then(r => r.json())
.then(data => {
if (data.valid) {
rutInput.value = data.rut;
const nameInput = document.getElementById('{{ form.name.id_for_label }}');
const girosInput = document.getElementById('{{ form.business_line.id_for_label }}');
if (nameInput && (!nameInput.value || data.source === 'SII_API')) nameInput.value = data.name;
if (girosInput && !girosInput.value) girosInput.value = data.business_line;
statusText.textContent = `✓ RUT Validador SII OK (${data.source})`;
statusText.style.color = '#16a34a';
saveDraft();
} else {
statusText.textContent = `✗ ${data.error || 'RUT inválido'}`;
statusText.style.color = '#dc2626';
}
})
.catch(err => {
statusText.textContent = 'Error al consultar API SII.';
statusText.style.color = '#dc2626';
});
}
</script>
{% endblock %}