FranciscoBorja12 10 months ago
parent 1cdf03de85
commit 331de4f08d
  1. 1
      .gitignore
  2. 22
      manage.py
  3. 0
      registro/__init__.py
  4. BIN
      registro/__pycache__/__init__.cpython-313.pyc
  5. BIN
      registro/__pycache__/settings.cpython-313.pyc
  6. BIN
      registro/__pycache__/urls.cpython-313.pyc
  7. BIN
      registro/__pycache__/wsgi.cpython-313.pyc
  8. 16
      registro/asgi.py
  9. 154
      registro/settings.py
  10. 33
      registro/urls.py
  11. 16
      registro/wsgi.py
  12. 0
      tasks/__init__.py
  13. BIN
      tasks/__pycache__/__init__.cpython-313.pyc
  14. BIN
      tasks/__pycache__/admin.cpython-313.pyc
  15. BIN
      tasks/__pycache__/apps.cpython-313.pyc
  16. BIN
      tasks/__pycache__/forms.cpython-313.pyc
  17. BIN
      tasks/__pycache__/models.cpython-313.pyc
  18. BIN
      tasks/__pycache__/views.cpython-313.pyc
  19. 3
      tasks/admin.py
  20. 6
      tasks/apps.py
  21. 36
      tasks/forms.py
  22. 46
      tasks/migrations/0001_initial.py
  23. 31
      tasks/migrations/0002_movimientos_cuentas_alter_cuenta_fecha_creacion_and_more.py
  24. 19
      tasks/migrations/0003_alter_movimientos_cuentas.py
  25. 34
      tasks/migrations/0004_movimientos_fecha_factura_movimientos_n_factura_and_more.py
  26. 0
      tasks/migrations/__init__.py
  27. BIN
      tasks/migrations/__pycache__/0001_initial.cpython-313.pyc
  28. BIN
      tasks/migrations/__pycache__/0002_movimientos_cuentas_alter_cuenta_fecha_creacion_and_more.cpython-313.pyc
  29. BIN
      tasks/migrations/__pycache__/0003_alter_movimientos_cuentas.cpython-313.pyc
  30. BIN
      tasks/migrations/__pycache__/0004_movimientos_fecha_factura_movimientos_n_factura_and_more.cpython-313.pyc
  31. BIN
      tasks/migrations/__pycache__/__init__.cpython-313.pyc
  32. 33
      tasks/models.py
  33. 77
      tasks/templates/base.html
  34. 21
      tasks/templates/cuentas.html
  35. 80
      tasks/templates/index.html
  36. 54
      tasks/templates/lista_cuentas.html
  37. 49
      tasks/templates/lista_movimientos.html
  38. 49
      tasks/templates/movimiento.html
  39. 108
      tasks/templates/singup.html
  40. 3
      tasks/tests.py
  41. 215
      tasks/views.py

1
.gitignore vendored

@ -0,0 +1 @@
venv/

@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'registro.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

@ -0,0 +1,16 @@
"""
ASGI config for registro project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'registro.settings')
application = get_asgi_application()

@ -0,0 +1,154 @@
"""
Django settings for registro project.
Generated by 'django-admin startproject' using Django 5.1.5.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-f2t$19_-10d9%01a9e#p17ehiz13e)17232df21+kd8*q9+s&%'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'tasks',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'registro.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'registro.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # Motor de MySQL
'NAME': 'registro', # Nombre de la base de datos
'USER': 'root', # Usuario de MySQL
'PASSWORD': '', # Contraseña de MySQL
'HOST': 'localhost', # Dirección del servidor (por defecto localhost)
'PORT': '3306', # Puerto de MySQL (por defecto 3306)
}
}
# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/
STATIC_URL = 'static/'
LOGIN_URL = 'signin'
# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'registrogestorpagos@gmail.com'
EMAIL_HOST_PASSWORD = 'mlsr wxwu hfey qgiv'
DEFAULT_FROM_EMAIL = 'registrogestorpagos@gmail.com'
LANGUAGE_CODE = 'es-es'
USE_I18N = True
USE_L10N = True
USE_TZ = True
DATE_FORMAT = "d/m/Y"
USE_THOUSAND_SEPARATOR = False
TIME_ZONE = 'America/El_Salvador'
FILE_MAX_SIZE = 910242880
SUPPORTED_MIME_TYPES = ['image/png', 'image/jpeg', 'application/pdf','video/mp4', 'text/csv', 'text/plain']
PATH_FILES = os.path.join(os.path.dirname(BASE_DIR), "files/")
SESSION_COOKIE_AGE = 60 * 15
SESSION_SAVE_EVERY_REQUEST = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True

