Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions essenza/Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gunicorn essenza.wsgi:application --workers 2 --log-file -
50 changes: 43 additions & 7 deletions essenza/deploy.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,43 @@
pip install -r requirements.txt && \
python manage.py flush --noinput && \
python manage.py migrate --noinput && \
# python manage.py collectstatic --noinput && \
python manage.py loaddata user/sample/sample.json && \
python manage.py loaddata product/sample/sample.json && \
python manage.py loaddata order/sample/sample.json
# ---------------------------------------------------------
# IMPORTANTE: Este archivo RESTAURA TOTALMENTE la BD del proyecto.
# ---------------------------------------------------------

set -e

echo ""
echo "--- Instalando dependencias (pip)..."
pip install -r requirements.txt

echo ""
echo "--- Recolectando estáticos (CSS/JS)..."
python3 manage.py collectstatic --no-input

echo ""
echo "--- Vaciando DB..."
python3 manage.py flush --no-input

echo ""
echo "--- Aplicando Migraciones (Migrate)..."
python3 manage.py migrate --no-input

echo ""
echo "--- Copiando imagenes de sampleo a 'media/'..."
mkdir -p media
cp -r _sample_assets/* media/ 2>/dev/null || echo "Aviso: No se encontraron assets en _sample_assets/"

echo ""
echo "--- Cargando datos de USER..."
python3 manage.py loaddata user/sample/sample.json

echo ""
echo "--- Cargando datos de PRODUCT..."
python3 manage.py loaddata product/sample/sample.json

echo ""
echo "--- Cargando datos de ORDER..."
python3 manage.py loaddata order/sample/sample.json

echo ""
echo "========================================================"
echo "!PROCESO COMPLETADO CON EXITO!"
echo "========================================================"
71 changes: 50 additions & 21 deletions essenza/essenza/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import os
from pathlib import Path

import dj_database_url
from dotenv import load_dotenv

load_dotenv()
Expand All @@ -34,6 +35,11 @@

ALLOWED_HOSTS = ["*"]

CSRF_TRUSTED_ORIGINS = [
"https://pgpi-g1-11.onrender.com",
"http://127.0.0.1:8000",
"http://localhost:8000",
]

# Application definition

Expand All @@ -45,6 +51,7 @@
"django.contrib.messages",
"django.contrib.staticfiles",
"django.contrib.humanize",
"anymail",
"user",
"product",
"order",
Expand Down Expand Up @@ -85,17 +92,18 @@
WSGI_APPLICATION = "essenza.wsgi.application"


# Database
# Database (Híbrida)
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases

DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
"default": dj_database_url.config(
# Si estás en Render, usará la variable DATABASE_URL automáticamente.
# Si estás en Local (no hay DATABASE_URL), usará este sqlite:
default="sqlite:///" + str(BASE_DIR / "db.sqlite3"),
conn_max_age=600,
)
}


# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators

Expand All @@ -119,10 +127,8 @@
# https://docs.djangoproject.com/en/5.2/topics/i18n/

LANGUAGE_CODE = "es"

TIME_ZONE = "Europe/Madrid"
USE_I18N = True

USE_TZ = True


Expand Down Expand Up @@ -156,18 +162,41 @@
# -----------------------------------------------------------------
STRIPE_PUBLIC_KEY = os.getenv("STRIPE_PUBLIC_KEY")
STRIPE_SECRET_KEY = os.getenv("STRIPE_SECRET_KEY")
DOMAIN_URL = os.getenv(
"DOMAIN_URL", "http://127.0.0.1:8000"
) # Default a localhost si falla
DOMAIN_URL = os.getenv("DOMAIN_URL", "http://127.0.0.1:8000")

EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True

# Leemos las credenciales del archivo .env (o las pones aquí directamente entre comillas si prefieres)
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")
# -----------------------------------------------------------------
# CONFIGURACIÓN DE EMAIL
# -----------------------------------------------------------------
if not DEBUG:
# -----------------------------------------------------------
# PRODUCCIÓN (Render) -> Usa SendGrid (Anymail)
# -----------------------------------------------------------
EMAIL_BACKEND = "anymail.backends.sendgrid.EmailBackend"
ANYMAIL = {"SENDGRID_API_KEY": os.getenv("SENDGRID_API_KEY")}
DEFAULT_FROM_EMAIL = "noreply.essenza@gmail.com"

# Logging para debuggear emails en la nube
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {"console": {"class": "logging.StreamHandler"}},
"loggers": {
"django.core.mail": {
"handlers": ["console"],
"level": "DEBUG",
"propagate": True,
},
},
}

# El remitente que aparecerá en los correos
DEFAULT_FROM_EMAIL = "Essenza <noreply@essenza.com>"
else:
# -----------------------------------------------------------
# LOCAL (Tu PC) -> Usa SMTP de Gmail
# -----------------------------------------------------------
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")
DEFAULT_FROM_EMAIL = "noreply@essenza.com"
20 changes: 13 additions & 7 deletions essenza/essenza/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
from info.views import info_view
from django.urls import include, path, re_path
from django.views.static import serve
from product.views import CatalogDetailView, CatalogView, DashboardView

urlpatterns = [
path("info/", info_view, name="info-home"),
path("info/", include("info.urls")),
path("user/", include("user.urls")),
path("admin/", admin.site.urls),
path("product/", include("product.urls")),
Expand All @@ -15,8 +14,15 @@
path("catalog/<int:pk>/", CatalogDetailView.as_view(), name="catalog_detail"),
path("cart/", include("cart.urls")),
path("order/", include("order.urls")),
path('info/', include('info.urls')),
]

if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Arreglo para muestra de imágenes en producción
urlpatterns += [
re_path(
r"^media/(?P<path>.*)$",
serve,
{
"document_root": settings.MEDIA_ROOT,
},
),
]
6 changes: 3 additions & 3 deletions essenza/info/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ def setUpTestData(cls):
OrderProduct.objects.create(order=cls.order1, product=cls.prod2, quantity=5)
OrderProduct.objects.create(order=cls.order2, product=cls.prod1, quantity=3)

cls.history_url = reverse("info:sales_reports_view", args=["history"])
cls.product_url = reverse("info:sales_reports_view", args=["product"])
cls.user_url = reverse("info:sales_reports_view", args=["user"])
cls.history_url = reverse("sales_reports_view", args=["history"])
cls.product_url = reverse("sales_reports_view", args=["product"])
cls.user_url = reverse("sales_reports_view", args=["user"])

def test_unauthenticated_access_is_denied(self):
self.client.logout()
Expand Down
19 changes: 14 additions & 5 deletions essenza/info/urls.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
# essenza/info/urls.py

from django.urls import path
from . import views

app_name = 'info'
from . import views

urlpatterns = [
path("reports/", views.SalesReportsView.as_view(), {'report_type': 'history'}, name="sales_history_report"),
path("reports/<str:report_type>/", views.SalesReportsView.as_view(), name="sales_reports_view"),
]
path("", views.info_view, name="info-home"),
path(
"reports/",
views.SalesReportsView.as_view(),
{"report_type": "history"},
name="sales_history_report",
),
path(
"reports/<str:report_type>/",
views.SalesReportsView.as_view(),
name="sales_reports_view",
),
]
99 changes: 59 additions & 40 deletions essenza/info/views.py
Original file line number Diff line number Diff line change
@@ -1,63 +1,82 @@
# essenza/info/views.py

from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.db.models import Sum, F, Count
from django.shortcuts import redirect, render
from django.db.models import F, Sum
from django.shortcuts import render
from django.urls import reverse
from django.views import View
from django.http import HttpResponseForbidden

from order.models import Order, OrderProduct


def info_view(request):
return render(request, "info/info.html")


class SalesReportsView(LoginRequiredMixin, UserPassesTestMixin, View):
"""
Maneja la visualización de los tres tipos de reportes:
history (Historial de Pedidos), product (Ventas por Producto), user (Ventas por Usuario).
"""
raise_exception = True

raise_exception = True

def test_func(self):
return self.request.user.is_authenticated and self.request.user.role == "admin"
def get(self, request, report_type='history'):

def get(self, request, report_type="history"):
reports_nav = [
{'id': 'history', 'name': 'Historial de Ventas', 'url': reverse('info:sales_reports_view', args=['history'])},
{'id': 'product', 'name': 'Ventas por Producto', 'url': reverse('info:sales_reports_view', args=['product'])},
{'id': 'user', 'name': 'Ventas por Usuario', 'url': reverse('info:sales_reports_view', args=['user'])},
{
"id": "history",
"name": "Historial de Ventas",
"url": reverse("sales_reports_view", args=["history"]),
},
{
"id": "product",
"name": "Ventas por Producto",
"url": reverse("sales_reports_view", args=["product"]),
},
{
"id": "user",
"name": "Ventas por Usuario",
"url": reverse("sales_reports_view", args=["user"]),
},
]

context = {
'reports_nav': reports_nav,
'current_report': report_type,
"reports_nav": reports_nav,
"current_report": report_type,
}

if report_type == 'product':
context['report_title'] = 'Ventas Totales por Producto'
context['template_name'] = 'info/product_sales.html'
context['sales_data'] = OrderProduct.objects.values(
'product__id', 'product__name'
).annotate(
total_sold=Sum('quantity'),
total_revenue=Sum(F('quantity') * F('product__price'))
).order_by('-total_revenue')

elif report_type == 'user':
context['report_title'] = 'Ventas Totales por Usuario'
context['template_name'] = 'info/user_sales.html'
context['sales_data'] = Order.objects.values(
'user__id', 'user__first_name', 'user__email'
).annotate(
total_spent=Sum(F('order_products__quantity') * F('order_products__product__price'))
).exclude(
user__isnull=True
).order_by('-total_spent')

else:
context['report_title'] = 'Historial Completo de Ventas'
context['template_name'] = 'info/sales_history.html'
context['orders'] = Order.objects.all().order_by('-placed_at')

return render(request, 'info/reports_master.html', context)

if report_type == "product":
context["report_title"] = "Ventas Totales por Producto"
context["template_name"] = "info/product_sales.html"
context["sales_data"] = (
OrderProduct.objects.values("product__id", "product__name")
.annotate(
total_sold=Sum("quantity"),
total_revenue=Sum(F("quantity") * F("product__price")),
)
.order_by("-total_revenue")
)

elif report_type == "user":
context["report_title"] = "Ventas Totales por Usuario"
context["template_name"] = "info/user_sales.html"
context["sales_data"] = (
Order.objects.values("user__id", "user__first_name", "user__email")
.annotate(
total_spent=Sum(
F("order_products__quantity")
* F("order_products__product__price")
)
)
.exclude(user__isnull=True)
.order_by("-total_spent")
)

else:
context["report_title"] = "Historial Completo de Ventas"
context["template_name"] = "info/sales_history.html"
context["orders"] = Order.objects.all().order_by("-placed_at")

return render(request, "info/reports_master.html", context)
6 changes: 6 additions & 0 deletions essenza/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
asgiref==3.10.0
certifi==2025.11.12
cffi==2.0.0
charset-normalizer==3.4.4
cryptography==46.0.3
dj-database-url==3.0.1
Django==5.2.8
django-anymail==13.1
gunicorn==23.0.0
idna==3.11
packaging==25.0
pillow==12.0.0
psycopg2-binary==2.9.11
pycparser==2.23
python-dotenv==1.2.1
requests==2.32.5
sqlparse==0.5.3
Expand Down
Loading