From 57ff4e304a14e7eff1b543ed6a2b9b05d7fa7b96 Mon Sep 17 00:00:00 2001 From: FranciscoBorja12 Date: Thu, 19 Dec 2024 15:49:09 -0600 Subject: [PATCH] first commit --- .gitignore | 1 + RegistroDePagos/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 172 bytes .../__pycache__/settings.cpython-313.pyc | Bin 0 -> 3231 bytes .../__pycache__/urls.cpython-313.pyc | Bin 0 -> 2768 bytes .../__pycache__/wsgi.cpython-313.pyc | Bin 0 -> 676 bytes RegistroDePagos/asgi.py | 16 ++ RegistroDePagos/settings.py | 154 +++++++++++ RegistroDePagos/urls.py | 39 +++ RegistroDePagos/wsgi.py | 16 ++ manage.py | 22 ++ requirements.txt | 49 ++++ tasks/__init__.py | 0 tasks/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 162 bytes tasks/__pycache__/admin.cpython-313.pyc | Bin 0 -> 206 bytes tasks/__pycache__/apps.cpython-313.pyc | Bin 0 -> 524 bytes tasks/__pycache__/forms.cpython-313.pyc | Bin 0 -> 1887 bytes tasks/__pycache__/models.cpython-313.pyc | Bin 0 -> 3532 bytes tasks/__pycache__/views.cpython-313.pyc | Bin 0 -> 11153 bytes tasks/admin.py | 3 + tasks/apps.py | 6 + tasks/forms.py | 22 ++ tasks/migrations/0001_initial.py | 39 +++ .../0002_remove_registrarcompra_estado.py | 17 ++ ...03_registrarcompra_user_compra_and_more.py | 36 +++ tasks/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-313.pyc | Bin 0 -> 2500 bytes ...ove_registrarcompra_estado.cpython-313.pyc | Bin 0 -> 702 bytes ...ompra_user_compra_and_more.cpython-313.pyc | Bin 0 -> 1667 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 173 bytes tasks/models.py | 46 ++++ tasks/templates/base.html | 78 ++++++ tasks/templates/crear_compra.html | 29 +++ tasks/templates/detalle_compra.html | 82 ++++++ tasks/templates/detalle_compra_pdf.html | 55 ++++ tasks/templates/index.html | 89 +++++++ tasks/templates/lista_compras.html | 64 +++++ tasks/templates/registrar_pago.html | 29 +++ tasks/templates/singnin.html | 0 tasks/templates/singup.html | 108 ++++++++ tasks/tests.py | 3 + tasks/views.py | 245 ++++++++++++++++++ 42 files changed, 1248 insertions(+) create mode 100644 .gitignore create mode 100644 RegistroDePagos/__init__.py create mode 100644 RegistroDePagos/__pycache__/__init__.cpython-313.pyc create mode 100644 RegistroDePagos/__pycache__/settings.cpython-313.pyc create mode 100644 RegistroDePagos/__pycache__/urls.cpython-313.pyc create mode 100644 RegistroDePagos/__pycache__/wsgi.cpython-313.pyc create mode 100644 RegistroDePagos/asgi.py create mode 100644 RegistroDePagos/settings.py create mode 100644 RegistroDePagos/urls.py create mode 100644 RegistroDePagos/wsgi.py create mode 100644 manage.py create mode 100644 requirements.txt create mode 100644 tasks/__init__.py create mode 100644 tasks/__pycache__/__init__.cpython-313.pyc create mode 100644 tasks/__pycache__/admin.cpython-313.pyc create mode 100644 tasks/__pycache__/apps.cpython-313.pyc create mode 100644 tasks/__pycache__/forms.cpython-313.pyc create mode 100644 tasks/__pycache__/models.cpython-313.pyc create mode 100644 tasks/__pycache__/views.cpython-313.pyc create mode 100644 tasks/admin.py create mode 100644 tasks/apps.py create mode 100644 tasks/forms.py create mode 100644 tasks/migrations/0001_initial.py create mode 100644 tasks/migrations/0002_remove_registrarcompra_estado.py create mode 100644 tasks/migrations/0003_registrarcompra_user_compra_and_more.py create mode 100644 tasks/migrations/__init__.py create mode 100644 tasks/migrations/__pycache__/0001_initial.cpython-313.pyc create mode 100644 tasks/migrations/__pycache__/0002_remove_registrarcompra_estado.cpython-313.pyc create mode 100644 tasks/migrations/__pycache__/0003_registrarcompra_user_compra_and_more.cpython-313.pyc create mode 100644 tasks/migrations/__pycache__/__init__.cpython-313.pyc create mode 100644 tasks/models.py create mode 100644 tasks/templates/base.html create mode 100644 tasks/templates/crear_compra.html create mode 100644 tasks/templates/detalle_compra.html create mode 100644 tasks/templates/detalle_compra_pdf.html create mode 100644 tasks/templates/index.html create mode 100644 tasks/templates/lista_compras.html create mode 100644 tasks/templates/registrar_pago.html create mode 100644 tasks/templates/singnin.html create mode 100644 tasks/templates/singup.html create mode 100644 tasks/tests.py create mode 100644 tasks/views.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f7275bb --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +venv/ diff --git a/RegistroDePagos/__init__.py b/RegistroDePagos/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/RegistroDePagos/__pycache__/__init__.cpython-313.pyc b/RegistroDePagos/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4974ce1d5bd380d0c9ae111cf22dad5675aaf5ba GIT binary patch literal 172 zcmey&%ge<81aqb&r-SInAOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl=2XRDad;?$zz zn6#qAyyO^{)Z*-t{DPSB{G#mQg2d$1n4r}3%;J)w{P=>z^!#Et*CjOo%#V-H%*!l^ lkJl@xyv1RYo1apelWJGQ3N#yJcQJ_Zk(rT^v4|PS0stn!Et>!U literal 0 HcmV?d00001 diff --git a/RegistroDePagos/__pycache__/settings.cpython-313.pyc b/RegistroDePagos/__pycache__/settings.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d70db283315c2e45e1c623d6bcc3b44bf004bea3 GIT binary patch literal 3231 zcmb7G%~RV*7MG2`zc9uSLP8>7l8lo_Q#+XmTM!Sn<*03#q^MO1yDU$ad*3J72A7kn$-3-T_xt$0kACl2pQ6za1D}8X ztyaH^Gt9s3!|)RrY(71AG0Z<0#1IC#kbBqllbg6%=D@wg987Ei&;Cfk{&RHQ92_La z#wZ8mzd$2CG(r5xLjq}r1k()izIKriJbmyCBR`2C;2;mOd7;;?RTm0#p0tZZQG~=$ z6vfaailZr%K+`D6`Bq(I63w7lkefkwoORAw@2T|b0PStSZs-KFY7@*f6jRlkEPTyl6%4By z{to=Wp<~l*8Q-VVHN9$ZeH%j$s(K@hn`r<{n|e#F8tI|q={~%9jM<~Mrm-4C!0Jc0 zfho-z7{V%H&lIhVjsM3M!AB#8{$tlm%0)dT#XPpQ23b&>2ClXVUU^B{r)Q}@s#A|6isL~HGd8MR=x1?6>-iUg(A4V5JEeL8J;D9F zO_T-}TXyjqBYMfPuH)K7GpzIg&BTqC293uZ0t{}}hK<2xogOR?*al5F)$ofcL(5db zIh`0OkM-?1%;!>E4Ij4a*3++$B={5>$h4jfWN&KL&g>VE?TlONW5PGt{DQ)em-0~m z7N6M_idpK(<|IS|e5oWmJHJ>e=foni#>W+RZq!vyX|*8h&XrPs4Ie3O&9vqQKGY6x z^VqmBPBj`MgPA9{?2$)3MAuEq(4eMQ7429zOzK%*UwxkPQJ+xU$rT0aDe?taD@c%N zpu{7zFG}F%D-o4pG>B4}Ou?M6UX8`=lwV6RjB}mTWSB*2&GC|}7?|*;-TZA6BekJw zirrOveF>ddFTNJ3pf=S;yOGDux_Nx7|Kr!{_q(k$@bMPl<<|lf+YLgNbv++1lJ zbfQwuSbq4&fUZkZSK3+_Aj8Ep^cvO-ZcD90W9r)~)@mb#26LyOsM_uI84a_=2{vu3 z-x*fSA=Mo*7$54c8*X7?c)u|5Or1EF{PIS|nr>(YVb6a#Z?nfo_<}vHt7n!M8!Ip$ zy|r{B{bt*m=AEFFw4ljI(aw~bPSn3lFibJ^i27w2{;p)1`djd)ps9xh7z_=_vOP#` zI>Cmnonh*0!SHOFG_b`Z!7gPQLPACo%JQ!8hI+HYR%M4q`Fvj77qYTlO^C*FMN|e7 z5TO1M4Ha_PY+l&sC4ojHQ7p?9DW4IG+ca1f3Z*<>77&f^qn(^BpU-7@yH#i~%a?iE zETVIKrTj`BR;|3p=X1amp~IqizPMB2cLX^jW(5Gu6$JUM2n8R2QkQdUe<)I)y(_;3 z&&z}^Y8L3Ski&gR!vi!=;@3G%im75Flpb&y|)6~q^_G7?I>WQR=? zkQ4+tf}q9SoFGH|bbd%f{GK2SdxG>vmW0<80UG?_kSF|H%1Iz!mbWBvA7+7^$%{z% z7xT$}OQ5@%?+@(8HV%%6(ySh2v2g-#E(g&3Cq_%D;)CJX?+>1vt(hD$2!PSi? zy2-ig2>X%oF2{Nk{$%JwFyi&~CSp-{C)i_|_ z@ZHyoFtIzG&~+#Zj)a)m%}XW$VNJd0M7z<2PN*A=!xB%zF|pQ}?9M*BWTv|_8@5=$ v0qojX{ly0C#%4O+({ym!p@W&<~S1kne?$X;7( zo(;N0KM-SGh+{4>07R$@G3XLQK%DPF9CwKmK%g$fNtZYU#KkVeX_puV;!+plj7yva z;)^cCh)awD5$i&Xxx{!bkeNJ8WTp=5l5JHs9Rk2M=FHnWU3M@~R;M}HKg;+Ed zyk9d4B6YE>2zoVv?r0iP4Ww5slh`SQio~p<>hrv6S#4!2O~GGl^Lz(<1ABG|uCUp1 zEE>Ji-dEYI;w#8dtN5EJpGV`t*@{Z()H-ZT+Y|MuEKIwM2~Q*;GVY}F9mM*aY&f4# zX`Tm(#SdhZHVT!RrW++Ff>@7ljh}RJKOb&SyG^Yot2_oA>u~k@!;RQkK-Q4r)pK*fQig2&K06Qb-&Onj^y~MUNTw; zuYbLl%QZ`B8ERHNz!lJpiX%)gs|TQsETU4y8Q{ID=G$_8uwy6?tP;gJCcfL|`nisE zS~u)eOUUcmjAFr+P@F;1x+5ydb47jRDBKy;u&q`qxDz;Z6lrNi#W_PTZ&;i^;C22e zFT7d6703r~az98%DC$S9QlL*^t1b;IH$U3 z>#+k$@p2(mWxA$o>tBC0qfFc81~z73r`^f=$RRzy=Qb>O_6W;TeB9~HLso@+9?C7= zAqiO6Ci*(*f!=cZ_jEFmxyEO!bb}HfpP$@pwLXhdlB>Nb-c= zNuhVr*>}?Q4*|*7_hRH_@>Oap_3GBvtzCKIPdV`7=zK zo1AIPtuXnK&`oTwG$S`z=qXY!iRjlG_4hHkLXjb`XJ3*Fug-EE|QVDhRkUf3RMhVQWOon|=A!s*@c zLL*aRa#85&f|=M}ZAQ{8l5R$pS!8)P Wl5MO~Cfn~X93S%j+)tuV@P7f1GZW(g literal 0 HcmV?d00001 diff --git a/RegistroDePagos/__pycache__/wsgi.cpython-313.pyc b/RegistroDePagos/__pycache__/wsgi.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ea461fa9bac3aae011306400130e9e6a00790d2 GIT binary patch literal 676 zcmZ8f!EVz)5Z$#yQ#mw3h*lg|6_-e0w})IRgoH?ml0b^$R$MGOTYKVcYwudS+l2Pk z6F&eq?ufrB0tv|%K!^i3P=0`QlhBGe?9S}y&3iMmv$Zt`8GH98O5S6Hew4w|Dhp?J zXq>mmM-utiw|ZD&bK5}Aafv1Q+k*Jp^8wF ziTEUi2PsS-B@-&yWs1`bVlo`kJWp9fH4}U|H1(buwoFJMj2q(YGC>44#hD@_mco6a z0GzSbx!Uo(SVYQ=k0?*XQd1-{54hKKx4p5LYT?C@r{XjNuDz*BnCJW|+wC`SaVG&C zmT%!T8O27c`?%lU3;o^w{Z7B_htKy~2hVoT?*2CgSAo`yC#q2`s$poPgkfO`Rot2j zawF4+Tb7r$AuOuECrk=nI0hKQh@Pans9j_JfM%t#qWxqiI8Y!}Fp`uu0Gf?%m6%*%EB3m5ANN=`RFEmj}ShyO^{O^8#APqh6F iS+AXQ=L=f@h}JJQ+>5R5XVm*%-~3d+cV3e>4d^cy7|Buq literal 0 HcmV?d00001 diff --git a/RegistroDePagos/asgi.py b/RegistroDePagos/asgi.py new file mode 100644 index 0000000..c361b45 --- /dev/null +++ b/RegistroDePagos/asgi.py @@ -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() diff --git a/RegistroDePagos/settings.py b/RegistroDePagos/settings.py new file mode 100644 index 0000000..7adde4b --- /dev/null +++ b/RegistroDePagos/settings.py @@ -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 \ No newline at end of file diff --git a/RegistroDePagos/urls.py b/RegistroDePagos/urls.py new file mode 100644 index 0000000..339f2a2 --- /dev/null +++ b/RegistroDePagos/urls.py @@ -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//pago/', views.registrar_pago, name='registrar_pago'), + path('compras//', views.detalle_compra, name='detalle_compra'), + path('compras//pdf/', views.reporte_detalle_compra, name='reporte_detalle_compra'), + path('compras//excel/', views.exportar_excel_detalle_compra, name='exportar_excel'), + path('compras//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///', auth_views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'), + path('password_reset_complete/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'), +] diff --git a/RegistroDePagos/wsgi.py b/RegistroDePagos/wsgi.py new file mode 100644 index 0000000..1088ed5 --- /dev/null +++ b/RegistroDePagos/wsgi.py @@ -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() diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..69e9f5b --- /dev/null +++ b/manage.py @@ -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() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..72a8ffe --- /dev/null +++ b/requirements.txt @@ -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 diff --git a/tasks/__init__.py b/tasks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tasks/__pycache__/__init__.cpython-313.pyc b/tasks/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1615ccedf64badedc01d5b7065fe648ed8e79f59 GIT binary patch literal 162 zcmey&%ge<81YXmU(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iOvsFxJacWU< zOj=Q5UUG~}YH@Z+enCumeo=ODL1J=hOi*fiW^qYTetbb8;e1VkIamWj77{q76AJ>DRlq< literal 0 HcmV?d00001 diff --git a/tasks/__pycache__/admin.cpython-313.pyc b/tasks/__pycache__/admin.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ccf3c9b6218812df193fc9dac034ec2b01cce21e GIT binary patch literal 206 zcmey&%ge<81YXmU)183yV-N=hn4pZ$0zk%8hG2$ZMsEf$#v(=qhIA%P=9eI8O~zZS zi7C06d48HqxA;=B67$mY^^)`RN{TX*ikN{4Rx*4Bsk>$8Y!wq)oLW>IlU9_NmmK4g zTAW>yUl3ECUzA;3keHkr6O@{sSzJ<-A77A|o?jeOl31Kw90N96ub}c4hfQvNN@-52 aT@eS+I7T2Y1~EP`Gcq#XV^An!19AY695xjI literal 0 HcmV?d00001 diff --git a/tasks/__pycache__/apps.cpython-313.pyc b/tasks/__pycache__/apps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c2178dee780be905c67e48b7c2b9d02e204191d GIT binary patch literal 524 zcmXv~O-md>5bd6ib>n6+u0}K@kmMlDWp9Fr28@z}0S|U^Ybe^8-eIz{)065RM6ZJ9 z9Mu1if8$Xaa>~hDV9nj`owW{CuXyhjRWg<>)~sN1`bW2shJlerV*y_BDVu z!W1b8W3V`FjMM`V=ywC;})FM}! z&vxaauJy1!NB`?t@^|XKOIPH6WBV>zyYCGy5AoGAf1HH2H-CdSqyK(u_sg`xWru8c hw>|q4uDI4eUaz|JFQkVQZv27|{7g3}lD}r4b*Us*fV~1%niD@R8?75Sz z_ElIGe09O?OP>S&0sS*FeQCKM3i}lBK}kjM$#d?cns!?RaUj3@b?&+6ocsIT3^SP| z!|#_LD)nzP#-7sP^wUS3VSt^-%w(o=o2@CJ6qp$^)!VT(71T8iv;v!9^UTz;%+xJq zLwY=Q616wgs0Bk>8fY2+7t44(QJfgiwxS~Rnhz~LP-qkrlN)Zm!2@vMmfz}tBQm22 z|Bh4lc_lh4LQG12!#A*b%q#|q$slGbpvq8U7>ar`78!wNYM|Hkq9LZwo$)aSFd%1V zyeFc4Z1~+X-YxXBjD`A40mF)KTp3x8HCQs$)J(k;GmUo@w0VAPanzMI@iCjLm)IoC z2{Tzz$&(o?sgN>LkglhT88LG~CZ%;Za0Yb2MO9?lerpR{yAl;M+49?gZ+EbiBIR#! z7alm3hTr}(O_#7#!Bz0R-RZL2Rj9fJ&nZNEdoiI`Jco0U>Ud7Y{nGcUF7)vJoFD+c zn^`B<1*d?{9T*TPMCQ|fj&ONT1*(G3z~A98joltAV-gA5ZaXd4wnfsmTYk0cQ9o_l z_q&caTA8%%S_63CHQH|5w{4*OgpSs(CyZLd^{O1?nG<*2uYyn7oo*mf-1DB)gEV>@zkBQY=?^*Y-NZhJ{)2>(_xx5+p z9c#yjChuSotW$Ezl5#6>coU6@(524TLZhpI6uP2tc^Tm$JH9gaNEcHdJTiXH&+prN z_Q7V~x*y(vFwc*(b4S^Q!|cLAx&QfgxY7)>-f^z5zq+@2Q0i}({VEUN3c_6Xx41T) z{5B4+p-Uk8%c#pe19CK&CCC%JL?8{WQ;%|sHoZ&X*_5E+uq z8Daca*-f1mODb84?z!{6=`i~KAJJVV%yH3OCd~7qOQPsfQgqjz$s>~7B*u-BOaHAg zr-~|zM#w#S%f=++~QHwIXUucV~|fiT9&7W)@{{EghTW5^G}iYgpG^KvV;Rz4oM*K^ literal 0 HcmV?d00001 diff --git a/tasks/__pycache__/models.cpython-313.pyc b/tasks/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..616bc40a77be7f78cd5ca3e5a376b2684bda2b2c GIT binary patch literal 3532 zcmdT{O>7&-6`tjCDgKJTrYu5@X^Bp}vMpGVrH=eOPWvw79I%w`Y zx=i8A3ajP(y9o&Kv?|T-KzT-Tgfar5E`gzJ&OK{KyVsrIid_Ob&QK57yZ76$h7k#z z;2C#OFL=Zb_2?43FoGZK$;0fqAXrkrvv2ZlgE>4z5(%!+#$1V%bxFS>n!&)k%cy9 z04rmy5e3&cBd}<=#GGz97htu39Jk8+S7URXu{|($*AZj8=40nzK9#-03hs;Zgh;TW zqIdrMY#SqcmVOb=)wcVJem;=3f@MuLG;u{LN(BuzE@)^5?xbC(c*qoY+zL2;@|SQ< z;1+P)>?-X=2j6X3EtF){kRQiy+r87bC7LfuN*Nn1KOQce15CraEee2-82L$&R!V*e`@ZKyO1{-WFPz!wzt0zEo07d(uW<@(^P7xJ|;FCwfqV0n7=qiZ+6)^r4 zjy)Y+zp{4aMb3;5Zw>E+$Ew~jdJ3l8Bw0RYc|}o`%Cab0eo+L7uPUeqMe%p5lG3t- zMRBP_bweqsvZ{$9MS`<@6U!2vD#=R03aMIok;-D;reiBO>Cmffv^UK1&B0A{oCDe- zT)y8+`I4qu{wa;hrJ_0`KcaX+t*(iy!o=0d>sDYDyjmmD0WeYINb8s=!tNTSyeJt4 zEiJAZvJNX6Ljmaoh!GN}MeZ z|DDpN(PkQm^*>9sxg(M+Bnc3?yC=behhFfp zuORQTbt_0^1(?5JBX$mQeNbA3RHSNOh*F_I1L%)b%d<3uI=)#bq%U|ufY`@@cw~s; z<^vG$ggYwTwm1WvI;9()MZh$O)&m7BELhs7zsCv|_ed8o50`!H#URrKIsKSTpF?r_ zjK%Pf;@pUM%Zm~(oaR^J@5B)qBsz^}Y?m zjj%Ut6qKWq1_Xb|hT&TSLT9pYjyCPuJbM*d_e5mVu$9^$&EBWQS@cJ~1gZ?j>*2H+ zPQMtf4~&@b7apm4N1V%uOpxW1^bC9{cA_{x9YTQtPz=E48j47yS5P1vtT1(+m0Fv} ze3O7ShqU}la(-FDSB^%9vB$eWKE%GEJ)|b<^cuE#SE#lkQ{$17V(>T)^3*uCp+Ly% ze+B_O6^PaYeYHT}^SOd6mk$qzQd+ZXfiqK<(wIxt=zDQ>{KmwYAd#V_2r})`xMBnO@|w! zX5xJ1<{LM|EigZLh^PBU7sHJ7j=dI*0dZg2#-E1dQ?k4_XahTfPhW!+7c|Uft(PerIW=81ic!oc)48y!4eXq!w lS0w$`bDX*GbZK2#Q{EC#-(CnYqfe*SZ>-(;iGXS^{byO&^VI+V literal 0 HcmV?d00001 diff --git a/tasks/__pycache__/views.cpython-313.pyc b/tasks/__pycache__/views.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e149252ab77933d353c8a35460141fef369bd7a GIT binary patch literal 11153 zcmdT~TW}lKd0qgE`;`O;n2+AwMwF*Easg<1HsIBFfSG24ip*+(4A!Sjrx&Jh=L z@w92wJ>p>=q|G7AXv0V&YaH=1FMqa*SJ@Ycsku-}O8aNctDURW6HZv*7nRH5)0(zzK z)I#>Ml*%Sz(QG1}8cMTy#S@k#cA8109CU9?rsonV#e|>qLRN7^%grQoD^e5ux_Gq+ zlfJZoLBv1B77KWbds3Q9$XOO;r_=Kp7FC?ZjP!-*Tv|pAZ%qw8kcO&SltnY3X=SDP zOfs6~ug)ao=r{_Dv#21)=#^R!k)Yk#Zb$T%FjXoGL+TFe)oMK$;rMTC2`t~W;N|Rm zGN5DB=$1|~(?ldWD^o+ia`N=CX_}}!JE+YJ{MgwCOT)$QmZGGDEB^5G+f` z!jN-9$81;%o8mjYAZOF_wLtk;s=8qRC>3I}({qFgcq{q@+|j5@8O!M=_$dlvGT!2`L$umF7r9&O%>ek!Uu{ z5;F@~Nk&y#EJpKUB!G%Y<7B&$q_N7>(H{#No~C8vM#IzJG^sV8KyIuknn(&lNr`G^WVZ-6Mac1U;@3LK8UD}a=?qa~s9z%*fPEkKa z`zRt2S8QiVY=&rK`79Xi2p=9BpmJw%1~Dm_j6?ty$B@er;%RjzT~wbU>Q1KKe}!#! zG^{&1KXi1i#dA+a^NyLNv%eJe##6d`0>!_y7>!N;E+A6JNzJRE%!CZurELdf<1GO@ zjfdJ0(jB`b2+>VJm@4gPeVI61RJ)WeteZ5~_EX;dlAHjwP}BP8ntox*u9b%LTK_#l z$bfbv>NQ~yBZvYAQA$8q69~UF#DeiP{$70;%czGw%)eJ3pb!<~u>M|sSVOjQA5#t5 zRAGedAxBs@pwlQsPsP4z`j^suc8h>>ToEr!gf1#3IWdJtq~9+-rt&eh?9w>RhL zMHNr`TGRTj1NmJC3ho0y^0!@Yx>gr+k3V$Jiz=laL)dd@$x@cdoR_%FO|t9@%HZa;bF(CuKsdn)HR^}r%{ z_7vQG%i^ZH`R3f}AG~|zcJ|#=!F?iUIl=Ly3Q9=bIh6bx62ApXywy-b#A2#sVyXnU z1R|G&Neeoz2_caiq55S;Dhrv88VHVJO||bVzmXVyP+y@gCCIr{;&T8Zr}pxYwG2c7 z+qhzQ_UuL0f>JIaTvHT_#t$Goy;L-eXmJP%kCs#js?futs z)3f=BxxDi-Y^B}3a(uI~b@j;F*k;q7jponYGa3%KwgrRNy(JhN?guu(*?6<>uEW3S z?O2^Cc>8mX{)fegrpCo6h(tBH))pOGK{a>);RfPU;Dnn`4+}v2l`jV4oQSFvRn|;! z24{nfA@2hIWe5fqVpFs&ANl&krrmpUxM1Ir6L&m3wvx5B^z+uD|_CM;!XWhZRq6@P>UfmN)wIRW!H7tA4c5N~E9 zh!z`S@c&p*nmIaiVGmeUu_ha)3iD}75i4UP#fqIsUa^_Ftt0>eLm8Giha?rD0?
j>WV-D$e*D%hXO ziBG9=MP@_`1Z*_!?HS}&%KTKR)+J9p2@2@|ZhGu#X@9jY1>v*yIc2D1RqaC9;4Lw8 zZP!yG`mMvT!s0&2Sjq?n=qkTWON9)aD}Eo+U%^Nw$uq49rT3V^rb*(oH9?azGWryv z%h)~BB*AP=2#Fyhq~9DiPm;FR1mu#NR1=7pr!1F->9_tok9`a8grUMachoe-W2nOS z^$Qb2m-><=&8g3g!{%gZH!-F<^U!7teA}hH5&Tj=*{wa5e?yj|@SmWUrB&8ir%Y31 zZ+HL~v|gGC8$-6Bp={m)_VHVKMGx9Dj#^bloTQM{b({Qv-GZN^oL3me3Zxq-uQq_W zmS$BhqgA~kyk+zY=Rw!(=x0tiVwlz4($Sk8^Wb=CI?Ra1(~4bvf&Q_@_>+v}$C0)y~lgcipo%-iT>U^!YPgj9ci-*Bk>Gl3>IvXXj>(4Am;JVx=1=W<2 zem-F0znpfGVGb&;IGpe#9DUVN>qPJ$q{5Z&~0 z88jdkqgD>S1jT(~Gn9XdB04EhJZfQTLAt`XGA)x`0rILr#a~zT#2M{O9RIReM+p22 zXV-VWdi&@H{qOhZou6MC0`+xzZ;Y*s!KckTItz}jrL&t}U*0$VzOmpNFL=k7#)uyK z*4_TR+rM@^@7}*OT#e)YwH5;Cf;(7+;~UiWbS*hImruAF)&6b@x|G=H0AD;WcxqQn|&NH;tLdKw7@a!tMyO+gZsK#L6&Y@gn zE`Q*1!Jf#8iHFTWiDsCvO8&0ziJvZrxYeaKUsn&?0PYjfhw#I8^U7P z7&e8(pfP2h0^F(rz-0`ZD&Io?~H^GX9@j~`IMK2(#qSDjIPtf$$r9L0 z6h$Z^L<<g1qpNA%xjXONU2yhtAn>+rH25}Jdp4RnHyT^-*-dWK z(z6dd0?xHTs0MEL73{}y;<1NGddUyPc@=37Vl~t=RFj0ZH384Sy~`tyCNnM{GsmGul-fCHERAATFLY!&c}`~Gc@pbp z87gWhNt9-zRGAXFOp(-Z&r}_Nj27AftV+Q-@8{8RosB=Ux4G zT7KC1gU+0*Kj#`+diI`2XzAK^n%hnH9Y`$?ZCQnu9UH#RbsyxVzu*gQw05nx4&+-0 z3atkom_@7Yo(&bZoPx1sOF+8^jYTQhpF1#=8~#G>iK&8pIwwy5`o0&%GVS?a_n!)g zRR`jfOU7{f4x&ob4GSfzN;^$;YC^c=go0R?B&v9PF5;+C42jraVX7oo_0BT7g-eG( zWc9#JW4#NQy~VdiLBA{BV#4W#p2%QWgbQpLUvx(^nPl{ivxBC*XH z+<~Rx4ZD{P;#Q9r?0c8aAU_v4mUj=LV9)ub;heKOFYexK^t~PY)8JZDzVYzV=!UuR z`d6=im8y0g{?S<8{anuS9J*PWc-W4YB{ik2s^r;Mf0FDQsQ9~kdq_w&P$VUW#~seXMN2$0KF6Q-KA43zt{bop1aAyxHS%jH^1 ztA}3d99!%!9m>JAzNl)=ROxs^I|#v1oPLt-Q)+?>*Hs4%VVri8HNu+hs;NM^W^Dvm z2{Sa^I?k>mf3e|=>fw^(dzu^lTjHW~NQzyKQq~3d%!`im3|2&v7f)3hsVk{?FrAT7*XEOSR};<3`_i+siI@~m#}?*cf`W1e2ZwR_vLwL< z1oNS{{&*RIBnRNOU z#T1QY6S#ynW)s<@gvSi(#1-2t&6*!S;QNqdI+|rAhg)%`D<`VjJpxL8oEfM~#TEV4 z8T`xKvu1yY$`6xpWe1KwfO$miui=uo2Kv~y(dr+l#j3 zpi)Mtlo&FRN==mvY(4k58!D$M7bki3!N~mo8H|b~RD}$1H*mq2e1+>O`xC6tqA`eP zRT0@6$OyRVX=7P!6a(rjrmHLgzeKU9zv-(Cr?Ucki=vY1u|K2!>Kj{9D)t?U$RT{# z*mjh(3$Lryt-nN*PpH{@h<*p%@^rr2o_8Or5%CvudtTB+917@axbcN=d|}m6aPETP z7H!ujUZ41|DSTIazNBuh8}1eNe^K4qD%5QY?s8L+nFbzLJn@&Kskt=Z6F3rE$jT}+ z7cnWm2a3H|;WEDF<2#_V6?4dczz0Y{`amzpzlc+>A4v}tZ=Fi&btEYazoJyy>(mMI zubpH@N~3hus}PrPD@tD#$c!#uafMIcN6G5(C`hIHZ4DzmVq}vQU$Ga>X$S(G@%|CTQ))O zxu>`5p4M#}klfptoT#{g-0wHlFsx&d-r!6EY^#T%&+s$})Fk8&7GrK^D zR4G!Wo^xoV9Jy3VZ|SL#dZDLs^ty*dBUO#m6E~-{_rBS+6Qnp%PPOda_vXEM^S<{t zK5A9DBvA5XghR&M%6Mqdz^18cB_qSr@P=9<$W(RBpu&c( z;IbjVGQe@M26GeyTaUPc;}PLS5AljT@xi&#>6RK%u}y3jJ0`rK6$7pQ zmRh_RckFb&>7-FkJn^|l>=L`h9%gIQFD54VH)893XKa0NV(JY(opgLU^-kDMzmwJ( z(0Y3W`avVz%AZ1K{@m;9`JNVQLoL>ZUtbfGVoDrvGL!!QPHkPW;(HT3WHrFDI?9tC zagZcRXEWz$=w`ET4ub$;k#^*9Ni|G@$dtZOA&By-GE4bSr!zhpMVqEB$pj-4D>PCe z>INb^(mLLuAp;W?mDT&0Mz=62>IRlHw1H`xrf(DpmSo4;p#q0M>xMVbJ*kYflDP)7 zu7TMKD(Qzjh)c~-9lBW;}u*f5czXS^^XLJL?^RIHhpuwH_d-~!Q01RMJO$c&b1GHjYK>}AZh5>`*n zhC|lp;7&kZ(^c?;B$%YrHRW@_3actED+XzJvLbx(KxxhfY504*VruNdH&yB}`ZiQ!se_?d5iu=<5m)rgfasxk8*p4vTUr^jpQ@n^D?zFv(@9mLN( z-m;V9wdDBTz`j`hQnHeFsxfpB@81>d)L1Pw_SCdeSE{j(4&r@}Kd?_-sGYj-H^*gf z@O6$`<`H{oe1(6(ai{MxCQkTzMCfXN;SYtv^{!*SrQYhBoHW7i-ayR-ISwlR&K%0tysAgJBO?ss#dna>2ExX48hqY2f;Fb5KlbrvHON=eZx%H*%>=~%gWxev&&X?xq4f+ZyOeT`^;)=v)&P8m?MBWk?0h@og*{NY-WPA&#k)= zr8wgLD;a4PAGyYADbkS3y0+@}Na*Dbr=6TWa0`T0plf`VZ4F*=`|~Q91D5?IG1$#k k_j){@BZ2b_{K2LEqC&0ZOrSF8}}l literal 0 HcmV?d00001 diff --git a/tasks/migrations/__pycache__/0002_remove_registrarcompra_estado.cpython-313.pyc b/tasks/migrations/__pycache__/0002_remove_registrarcompra_estado.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e2bece87ed1f5901d96b9cf7645dc0f1a1465898 GIT binary patch literal 702 zcmY*Xzi-qq7`2m2nxuDXDL+aPs-WsXgw!5L3{M>FPZgv5weSYOY_AEF> z2}L$h}8jBp57P3_7f{W1KLHqXf z6cGeCLEt19+{D?%&>`NS`>&wI_6ly&NxY{uXDRU^$d}^4gm30kE__Rj9ph=HM#^;i z{r)2=N}&ZS%nI;aC=Co!Ij(>ipDWEWY5CDuX1t&!8*|gC>sNFbhbExS5v3-g)KVq| zx428`+X*Xbn(bC79STso5G5~V{W#+lFEd`If-9(FG0Ou!mhbpW!Hdj9vf@{nn46T^ zD`=5YrZtG;iROw@=vmRMwrqya_sEgrpvVwdnUWV=jkK)DdkG^|v6PcRYf93JuH3->u15{k6`nB z{lWRQjbEM{MpM^Y^FJNHt-1Ozc1@U_urilB*>PjOeRb`qrL9|Jf@f`-`>zW>`_#{QS+Y<2tl>h>kFO{4w;=~%d@ literal 0 HcmV?d00001 diff --git a/tasks/migrations/__pycache__/0003_registrarcompra_user_compra_and_more.cpython-313.pyc b/tasks/migrations/__pycache__/0003_registrarcompra_user_compra_and_more.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fcfc6bae100fe2e62a151ade1ccf1d91523de7f GIT binary patch literal 1667 zcma)6&u<$=6rS~Z*Xxb*qg(e!nx>>_L*oN(6oiBkLbc*VG-*NIZ7$VHqm6g6TC(0< zX4WLRAt7<*!v9ci9N|*n#svwRgQJlU2e{(Q*a!J6N7XS!N zpBaM}l3-=0jA+i1sBGozyqWH2Gywr8Tb;-T5X5&sM>WVaGHbMj-N;tn*3GM<-fRqW zid`~qjL(?m3BH>Xe7DAZlbKp-O|}X(8CWij=cFt(V7WYTu4w7jR4w<){E{{O%6!F| z`40vqYxe&k+|N_PnxmJ!OBI6`9}la2ssVYZzmhoYfbr$EwY7CjNh|ty$Bq}B=77$r5EF2kaY@r}IRTCXr%4#ZIY-#_JwIW5`iw**vKXJrAjlEWt;kg9 zhF%km&@C^WF97_%0_~T%hEpOKa~)$K)(!}aOBjFLb^<{TtXYireae!+4@nqdOyQ~` zUS@|*96NiE;F6exE(x2*RNOE=Ti<>9KHjx9P5k)7#^xhlI*;&yzgBnMEwN)N?l{-C zB1(KO+#$zYbw6@KFXB0Ok8A0v!A<0ajk>i_Z)|eC9wdaOxxCcKP9?MSC?>;udj(^t zP~tbSlO)vNYbS&O>jhYB0Dr_=8}Hb=P%CEdQzvZN4Z;qRD7FtHdca~hW82J1o)v@H zsVMA=r@;n!yg8uUF&YrWM6 zoz(}wX!X7#m(@=n^-HLDxu-33wS|*wt0$Lkofy}9#+|Nl=Xv>?tKZFkJKw84?9?9i zYG$Wq_G-^MwP!zLuZIsi@H38H7V<^Cucsi3U2QQ1SxiAzy2i@aPrhI87+d2}uMd|R zM*Z067*8g~z4i~Zk+|De4E3{Z`o?hE6^+klb*;L4)$CEN4vR}yAdF9)%{QZPKhse0 zJR_A`DwXUcMIAHnsz}w|&1Cx?T?bRVsZ2aXeOZ#Efr_NtzoMnz(CVM+O-cPi@6Fxq M%-#J9fstbT4QKwIMgRZ+ literal 0 HcmV?d00001 diff --git a/tasks/migrations/__pycache__/__init__.cpython-313.pyc b/tasks/migrations/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..757f3ef40fbaad454174558079e04259e34df5d7 GIT binary patch literal 173 zcmey&%ge<81YXmU(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~i4vsFxJacWU< zOj=Q5UUG~}YH@Z+enCumeo=ODL1J=hOi*fiW^qYTetbb&ryk0@&Ee@O9{FKt1RJ$Tppy43Pi$RQ!%#4hTMa)1J06z>a82|tP literal 0 HcmV?d00001 diff --git a/tasks/models.py b/tasks/models.py new file mode 100644 index 0000000..0d74c95 --- /dev/null +++ b/tasks/models.py @@ -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 \ No newline at end of file diff --git a/tasks/templates/base.html b/tasks/templates/base.html new file mode 100644 index 0000000..c9b8c60 --- /dev/null +++ b/tasks/templates/base.html @@ -0,0 +1,78 @@ + + + + + + {% block title %}Gestor de Pagos{% endblock %} + + + + + + +
+ {% block content %} + {% endblock %} +
+
+