@ -0,0 +1,33 @@
"""
URL configuration for registro project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/5.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from tasks import views
urlpatterns = [
path('admin/', admin.site.urls),
path('',views.signin, name='signin'),
path('signup/',views.signup, name='signup'),
path('cuentas/', views.lista_cuentas, name='lista_cuentas'),
path('cuentas/nueva/', views.registrar_cuenta, name='registrar_cuenta'),
path('signout/',views.signout, name='signout'),
path('movimientos/<int:cuenta_id>/', views.lista_movimientos, name='lista_movimientos'),
path('movimientos/<int:cuenta_id>/nuevo', views.registrar_movimiento, name='registrar_movimiento'),
path('movimientos/editar/<int:movimiento_id>/', views.actualizar_movimiento, name='actualizar_movimiento'),
path('movimientos/excel/<int:cuenta_id>/', views.exportar_movimientos_excel, name='exportar_movimientos_excel'),
]

@ -0,0 +1,16 @@
"""
WSGI config for registro project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'registro.settings')
application = get_wsgi_application()

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -0,0 +1,6 @@
from django.apps import AppConfig
class TasksConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'tasks'

@ -0,0 +1,36 @@
from django import forms
from .models import Cuenta, Movimientos, TipoMovimiento
class RegistroCuentaForm(forms.ModelForm):
class Meta:
model = Cuenta
fields = ['cuenta', 'observacion']
widgets = {
'cuenta': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Nombre de la cuenta'}),
'observacion': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Observaciones'}),
}
class RegistroMovimientosForm(forms.ModelForm):
tipo_movimiento = forms.ModelChoiceField(
queryset=TipoMovimiento.objects.all(),
empty_label="Seleccione un tipo de movimiento",
widget=forms.Select(attrs={'class': 'form-control'}),
label="Tipo de movimiento"
)
fecha_factura = forms.DateField(
widget=forms.DateInput(
attrs={'type': 'date', 'class': 'form-control'}
),
label="Fecha de factura",
)
class Meta:
model = Movimientos
fields = ['tipo_movimiento','fecha_factura', 'saldo','n_factura', 'observacion','proveedor','responsable_cuenta']
widgets = {
'saldo': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'Monto'}),
'observacion': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Observaciones'}),
'n_factura': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'Ingrese numero de factura'}),
'proveedor': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Nombre del proveedor'}),
'responsable_cuenta': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Nombre del responsable'}),
}

@ -0,0 +1,46 @@
# Generated by Django 5.1.5 on 2025-02-06 13:47
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='TipoMovimiento',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tipo_movimiento', models.CharField(max_length=100)),
],
),
migrations.CreateModel(
name='Cuenta',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cuenta', models.CharField(max_length=100)),
('fecha_creacion', models.DateField(auto_now_add=True)),
('saldo', models.DecimalField(decimal_places=2, default=0.0, max_digits=15, null=True)),
('observacion', models.TextField(blank=True)),
('responsable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Movimientos',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('saldo', models.DecimalField(decimal_places=2, default=0.0, max_digits=15, null=True)),
('fecha_insersion', models.DateField(auto_now_add=True)),
('observacion', models.TextField(blank=True)),
('responsable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('tipo_movimiento', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.tipomovimiento')),
],
),
]

@ -0,0 +1,31 @@
# Generated by Django 5.1.5 on 2025-02-06 15:50
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tasks', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='movimientos',
name='cuentas',
field=models.ForeignKey(blank=True, default=1, on_delete=django.db.models.deletion.CASCADE, to='tasks.cuenta'),
preserve_default=False,
),
migrations.AlterField(
model_name='cuenta',
name='fecha_creacion',
field=models.DateField(default=django.utils.timezone.now),
),
migrations.AlterField(
model_name='movimientos',
name='fecha_insersion',
field=models.DateField(default=django.utils.timezone.now),
),
]

