first commit

main
FranciscoBorja12 12 months ago
parent 0d9f0905fb
commit 57ff4e304a
  1. 1
      .gitignore
  2. 0
      RegistroDePagos/__init__.py
  3. BIN
      RegistroDePagos/__pycache__/__init__.cpython-313.pyc
  4. BIN
      RegistroDePagos/__pycache__/settings.cpython-313.pyc
  5. BIN
      RegistroDePagos/__pycache__/urls.cpython-313.pyc
  6. BIN
      RegistroDePagos/__pycache__/wsgi.cpython-313.pyc
  7. 16
      RegistroDePagos/asgi.py
  8. 154
      RegistroDePagos/settings.py
  9. 39
      RegistroDePagos/urls.py
  10. 16
      RegistroDePagos/wsgi.py
  11. 22
      manage.py
  12. 49
      requirements.txt
  13. 0
      tasks/__init__.py
  14. BIN
      tasks/__pycache__/__init__.cpython-313.pyc
  15. BIN
      tasks/__pycache__/admin.cpython-313.pyc
  16. BIN
      tasks/__pycache__/apps.cpython-313.pyc
  17. BIN
      tasks/__pycache__/forms.cpython-313.pyc
  18. BIN
      tasks/__pycache__/models.cpython-313.pyc
  19. BIN
      tasks/__pycache__/views.cpython-313.pyc
  20. 3
      tasks/admin.py
  21. 6
      tasks/apps.py
  22. 22
      tasks/forms.py
  23. 39
      tasks/migrations/0001_initial.py
  24. 17
      tasks/migrations/0002_remove_registrarcompra_estado.py
  25. 36
      tasks/migrations/0003_registrarcompra_user_compra_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_remove_registrarcompra_estado.cpython-313.pyc
  29. BIN
      tasks/migrations/__pycache__/0003_registrarcompra_user_compra_and_more.cpython-313.pyc
  30. BIN
      tasks/migrations/__pycache__/__init__.cpython-313.pyc
  31. 46
      tasks/models.py
  32. 78
      tasks/templates/base.html
  33. 29
      tasks/templates/crear_compra.html
  34. 82
      tasks/templates/detalle_compra.html
  35. 55
      tasks/templates/detalle_compra_pdf.html
  36. 89
      tasks/templates/index.html
  37. 64
      tasks/templates/lista_compras.html
  38. 29
      tasks/templates/registrar_pago.html
  39. 0
      tasks/templates/singnin.html
  40. 108
      tasks/templates/singup.html
  41. 3
      tasks/tests.py
  42. 245
      tasks/views.py

1
.gitignore vendored

@ -0,0 +1 @@
venv/

