Initial commit: EFINTEL ERP Django Port

This commit is contained in:
Nachou-u
2026-08-19 18:04:18 -04:00
parent 1cd6ff1a6f
commit eb065909bd
39 changed files with 3852 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
{% extends 'base.html' %}
{% load static %}
{% 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" }} CLP</div>
<div class="kpi-subtext">
<span>Mes actual: ${{ total_month|floatformat:"0" }} 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" }} CLP</div>
<div class="kpi-subtext">
<span>Saldo Restante: ${{ remaining_budget|floatformat:"0" }} 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>
<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" }} CLP</div>
<div style="font-size:0.68rem; color:var(--efintel-gray); font-family:var(--font-mono);">${{ expense.amount_usd|floatformat:"2" }} 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 %}
+114
View File
@@ -0,0 +1,114 @@
{% 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), 0 2px 4px -1px rgba(0,0,0,0.03);">
<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 %}Modificar Gasto Registrado{% else %}Registro de Nueva Erogación{% endif %}
</h2>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
<div class="form-group">
<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 }}
{% 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">
<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 }}
{% if form.category.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.category.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.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.currency.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Moneda Original</label>
{{ form.currency }}
{% if form.currency.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.currency.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.date.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Fecha de Emisión / Gasto</label>
{{ form.date }}
{% if form.date.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.date.errors }}</div>{% endif %}
</div>
<div class="form-group">
<label for="{{ form.vendor.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-dark);">Proveedor (Opcional)</label>
{{ form.vendor }}
{% if form.vendor.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.vendor.errors }}</div>{% endif %}
</div>
</div>
<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);">Detalle de Imputación (Justificación)</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 class="form-group" style="margin-bottom: 1.5rem; background: #f8fafc; padding: 1rem; border-radius: 6px; border: 1px dashed var(--efintel-blue);">
<label for="{{ form.receipt.id_for_label }}" style="font-weight: 600; font-size: 0.85rem; color: var(--efintel-petroleum); display:flex; align-items:center; gap:0.3rem;"><i data-lucide="paperclip" style="width:14px;"></i> Adjuntar Evidencia (Boleta / Factura)</label>
{{ form.receipt }}
<small style="color: var(--efintel-gray); font-size: 0.7rem; display:block; margin-top:0.3rem;">Formatos aceptados: Imágenes (.png, .jpg) o Documentos PDF.</small>
{% if form.receipt.errors %}<div style="color: var(--efintel-danger); font-size: 0.75rem; margin-top: 4px;">{{ form.receipt.errors }}</div>{% endif %}
{% if form.instance.pk and form.instance.documents.first %}
<div style="margin-top:0.8rem; font-size:0.8rem;">
<strong>Evidencia Actual:</strong> <a href="{{ form.instance.documents.first.file.url }}" target="_blank" style="color:var(--efintel-blue);">Ver Archivo Adjunto</a>
</div>
{% endif %}
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin-bottom: 2rem; background: var(--efintel-light); padding: 1.5rem; border-radius: 6px;">
<h4 style="grid-column: 1 / -1; margin: 0 0 1rem 0; font-size: 0.85rem; color: var(--efintel-petroleum); display:flex; align-items:center; gap:0.3rem;"><i data-lucide="map-pin" style="width:14px;"></i> Datos Logísticos (Opcional)</h4>
<div class="form-group">
<label for="{{ form.region.id_for_label }}" style="font-size: 0.75rem; font-weight: 600;">Región Operativa</label>
{{ form.region }}
</div>
<div class="form-group">
<label for="{{ form.comuna.id_for_label }}" style="font-size: 0.75rem; font-weight: 600;">Comuna</label>
{{ form.comuna }}
</div>
<div class="form-group">
<label for="{{ form.vehicle_ownership.id_for_label }}" style="font-size: 0.75rem; font-weight: 600;">Vehículo Asignado</label>
{{ form.vehicle_ownership }}
</div>
</div>
<div style="display: flex; gap: 1rem; justify-content: flex-end; border-top: 1px solid var(--efintel-border); padding-top: 1.5rem;">
<a href="{% url 'expense_list' %}" class="btn-outline" style="padding: 0.75rem 1.5rem; text-decoration: none; color: var(--efintel-gray);">Cancelar</a>
<button type="submit" class="btn-primary" style="padding: 0.75rem 2rem; background: var(--efintel-blue); font-weight: 600;">
<i data-lucide="save" style="width: 16px; margin-right: 6px;"></i> Guardar Registro
</button>
</div>
</form>
</div>
</div>
<script>
document.querySelectorAll('input, select, textarea').forEach(el => {
el.style.width = '100%';
el.style.padding = '0.65rem';
el.style.border = '1px solid var(--efintel-border)';
el.style.borderRadius = '4px';
el.style.fontFamily = "'Inter', sans-serif";
el.style.fontSize = '0.9rem';
el.style.boxSizing = 'border-box';
});
</script>
{% endblock %}
+122
View File
@@ -0,0 +1,122 @@
{% extends 'base.html' %}
{% load static %}
{% block content %}
<section class="page-view active">
<div class="view-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem;">
<div class="view-title">
<h2 style="margin:0; color:var(--efintel-petroleum); display:flex; align-items:center; gap:0.5rem;"><i data-lucide="truck" style="color:var(--efintel-blue);"></i> Control de Erogaciones (Flujo Operativo)</h2>
<p style="margin-top:0.25rem; font-size:0.9rem; color:var(--efintel-gray);">Bandeja de gestión y aprobación de costos y gastos registrados.</p>
</div>
<div>
<a href="{% url 'expense_create' %}" class="btn-primary" style="text-decoration: none;">
<i data-lucide="plus-circle" style="width:16px;"></i> Registrar Nuevo
</a>
</div>
</div>
<div class="data-card" style="margin-bottom: 2rem;">
<form method="get" id="filter-form" style="display: flex; gap: 1rem; align-items: flex-end; flex-wrap: wrap;">
<div class="form-group" style="margin-bottom: 0; flex: 1; min-width: 200px;">
<label for="project" style="font-size: 0.8rem; font-weight: 600;">Centro de Costo</label>
<select name="project" id="project" style="width: 100%; padding: 0.65rem; border: 1px solid var(--efintel-border); border-radius: 4px; outline: none; font-family: 'Inter';" onchange="document.getElementById('filter-form').submit();">
<option value="">Todos los Centros</option>
{% for p in projects %}
<option value="{{ p.id }}" {% if request.GET.project == p.id|stringformat:"s" %}selected{% endif %}>{{ p.code }} - {{ p.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group" style="margin-bottom: 0; flex: 1; min-width: 200px;">
<label for="category" style="font-size: 0.8rem; font-weight: 600;">Categoría</label>
<select name="category" id="category" style="width: 100%; padding: 0.65rem; border: 1px solid var(--efintel-border); border-radius: 4px; outline: none; font-family: 'Inter';" onchange="document.getElementById('filter-form').submit();">
<option value="">Todas las Categorías</option>
{% for c in categories %}
<option value="{{ c.id }}" {% if request.GET.category == c.id|stringformat:"s" %}selected{% endif %}>{{ c.name }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn-outline" style="height: 42px;"><i data-lucide="filter" style="width:16px;"></i> Filtrar</button>
</form>
</div>
<div class="data-card">
<div class="data-table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>ID Transacción</th>
<th>Fecha</th>
<th>Centro / Proyecto</th>
<th>Detalle Imputación</th>
<th>Monto Neto ($ CLP / USD)</th>
<th>Estado de Aprobación</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{% for expense in expenses %}
<tr>
<td style="font-family: var(--font-mono); font-size: 0.75rem; color: var(--efintel-gray);">{{ expense.expense_code|truncatechars:12 }}</td>
<td style="font-size:0.8rem; 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); font-size: 0.85rem;">
{{ expense.description|truncatechars:40 }}<br>
<span style="font-size:0.65rem; color:var(--efintel-gray);">{{ expense.responsible_person.username|default:expense.user.username }}</span>
{% if expense.documents.first %}
<br><a href="{{ expense.documents.first.file.url }}" target="_blank" style="font-size:0.65rem; color:var(--efintel-blue); text-decoration:none; display:inline-flex; align-items:center; gap:0.2rem; margin-top:0.2rem;"><i data-lucide="paperclip" style="width:10px;"></i> Evidencia Adjunta</a>
{% 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>
</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;">
{% 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 %}
{% 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 %}
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" style="text-align: center; padding: 2rem; color: var(--efintel-gray);">No hay gastos registrados para este filtro.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</section>
{% endblock %}
+213
View File
@@ -0,0 +1,213 @@
{% extends 'base.html' %}
{% 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>
<!-- 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>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>Nombre</th>
<th>Descripción</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>
{% if project.manager %}
<span style="font-weight: 500;">{{ project.manager.username }}</span>
{% else %}
<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>
<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>
</form>
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" style="text-align: center;">No hay proyectos registrados.</td>
</tr>
{% endfor %}
</tbody>
</table>
</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>
<div class="table-container">
<table>
<thead>
<tr>
<th>Usuario</th>
<th>Correo</th>
<th>Rol / Permiso</th>
<th>Descripción / Bio</th>
<th>F. Registro</th>
<th>F. Modificación</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{% for resp in responsables %}
<tr>
<td style="font-weight: 600; color: var(--text-primary);">{{ resp.username }}</td>
<td>{{ resp.email|default:"-" }}</td>
<td>
{% if resp.groups.all %}
{% with role=resp.groups.all.0.name %}
{% if role == 'Administrador' %}
<span class="badge badge-admin">Administrador</span>
{% elif role == 'Director de Proyecto' %}
<span class="badge badge-director">Director</span>
{% else %}
<span class="badge badge-gestor">Gestor</span>
{% endif %}
{% endwith %}
{% else %}
<span class="badge badge-gestor">Sin Rol</span>
{% endif %}
</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>
{% 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>
</form>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr>
<td colspan="7" style="text-align: center;">No hay responsables registrados.</td>
</tr>
{% endfor %}
</tbody>
</table>
</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>
</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 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>
<div>
<a href="{% url 'settings_edit' %}" class="btn btn-primary">✏️ Modificar Presupuesto General</a>
</div>
</div>
</div>
<!-- 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>
<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">
{% 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 }}
{% elif "Eliminado" in log.action or "Eliminación" in log.action %}
🔴 {{ log.action }}
{% else %}
🟡 {{ log.action }}
{% endif %}
</div>
<div class="log-desc">{{ log.description }}</div>
</div>
<div class="log-meta">
<div style="font-weight: 500; color: var(--accent-primary);">@{{ log.user.username|default:"Sistema" }}</div>
<div style="font-size: 0.75rem; color: var(--text-secondary); margin-top: 0.25rem;">{{ log.timestamp|date:"d/m/Y H:i:s" }}</div>
</div>
</div>
{% empty %}
<div style="text-align: center; color: var(--text-secondary); padding: 2rem;">No hay registros en la bitácora de auditoría.</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
function switchTab(evt, tabId) {
// Ocultar todos los contenidos de pestaña
const contents = document.querySelectorAll('.tab-content');
contents.forEach(content => content.classList.remove('active'));
// Desactivar todos los botones de pestaña
const buttons = document.querySelectorAll('.tab-btn');
buttons.forEach(btn => btn.classList.remove('active'));
// Activar pestaña actual
document.getElementById(tabId).classList.add('active');
evt.currentTarget.classList.add('active');
// Guardar la pestaña activa en localStorage
localStorage.setItem('activeTab', tabId);
}
// Restaurar pestaña activa al cargar la página
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));
if (btn) {
// Simular clic en el botón
btn.click();
}
}
});
</script>
{% endblock %}
+51
View File
@@ -0,0 +1,51 @@
{% 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>
<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>
{% endif %}
</div>
<div class="form-group">
<label for="{{ form.description.id_for_label }}">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>
{% endif %}
</div>
<div class="form-group">
<label for="{{ form.manager.id_for_label }}">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>
{% 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>
</form>
</div>
</div>
<script>
// Apply form-control class to all inputs
document.querySelectorAll('input, select, textarea').forEach(el => {
el.classList.add('form-control');
});
</script>
{% endblock %}
+36
View File
@@ -0,0 +1,36 @@
{% extends 'base.html' %}
{% block content %}
<div style="margin-bottom: 2rem;">
<h1>Proyectos</h1>
</div>
<div class="project-grid">
{% for project in projects %}
<div class="glass-card project-card">
<div class="project-header">
<div class="project-title">{{ project.name }}</div>
</div>
<div style="color: var(--text-secondary); font-size: 0.9rem;">
<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>
</div>
</div>
{% empty %}
<div class="glass-card" style="padding: 2rem; text-align: center; grid-column: 1 / -1;">
No hay proyectos registrados.
</div>
{% endfor %}
</div>
{% endblock %}
+72
View File
@@ -0,0 +1,72 @@
{% extends 'base.html' %}
{% block content %}
<div class="form-container">
<div class="glass-card" style="padding: 2.5rem;">
<h1 style="margin-bottom: 2rem;">
{% if edit_mode %}Editar Colaborador (Responsable): {{ target_user.username }}{% else %}Nuevo Colaborador (Responsable){% endif %}
</h1>
<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>
{% 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.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>
<div class="form-group">
<label for="{{ form.description.id_for_label }}">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>
{% 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>
</form>
</div>
</div>
<script>
// Apply form-control class to all inputs
document.querySelectorAll('input, select, textarea').forEach(el => {
el.classList.add('form-control');
});
</script>
{% endblock %}
+33
View File
@@ -0,0 +1,33 @@
{% extends 'base.html' %}
{% block content %}
<div class="form-container">
<div class="glass-card" style="padding: 2.5rem;">
<h1 style="margin-bottom: 2rem;">Editar Configuración del Sistema</h1>
<form method="post">
{% csrf_token %}
<div class="form-group">
<label for="{{ form.global_budget.id_for_label }}">Presupuesto General Asignado ($)</label>
{{ form.global_budget }}
{% if form.global_budget.errors %}
<div style="color: var(--danger); font-size: 0.8rem; margin-top: 0.25rem;">{{ form.global_budget.errors }}</div>
{% endif %}
</div>
<div style="display: flex; gap: 1rem; margin-top: 2rem;">
<button type="submit" class="btn btn-primary">Guardar Configuración</button>
<a href="{% url 'gestion_panel' %}" class="btn btn-outline">Cancelar</a>
</div>
</form>
</div>
</div>
<script>
// Apply form-control class to all inputs
document.querySelectorAll('input, select, textarea').forEach(el => {
el.classList.add('form-control');
});
</script>
{% endblock %}