© 2024 Gestor de Pagos

+
+ + + \ No newline at end of file diff --git a/tasks/templates/crear_compra.html b/tasks/templates/crear_compra.html new file mode 100644 index 0000000..085b4b6 --- /dev/null +++ b/tasks/templates/crear_compra.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Registrar Nueva Compra

+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+ + Volver a la Lista + + +
+
+
+{% endblock %} + +{% block footer %} +
+
+ © 2024 Mi Aplicación de Compras. Todos los derechos reservados. +
+
+{% endblock %} \ No newline at end of file diff --git a/tasks/templates/detalle_compra.html b/tasks/templates/detalle_compra.html new file mode 100644 index 0000000..6538b9d --- /dev/null +++ b/tasks/templates/detalle_compra.html @@ -0,0 +1,82 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Detalle de Compra: {{ compra.nombre_compra }}

+ + +
+ + + + + + + + + + + {% for pago in pagos %} + + + + + + + {% empty %} + + + + {% endfor %} + +
Fecha de PagoObservacionesMonto ExtraMonto Pagado
{{ pago.fecha_pago }}{{ pago.observaciones }}${{ pago.monto_extra }}${{ pago.monto_pagado }}
No se han registrado pagos para esta compra.
+
+ + + + +
+
Monto Total: ${{ compra.monto_pago }}
+
Total Pagado: ${{ total_pagado }}
+
Restante: ${{ restante }}
+
Estado: + + {{ estado }} + +
+
+
+{% endblock %} + +{% block footer %} +
+
+ © 2024 Mi Aplicación de Compras. Todos los derechos reservados. +
+
+{% endblock %} \ No newline at end of file diff --git a/tasks/templates/detalle_compra_pdf.html b/tasks/templates/detalle_compra_pdf.html new file mode 100644 index 0000000..ea0001e --- /dev/null +++ b/tasks/templates/detalle_compra_pdf.html @@ -0,0 +1,55 @@ + + + + + + +
+

