Feat: dashboard con gráficos, estructura CBS/Portafolios, semáforo de proyectos y cartera de especialistas
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block content %}
|
||||
<div class="view-header">
|
||||
<div class="view-title">
|
||||
<h2>Cuentas por Pagar (CXP)</h2>
|
||||
<p>Consolidado de deudas por proveedor en estado Solicitado a Finanzas.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="main-content">
|
||||
<div class="data-card">
|
||||
<div class="data-table-wrapper">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>RUT</th>
|
||||
<th>Razón Social</th>
|
||||
<th>Datos de Pago</th>
|
||||
<th>Cantidad de Facturas</th>
|
||||
<th>Total Deuda (CLP)</th>
|
||||
<th>Total Deuda (USD)</th>
|
||||
<th>Acción Sugerida</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for debt in debts %}
|
||||
<tr>
|
||||
<td style="font-weight: 600; color: var(--efintel-blue);">{{ debt.vendor__rut }}</td>
|
||||
<td style="font-weight: 600;">{{ debt.vendor__name }}</td>
|
||||
<td>
|
||||
{% if debt.vendor__bank %}
|
||||
<div style="font-size: 0.85rem;">{{ debt.vendor__bank }}</div>
|
||||
<div style="font-size: 0.75rem; color: var(--efintel-gray);">{{ debt.vendor__account_type }} - {{ debt.vendor__account_number }}</div>
|
||||
{% else %}
|
||||
<em style="color: var(--efintel-gray);">No registrados</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align: center;"><span class="badge badge-amber">{{ debt.expense_count }} pendientes</span></td>
|
||||
<td class="amount-cell" style="font-size: 1rem; color: var(--efintel-danger);">
|
||||
${{ debt.total_clp|floatformat:"0"|intcomma }}
|
||||
</td>
|
||||
<td class="amount-cell" style="font-size: 0.85rem; color: var(--efintel-gray);">
|
||||
${{ debt.total_usd|floatformat:"2"|intcomma }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'expense_list' %}?vendor={{ debt.vendor__id }}" class="btn-primary" style="font-size: 0.75rem;">Ver Detalle</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="7" style="text-align: center; padding: 3rem; color: var(--efintel-gray);">
|
||||
No hay cuentas por pagar pendientes en este momento.
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,62 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% 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 Categoría Contable{% else %}Nueva Categoría Contable{% endif %}
|
||||
</h2>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.code.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Código</label>
|
||||
{{ form.code }}
|
||||
{% if form.code.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.code.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.name.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Nombre de Categoría</label>
|
||||
{{ form.name }}
|
||||
{% if form.name.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.name.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom: 1.5rem;">
|
||||
<label for="{{ form.parent.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Partida (Categoría Padre)</label>
|
||||
<p style="font-size: 0.75rem; color: var(--text-secondary); margin-bottom: 0.25rem;">Dejar en blanco si esta es una Partida principal.</p>
|
||||
{{ form.parent }}
|
||||
{% if form.parent.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.parent.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom: 2rem;">
|
||||
<label for="{{ form.description.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Descripción (Opcional)</label>
|
||||
{{ form.description }}
|
||||
{% if form.description.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.description.errors }}</div>
|
||||
{% endif %}
|
||||
</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 Categoría</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('input, select, textarea').forEach(el => {
|
||||
el.classList.add('form-control');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block content %}
|
||||
<section class="page-view active">
|
||||
@@ -20,9 +21,9 @@
|
||||
<span class="kpi-title">EJECUCIÓN HISTÓRICA / MES</span>
|
||||
<div class="kpi-icon"><i data-lucide="credit-card"></i></div>
|
||||
</div>
|
||||
<div class="kpi-value" id="kpi-spend">${{ total_all|floatformat:"0" }} CLP</div>
|
||||
<div class="kpi-value" id="kpi-spend">${{ total_all|floatformat:"0"|intcomma }} CLP</div>
|
||||
<div class="kpi-subtext">
|
||||
<span>Mes actual: ${{ total_month|floatformat:"0" }} CLP</span>
|
||||
<span>Mes actual: ${{ total_month|floatformat:"0"|intcomma }} CLP</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,9 +32,9 @@
|
||||
<span class="kpi-title">PRESUPUESTO GENERAL I+D+i</span>
|
||||
<div class="kpi-icon"><i data-lucide="wallet"></i></div>
|
||||
</div>
|
||||
<div class="kpi-value" id="kpi-remaining">${{ global_budget|floatformat:"0" }} CLP</div>
|
||||
<div class="kpi-value" id="kpi-remaining">${{ global_budget|floatformat:"0"|intcomma }} CLP</div>
|
||||
<div class="kpi-subtext">
|
||||
<span>Saldo Restante: ${{ remaining_budget|floatformat:"0" }} CLP</span>
|
||||
<span>Saldo Restante: ${{ remaining_budget|floatformat:"0"|intcomma }} CLP</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,6 +50,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gráficos de Inteligencia de Negocios (BI) -->
|
||||
<div class="charts-grid" style="display: grid; grid-template-columns: 1fr 2fr; gap: 1.5rem; margin-bottom: 2rem;">
|
||||
<!-- Gráfico 1: Categorías -->
|
||||
<div class="data-card" style="padding: 1.5rem;">
|
||||
<h3 style="color: var(--efintel-petroleum); font-size: 1rem; margin-bottom: 1rem; border-bottom: 1px solid var(--efintel-light); padding-bottom: 0.5rem;">Distribución por Categoría</h3>
|
||||
<div style="position: relative; height: 250px;">
|
||||
<canvas id="categoryChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gráfico 2: Proyectos -->
|
||||
<div class="data-card" style="padding: 1.5rem;">
|
||||
<h3 style="color: var(--efintel-petroleum); font-size: 1rem; margin-bottom: 1rem; border-bottom: 1px solid var(--efintel-light); padding-bottom: 0.5rem;">Ejecución por Proyectos / Centros</h3>
|
||||
<div style="position: relative; height: 250px;">
|
||||
<canvas id="projectChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="data-card">
|
||||
<div class="data-card-header">
|
||||
<div style="font-weight:700; font-size:0.95rem; color:var(--efintel-dark);">Últimas Erogaciones Registradas (Flujo ERP)</div>
|
||||
@@ -79,8 +99,8 @@
|
||||
<span style="font-size:0.65rem; color:var(--efintel-gray);">{{ expense.responsible_person.username|default:expense.user.username }}</span>
|
||||
</td>
|
||||
<td class="amount-cell">
|
||||
<div>${{ expense.amount_clp|floatformat:"0" }} CLP</div>
|
||||
<div style="font-size:0.68rem; color:var(--efintel-gray); font-family:var(--font-mono);">${{ expense.amount_usd|floatformat:"2" }} USD</div>
|
||||
<div>${{ expense.amount_clp|floatformat:"0"|intcomma }} CLP</div>
|
||||
<div style="font-size:0.68rem; color:var(--efintel-gray); font-family:var(--font-mono);">${{ expense.amount_usd|floatformat:"2"|intcomma }} USD</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if expense.approval_status == 'EN_REVISION_DOCUMENTAL' %}
|
||||
@@ -120,3 +140,112 @@
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Colores corporativos EFINTEL
|
||||
const colors = [
|
||||
'#053B50', // petroleum
|
||||
'#176B87', // blue
|
||||
'#64CCC5', // turquoise
|
||||
'#107c41', // emerald
|
||||
'#d97706', // amber
|
||||
'#475569', // slate
|
||||
'#0f172a' // dark
|
||||
];
|
||||
|
||||
// Data inyectada desde el backend (JSON)
|
||||
const catLabels = JSON.parse('{{ category_labels_json|safe }}');
|
||||
const catData = JSON.parse('{{ category_data_json|safe }}');
|
||||
|
||||
const projLabels = JSON.parse('{{ project_labels_json|safe }}');
|
||||
const projData = JSON.parse('{{ project_data_json|safe }}');
|
||||
|
||||
// 1. Gráfico de Dona: Categorías
|
||||
if (document.getElementById('categoryChart') && catData.length > 0) {
|
||||
new Chart(document.getElementById('categoryChart'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: catLabels,
|
||||
datasets: [{
|
||||
data: catData,
|
||||
backgroundColor: colors,
|
||||
borderWidth: 0,
|
||||
hoverOffset: 4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { position: 'right', labels: { boxWidth: 12, font: { family: 'Inter', size: 11 } } },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let value = context.raw || 0;
|
||||
return ' $' + value.toLocaleString('es-CL') + ' CLP';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
cutout: '70%'
|
||||
}
|
||||
});
|
||||
} else if(document.getElementById('categoryChart')) {
|
||||
document.getElementById('categoryChart').parentElement.innerHTML = '<div style="display:flex; height:100%; align-items:center; justify-content:center; color:#94a3b8;">Sin datos suficientes</div>';
|
||||
}
|
||||
|
||||
// 2. Gráfico de Barras: Proyectos
|
||||
if (document.getElementById('projectChart') && projData.length > 0) {
|
||||
new Chart(document.getElementById('projectChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: projLabels,
|
||||
datasets: [{
|
||||
label: 'Gasto Acumulado (CLP)',
|
||||
data: projData,
|
||||
backgroundColor: '#176B87',
|
||||
borderRadius: 4,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let value = context.raw || 0;
|
||||
return ' $' + value.toLocaleString('es-CL') + ' CLP';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: '#f1f5f9', drawBorder: false },
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
if (value >= 1000000) return '$' + (value / 1000000).toFixed(1) + 'M';
|
||||
if (value >= 1000) return '$' + (value / 1000).toFixed(0) + 'k';
|
||||
return '$' + value;
|
||||
},
|
||||
font: { family: 'Inter', size: 10 }
|
||||
}
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { font: { family: 'Inter', size: 11 } }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if(document.getElementById('projectChart')) {
|
||||
document.getElementById('projectChart').parentElement.innerHTML = '<div style="display:flex; height:100%; align-items:center; justify-content:center; color:#94a3b8;">Sin datos suficientes</div>';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -12,15 +12,27 @@
|
||||
{% csrf_token %}
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<div class="form-group" style="position: relative;">
|
||||
<label for="{{ form.project.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Centro de Costo / Proyecto</label>
|
||||
{{ form.project }}
|
||||
<div class="custom-select-wrapper" style="position: relative;">
|
||||
{{ form.project }}
|
||||
<input type="text" id="project-search-input" placeholder="Escriba código o nombre del proyecto..." autocomplete="off">
|
||||
<div id="project-dropdown-list" class="custom-dropdown-list" style="display: none; position: absolute; background: white; border: 1px solid var(--efintel-border); border-radius: 4px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); width: 100%; z-index: 1000; max-height: 250px; overflow-y: auto; margin-top: 4px; box-sizing: border-box;">
|
||||
<!-- Items dynamically populated -->
|
||||
</div>
|
||||
</div>
|
||||
{% if form.project.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.project.errors }}</div>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group" style="position: relative;">
|
||||
<label for="{{ form.category.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Categoría Contable</label>
|
||||
{{ form.category }}
|
||||
<div class="custom-select-wrapper" style="position: relative;">
|
||||
{{ form.category }}
|
||||
<input type="text" id="category-search-input" placeholder="Escriba código o nombre para buscar..." autocomplete="off">
|
||||
<div id="category-dropdown-list" class="custom-dropdown-list" style="display: none; position: absolute; background: white; border: 1px solid var(--efintel-border); border-radius: 4px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); width: 100%; z-index: 1000; max-height: 250px; overflow-y: auto; margin-top: 4px; box-sizing: border-box;">
|
||||
<!-- Items dynamically populated -->
|
||||
</div>
|
||||
</div>
|
||||
{% if form.category.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.category.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,10 +41,32 @@
|
||||
<div class="form-group">
|
||||
<label for="{{ form.amount_clp.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Monto Neto (CLP)</label>
|
||||
{{ form.amount_clp }}
|
||||
<small style="color: var(--efintel-gray); font-size: 0.7rem;">El equivalente en USD será calculado automáticamente por el sistema.</small>
|
||||
{% if form.amount_clp.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.amount_clp.errors }}</div>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.tax_type.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Tipo de Impuesto</label>
|
||||
{{ form.tax_type }}
|
||||
{% if form.tax_type.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.tax_type.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.tax_amount_clp.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Monto Impuesto (CLP)</label>
|
||||
{{ form.tax_amount_clp }}
|
||||
{% if form.tax_amount_clp.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.tax_amount_clp.errors }}</div>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.total_amount_clp.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Monto Total (CLP)</label>
|
||||
{{ form.total_amount_clp }}
|
||||
<small style="color: var(--efintel-gray); font-size: 0.7rem;">El equivalente en USD será calculado automáticamente por el sistema.</small>
|
||||
{% if form.total_amount_clp.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.total_amount_clp.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.currency.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Moneda Original</label>
|
||||
{{ form.currency }}
|
||||
@@ -110,5 +144,201 @@
|
||||
el.style.fontSize = '0.9rem';
|
||||
el.style.boxSizing = 'border-box';
|
||||
});
|
||||
|
||||
const amountInput = document.getElementById('id_amount_clp');
|
||||
const taxTypeSelect = document.getElementById('id_tax_type');
|
||||
const taxAmountInput = document.getElementById('id_tax_amount_clp');
|
||||
const totalAmountInput = document.getElementById('id_total_amount_clp');
|
||||
|
||||
function calculateTaxes() {
|
||||
const amount = parseFloat(amountInput.value) || 0;
|
||||
const taxType = taxTypeSelect.value;
|
||||
let taxAmount = 0;
|
||||
|
||||
if (taxType === 'IVA_19') {
|
||||
taxAmount = amount * 0.19;
|
||||
} else if (taxType === 'HONORARIOS_13_75') {
|
||||
taxAmount = amount * 0.1375;
|
||||
}
|
||||
|
||||
taxAmountInput.value = taxAmount.toFixed(0);
|
||||
totalAmountInput.value = (amount + taxAmount).toFixed(0);
|
||||
}
|
||||
|
||||
amountInput.addEventListener('input', calculateTaxes);
|
||||
taxTypeSelect.addEventListener('change', calculateTaxes);
|
||||
|
||||
// Initial calculation
|
||||
calculateTaxes();
|
||||
|
||||
// Custom Searchable Dropdown for Category
|
||||
const categorySelect = document.getElementById('id_category');
|
||||
const searchInput = document.getElementById('category-search-input');
|
||||
const dropdownList = document.getElementById('category-dropdown-list');
|
||||
|
||||
if (categorySelect && searchInput && dropdownList) {
|
||||
// Hide original select (make it display none so form data is still submitted)
|
||||
categorySelect.style.display = 'none';
|
||||
|
||||
// Extract options from the django-generated select
|
||||
const options = Array.from(categorySelect.options)
|
||||
.filter(opt => opt.value !== '')
|
||||
.map(opt => ({
|
||||
value: opt.value,
|
||||
text: opt.textContent
|
||||
}));
|
||||
|
||||
// Render option list dynamically
|
||||
function renderList(filteredOptions) {
|
||||
dropdownList.innerHTML = '';
|
||||
if (filteredOptions.length === 0) {
|
||||
dropdownList.innerHTML = '<div style="padding: 0.65rem; color: var(--efintel-gray); font-size: 0.85rem; text-align: center;">No se encontraron categorías</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
filteredOptions.forEach(opt => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'dropdown-item';
|
||||
item.style.padding = '0.65rem 1rem';
|
||||
item.style.cursor = 'pointer';
|
||||
item.style.fontSize = '0.85rem';
|
||||
item.style.borderBottom = '1px solid #f1f5f9';
|
||||
item.textContent = opt.text;
|
||||
|
||||
// Mark parent categories differently (they usually have a badge in representation or just end with standard code)
|
||||
// Note: we can detect if it's a main partida (ends in '000: ' or contains 'Partida')
|
||||
const isParent = opt.text.includes('1000: ') || opt.text.includes('2000: ') || opt.text.includes('3000: ') || opt.text.includes('4000: ');
|
||||
if (isParent) {
|
||||
item.style.fontWeight = '700';
|
||||
item.style.background = '#f8fafc';
|
||||
item.style.color = 'var(--efintel-petroleum)';
|
||||
}
|
||||
|
||||
item.addEventListener('click', () => {
|
||||
categorySelect.value = opt.value;
|
||||
searchInput.value = opt.text;
|
||||
dropdownList.style.display = 'none';
|
||||
categorySelect.dispatchEvent(new Event('change'));
|
||||
});
|
||||
|
||||
item.addEventListener('mouseenter', () => {
|
||||
item.style.background = 'var(--efintel-light)';
|
||||
item.style.color = 'var(--efintel-blue)';
|
||||
});
|
||||
item.addEventListener('mouseleave', () => {
|
||||
item.style.background = isParent ? '#f8fafc' : 'white';
|
||||
item.style.color = isParent ? 'var(--efintel-petroleum)' : 'inherit';
|
||||
});
|
||||
|
||||
dropdownList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize input value if edit mode
|
||||
const selectedOption = categorySelect.options[categorySelect.selectedIndex];
|
||||
if (selectedOption && selectedOption.value !== '') {
|
||||
searchInput.value = selectedOption.textContent;
|
||||
}
|
||||
|
||||
// Show dropdown list on focus
|
||||
searchInput.addEventListener('focus', () => {
|
||||
renderList(options);
|
||||
dropdownList.style.display = 'block';
|
||||
});
|
||||
|
||||
// Dynamic filtering
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const query = e.target.value.toLowerCase();
|
||||
const filtered = options.filter(opt => opt.text.toLowerCase().includes(query));
|
||||
renderList(filtered);
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!searchInput.contains(e.target) && !dropdownList.contains(e.target)) {
|
||||
dropdownList.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
// Custom Searchable Dropdown for Project
|
||||
const projectSelect = document.getElementById('id_project');
|
||||
const projectSearchInput = document.getElementById('project-search-input');
|
||||
const projectDropdownList = document.getElementById('project-dropdown-list');
|
||||
|
||||
if (projectSelect && projectSearchInput && projectDropdownList) {
|
||||
projectSelect.style.display = 'none';
|
||||
|
||||
const projectOptions = Array.from(projectSelect.options)
|
||||
.filter(opt => opt.value !== '')
|
||||
.map(opt => ({
|
||||
value: opt.value,
|
||||
text: opt.textContent
|
||||
}));
|
||||
|
||||
function renderProjectList(filteredOptions) {
|
||||
projectDropdownList.innerHTML = '';
|
||||
if (filteredOptions.length === 0) {
|
||||
projectDropdownList.innerHTML = '<div style="padding: 0.65rem; color: var(--efintel-gray); font-size: 0.85rem; text-align: center;">No se encontraron proyectos</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
filteredOptions.forEach(opt => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'dropdown-item';
|
||||
item.style.padding = '0.65rem 1rem';
|
||||
item.style.cursor = 'pointer';
|
||||
item.style.fontSize = '0.85rem';
|
||||
item.style.borderBottom = '1px solid #f1f5f9';
|
||||
item.textContent = opt.text;
|
||||
|
||||
const isPortfolio = opt.text.startsWith('PORT-');
|
||||
if (isPortfolio) {
|
||||
item.style.fontWeight = '700';
|
||||
item.style.background = '#f8fafc';
|
||||
item.style.color = 'var(--efintel-petroleum)';
|
||||
}
|
||||
|
||||
item.addEventListener('click', () => {
|
||||
projectSelect.value = opt.value;
|
||||
projectSearchInput.value = opt.text;
|
||||
projectDropdownList.style.display = 'none';
|
||||
projectSelect.dispatchEvent(new Event('change'));
|
||||
});
|
||||
|
||||
item.addEventListener('mouseenter', () => {
|
||||
item.style.background = 'var(--efintel-light)';
|
||||
item.style.color = 'var(--efintel-blue)';
|
||||
});
|
||||
item.addEventListener('mouseleave', () => {
|
||||
item.style.background = isPortfolio ? '#f8fafc' : 'white';
|
||||
item.style.color = isPortfolio ? 'var(--efintel-petroleum)' : 'inherit';
|
||||
});
|
||||
|
||||
projectDropdownList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
const selectedProjectOption = projectSelect.options[projectSelect.selectedIndex];
|
||||
if (selectedProjectOption && selectedProjectOption.value !== '') {
|
||||
projectSearchInput.value = selectedProjectOption.textContent;
|
||||
}
|
||||
|
||||
projectSearchInput.addEventListener('focus', () => {
|
||||
renderProjectList(projectOptions);
|
||||
projectDropdownList.style.display = 'block';
|
||||
});
|
||||
|
||||
projectSearchInput.addEventListener('input', (e) => {
|
||||
const query = e.target.value.toLowerCase();
|
||||
const filtered = projectOptions.filter(opt => opt.text.toLowerCase().includes(query));
|
||||
renderProjectList(filtered);
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!projectSearchInput.contains(e.target) && !projectDropdownList.contains(e.target)) {
|
||||
projectDropdownList.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block content %}
|
||||
<section class="page-view active">
|
||||
@@ -71,8 +72,8 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="amount-cell">
|
||||
<div>${{ expense.amount_clp|floatformat:"0" }} CLP</div>
|
||||
<div style="font-size:0.68rem; color:var(--efintel-gray); font-family:var(--font-mono);">${{ expense.amount_usd|floatformat:"2" }} USD</div>
|
||||
<div>${{ expense.amount_clp|floatformat:"0"|intcomma }} CLP</div>
|
||||
<div style="font-size:0.68rem; color:var(--efintel-gray); font-family:var(--font-mono);">${{ expense.amount_usd|floatformat:"2"|intcomma }} USD</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if expense.approval_status == 'EN_REVISION_DOCUMENTAL' %}
|
||||
@@ -96,15 +97,19 @@
|
||||
<a href="{% url 'approve_expense' expense.id 'rechazar' %}" class="btn-danger" style="padding:0.3rem; font-size:0.65rem;" title="Rechazar"><i data-lucide="x"></i></a>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.groups.all.0.name == 'Administrador' or request.user.is_superuser or expense.user == request.user %}
|
||||
<a href="/gastos/{{ expense.id }}/editar/" class="btn-secondary" style="padding: 0.3rem; background: var(--efintel-light); color: var(--efintel-petroleum);" title="Editar"><i data-lucide="pencil" style="width:14px;"></i></a>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.groups.all.0.name == 'Administrador' or request.user.is_superuser %}
|
||||
<form method="post" action="/gastos/{{ expense.id }}/eliminar/" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn-danger" style="padding: 0.3rem;" onclick="return confirm('¿Seguro que desea eliminar esta erogación? Esta acción quedará en el Log de Auditoría.');" title="Eliminar"><i data-lucide="trash-2" style="width:14px;"></i></button>
|
||||
</form>
|
||||
{% if expense.approval_status != 'SOLICITADO_FINANZAS' and expense.approval_status != 'LIBERADO_PAGADO' %}
|
||||
{% if request.user.groups.all.0.name == 'Administrador' or request.user.is_superuser or expense.user == request.user %}
|
||||
<a href="/gastos/{{ expense.id }}/editar/" class="btn-secondary" style="padding: 0.3rem; background: var(--efintel-light); color: var(--efintel-petroleum);" title="Editar"><i data-lucide="pencil" style="width:14px;"></i></a>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.groups.all.0.name == 'Administrador' or request.user.is_superuser %}
|
||||
<form method="post" action="/gastos/{{ expense.id }}/eliminar/" style="display:inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn-danger" style="padding: 0.3rem;" onclick="return confirm('¿Seguro que desea eliminar esta erogación? Esta acción quedará en el Log de Auditoría.');" title="Eliminar"><i data-lucide="trash-2" style="width:14px;"></i></button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span style="font-size:0.75rem; color:var(--efintel-gray); padding: 0.3rem;" title="Comprobante Bloqueado"><i data-lucide="lock" style="width:14px;"></i></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
+307
-47
@@ -1,41 +1,149 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block content %}
|
||||
<div class="header-section" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
|
||||
<h1>Panel de Gestión Administrativa</h1>
|
||||
</div>
|
||||
|
||||
<div class="glass-card tabs-container" style="padding: 2rem;">
|
||||
<nav class="tabs-nav">
|
||||
<button class="tab-btn active" onclick="switchTab(event, 'tab-proyectos')">📁 Proyectos</button>
|
||||
<button class="tab-btn" onclick="switchTab(event, 'tab-responsables')">👥 Colaboradores / Responsables</button>
|
||||
<button class="tab-btn" onclick="switchTab(event, 'tab-config')">⚙️ Configuración</button>
|
||||
<button class="tab-btn" onclick="switchTab(event, 'tab-bitacora')">📜 Bitácora de Auditoría</button>
|
||||
</nav>
|
||||
<style>
|
||||
.sidebar-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 0.8rem 1rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: var(--efintel-dark);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.sidebar-btn:hover {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: var(--efintel-blue);
|
||||
}
|
||||
.sidebar-btn.active {
|
||||
background: rgba(5, 59, 80, 0.08); /* efintel-petroleum with opacity */
|
||||
color: var(--efintel-petroleum);
|
||||
font-weight: 600;
|
||||
border-left: 4px solid var(--efintel-petroleum);
|
||||
}
|
||||
.sidebar-btn i {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
.tab-content {
|
||||
display: none;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="gestion-layout" style="display: grid; grid-template-columns: 260px 1fr; gap: 2rem; align-items: start;">
|
||||
|
||||
<!-- Sidebar Navigation -->
|
||||
<div class="data-card" style="padding: 1rem; position: sticky; top: 2rem; background: white; border-radius: 8px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);">
|
||||
<nav class="sidebar-nav" style="display: flex; flex-direction: column; gap: 0.25rem;">
|
||||
<button class="sidebar-btn active" onclick="switchTab(event, 'tab-proyectos')">
|
||||
<i data-lucide="folder-kanban"></i> Proyectos y Centros
|
||||
</button>
|
||||
<button class="sidebar-btn" onclick="switchTab(event, 'tab-categorias')">
|
||||
<i data-lucide="tags"></i> Categorías Contables
|
||||
</button>
|
||||
<button class="sidebar-btn" onclick="switchTab(event, 'tab-proveedores')">
|
||||
<i data-lucide="building-2"></i> Proveedores
|
||||
</button>
|
||||
<button class="sidebar-btn" onclick="switchTab(event, 'tab-responsables')">
|
||||
<i data-lucide="users"></i> Responsables
|
||||
</button>
|
||||
<hr style="border: none; border-top: 1px solid var(--efintel-light); margin: 0.75rem 0;" />
|
||||
<button class="sidebar-btn" onclick="switchTab(event, 'tab-config')">
|
||||
<i data-lucide="settings"></i> Configuración General
|
||||
</button>
|
||||
<button class="sidebar-btn" onclick="switchTab(event, 'tab-bitacora')">
|
||||
<i data-lucide="scroll-text"></i> Bitácora de Auditoría
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<div class="gestion-content">
|
||||
|
||||
<!-- Pestaña Proyectos -->
|
||||
<div id="tab-proyectos" class="tab-content active">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
|
||||
<h2>Proyectos del Sistema</h2>
|
||||
<a href="{% url 'project_create' %}" class="btn btn-primary">➕ Nuevo Proyecto</a>
|
||||
<h2>Proyectos y Centros de Costo</h2>
|
||||
<a href="{% url 'project_create' %}" class="btn btn-primary">Nuevo Proyecto / Centro</a>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<div class="data-card">
|
||||
<div class="data-table-wrapper">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Código</th>
|
||||
<th>Nombre</th>
|
||||
<th>Descripción</th>
|
||||
<th>Portafolio / Jerarquía</th>
|
||||
<th>Categoría / Especialidad</th>
|
||||
<th>Estado</th>
|
||||
<th>Especialistas Habilitados</th>
|
||||
<th>Encargado (Responsable)</th>
|
||||
<th>Creado Por</th>
|
||||
<th>Última Modificación</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for project in projects %}
|
||||
<tr>
|
||||
<td style="font-weight: 600; color: var(--accent-secondary);">{{ project.name }}</td>
|
||||
<td>{{ project.description|default:"Sin descripción" }}</td>
|
||||
<td style="font-weight: 600; color: var(--efintel-blue);">{{ project.code }}</td>
|
||||
<td style="font-weight: 600; color: var(--accent-secondary);">
|
||||
{% if project.parent %}<span style="color:var(--efintel-gray); margin-right:5px;">↳</span>{% endif %}
|
||||
{{ project.name }}
|
||||
</td>
|
||||
<td>
|
||||
{% if project.parent %}
|
||||
<span style="font-size: 0.85rem; color: var(--efintel-blue); font-weight: 500;">{{ project.parent.code }} - {{ project.parent.name }}</span>
|
||||
{% else %}
|
||||
<span class="badge badge-emerald" style="font-size: 0.75rem;">Portafolio Principal</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if project.category %}
|
||||
<span style="font-size: 0.8rem; font-weight: 600; color: var(--efintel-petroleum);">{{ project.category.code }} - {{ project.category.name }}</span>
|
||||
{% else %}
|
||||
<span style="color: var(--efintel-gray); font-size: 0.75rem; font-style: italic;">Sin asignar</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if project.status == 'ACTIVO' %}
|
||||
<span class="badge badge-emerald"><i class="status-dot green"></i> Activo</span>
|
||||
{% elif project.status == 'PAUSADA' %}
|
||||
<span class="badge badge-amber"><i class="status-dot orange"></i> En Pausa</span>
|
||||
{% else %}
|
||||
<span class="badge badge-red"><i class="status-dot red"></i> Terminado</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if project.category %}
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 0.25rem; max-width: 250px;">
|
||||
{% for spec in project.category.specialists.all %}
|
||||
<span class="badge badge-blue" style="font-size: 0.7rem; font-weight: 600; padding: 0.15rem 0.35rem; border-radius: 4px;">{{ spec.user.username }}</span>
|
||||
{% empty %}
|
||||
<span style="color: var(--efintel-gray); font-size: 0.75rem; font-style: italic;">Sin especialistas</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<span style="color: var(--efintel-gray); font-size: 0.75rem; font-style: italic;">Asigna una categoría para ver especialistas</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if project.manager %}
|
||||
<span style="font-weight: 500;">{{ project.manager.username }}</span>
|
||||
@@ -43,44 +151,181 @@
|
||||
<em style="color: var(--text-secondary);">No asignado</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ project.created_by.username|default:"Sistema" }}</td>
|
||||
<td>{{ project.updated_at|date:"d/m/Y H:i" }}</td>
|
||||
<td>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<a href="{% url 'project_edit' project.id %}" class="btn btn-outline" style="padding: 0.35rem 0.75rem; font-size: 0.8rem;">✏️ Editar</a>
|
||||
<div style="display: flex; gap: 0.25rem;">
|
||||
<a href="{% url 'project_edit' project.id %}" class="btn-icon" title="Editar" style="color: var(--efintel-blue); padding: 0.4rem; border-radius: 4px; transition: background 0.2s;">
|
||||
<i data-lucide="edit-3" style="width: 16px;"></i>
|
||||
</a>
|
||||
<form action="{% url 'project_delete' project.id %}" method="POST" style="display: inline;" onsubmit="return confirm('¿Estás seguro de que deseas eliminar este proyecto? Se eliminarán todos los gastos asociados.');">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger" style="padding: 0.35rem 0.75rem; font-size: 0.8rem;">🗑️ Eliminar</button>
|
||||
<button type="submit" class="btn-icon" title="Eliminar" style="color: var(--efintel-danger); padding: 0.4rem; border-radius: 4px; transition: background 0.2s; background: transparent; border: none; cursor: pointer;">
|
||||
<i data-lucide="trash-2" style="width: 16px;"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" style="text-align: center;">No hay proyectos registrados.</td>
|
||||
<td colspan="8" style="text-align: center;">No hay proyectos registrados.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pestaña Categorías -->
|
||||
<div id="tab-categorias" class="tab-content">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
|
||||
<div>
|
||||
<h2 style="color: var(--efintel-petroleum); margin-bottom: 0.25rem;">Categorías Contables</h2>
|
||||
<p style="color: var(--efintel-gray); font-size: 0.9rem;">Estructura de imputación financiera.</p>
|
||||
</div>
|
||||
<a href="{% url 'category_create' %}" class="btn-primary" style="text-decoration: none;">
|
||||
<i data-lucide="plus-circle" style="width: 16px;"></i> Nueva Categoría
|
||||
</a>
|
||||
</div>
|
||||
<div class="data-card" style="padding: 0;">
|
||||
<div class="data-table-wrapper" style="margin: 0; padding: 1.5rem;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Código</th>
|
||||
<th>Nombre</th>
|
||||
<th>Partida / Jerarquía</th>
|
||||
<th>Descripción</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cat in categories %}
|
||||
<tr>
|
||||
<td style="font-weight: 600; color: var(--accent-primary);">{{ cat.code }}</td>
|
||||
<td style="font-weight: 600; color: var(--text-primary);">
|
||||
{% if cat.parent %}<span style="color:var(--efintel-gray); margin-right:5px;">↳</span>{% endif %}
|
||||
{{ cat.name }}
|
||||
</td>
|
||||
<td>
|
||||
{% if cat.parent %}
|
||||
<span style="font-size: 0.85rem; color: var(--efintel-blue); font-weight: 500;">{{ cat.parent.code }} - {{ cat.parent.name }}</span>
|
||||
{% else %}
|
||||
<span class="badge badge-amber" style="font-size: 0.75rem;">Partida Principal</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ cat.description|default:"Sin descripción" }}</td>
|
||||
<td>
|
||||
<div style="display: flex; gap: 0.25rem;">
|
||||
<a href="{% url 'category_edit' cat.id %}" class="btn-icon" title="Editar" style="color: var(--efintel-blue); padding: 0.4rem; border-radius: 4px; transition: background 0.2s;">
|
||||
<i data-lucide="edit-3" style="width: 16px;"></i>
|
||||
</a>
|
||||
<form action="{% url 'category_delete' cat.id %}" method="POST" style="display: inline;" onsubmit="return confirm('¿Estás seguro de que deseas eliminar esta categoría? Se podrían perder asociaciones con los gastos.');">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn-icon" title="Eliminar" style="color: var(--efintel-danger); padding: 0.4rem; border-radius: 4px; transition: background 0.2s; background: transparent; border: none; cursor: pointer;">
|
||||
<i data-lucide="trash-2" style="width: 16px;"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center;">No hay categorías registradas.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pestaña Proveedores -->
|
||||
<div id="tab-proveedores" class="tab-content">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
|
||||
<div>
|
||||
<h2 style="color: var(--efintel-petroleum); margin-bottom: 0.25rem;">Directorio de Proveedores</h2>
|
||||
<p style="color: var(--efintel-gray); font-size: 0.9rem;">Registro centralizado de proveedores habilitados.</p>
|
||||
</div>
|
||||
<a href="{% url 'vendor_create' %}" class="btn-primary" style="text-decoration: none;">
|
||||
<i data-lucide="plus-circle" style="width: 16px;"></i> Nuevo Proveedor
|
||||
</a>
|
||||
</div>
|
||||
<div class="data-card" style="padding: 0;">
|
||||
<div class="data-table-wrapper" style="margin: 0; padding: 1.5rem;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>RUT</th>
|
||||
<th>Razón Social</th>
|
||||
<th>Giro Comercial</th>
|
||||
<th>Datos Bancarios</th>
|
||||
<th>Plazo Pago</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for vendor in vendors %}
|
||||
<tr>
|
||||
<td style="font-weight: 600; color: var(--accent-primary);">{{ vendor.rut }}</td>
|
||||
<td style="font-weight: 600; color: var(--text-primary);">{{ vendor.name }}</td>
|
||||
<td>{{ vendor.business_line|default:"-" }}</td>
|
||||
<td>
|
||||
{% if vendor.bank %}
|
||||
<div style="font-size: 0.85rem;">{{ vendor.bank }}</div>
|
||||
<div style="font-size: 0.75rem; color: var(--text-secondary);">{{ vendor.account_type }} - {{ vendor.account_number }}</div>
|
||||
{% else %}
|
||||
<em style="color: var(--text-secondary);">No registrados</em>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ vendor.payment_terms }} días</td>
|
||||
<td>
|
||||
<div style="display: flex; gap: 0.25rem;">
|
||||
<a href="{% url 'vendor_edit' vendor.id %}" class="btn-icon" title="Editar" style="color: var(--efintel-blue); padding: 0.4rem; border-radius: 4px; transition: background 0.2s;">
|
||||
<i data-lucide="edit-3" style="width: 16px;"></i>
|
||||
</a>
|
||||
<form action="{% url 'vendor_delete' vendor.id %}" method="POST" style="display: inline;" onsubmit="return confirm('¿Estás seguro de que deseas eliminar este proveedor? Sus gastos quedarán huérfanos.');">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn-icon" title="Eliminar" style="color: var(--efintel-danger); padding: 0.4rem; border-radius: 4px; transition: background 0.2s; background: transparent; border: none; cursor: pointer;">
|
||||
<i data-lucide="trash-2" style="width: 16px;"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" style="text-align: center;">No hay proveedores registrados.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pestaña Responsables -->
|
||||
<div id="tab-responsables" class="tab-content">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem;">
|
||||
<h2>Responsables y Colaboradores</h2>
|
||||
<a href="{% url 'responsable_create' %}" class="btn btn-primary">➕ Nuevo Responsable</a>
|
||||
<div>
|
||||
<h2 style="color: var(--efintel-petroleum); margin-bottom: 0.25rem;">Responsables y Colaboradores</h2>
|
||||
<p style="color: var(--efintel-gray); font-size: 0.9rem;">Gestión de acceso y roles del personal.</p>
|
||||
</div>
|
||||
<a href="{% url 'responsable_create' %}" class="btn-primary" style="text-decoration: none;">
|
||||
<i data-lucide="plus-circle" style="width: 16px;"></i> Nuevo Responsable
|
||||
</a>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<table>
|
||||
<div class="data-card" style="padding: 0;">
|
||||
<div class="data-table-wrapper" style="margin: 0; padding: 1.5rem;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Usuario</th>
|
||||
<th>Correo</th>
|
||||
<th>Rol / Permiso</th>
|
||||
<th>Especialidades Habilitadas (CBS)</th>
|
||||
<th>Descripción / Bio</th>
|
||||
<th>F. Registro</th>
|
||||
<th>F. Modificación</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -93,27 +338,39 @@
|
||||
{% if resp.groups.all %}
|
||||
{% with role=resp.groups.all.0.name %}
|
||||
{% if role == 'Administrador' %}
|
||||
<span class="badge badge-admin">Administrador</span>
|
||||
<span class="badge badge-red">Administrador</span>
|
||||
{% elif role == 'Director de Proyecto' %}
|
||||
<span class="badge badge-director">Director</span>
|
||||
<span class="badge badge-blue">Director</span>
|
||||
{% else %}
|
||||
<span class="badge badge-gestor">Gestor</span>
|
||||
<span class="badge badge-emerald">Gestor</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<span class="badge badge-gestor">Sin Rol</span>
|
||||
<span class="badge badge-amber">Sin Rol</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 0.25rem; max-width: 250px;">
|
||||
{% for spec in resp.profile.specialties.all %}
|
||||
<span class="badge badge-blue" style="font-size: 0.7rem; padding: 0.15rem 0.35rem;" title="{{ spec.name }}">{{ spec.code }}</span>
|
||||
{% empty %}
|
||||
<span style="color: var(--efintel-gray); font-size: 0.75rem; font-style: italic;">Ninguna</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ resp.profile.description|default:"Sin descripción personal" }}</td>
|
||||
<td>{{ resp.profile.created_at|date:"d/m/Y"|default:"-" }}</td>
|
||||
<td>{{ resp.profile.updated_at|date:"d/m/Y H:i"|default:"-" }}</td>
|
||||
<td>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<a href="{% url 'responsable_edit' resp.id %}" class="btn btn-outline" style="padding: 0.35rem 0.75rem; font-size: 0.8rem;">✏️ Editar</a>
|
||||
<div style="display: flex; gap: 0.25rem;">
|
||||
<a href="{% url 'responsable_edit' resp.id %}" class="btn-icon" title="Editar" style="color: var(--efintel-blue); padding: 0.4rem; border-radius: 4px; transition: background 0.2s;">
|
||||
<i data-lucide="edit-3" style="width: 16px;"></i>
|
||||
</a>
|
||||
{% if resp != request.user %}
|
||||
<form action="{% url 'responsable_delete' resp.id %}" method="POST" style="display: inline;" onsubmit="return confirm('¿Estás seguro de que deseas eliminar este responsable?');">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger" style="padding: 0.35rem 0.75rem; font-size: 0.8rem;">🗑️ Eliminar</button>
|
||||
<button type="submit" class="btn-icon" title="Eliminar" style="color: var(--efintel-danger); padding: 0.4rem; border-radius: 4px; transition: background 0.2s; background: transparent; border: none; cursor: pointer;">
|
||||
<i data-lucide="trash-2" style="width: 16px;"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -126,21 +383,23 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pestaña Configuración -->
|
||||
<div id="tab-config" class="tab-content">
|
||||
<div style="margin-bottom: 1.5rem;">
|
||||
<h2>Configuración General del Sistema</h2>
|
||||
<h2 style="color: var(--efintel-petroleum); margin-bottom: 0.25rem;">Configuración General del Sistema</h2>
|
||||
<p style="color: var(--efintel-gray); font-size: 0.9rem;">Parámetros y límites globales del ERP.</p>
|
||||
</div>
|
||||
<div style="max-width: 500px; display: flex; flex-direction: column; gap: 1.5rem;">
|
||||
<div class="glass-card" style="padding: 1.5rem; background: rgba(0, 0, 0, 0.2);">
|
||||
<div class="data-card" style="padding: 2rem;">
|
||||
<div style="color: var(--text-secondary); font-size: 0.9rem; margin-bottom: 0.5rem;">Presupuesto General Asignado</div>
|
||||
<div style="font-size: 2rem; font-weight: 700; color: var(--accent-secondary);">${{ settings.global_budget|default:"100000.00" }}</div>
|
||||
<div style="font-size: 2.5rem; font-weight: 700; color: var(--efintel-petroleum);">${{ settings.global_budget|floatformat:"0"|intcomma|default:"100000" }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'settings_edit' %}" class="btn btn-primary">✏️ Modificar Presupuesto General</a>
|
||||
<a href="{% url 'settings_edit' %}" class="btn-primary" style="text-decoration: none;">Modificar Presupuesto General</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -148,20 +407,20 @@
|
||||
<!-- Pestaña Bitácora -->
|
||||
<div id="tab-bitacora" class="tab-content">
|
||||
<div style="margin-bottom: 1.5rem;">
|
||||
<h2>Bitácora de Auditoría y Trazabilidad (Logs)</h2>
|
||||
<h2 style="color: var(--efintel-petroleum); margin-bottom: 0.25rem;">Bitácora de Auditoría y Trazabilidad (Logs)</h2>
|
||||
<p style="color: var(--text-secondary); font-size: 0.9rem;">Historial detallado de operaciones realizadas por los administradores en el sistema.</p>
|
||||
</div>
|
||||
<div class="log-list">
|
||||
<div class="data-card" style="padding: 1.5rem;">
|
||||
{% for log in logs %}
|
||||
<div class="log-item">
|
||||
<div class="log-info">
|
||||
<div class="log-action">
|
||||
{% if "Creado" in log.action or "Creación" in log.action %}
|
||||
🟢 {{ log.action }}
|
||||
{{ log.action }}
|
||||
{% elif "Eliminado" in log.action or "Eliminación" in log.action %}
|
||||
🔴 {{ log.action }}
|
||||
{{ log.action }}
|
||||
{% else %}
|
||||
🟡 {{ log.action }}
|
||||
{{ log.action }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="log-desc">{{ log.description }}</div>
|
||||
@@ -176,7 +435,8 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- End Main Content Area -->
|
||||
</div> <!-- End Gestion Layout -->
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
@@ -187,7 +447,7 @@
|
||||
contents.forEach(content => content.classList.remove('active'));
|
||||
|
||||
// Desactivar todos los botones de pestaña
|
||||
const buttons = document.querySelectorAll('.tab-btn');
|
||||
const buttons = document.querySelectorAll('.sidebar-btn');
|
||||
buttons.forEach(btn => btn.classList.remove('active'));
|
||||
|
||||
// Activar pestaña actual
|
||||
@@ -202,7 +462,7 @@
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const activeTab = localStorage.getItem('activeTab');
|
||||
if (activeTab && document.getElementById(activeTab)) {
|
||||
const btn = Array.from(document.querySelectorAll('.tab-btn')).find(b => b.getAttribute('onclick').includes(activeTab));
|
||||
const btn = Array.from(document.querySelectorAll('.sidebar-btn')).find(b => b.getAttribute('onclick').includes(activeTab));
|
||||
if (btn) {
|
||||
// Simular clic en el botón
|
||||
btn.click();
|
||||
|
||||
@@ -1,42 +1,98 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="form-container">
|
||||
<div class="glass-card" style="padding: 2.5rem;">
|
||||
<h1 style="margin-bottom: 2rem;">
|
||||
{% if form.instance.pk %}Editar Proyecto{% else %}Nuevo Proyecto{% endif %}
|
||||
</h1>
|
||||
<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 Proyecto o Centro de Costo{% else %}Nuevo Proyecto o Centro de Costo{% endif %}
|
||||
</h2>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.name.id_for_label }}">Nombre del Proyecto</label>
|
||||
{{ form.name }}
|
||||
{% if form.name.errors %}
|
||||
<div style="color: var(--danger); font-size: 0.8rem; margin-top: 0.25rem;">{{ form.name.errors }}</div>
|
||||
<div style="display: grid; grid-template-columns: 1fr 2fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.code.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Código</label>
|
||||
{{ form.code }}
|
||||
{% if form.code.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.code.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.name.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Nombre del Proyecto / Centro</label>
|
||||
{{ form.name }}
|
||||
{% if form.name.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.name.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.type.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Tipo de Centro</label>
|
||||
{{ form.type }}
|
||||
{% if form.type.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.type.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.assigned_budget_clp.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Presupuesto Asignado (CLP)</label>
|
||||
{{ form.assigned_budget_clp }}
|
||||
{% if form.assigned_budget_clp.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.assigned_budget_clp.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.category.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Categoría del Proyecto (Especialidad)</label>
|
||||
{{ form.category }}
|
||||
{% if form.category.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.category.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.status.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Estado del Proyecto</label>
|
||||
{{ form.status }}
|
||||
{% if form.status.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.status.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom: 1.5rem;">
|
||||
<label for="{{ form.parent.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Portafolio Padre (Opcional)</label>
|
||||
<p style="font-size: 0.75rem; color: var(--text-secondary); margin-bottom: 0.25rem;">Dejar en blanco si este proyecto actúa como un Portafolio principal.</p>
|
||||
{{ form.parent }}
|
||||
{% if form.parent.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.parent.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.description.id_for_label }}">Descripción</label>
|
||||
<div class="form-group" style="margin-bottom: 1.5rem;">
|
||||
<label for="{{ form.description.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Descripción</label>
|
||||
{{ form.description }}
|
||||
{% if form.description.errors %}
|
||||
<div style="color: var(--danger); font-size: 0.8rem; margin-top: 0.25rem;">{{ form.description.errors }}</div>
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.description.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.manager.id_for_label }}">Encargado / Responsable del Proyecto</label>
|
||||
|
||||
<div class="form-group" style="margin-bottom: 2rem;">
|
||||
<label for="{{ form.manager.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Encargado / Responsable del Proyecto</label>
|
||||
{{ form.manager }}
|
||||
{% if form.manager.errors %}
|
||||
<div style="color: var(--danger); font-size: 0.8rem; margin-top: 0.25rem;">{{ form.manager.errors }}</div>
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.manager.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 1rem; margin-top: 2rem;">
|
||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
||||
<a href="{% url 'gestion_panel' %}" class="btn btn-outline">Cancelar</a>
|
||||
<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 Proyecto</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>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block content %}
|
||||
<div style="margin-bottom: 2rem;">
|
||||
<h1>Proyectos</h1>
|
||||
<div class="view-header">
|
||||
<div class="view-title">
|
||||
<h2>Proyectos</h2>
|
||||
<p>Monitoreo de Centros de Costo</p>
|
||||
</div>
|
||||
<a href="{% url 'project_create' %}" class="btn-primary">Nuevo Proyecto / Centro</a>
|
||||
</div>
|
||||
|
||||
<div class="project-grid">
|
||||
@@ -15,15 +20,23 @@
|
||||
<strong>Gestor:</strong> {{ project.manager.username|default:"No asignado" }}
|
||||
</div>
|
||||
<div class="project-stats">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<span style="font-size: 0.8rem; color: var(--text-secondary);">Gastos</span>
|
||||
<span style="font-weight: 600; font-size: 1.1rem;">{{ project.expense_count|default:"0" }}</span>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column; text-align: right;">
|
||||
<span style="font-size: 0.8rem; color: var(--text-secondary);">Total</span>
|
||||
<span style="font-weight: 600; font-size: 1.1rem; color: var(--accent-secondary);">
|
||||
${{ project.total_expenses|default:"0.00" }}
|
||||
</span>
|
||||
<div style="display: flex; flex-direction: column; gap: 0.5rem; width: 100%;">
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span style="font-size: 0.8rem; color: var(--efintel-gray);">Presupuesto Asignado</span>
|
||||
<span style="font-weight: 700; font-family: var(--font-mono); color: var(--efintel-petroleum);">
|
||||
${{ project.assigned_budget_clp|floatformat:"0"|intcomma }}
|
||||
</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span style="font-size: 0.8rem; color: var(--efintel-gray);">Costo Real Acumulado</span>
|
||||
<span style="font-weight: 700; font-family: var(--font-mono); color: var(--efintel-danger);">
|
||||
${{ project.actual_cost_clp|floatformat:"0"|intcomma }}
|
||||
</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; margin-top: 0.5rem; padding-top: 0.5rem; border-top: 1px solid var(--efintel-border);">
|
||||
<span style="font-size: 0.8rem; color: var(--efintel-gray);">Nº Erogaciones (Facturas)</span>
|
||||
<span style="font-weight: 700;">{{ project.expense_count|default:"0" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,71 +1,108 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="form-container">
|
||||
<div class="glass-card" style="padding: 2.5rem;">
|
||||
<h1 style="margin-bottom: 2rem;">
|
||||
<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 edit_mode %}edit-3{% else %}plus-circle{% endif %}"></i>
|
||||
{% if edit_mode %}Editar Colaborador (Responsable): {{ target_user.username }}{% else %}Nuevo Colaborador (Responsable){% endif %}
|
||||
</h1>
|
||||
</h2>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.username.id_for_label }}">Nombre de Usuario</label>
|
||||
{{ form.username }}
|
||||
{% if form.username.errors %}
|
||||
<div style="color: var(--danger); font-size: 0.8rem; margin-top: 0.25rem;">{{ form.username.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.email.id_for_label }}">Correo Electrónico</label>
|
||||
{{ form.email }}
|
||||
{% if form.email.errors %}
|
||||
<div style="color: var(--danger); font-size: 0.8rem; margin-top: 0.25rem;">{{ form.email.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.password.id_for_label }}">
|
||||
Contraseña
|
||||
{% if edit_mode %}
|
||||
<span style="font-size: 0.8rem; color: var(--text-secondary);">(Opcional: Dejar en blanco para no cambiarla)</span>
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.username.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Nombre de Usuario</label>
|
||||
{{ form.username }}
|
||||
{% if form.username.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.username.errors }}</div>
|
||||
{% endif %}
|
||||
</label>
|
||||
{{ form.password }}
|
||||
{% if form.password.errors %}
|
||||
<div style="color: var(--danger); font-size: 0.8rem; margin-top: 0.25rem;">{{ form.password.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.email.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Correo Electrónico</label>
|
||||
{{ form.email }}
|
||||
{% if form.email.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.email.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.role.id_for_label }}">Rol / Grupo del Sistema</label>
|
||||
{{ form.role }}
|
||||
{% if form.role.errors %}
|
||||
<div style="color: var(--danger); font-size: 0.8rem; margin-top: 0.25rem;">{{ form.role.errors }}</div>
|
||||
{% endif %}
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.password.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">
|
||||
Contraseña
|
||||
{% if edit_mode %}
|
||||
<span style="font-size: 0.75rem; color: var(--efintel-gray); font-weight: 400;">(Opcional)</span>
|
||||
{% endif %}
|
||||
</label>
|
||||
{{ form.password }}
|
||||
{% if form.password.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.password.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.role.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Rol / Grupo del Sistema</label>
|
||||
{{ form.role }}
|
||||
{% if form.role.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.role.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.description.id_for_label }}">Descripción / Bio del Responsable</label>
|
||||
<div class="form-group" style="margin-bottom: 2rem;">
|
||||
<label for="{{ form.description.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Descripción / Bio del Responsable</label>
|
||||
{{ form.description }}
|
||||
{% if form.description.errors %}
|
||||
<div style="color: var(--danger); font-size: 0.8rem; margin-top: 0.25rem;">{{ form.description.errors }}</div>
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.description.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 1rem; margin-top: 2rem;">
|
||||
<button type="submit" class="btn btn-primary">Guardar</button>
|
||||
<a href="{% url 'gestion_panel' %}" class="btn btn-outline">Cancelar</a>
|
||||
<div class="form-group" style="margin-bottom: 2rem;">
|
||||
<label style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Especialidades Técnicas (Categorías Asociadas)</label>
|
||||
<p style="font-size: 0.75rem; color: var(--efintel-gray); margin-bottom: 0.75rem;">Selecciona las áreas de especialidad de este colaborador en la cartera técnica.</p>
|
||||
<div class="specialties-container" style="max-height: 220px; overflow-y: auto; padding: 1rem; border: 1px solid var(--efintel-border); border-radius: 4px; background: #f8fafc;">
|
||||
<style>
|
||||
.specialties-container ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.specialties-container li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.4rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--efintel-dark);
|
||||
}
|
||||
.specialties-container input[type="checkbox"] {
|
||||
margin-top: 0.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
{{ form.specialties }}
|
||||
</div>
|
||||
{% if form.specialties.errors %}
|
||||
<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.specialties.errors }}</div>
|
||||
{% endif %}
|
||||
</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 Responsable</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Apply form-control class to all inputs
|
||||
document.querySelectorAll('input, select, textarea').forEach(el => {
|
||||
// Apply form-control class to all inputs except checkboxes
|
||||
document.querySelectorAll('input:not([type="checkbox"]), select, textarea').forEach(el => {
|
||||
el.classList.add('form-control');
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% 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 %}
|
||||
</h2>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<h3 style="margin-bottom: 1rem; color: var(--efintel-dark); font-size: 1rem;">Información General</h3>
|
||||
|
||||
<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 }}
|
||||
{% if form.rut.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.rut.errors }}</div>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.name.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Razón Social / Nombre</label>
|
||||
{{ form.name }}
|
||||
{% if form.name.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.name.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom: 2rem;">
|
||||
<label for="{{ form.business_line.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Giro Comercial</label>
|
||||
{{ form.business_line }}
|
||||
{% if form.business_line.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.business_line.errors }}</div>{% endif %}
|
||||
</div>
|
||||
|
||||
<h3 style="margin-top: 2.5rem; margin-bottom: 1rem; color: var(--efintel-dark); font-size: 1rem; border-top: 1px solid var(--efintel-light); padding-top: 1.5rem;">Datos Bancarios y Facturación</h3>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.bank.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Banco</label>
|
||||
{{ form.bank }}
|
||||
{% if form.bank.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.bank.errors }}</div>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.account_type.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Tipo de Cuenta</label>
|
||||
{{ form.account_type }}
|
||||
{% if form.account_type.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.account_type.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-group">
|
||||
<label for="{{ form.account_number.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Número de Cuenta</label>
|
||||
{{ form.account_number }}
|
||||
{% if form.account_number.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.account_number.errors }}</div>{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="{{ form.email.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Correo de Pago/Facturación</label>
|
||||
{{ form.email }}
|
||||
{% if form.email.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.email.errors }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" style="margin-bottom: 2rem; width: 50%;">
|
||||
<label for="{{ form.payment_terms.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Plazo de Pago (Días)</label>
|
||||
{{ form.payment_terms }}
|
||||
{% if form.payment_terms.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.payment_terms.errors }}</div>{% endif %}
|
||||
</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>
|
||||
<a href="{% url 'gestion_panel' %}" class="btn-secondary" style="padding: 0.6rem 2rem; font-size: 0.9rem; text-decoration: none;">Cancelar</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.querySelectorAll('input, select, textarea').forEach(el => {
|
||||
el.classList.add('form-control');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user