@ -0,0 +1,19 @@
# Generated by Django 5.1.5 on 2025-02-06 18:13
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tasks', '0002_movimientos_cuentas_alter_cuenta_fecha_creacion_and_more'),
]
operations = [
migrations.AlterField(
model_name='movimientos',
name='cuentas',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.cuenta'),
),
]

@ -0,0 +1,34 @@
# Generated by Django 5.1.5 on 2025-02-07 01:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tasks', '0003_alter_movimientos_cuentas'),
]
operations = [
migrations.AddField(
model_name='movimientos',
name='fecha_factura',
field=models.DateField(blank=True, null=True),
),
migrations.AddField(
model_name='movimientos',
name='n_factura',
field=models.PositiveIntegerField(default=1),
preserve_default=False,
),
migrations.AddField(
model_name='movimientos',
name='proveedor',
field=models.CharField(blank=True, max_length=100),
),
migrations.AddField(
model_name='movimientos',
name='responsable_cuenta',
field=models.CharField(blank=True, max_length=100),
),
]

@ -0,0 +1,33 @@
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from decimal import Decimal
# Create your models here.
class TipoMovimiento(models.Model):
tipo_movimiento=models.CharField(max_length=100)
def __str__(self):
return self.tipo_movimiento # Retorna el nombre legible
class Cuenta (models.Model):
cuenta=models.CharField(max_length=100)
fecha_creacion=models.DateField(default=timezone.now)
saldo=models.DecimalField(max_digits=15, decimal_places=2, null=True, default=Decimal('0.00'))
responsable = models.ForeignKey(User, on_delete=models.CASCADE)
observacion= models.TextField(blank=True)
def __str__(self):
return self.cuenta
class Movimientos(models.Model):
responsable = models.ForeignKey(User, on_delete=models.CASCADE)
tipo_movimiento=models.ForeignKey(TipoMovimiento, on_delete=models.CASCADE)
saldo=models.DecimalField(max_digits=15, decimal_places=2, null=True, default=Decimal('0.00'))
fecha_insersion=models.DateField(default=timezone.now)
observacion= models.TextField(blank=True)
cuentas= models.ForeignKey(Cuenta, on_delete=models.CASCADE)
n_factura=models.PositiveIntegerField()
fecha_factura = models.DateField(blank=True)
proveedor= models.CharField(max_length=100, blank=True)
responsable_cuenta=models.CharField(max_length=100, blank=True)

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Registro{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet">
</head>
<style>
.badge-success {
background-color: #28a745;
}
.badge-warning {
background-color: #ffc107;
}
.badge-danger {
background-color: #dc3545;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: #f9f9f9;
}
.btn-primary {
background-color: #007bff;
border-color: #007bff;
}
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
}
.btn-sm {
font-size: 0.875rem;
}
html, body {
height: 100%;
margin: 0;
}
.container {
min-height: 80%;
display: flex;
flex-direction: column;
}
.footer {
margin-top: auto;
}
</style>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand">Registro</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
{% if user.is_authenticated %}
<li class="nav-item"><a class="nav-link" href="{% url 'lista_cuentas' %}">Nuevo registro de cuenta</a></li>
<li class="nav-item"><a class="nav-link" href="{% url 'signout' %}">Cerrar Sesión</a></li>
{% else %}
<li class="nav-item"><a class="nav-link" href="{% url 'signin' %}">Inicio de Sesión</a></li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
{% block content %}
{% endblock %}
</div>
<footer class="bg-dark text-white text-center py-3 mt-4">
<p>© 2025 registro</p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