@ -0,0 +1,16 @@
"""
ASGI config for RegistroDePagos 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', 'RegistroDePagos.settings')
application = get_asgi_application()

@ -0,0 +1,154 @@
"""
Django settings for RegistroDePagos project.
Generated by 'django-admin startproject' using Django 5.1.4.
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/
"""
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-@ruqv)^ecy3zuj%gn3g%vrl!r!z5r2-ztf+vhplqm9bu@=gy4#'
# 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 = 'RegistroDePagos.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 = 'RegistroDePagos.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_pagos', # 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='/'
# 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'
SESSION_COOKIE_AGE = 60 * 15
SESSION_SAVE_EVERY_REQUEST = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True

@ -0,0 +1,39 @@
"""
URL configuration for RegistroDePagos 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
from django.contrib.auth import views as auth_views
urlpatterns = [
path('admin/', admin.site.urls),
path('',views.signin, name='signin'),
path('signup/',views.signup, name='signup'),
path('signout/',views.signout, name='signout'),
path('compras/', views.lista_compras, name='lista_compras'),
path('compras/crear/', views.crear_compra, name='crear_compra'),
path('compras/<int:compra_id>/pago/', views.registrar_pago, name='registrar_pago'),
path('compras/<int:compra_id>/', views.detalle_compra, name='detalle_compra'),
path('compras/<int:compra_id>/pdf/', views.reporte_detalle_compra, name='reporte_detalle_compra'),
path('compras/<int:compra_id>/excel/', views.exportar_excel_detalle_compra, name='exportar_excel'),
path('compras/<int:compra_id>/csv/', views.exportar_csv_detalle_compra, name='exportar_csv'),
path('password_reset/', auth_views.PasswordResetView.as_view(), name='password_reset'),
path('password_reset_done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
path('password_reset_confirm/<uidb64>/<token>/', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('password_reset_complete/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]

@ -0,0 +1,16 @@
"""
WSGI config for RegistroDePagos 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', 'RegistroDePagos.settings')
application = get_wsgi_application()

@ -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', 'RegistroDePagos.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,49 @@
arabic-reshaper==3.0.0
asgiref==3.8.1
asn1crypto==1.5.1
Brotli==1.1.0
certifi==2024.12.14
cffi==1.17.1
chardet==5.2.0
charset-normalizer==3.4.0
click==8.1.7
colorama==0.4.6
cryptography==44.0.0
cssselect2==0.7.0
Django==5.1.4
et_xmlfile==2.0.0
fonttools==4.55.3
html5lib==1.1
idna==3.10
lxml==5.3.0
mysqlclient==2.2.6
numpy==2.2.0
openpyxl==3.1.5
oscrypto==1.3.0
pandas==2.2.3
pillow==11.0.0
pycparser==2.22
pydyf==0.11.0
pyHanko==0.25.3
pyhanko-certvalidator==0.26.5
pypdf==5.1.0
pyphen==0.17.0
python-bidi==0.6.3
python-dateutil==2.9.0.post0
pytz==2024.2
PyYAML==6.0.2
qrcode==8.0
reportlab==4.2.5
requests==2.32.3
six==1.17.0
sqlparse==0.5.3
svglib==1.5.1
tinycss2==1.4.0
tinyhtml5==2.0.0
tzdata==2024.2
tzlocal==5.2
uritools==4.0.3
urllib3==2.2.3
webencodings==0.5.1
xhtml2pdf==0.2.16
zopfli==0.2.3.post1

@ -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,22 @@
from django import forms
from .models import RegistrarCompra, RegistroPagos
class RegistrarCompraForm(forms.ModelForm):
class Meta:
model = RegistrarCompra
fields = ['nombre_compra', 'monto_pago', 'observacion']
widgets = {
'nombre_compra': forms.TextInput(attrs={'class': 'form-control', 'placeholder': 'Nombre de la compra'}),
'monto_pago': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'Monto a pagar'}),
'observacion': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Observaciones'}),
}
class RegistroPagosForm(forms.ModelForm):
class Meta:
model = RegistroPagos
fields = ['monto_pagado', 'monto_extra', 'observaciones']
widgets = {
'monto_pagado': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'Monto pagado'}),
'monto_extra': forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'Monto extra'}),
'observaciones': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': 'Observaciones'}),
}

@ -0,0 +1,39 @@
# Generated by Django 5.1.4 on 2024-12-19 04:48
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='RegistrarCompra',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nombre_compra', models.CharField(max_length=100)),
('monto_pago', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)),
('observacion', models.TextField(blank=True)),
('estado', models.CharField(choices=[('Pendiente', 'Pendiente'), ('Progreso', 'En Progreso'), ('Completado', 'Completado')], default='Pendiente', max_length=20)),
],
),
migrations.CreateModel(
name='RegistroPagos',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('fecha_pago', models.DateField(auto_now_add=True)),
('monto_pagado', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)),
('monto_extra', models.DecimalField(decimal_places=2, default=0.0, max_digits=10)),
('observaciones', models.TextField(blank=True)),
('registro_compra', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pagos', to='tasks.registrarcompra')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

@ -0,0 +1,17 @@
# Generated by Django 5.1.4 on 2024-12-19 05:30
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('tasks', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='registrarcompra',
name='estado',
),
]

@ -0,0 +1,36 @@
# Generated by Django 5.1.4 on 2024-12-19 06:35
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tasks', '0002_remove_registrarcompra_estado'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='registrarcompra',
name='user_compra',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='registrarcompra',
name='monto_pago',
field=models.DecimalField(decimal_places=2, max_digits=10),
),
migrations.AlterField(
model_name='registropagos',
name='monto_extra',
field=models.DecimalField(decimal_places=2, max_digits=10, null=True),
),
migrations.AlterField(
model_name='registropagos',
name='monto_pagado',
field=models.DecimalField(decimal_places=2, max_digits=10, null=True),
),
]

@ -0,0 +1,46 @@
from django.db import models
from django.contrib.auth.models import User
from django.db.models import Sum
# Create your models here.
class RegistrarCompra(models.Model):
nombre_compra = models.CharField(max_length=100)
monto_pago = models.DecimalField(max_digits=10, decimal_places=2)
observacion = models.TextField(blank=True)
user_compra = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
def calcular_estado(self):
# Usamos 'pagos' en lugar de 'registropagos_set' y sumamos 'monto_extra'
pagos = self.pagos.aggregate(total_pagado=Sum('monto_pagado'))['total_pagado'] or 0
pagos_extra = self.pagos.aggregate(total_extra=Sum('monto_extra'))['total_extra'] or 0
total_pagado = pagos + pagos_extra
if total_pagado == 0:
return "Pendiente"
elif total_pagado < self.monto_pago:
return "Pagado Parcialmente"
else:
return "Completado"
def calcular_restante(self):
# Usamos 'pagos' en lugar de 'registropagos_set' y sumamos 'monto_extra'
pagos = self.pagos.aggregate(total_pagado=Sum('monto_pagado'))['total_pagado'] or 0
pagos_extra = self.pagos.aggregate(total_extra=Sum('monto_extra'))['total_extra'] or 0
total_pagado = pagos + pagos_extra
return max(self.monto_pago - total_pagado, 0)
class RegistroPagos(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
registro_compra = models.ForeignKey(RegistrarCompra, on_delete=models.CASCADE, related_name='pagos') # Aquí agregamos `related_name`
fecha_pago = models.DateField(auto_now_add=True)
monto_pagado = models.DecimalField(max_digits=10, decimal_places=2, null=True)
monto_extra = models.DecimalField(max_digits=10, decimal_places=2, null=True)
observaciones = models.TextField(blank=True)
def __str__(self):
return f"Pago de {self.user.username} - {self.monto_pagado} (+{self.monto_extra})"
@property
def total_pagado(self):
return self.monto_pagado + self.monto_extra

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Gestor de Pagos{% 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" href="{% url 'signin' %}">Gestor de Pagos</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_compras' %}">Nueva Compra</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>
<li class="nav-item"><a class="nav-link" href="{% url 'signup' %}">Registrarse</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>© 2024 Gestor de Pagos</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,29 @@
{% extends 'base.html' %}
{% block content %}
<div class="container">
<h2>Registrar Nueva Compra</h2>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<div class="d-flex justify-content-end mt-3">
<a href="{% url 'lista_compras' %}" class="btn btn-secondary btn-sm me-2">
Volver a la Lista
</a>
<button type="submit" class="btn btn-success btn-sm">
Guardar
</button>
</div>
</form>
</div>
{% 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 de Compras. Todos los derechos reservados.</span>
</div>
</footer>
{% endblock %}

@ -0,0 +1,82 @@
{% extends 'base.html' %}
{% block content %}
<div class="container">
<h2>Detalle de Compra: {{ compra.nombre_compra }}</h2>
<!-- Contenedor con tabla responsiva -->
<div class="table-responsive">
<table class="table table-bordered">
<thead>
<tr>
<th>Fecha de Pago</th>
<th>Observaciones</th>
<th>Monto Extra</th>
<th>Monto Pagado</th>
</tr>
</thead>
<tbody>
{% for pago in pagos %}
<tr>
<td>{{ pago.fecha_pago }}</td>
<td>{{ pago.observaciones }}</td>
<td>${{ pago.monto_extra }}</td>
<td>${{ pago.monto_pagado }}</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center">No se han registrado pagos para esta compra.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Botones de acción -->
<div class="d-flex justify-content-end mt-3">
<div class="dropdown">
<button class="btn btn-dark btn-sm me-2 dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Descargar Reportes
</button>
<ul class="dropdown-menu dropdown-menu-dark">
<li> <a href="{% url 'reporte_detalle_compra' compra.id %}" class="dropdown-item"> Generar PDF </a></li>
<li> <a href="{% url 'exportar_excel' compra.id %}" class="dropdown-item">Generar Excel</a> </li>
<li> <a href="{% url 'exportar_csv' compra.id %}" class="dropdown-item">Generar CSV</a> </li>
</ul>
</div>
<a href="{% url 'lista_compras' %}" class="btn btn-secondary btn-sm me-2">
Volver a la Lista
</a>
<a href="{% url 'registrar_pago' compra.id %}" class="btn btn-primary btn-sm me-2">
Registrar Pago
</a>
</div>
<!-- Información de pago -->
<div class="mt-3">
<h5>Monto Total: ${{ compra.monto_pago }}</h5>
<h5>Total Pagado: ${{ total_pagado }}</h5>
<h5>Restante: ${{ restante }}</h5>
<h5>Estado:
<span class="badge
{% if estado == 'Completado' %}
badge-success
{% elif estado == 'Pagado Parcialmente' %}
badge-warning
{% else %}
badge-danger
{% endif %}">
{{ estado }}
</span>
</h5>
</div>
</div>
{% 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 de Compras. Todos los derechos reservados.</span>
</div>
</footer>
{% endblock %}

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="es">
<head>
<style>
body { font-family: 'Helvetica', sans-serif; margin: 20px; color: #333; }
.header { text-align: center; margin-bottom: 30px; }
.header h1 { font-size: 24px; color: #555; }
.details { margin-bottom: 20px; }
.details p { margin: 5px 0; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background-color: #007bff; color: white; }
.footer { text-align: center; margin-top: 30px; font-size: 12px; color: #777; }
td {
word-wrap: break-word; /* Ajusta el texto al ancho de la celda */
white-space: pre-wrap; /* Respeta saltos de línea */
max-width: 300px; /* Limita el ancho de la celda */
}
</style>
</head>
<body>
<div class="header">
<h1>Reporte de Detalle de Compra</h1>
</div>
<div class="details">
<p><strong>Nombre de Compra:</strong> {{ compra.nombre_compra }}</p>
<p><strong>Monto Total:</strong> ${{ compra.monto_pago }}</p>
<p><strong>Total Pagado:</strong> ${{ total_pagado }}</p>
<p><strong>Restante:</strong> ${{ restante }}</p>
</div>
<table>
<thead>
<tr>
<th>Fecha</th>
<th>Monto Pagado</th>
<th>Monto Extra</th>
<th>Observaciones</th>
</tr>
</thead>
<tbody>
{% for pago in pagos %}
<tr>
<td>{{ pago.fecha_pago }}</td>
<td>${{ pago.monto_pagado }}</td>
<td>${{ pago.monto_extra }}</td>
<td>{{ pago.observaciones|default:"Sin observaciones" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="footer">
<p>Reporte generado automáticamente | © 2024 Gestor de Pagos</p>
</div>
</body>
</html>

@ -0,0 +1,89 @@
{% 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>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="rememberMe">
<label class="form-check-label" for="rememberMe" style="color: #333;">Recordarme</label>
</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 'password_reset' %}" class="text-muted" style="font-size: 14px;">¿Olvidaste tu contraseña?</a>
</div>
<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,64 @@
{% extends 'base.html' %}
{% block content %}
<div class="container">
<h2>Lista de Compras</h2>
<!-- Contenedor de la tabla responsiva -->
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>Nombre de Compra</th>
<th>Monto Total</th>
<th>Estado</th>
<th>Restante</th>
<th>Opciones</th>
</tr>
</thead>
<tbody>
{% for item in compras_con_estado %}
<tr>
<td>{{ item.compra.nombre_compra }}</td>
<td>${{ item.compra.monto_pago }}</td>
<td>
<span class="badge
{% if item.estado == 'Completado' %}
badge-success
{% elif item.estado == 'Pagado Parcialmente' %}
badge-warning
{% else %}
badge-danger
{% endif %}">
{{ item.estado }}
</span>
</td>
<td>${{ item.restante }}</td>
<td>
<a href="{% url 'detalle_compra' item.compra.id %}" class="btn btn-sm btn-primary">Ver Detalle</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center">No hay compras registradas.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Botón flotante para crear nueva compra -->
<a href="{% url 'crear_compra' %}" class="btn btn-success btn-lg rounded-circle position-fixed bottom-0 end-0 m-3">
<i class="fas fa-plus"></i>
</a>
</div>
{% 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 de Compras. Todos los derechos reservados.</span>
</div>
</footer>
{% endblock %}

@ -0,0 +1,29 @@
{% extends 'base.html' %}
{% block content %}
<div class="container">
<h2>Registrar Pago para: {{ compra.nombre_compra }}</h2>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<div class="d-flex justify-content-end mt-3">
<a href="{% url 'detalle_compra' compra.id %}" class="btn btn-secondary btn-sm me-2">
Volver al Detalle de la Compra
</a>
<button type="submit" class="btn btn-success btn-sm">
Registrar Pago
</button>
</div>
</form>
</div>
{% 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 de Compras. Todos los derechos reservados.</span>
</div>
</footer>
{% 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,245 @@
from django.shortcuts import render,redirect,get_object_or_404
from django.http import HttpResponse
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from .models import RegistrarCompra, RegistroPagos
from .forms import RegistrarCompraForm, RegistroPagosForm
from django.template.loader import get_template
from xhtml2pdf import pisa
import openpyxl
from django import forms
import csv
# Create your views here.
def base(request):
return render(request, 'index.html')
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})
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_compras')
def signout(request):
logout(request)
return redirect('signin')
@login_required
def crear_compra(request):
if request.method == 'POST':
form = RegistrarCompraForm(request.POST)
if form.is_valid():
# Guardamos el formulario y asignamos el usuario autenticado al campo 'user_compra'
compra = form.save(commit=False) # No guardamos todavía en la base de datos
compra.user_compra = request.user # Asignamos el usuario autenticado
compra.save() # Guardamos la compra en la base de datos
return redirect('lista_compras') # Redirige a una vista que liste las compras
else:
form = RegistrarCompraForm()
return render(request, 'crear_compra.html', {'form': form})
@login_required
def registrar_pago(request, compra_id):
compra = get_object_or_404(RegistrarCompra, id=compra_id, user_compra=request.user) # Filtrar por el usuario
# Calcular el total pagado actual (monto_pagado + monto_extra)
pagos = compra.pagos.all()
total_pagado = sum(pago.monto_pagado + pago.monto_extra for pago in pagos)
# Calcular el monto restante de la compra
restante = compra.monto_pago - total_pagado
if request.method == 'POST':
form = RegistroPagosForm(request.POST)
if form.is_valid():
pago = form.save(commit=False)
# Verificar si el nuevo pago no excede el monto restante
total_nuevo_pago = pago.monto_pagado + pago.monto_extra
if total_pagado + total_nuevo_pago > compra.monto_pago:
form.add_error(None, "El pago total no puede superar el monto de la compra.")
else:
pago.registro_compra = compra # Relacionar con la compra específica
pago.user = request.user # Asignar el usuario autenticado
pago.save()
return redirect('detalle_compra', compra_id=compra.id) # Redirige a la vista de detalle de la compra
else:
form = RegistroPagosForm()
return render(request, 'registrar_pago.html', {'form': form, 'compra': compra, 'restante': restante})
@login_required
def detalle_compra(request, compra_id):
compra = get_object_or_404(RegistrarCompra, id=compra_id, user_compra=request.user) # Filtrar por el usuario
pagos = compra.pagos.all() # Obtén todos los pagos relacionados con esta compra
# Calcular el total pagado (monto_pagado + monto_extra)
total_pagado = sum(pago.monto_pagado + pago.monto_extra for pago in pagos)
# Calcular el monto restante
restante = max(0, compra.monto_pago - total_pagado)
# Calcular el estado
estado = compra.calcular_estado() # Utilizamos el método que definimos en el modelo
context = {
'compra': compra,
'pagos': pagos,
'total_pagado': total_pagado,
'restante': restante,
'estado': estado,
}
return render(request, 'detalle_compra.html', context)
@login_required
def lista_compras(request):
compras = RegistrarCompra.objects.filter(user_compra=request.user) # Filtrar por el usuario actual
compras_con_estado = [
{
'compra': compra,
'estado': compra.calcular_estado(), # Calcula el estado de la compra
'restante': compra.calcular_restante(), # Calcula el monto restante
}
for compra in compras
]
return render(request, 'lista_compras.html', {'compras_con_estado': compras_con_estado})
def generar_pdf(template_src, context_dict={}):
template = get_template(template_src)
html = template.render(context_dict)
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'inline; filename="detalle_compra.pdf"'
pisa_status = pisa.CreatePDF(html, dest=response)
if pisa_status.err:
return HttpResponse('Error al generar el PDF', content_type='text/plain')
return response
def reporte_detalle_compra(request, compra_id):
compra = get_object_or_404(RegistrarCompra, id=compra_id)
pagos = compra.pagos.all()
total_pagado = sum(pago.total_pagado for pago in pagos)
restante = max(0, compra.monto_pago - total_pagado)
context = {
'compra': compra,
'pagos': pagos,
'total_pagado': total_pagado,
'restante': restante,
}
return generar_pdf('detalle_compra_pdf.html', context)
def exportar_excel_detalle_compra(request, compra_id):
compra = get_object_or_404(RegistrarCompra, id=compra_id)
pagos = compra.pagos.all()
# Crear el archivo de Excel
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Detalle de Compra"
# Escribir encabezados
ws.append(["Fecha de Pago", "Monto Pagado", "Monto Extra", "Observaciones"])
# Agregar los datos de los pagos
for pago in pagos:
ws.append([
pago.fecha_pago,
float(pago.monto_pagado),
float(pago.monto_extra),
pago.observaciones,
])
# Configurar la respuesta HTTP
response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
response['Content-Disposition'] = f'attachment; filename="detalle_compra_{compra.id}.xlsx"'
wb.save(response)
return response
def exportar_csv_detalle_compra(request, compra_id):
compra = get_object_or_404(RegistrarCompra, id=compra_id)
pagos = compra.pagos.all()
# Configurar la respuesta HTTP
response = HttpResponse(content_type="text/csv")
response['Content-Disposition'] = f'attachment; filename="detalle_compra_{compra.id}.csv"'
# Crear el escritor CSV
writer = csv.writer(response)
writer.writerow(["Fecha de Pago", "Monto Pagado", "Monto Extra", "Observaciones"]) # Encabezados
# Agregar los datos de los pagos
for pago in pagos:
writer.writerow([
pago.fecha_pago,
float(pago.monto_pagado),
float(pago.monto_extra),
pago.observaciones,
])
return response
Loading…
Cancel
Save