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
+1050
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

+28
View File
@@ -0,0 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 180" width="100%" height="100%">
<defs>
<style>
.bg-symbol { fill: #0078A8; }
.e-cutout { fill: #FFFFFF; }
.text-fintel { fill: #4B4B4B; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-weight: 700; font-size: 110px; }
.text-reg { fill: #4B4B4B; font-family: 'Inter', sans-serif; font-size: 24px; font-weight: 600; }
.text-tagline { fill: #4B4B4B; font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-weight: 400; font-size: 32px; letter-spacing: -0.5px; }
</style>
</defs>
<!-- Symbol: Circle with stylized 'e' -->
<g transform="translate(10, 10)">
<!-- Blue outer circle -->
<circle cx="65" cy="65" r="62" class="bg-symbol" />
<!-- White Inner 'e' shape -->
<!-- Top inner curve & horizontal bar -->
<path d="M 32 65 C 32 45, 48 33, 65 33 C 82 33, 98 45, 98 65 L 42 65 C 43 78, 52 86, 66 86 C 76 86, 85 81, 91 74 L 97 82 C 89 92, 77 98, 65 98 C 45 98, 32 83, 32 65 Z M 42 54 L 88 54 C 86 44, 77 41, 65 41 C 53 41, 44 44, 42 54 Z" class="e-cutout" />
</g>
<!-- Wordmark 'fintel' -->
<text x="145" y="105" class="text-fintel">fintel</text>
<text x="475" y="55" class="text-reg">®</text>
<!-- Tagline 'eficiencia energetica inteligente' -->
<text x="5" y="165" class="text-tagline">eficiencia energetica inteligente</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+142
View File
@@ -0,0 +1,142 @@
// App Logic for Control de Gastos
// Toast notification system
function showToast(message, type = 'info') {
const container = document.getElementById('toast-container');
if (!container) return;
const toast = document.createElement('div');
toast.className = 'toast';
if (type === 'success') {
toast.style.borderLeftColor = 'var(--accent-secondary)';
} else if (type === 'danger') {
toast.style.borderLeftColor = 'var(--danger)';
}
toast.innerHTML = `<div>${message}</div>`;
container.appendChild(toast);
// Trigger animation
setTimeout(() => {
toast.classList.add('show');
}, 10);
// Remove toast after 5 seconds
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => {
if (container.contains(toast)) {
container.removeChild(toast);
}
}, 300);
}, 5000);
}
// WebSocket connection for real-time updates
let wsReconnectAttempts = 0;
const maxWsReconnectAttempts = 5;
function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/expenses/`;
// We only connect if we are not on the login page
if (document.querySelector('.login-page')) return;
const ws = new WebSocket(wsUrl);
ws.onopen = function(e) {
console.log('WebSocket connected');
wsReconnectAttempts = 0;
};
ws.onmessage = function(e) {
const data = JSON.parse(e.data);
const action = data.action;
const expense = data.expense;
const tbody = document.getElementById('expenses-table-body');
if (!tbody || !expense) return;
if (action === 'created') {
const tr = document.createElement('tr');
tr.id = `expense-row-${expense.id}`;
tr.innerHTML = `
<td>${expense.date}</td>
<td>${expense.project}</td>
<td>${expense.category}</td>
${document.querySelector('th:nth-child(4)') && document.querySelector('th:nth-child(4)').textContent === 'Descripción' ? `<td>${expense.description || ''}</td>` : ''}
<td>$${expense.amount}</td>
<td>${expense.user}</td>
${document.querySelector('th:nth-child(7)') ? `
<td>
<div style="display: flex; gap: 0.5rem;">
<span style="font-size:0.8rem; color:var(--text-secondary);">Recarga para ver acciones</span>
</div>
</td>` : ''}
`;
// Add animation class
tr.style.backgroundColor = 'rgba(0, 212, 170, 0.2)';
tr.style.transition = 'background-color 2s ease';
tbody.insertBefore(tr, tbody.firstChild);
// Remove highlight after animation
setTimeout(() => {
tr.style.backgroundColor = '';
}, 2000);
showToast('Nuevo gasto registrado', 'success');
}
else if (action === 'updated') {
const row = document.getElementById(`expense-row-${expense.id}`);
if (row) {
// Flash the row
row.style.backgroundColor = 'rgba(108, 99, 255, 0.2)';
row.style.transition = 'background-color 2s ease';
setTimeout(() => {
row.style.backgroundColor = '';
}, 2000);
showToast('Gasto actualizado', 'info');
}
}
else if (action === 'deleted') {
const row = document.getElementById(`expense-row-${expense.id}`);
if (row) {
row.style.transform = 'translateX(100%)';
row.style.opacity = '0';
row.style.transition = 'all 0.3s ease';
setTimeout(() => {
if (row.parentNode) {
row.parentNode.removeChild(row);
}
}, 300);
showToast('Gasto eliminado', 'danger');
}
}
};
ws.onclose = function(e) {
console.error('WebSocket closed unexpectedly');
if (wsReconnectAttempts < maxWsReconnectAttempts) {
wsReconnectAttempts++;
const timeout = Math.min(1000 * Math.pow(2, wsReconnectAttempts), 30000);
console.log(`Attempting reconnect in ${timeout}ms...`);
setTimeout(connectWebSocket, timeout);
}
};
}
document.addEventListener('DOMContentLoaded', () => {
connectWebSocket();
// Convert CSRF token for fetch requests if needed later
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
});