@ -0,0 +1,21 @@
{% extends 'base.html' %}
{% block content %}
<div class="container mt-5">
<div class="card shadow-lg">
<div class="card-header bg-primary text-white">
<h3>Registrar Nueva Cuenta</h3>
</div>
<div class="card-body">
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-success">Guardar</button>
<a href="{% url 'lista_cuentas' %}" class="btn btn-secondary">Cancelar</a>
</form>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,80 @@
{% extends "Base.html" %}
{% block content %}
{% if user.is_authenticated %}
{% else %}
<style>
body {
background: #2c2f36; /* Color de fondo oscuro */
color: #fff; /* Texto en blanco para contraste */
}
.card {
background-color: #ffffff; /* Fondo blanco para el formulario */
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.card-body {
padding: 30px;
}
.form-label {
color: #333; /* Color de las etiquetas */
}
.btn-primary {
background-color: #007bff; /* Botón azul */
border-color: #007bff;
}
.btn-primary:hover {
background-color: #0056b3; /* Color al pasar el ratón */
border-color: #0056b3;
}
.alert {
background-color: #f8d7da; /* Fondo rojo suave para los errores */
border-color: #f5c6cb;
}
.alert p {
margin-bottom: 0;
}
</style>
<div class="container-fluid p-0" style="height: 100vh; background-color: #2c2f36;">
<div class="d-flex justify-content-center align-items-center" style="height: 100vh;">
<div class="card shadow-lg p-4" style="max-width: 400px; border-radius: 12px; background-color: #ffffff;">
<div class="card-body">
<h3 class="text-center mb-4" style="color: #333;">Iniciar Sesión</h3>
<form method="post">
{% csrf_token %}
<div class="mb-3">
<label for="username" class="form-label" style="color: #333;">Nombre de Usuario</label>
<input type="text" class="form-control" id="username" name="username" required
style="border-radius: 10px; box-shadow: none; transition: all 0.3s ease;">
</div>
<div class="mb-3">
<label for="password" class="form-label" style="color: #333;">Contraseña</label>
<input type="password" class="form-control" id="password" name="password" required
style="border-radius: 10px; box-shadow: none; transition: all 0.3s ease;">
</div>
<button type="submit" class="btn btn-dark w-100" style="border-radius: 10px; font-size: 16px; letter-spacing: 1px;">
Iniciar Sesión
</button>
</form>
<div class="text-center mt-3">
<a href="{% url 'signup' %}" class="text-muted" style="font-size: 14px;">Registrar</a>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block footer %}
<footer class="footer mt-auto py-3 bg-dark text-white">
<div class="container">
<span class="text-muted">© 2024 Mi Aplicación. Todos los derechos reservados.</span>
</div>
</footer>
{% endblock %}

@ -0,0 +1,54 @@
{% extends 'base.html' %}
{% block content %}
<div class="container mt-5">
<h2>Lista de Cuentas</h2>
<div class="d-flex gap-2 mb-3">
{% if es_superusuario %}
<a href="{% url 'lista_cuentas' %}?todas=1" class="btn btn-secondary">
Ver Todas las Cuentas
</a>
<a href="{% url 'lista_cuentas' %}" class="btn btn-secondary">
Ver Mis Cuentas
</a>
{% endif %}
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Nombre</th>
<th>Saldo</th>
<th>Responsable</th>
<th>Fecha Creación</th>
<th>Observaciones</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{% for cuenta in cuentas %}
<tr>
<td>{{ cuenta.cuenta }}</td>
<td>{{ cuenta.saldo }}</td>
<td>{{ cuenta.responsable.username }}</td>
<td>{{ cuenta.fecha_creacion }}</td>
<td>{{ cuenta.observacion }}</td>
<td>
<a href="{% url 'lista_movimientos' cuenta.id %}" class="btn btn-info btn-sm">
Ver Movimientos
</a>
<a href="{% url 'exportar_movimientos_excel' cuenta.id %}" class="btn btn-success btn-sm">
Exportar a Excel
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<a href="{% url 'registrar_cuenta' %}" class="btn btn-success btn-lg rounded-circle position-fixed bottom-0 end-0 m-3 shadow-lg">
<i class="fas fa-plus"></i>
</a>
{% endblock %}