Reporte de Detalle de Compra

+
+
+

Nombre de Compra: {{ compra.nombre_compra }}

+

Monto Total: ${{ compra.monto_pago }}

+

Total Pagado: ${{ total_pagado }}

+

Restante: ${{ restante }}

+
+ + + + + + + + + + + {% for pago in pagos %} + + + + + + + {% endfor %} + +
FechaMonto PagadoMonto ExtraObservaciones
{{ pago.fecha_pago }}${{ pago.monto_pagado }}${{ pago.monto_extra }}{{ pago.observaciones|default:"Sin observaciones" }}
+ + + \ No newline at end of file diff --git a/tasks/templates/index.html b/tasks/templates/index.html new file mode 100644 index 0000000..831580b --- /dev/null +++ b/tasks/templates/index.html @@ -0,0 +1,89 @@ +{% extends "Base.html" %} + +{% block content %} +{% if user.is_authenticated %} + +{% else %} + +
+
+
+
+

Iniciar Sesión

+ +
+ {% csrf_token %} +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ Registrar +
+
+
+
+
+{% endif %} + +{% endblock %} +{% block footer %} +
+
+ © 2024 Mi Aplicación. Todos los derechos reservados. +
+
+{% endblock %} + \ No newline at end of file diff --git a/tasks/templates/lista_compras.html b/tasks/templates/lista_compras.html new file mode 100644 index 0000000..ab95b68 --- /dev/null +++ b/tasks/templates/lista_compras.html @@ -0,0 +1,64 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Lista de Compras

