iteracion 2
This commit is contained in:
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user