252 lines
11 KiB
HTML
252 lines
11 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
{% load humanize %}
|
|
|
|
{% block content %}
|
|
<section class="page-view active">
|
|
<div class="view-header">
|
|
<div class="view-title">
|
|
<h2><i data-lucide="pie-chart" style="color:var(--efintel-blue);"></i> EFINTEL ERP — Centro de Control Financiero</h2>
|
|
<p>Supervisión consolidada de presupuestos, costos acumulados y flujo multimoneda ($ CLP / $ USD)</p>
|
|
</div>
|
|
<div style="display:flex; gap:0.5rem; align-items:center;">
|
|
<span class="badge badge-blue"><i data-lucide="coins" style="width:12px;"></i> Multimoneda Integrado</span>
|
|
<span class="badge badge-emerald"><i data-lucide="shield-check" style="width:12px;"></i> Flujo 3 Pasos PMBOK</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpi-grid-3">
|
|
<div class="kpi-card accent-blue">
|
|
<div class="kpi-header">
|
|
<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"|intcomma }} CLP</div>
|
|
<div class="kpi-subtext">
|
|
<span>Mes actual: ${{ total_month|floatformat:"0"|intcomma }} CLP</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpi-card accent-turquoise">
|
|
<div class="kpi-header">
|
|
<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"|intcomma }} CLP</div>
|
|
<div class="kpi-subtext">
|
|
<span>Saldo Restante: ${{ remaining_budget|floatformat:"0"|intcomma }} CLP</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="kpi-card accent-success">
|
|
<div class="kpi-header">
|
|
<span class="kpi-title">PROYECTOS ACTIVOS</span>
|
|
<div class="kpi-icon"><i data-lucide="trending-up"></i></div>
|
|
</div>
|
|
<div class="kpi-value" id="kpi-evm">{{ project_count }}</div>
|
|
<div class="kpi-subtext">
|
|
<span class="status-dot green"></span> <span style="font-weight:600; color:var(--efintel-dark);">En línea</span>
|
|
</div>
|
|
</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>
|
|
</div>
|
|
<div class="data-table-wrapper">
|
|
<table class="data-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Fecha</th>
|
|
<th>Centro / Proyecto</th>
|
|
<th>Detalle Imputación</th>
|
|
<th>Monto Neto ($ CLP / USD)</th>
|
|
<th>Flujo Aprobación & Pago</th>
|
|
<th>Acciones / Gestión</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for expense in recent_expenses %}
|
|
<tr>
|
|
<td style="font-size:0.75rem; font-weight: 600;">{{ expense.date|date:"d/m/Y" }}</td>
|
|
<td>
|
|
<span class="project-tag" {% if expense.project.type == 'GASTO_OPERACIONAL_OVERHEAD' %}style="background:#e2e8f0; color:#475569;"{% endif %}>
|
|
{{ expense.project.code }}
|
|
</span>
|
|
</td>
|
|
<td style="font-weight:600; color:var(--efintel-dark);">
|
|
{{ expense.description|truncatechars:50 }}<br>
|
|
<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"|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' %}
|
|
<span class="badge badge-amber"><i data-lucide="clock" style="width:12px;"></i> En Revisión</span>
|
|
{% elif expense.approval_status == 'SOLICITADO_FINANZAS' %}
|
|
<span class="badge badge-blue"><i data-lucide="send" style="width:12px;"></i> Solicitado Finanzas</span>
|
|
{% elif expense.approval_status == 'LIBERADO_PAGADO' %}
|
|
<span class="badge badge-emerald"><i data-lucide="check-check" style="width:12px;"></i> Pagado</span>
|
|
{% else %}
|
|
<span class="badge badge-red"><i data-lucide="x-circle" style="width:12px;"></i> Rechazado</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div style="display:flex; gap:0.3rem;">
|
|
<!-- Flujo de 3 pasos (Aprobaciones) -->
|
|
{% if expense.approval_status == 'EN_REVISION_DOCUMENTAL' %}
|
|
<a href="{% url 'approve_expense' expense.id 'solicitar' %}" class="btn-primary" style="padding:0.3rem; font-size:0.65rem; background: var(--efintel-blue);" title="Elevar a Finanzas"><i data-lucide="arrow-right"></i></a>
|
|
{% endif %}
|
|
|
|
{% if expense.approval_status == 'SOLICITADO_FINANZAS' and 'Administrador Finanzas' in request.user.groups.all.0.name or request.user.is_superuser %}
|
|
<a href="{% url 'approve_expense' expense.id 'pagar' %}" class="btn-primary" style="padding:0.3rem; font-size:0.65rem; background: var(--efintel-success);" title="Liberar Pago"><i data-lucide="check"></i></a>
|
|
<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 %}
|
|
|
|
<a href="{% url 'expense_edit' expense.id %}" class="btn-secondary" style="padding:0.3rem; font-size:0.65rem; background:var(--efintel-blue); color:white;" title="Editar"><i data-lucide="pencil" style="width:12px;"></i></a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr>
|
|
<td colspan="6" style="text-align:center; padding: 2rem; color: var(--efintel-gray);">No hay erogaciones recientes.</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</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 %}
|