@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% block content %}
<div class="container mt-5">
<h2>Movimientos de la cuenta: {{ cuenta.cuenta }}</h2>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Tipo</th>
<th>Monto</th>
<th>Fecha</th>
<th>Observación</th>
<th># de factura</th>
<th>fecha de factura</th>
<th>responsable</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{% for movimiento in movimientos %}
<tr>
<td>{{ movimiento.tipo_movimiento }}</td>
<td>{{ movimiento.saldo }}</td>
<td>{{ movimiento.fecha_insersion }}</td>
<td>{{ movimiento.observacion }}</td>
<td>{{ movimiento.n_factura }}</td>
<td>{{ movimiento.fecha_factura }}</td>
<td>{{ movimiento.responsable_cuenta }}</td>
<td>
<a href="{% url 'actualizar_movimiento' movimiento.id %}" class="btn btn-warning btn-sm">
Editar
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center">No hay movimientos registrados.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<a href="{% url 'registrar_movimiento' cuenta.id %}" class="btn btn-success btn-lg rounded-circle position-fixed bottom-0 end-0 m-3 shadow-lg">
<i class="fas fa-plus"></i>
</a>
{% endblock %}

@ -0,0 +1,49 @@
{% extends 'base.html' %}
{% block content %}
<div class="container mt-5">
<div class="card shadow-lg">
<div class="card-header bg-info text-white">
<h3>Registrar Nuevo Movimiento</h3>
</div>
<div class="card-body">
<form method="POST">
{% csrf_token %}
<input type="hidden" name="cuentas" value="{{ cuenta.id }}"> <!-- Captura automática -->
<div class="mb-3">
<label class="form-label">Cuenta</label>
<input type="text" class="form-control" value="{{ cuenta.cuenta }} (Saldo: {{ cuenta.saldo }})" disabled>
</div>
<div class="mb-3">
<label for="id_tipo_movimiento" class="form-label">Tipo de Movimiento</label>
{{ form.tipo_movimiento }}
</div>
<div class="mb-3">
<label for="id_saldo" class="form-label">Monto</label>
{{ form.saldo }}
</div>
<div class="mb-3">
<label for="id_observacion" class="form-label">Observación</label>
{{ form.observacion }}
</div>
<div class="mb-3">
<label for="id_n_factura" class="form-label"># de factura</label>
{{ form.n_factura }}
</div> <div class="mb-3">
<label for="id_fecha_factura" class="form-label">fecha de factura</label>
{{ form.fecha_factura }}
</div> <div class="mb-3">
<label for="proveedor" class="form-label">proveedor</label>
{{ form.proveedor }}
</div> <div class="mb-3">
<label for="id_responsable_cuenta" class="form-label">responsable</label>
{{ form.responsable_cuenta }}
</div>
<button type="submit" class="btn btn-success">Guardar</button>
<a href="{% url 'lista_movimientos' cuenta.id %}" class="btn btn-secondary">Cancelar</a>
</form>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,108 @@
{% extends "Base.html" %}
{% block content %}
<style>
body {
background: #2c2f36; /* Color de fondo oscuro */
color: #fff; /* Texto en blanco para contraste */
}
.card {
background-color: #ffffff; /* Fondo blanco para el formulario */
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.card-body {
padding: 30px;
}
.form-label {
color: #333; /* Color de las etiquetas */
}
.btn-primary {
background-color: #007bff; /* Botón azul */
border-color: #007bff;
}
.btn-primary:hover {
background-color: #0056b3; /* Color al pasar el ratón */
border-color: #0056b3;
}
.alert {
background-color: #f8d7da; /* Fondo rojo suave para los errores */
border-color: #f5c6cb;
}
.alert p {
margin-bottom: 0;
}
</style>
<div class="container">
<div class="row justify-content-center mt-5">
<div class="col-md-6">
<div class="card shadow-lg">
<div class="card-body">
<h2 class="text-center mb-4" style="color: #333;">Registrar Nueva Cuenta</h2>
<form method="POST">
{% csrf_token %}
<div class="form-group mb-3">
<label for="username" class="form-label">Nombre de Usuario</label>
<input type="text" class="form-control" id="username" name="username" value="{{ form.username.value }}">
{% if form.username.errors %}
<div class="alert alert-danger mt-2">
{% for error in form.username.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
<div class="form-group mb-3">
<label for="email" class="form-label">Correo Electrónico</label>
<input type="email" class="form-control" id="email" name="email" value="{{ form.email.value }}">
{% if form.email.errors %}
<div class="alert alert-danger mt-2">
{% for error in form.email.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
<div class="form-group mb-3">
<label for="password1" class="form-label">Contraseña</label>
<input type="password" class="form-control" id="password1" name="password1" value="{{ form.password1.value }}">
{% if form.password1.errors %}
<div class="alert alert-danger mt-2">
{% for error in form.password1.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
<div class="form-group mb-3">
<label for="password2" class="form-label">Confirmar Contraseña</label>
<input type="password" class="form-control" id="password2" name="password2" value="{{ form.password2.value }}">
{% if form.password2.errors %}
<div class="alert alert-danger mt-2">
{% for error in form.password2.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary w-100">Registrar</button>
</div>
</form>
<div class="text-center mt-3">
<a href="{% url 'signin' %}" class="text-muted" style="font-size: 14px;">¿Ya tienes cuenta? Inicia sesión</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -0,0 +1,215 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.utils import timezone
from decimal import Decimal
from django.http import HttpResponse
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from .models import Cuenta, Movimientos, TipoMovimiento
from django import forms
import openpyxl
from django.contrib.auth.models import User
from .forms import RegistroCuentaForm, RegistroMovimientosForm
def base(request):
return render(request, 'index.html')
def signin(request):
if request.method == 'GET':
return render ( request, 'index.html',{
'form': AuthenticationForm
})
else:
user=authenticate(request, username=request.POST['username'], password=request.POST['password'])
if user is None:
return render ( request, 'index.html',{
'form': AuthenticationForm ,
'error': 'Username or password is incorrect'
})
else:
login(request, user)
return redirect('lista_cuentas')
def signout(request):
logout(request)
return redirect('signin')
class CustomUserCreationForm(UserCreationForm):
email = forms.EmailField(required=True, help_text="Introduce un correo electrónico válido.")
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password2')
def save(self, commit=True):
user = super().save(commit=False)
user.email = self.cleaned_data["email"]
if commit:
user.save()
return user
def signup(request):
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
# Verificamos si el formulario es válido
if form.is_valid():
# Guardamos el usuario si todo está bien
form.save()
# Mensaje de éxito
return redirect('signin') # Redirige a la página de inicio de sesión
else:
# Si el formulario no es válido, mostramos los errores en los campos específicos
if 'username' not in form.cleaned_data:
form.add_error('username', 'El nombre de usuario es obligatorio.')
if 'email' not in form.cleaned_data:
form.add_error('email', 'El correo electrónico es obligatorio.')
if 'password1' not in form.cleaned_data:
form.add_error('password1', 'La contraseña es obligatoria.')
if 'password2' not in form.cleaned_data:
form.add_error('password2', 'Debes confirmar la contraseña.')
return render(request, 'singup.html', {'form': form})
else:
# Si es un GET, mostramos un formulario vacío
form = CustomUserCreationForm()
return render(request, 'singup.html', {'form': form})
@login_required
def registrar_cuenta(request):
if request.method == "POST":
form = RegistroCuentaForm(request.POST)
if form.is_valid():
cuenta = form.save(commit=False)
cuenta.responsable = request.user
cuenta.save()
return redirect('lista_cuentas') # Cambia el nombre de la vista destino
else:
form = RegistroCuentaForm()
return render(request, 'cuentas.html', {'form': form})
@login_required
def registrar_movimiento(request, cuenta_id):
cuenta = get_object_or_404(Cuenta, id=cuenta_id) # Obtiene la cuenta específica
if request.method == "POST":
form = RegistroMovimientosForm(request.POST)
if form.is_valid():
movimiento = form.save(commit=False)
movimiento.cuentas = cuenta # Asigna la cuenta automáticamente
movimiento.responsable = request.user # Asigna el usuario actual
# Ajusta el saldo según el tipo de movimiento
if movimiento.tipo_movimiento.id == 1: # Supongamos que ID 1 es ingreso
cuenta.saldo += movimiento.saldo
elif movimiento.tipo_movimiento.id == 2: # ID 2 es egreso
cuenta.saldo -= movimiento.saldo
cuenta.save()
movimiento.save()
return redirect('lista_movimientos', cuenta_id=cuenta.id)
else:
form = RegistroMovimientosForm()
return render(request, 'movimiento.html', {'form': form, 'cuenta': cuenta})
@login_required
def actualizar_movimiento(request, movimiento_id):
movimiento = get_object_or_404(Movimientos, id=movimiento_id)
cuenta = movimiento.cuentas # Obtener la cuenta asociada
saldo_anterior = movimiento.saldo # Guardar saldo anterior
tipo_movimiento_anterior = movimiento.tipo_movimiento_id # Guardar tipo anterior
if request.method == "POST":
form = RegistroMovimientosForm(request.POST, instance=movimiento)
if form.is_valid():
nuevo_movimiento = form.save(commit=False)
# Restaurar saldo antes de actualizar (solo si cambió el tipo o saldo)
if tipo_movimiento_anterior == 1: # Ingreso anterior
cuenta.saldo -= saldo_anterior
elif tipo_movimiento_anterior == 2: # Egreso anterior
cuenta.saldo += saldo_anterior
# Aplicar nuevo saldo
if nuevo_movimiento.tipo_movimiento_id == 1: # Nuevo ingreso
cuenta.saldo += nuevo_movimiento.saldo
elif nuevo_movimiento.tipo_movimiento_id == 2: # Nuevo egreso
cuenta.saldo -= nuevo_movimiento.saldo
cuenta.save() # Guardar la cuenta con el saldo actualizado
nuevo_movimiento.save() # Guardar el movimiento
return redirect('lista_movimientos', cuenta_id=cuenta.id)
else:
form = RegistroMovimientosForm(instance=movimiento)
return render(request, 'movimiento.html', {'form': form, 'cuenta': cuenta, 'movimiento': movimiento})
@login_required
def lista_cuentas(request):
if request.user.is_superuser:
cuentas = Cuenta.objects.all() # Si es superusuario, puede ver todas las cuentas
mostrar_todas = request.GET.get('todas', False) # Verifica si quiere ver todas las cuentas
if not mostrar_todas:
cuentas = cuentas.filter(responsable=request.user) # Muestra solo las propias si no activa la opción
else:
cuentas = Cuenta.objects.filter(responsable=request.user) # Usuario normal solo ve sus cuentas
return render(request, 'lista_cuentas.html', {'cuentas': cuentas, 'es_superusuario': request.user.is_superuser})
@login_required
def lista_movimientos(request, cuenta_id):
cuenta = get_object_or_404(Cuenta, id=cuenta_id) # Obtiene la cuenta o muestra 404
movimientos = Movimientos.objects.filter(cuentas=cuenta) # Filtra por cuenta
return render(request, 'lista_movimientos.html', {
'movimientos': movimientos,
'cuenta': cuenta # Para mostrar el nombre en el template
})
@login_required
def exportar_movimientos_excel(request, cuenta_id):
# Obtener la cuenta
cuenta = get_object_or_404(Cuenta, id=cuenta_id)
# Obtener los movimientos de la cuenta
movimientos = Movimientos.objects.filter(cuentas=cuenta)
# Crear el archivo de Excel
wb = openpyxl.Workbook()
ws = wb.active
ws.title = f"Movimientos de {cuenta.cuenta}"
# Establecer el nombre de la cuenta en la primera fila
ws.append([f"Cuenta: {cuenta.cuenta}"])
ws.append([]) # Espacio vacío antes de la tabla
# Agregar encabezados
encabezados = ["Fecha Inserción", "Tipo de Movimiento", "N° Factura", "Fecha Factura", "Proveedor", "Responsable", "Responsable Cuenta", "Saldo"]
ws.append(encabezados)
# Agregar datos de los movimientos
for mov in movimientos:
ws.append([
mov.fecha_insersion.strftime('%Y-%m-%d'),
mov.tipo_movimiento.tipo_movimiento, # Asegúrate de que el modelo tenga 'nombre'
mov.n_factura,
mov.fecha_factura.strftime('%Y-%m-%d') if mov.fecha_factura else '',
mov.proveedor,
mov.responsable.username,
mov.responsable_cuenta,
mov.saldo
])
# Crear la respuesta HTTP con el archivo Excel
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = f'attachment; filename="Movimientos_{cuenta.cuenta}.xlsx"'
wb.save(response)
return response
Loading…
Cancel
Save