+ + +
+ + + + + + + + + + + + {% for item in compras_con_estado %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
Nombre de CompraMonto TotalEstadoRestanteOpciones
{{ item.compra.nombre_compra }}${{ item.compra.monto_pago }} + + {{ item.estado }} + + ${{ item.restante }} + Ver Detalle +
No hay compras registradas.
+
+ + + + + +
+ +{% endblock %} + +{% block footer %} +
+
+ © 2024 Mi Aplicación de Compras. Todos los derechos reservados. +
+
+{% endblock %} diff --git a/tasks/templates/registrar_pago.html b/tasks/templates/registrar_pago.html new file mode 100644 index 0000000..d44f4a0 --- /dev/null +++ b/tasks/templates/registrar_pago.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block content %} +
+

Registrar Pago para: {{ compra.nombre_compra }}

+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+ + Volver al Detalle de la Compra + + +
+
+
+{% endblock %} + +{% block footer %} +
+
+ © 2024 Mi Aplicación de Compras. Todos los derechos reservados. +
+
+{% endblock %} \ No newline at end of file diff --git a/tasks/templates/singnin.html b/tasks/templates/singnin.html new file mode 100644 index 0000000..e69de29 diff --git a/tasks/templates/singup.html b/tasks/templates/singup.html new file mode 100644 index 0000000..cb5661e --- /dev/null +++ b/tasks/templates/singup.html @@ -0,0 +1,108 @@ +{% extends "Base.html" %} + +{% block content %} + + + +
+
+
+
+
+

Registrar Nueva Cuenta

+ +
+ {% csrf_token %} + +
+ + + {% if form.username.errors %} +
+ {% for error in form.username.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ + + {% if form.email.errors %} +
+ {% for error in form.email.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ + + {% if form.password1.errors %} +
+ {% for error in form.password1.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ + + {% if form.password2.errors %} +
+ {% for error in form.password2.errors %} +

{{ error }}

+ {% endfor %} +
+ {% endif %} +
+ +
+ +
+
+ +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/tasks/tests.py b/tasks/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/tasks/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/tasks/views.py b/tasks/views.py new file mode 100644 index 0000000..819fd02 --- /dev/null +++ b/tasks/views.py @@ -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 \ No newline at end of file