diff --git a/.github/agents/docs-writer.agent.md b/.github/agents/docs-writer.agent.md
new file mode 100644
index 0000000..631da05
--- /dev/null
+++ b/.github/agents/docs-writer.agent.md
@@ -0,0 +1,29 @@
+---
+description: "Use when creating or updating Markdown documentation in docs/; writes polished docs with GitHub-style NOTE/IMPORTANT/CAUTION callouts and no terminal use."
+name: "Docs Writer"
+tools: [read, edit, search]
+user-invocable: true
+---
+You are a documentation specialist for this repository. Your job is to create and improve Markdown documentation files under docs/ only.
+
+## Constraints
+- DO NOT use the terminal.
+- DO NOT edit files outside docs/.
+- MUST use only Python files inside tienda/ to make the documentations
+- ONLY create or modify Markdown documentation.
+- Prefer concise, well-structured prose.
+- Use GitHub-style Markdown callouts for important information: > [!NOTE], > [!IMPORTANT], > [!CAUTION], and > [!TIP].
+- Keep wording clear and practical.
+
+## Approach
+1. Inspect the existing documentation and related code only as needed.
+2. Write content in Markdown with headings, short paragraphs, and callouts where useful.
+3. Keep changes strictly scoped to documentation files under docs/.
+4. Docs must follow this examples:
+- tienda/views.py#function -> docs/views/function.md
+- tienda/views.py#another_function -> docs/views/another_function.md
+- tienda/pdf.py#class#function -> docs/pdf/class/function.md
+5. Every folder in the docs folder must have a INDEX.md with a beautiful list of every md file in the folder and then every index.md of the folders in that folder.
+
+## Output Format
+Return the docs files changed and a short summary of what was added or improved.
\ No newline at end of file
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index bc47a16..2d61140 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -77,6 +77,7 @@ Templates use Django's inheritance pattern:
- Bootstrap requires JavaScript for modals/toggles (reference data-bs-* attributes in base.html)
- SECRET_KEY is development-only (contains 'insecure' marker)
- DEBUG=True - not production-ready
+- Search for a virtual environment in the project files, if there is one use it, if not, create it.
## When Adding Features
- New models: Add to [tienda/models.py](tienda/models.py), run migrations, register in [tienda/admin.py](tienda/admin.py)
diff --git a/Dockerfile b/Dockerfile
index 074bcda..90766f0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -14,5 +14,6 @@ COPY . /app/
RUN chmod +x /app/entrypoint.sh
EXPOSE 8000
-
+RUN mkdir -pv /fonts
+COPY tienda/static/fonts/ /fonts/
CMD ["/app/entrypoint.sh"]
\ No newline at end of file
diff --git a/proyecto/__pycache__/__init__.cpython-314.pyc b/proyecto/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index 3adf1a0..0000000
Binary files a/proyecto/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/proyecto/__pycache__/settings.cpython-314.pyc b/proyecto/__pycache__/settings.cpython-314.pyc
deleted file mode 100644
index 30642f3..0000000
Binary files a/proyecto/__pycache__/settings.cpython-314.pyc and /dev/null differ
diff --git a/proyecto/__pycache__/urls.cpython-314.pyc b/proyecto/__pycache__/urls.cpython-314.pyc
deleted file mode 100644
index 658ce21..0000000
Binary files a/proyecto/__pycache__/urls.cpython-314.pyc and /dev/null differ
diff --git a/proyecto/__pycache__/wsgi.cpython-314.pyc b/proyecto/__pycache__/wsgi.cpython-314.pyc
deleted file mode 100644
index dd040cd..0000000
Binary files a/proyecto/__pycache__/wsgi.cpython-314.pyc and /dev/null differ
diff --git a/requirements.txt b/requirements.txt
index 281a6ae..0f5f3e8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -42,4 +42,5 @@ urllib3==2.6.3
vine==5.1.0
wcwidth==0.6.0
whitenoise==6.12.0
-mysqlclient
\ No newline at end of file
+mysqlclient
+fpdf2==2.8.7
\ No newline at end of file
diff --git a/service.sh b/service.sh
new file mode 100755
index 0000000..0b50daf
--- /dev/null
+++ b/service.sh
@@ -0,0 +1,101 @@
+#!/bin/bash
+
+set -u
+
+readonly HOSTS=(
+ "aws-docker-mysql"
+ "aws-docker-redis"
+ "aws-docker-celery"
+ "aws-docker"
+)
+
+readonly WAIT_SECONDS=5
+readonly REMOTE_DEPLOY_DIR="/root/deploys"
+
+usage() {
+ echo "Uso: $0 {start|stop|restart|update}"
+}
+
+print_status() {
+ local action="$1"
+ local host="$2"
+ local status="$3"
+
+ # Estilo similar al output de OpenRC.
+ printf "* %-8s %-16s [%s]\n" "$action" "$host" "$status"
+}
+
+run_remote_compose() {
+ local host="$1"
+ local command="$2"
+
+ ssh -o BatchMode=yes -o LogLevel=ERROR -T "$host" "sudo -n sh -c \"cd '$REMOTE_DEPLOY_DIR' || exit 1; if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then docker compose $command; elif command -v docker-compose >/dev/null 2>&1; then docker-compose $command; else exit 1; fi\"" >/dev/null 2>&1
+}
+
+run_for_all_hosts() {
+ local mode="$1"
+ local host=""
+ local i=0
+ local total=${#HOSTS[@]}
+
+ for host in "${HOSTS[@]}"; do
+ case "$mode" in
+ start)
+ if run_remote_compose "$host" "up -d"; then
+ print_status "Started" "$host" "ok"
+ else
+ print_status "Started" "$host" "fail"
+ exit 1
+ fi
+ ;;
+ stop)
+ if run_remote_compose "$host" "down"; then
+ print_status "Stopped" "$host" "ok"
+ else
+ print_status "Stopped" "$host" "fail"
+ exit 1
+ fi
+ ;;
+ restart)
+ if run_remote_compose "$host" "down" && run_remote_compose "$host" "up -d"; then
+ print_status "Restarted" "$host" "ok"
+ else
+ print_status "Restarted" "$host" "fail"
+ exit 1
+ fi
+ ;;
+ update)
+ if run_remote_compose "$host" "pull" && run_remote_compose "$host" "down" && run_remote_compose "$host" "up -d"; then
+ print_status "Updated" "$host" "ok"
+ else
+ print_status "Updated" "$host" "fail"
+ exit 1
+ fi
+ ;;
+ *)
+ usage
+ exit 1
+ ;;
+ esac
+
+ i=$((i + 1))
+ if [ "$i" -lt "$total" ]; then
+ sleep "$WAIT_SECONDS"
+ fi
+ done
+}
+
+if [ "$#" -ne 1 ]; then
+ usage
+ exit 1
+fi
+
+case "$1" in
+ start|stop|restart|update)
+ run_for_all_hosts "$1"
+ ;;
+ *)
+ usage
+ exit 1
+ ;;
+esac
\ No newline at end of file
diff --git a/templates/jinja2/emails/reset_pass.html b/templates/jinja2/emails/reset_pass.html
new file mode 100644
index 0000000..32dc1a9
--- /dev/null
+++ b/templates/jinja2/emails/reset_pass.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ ¡Hola {{ name }}!
+
+
+
+
+ alert alert
+
+
+
+
+ ¡Alguien esta intentando cambiar la contraseña de tu cuenta!
+ Si has sido tu, haga click en el siguiente enlace. Si no, Elimine el correo de inmediato
+
+ Para resetear tu contraseña, Haga click aqui
+ Este email ha sido enviado automaticamente, no responda a este correo.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tienda/__pycache__/__init__.cpython-314.pyc b/tienda/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index f69534a..0000000
Binary files a/tienda/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/tienda/__pycache__/admin.cpython-314.pyc b/tienda/__pycache__/admin.cpython-314.pyc
deleted file mode 100644
index 1b0d656..0000000
Binary files a/tienda/__pycache__/admin.cpython-314.pyc and /dev/null differ
diff --git a/tienda/__pycache__/apps.cpython-314.pyc b/tienda/__pycache__/apps.cpython-314.pyc
deleted file mode 100644
index 9082874..0000000
Binary files a/tienda/__pycache__/apps.cpython-314.pyc and /dev/null differ
diff --git a/tienda/__pycache__/context_processors.cpython-314.pyc b/tienda/__pycache__/context_processors.cpython-314.pyc
deleted file mode 100644
index 0643b39..0000000
Binary files a/tienda/__pycache__/context_processors.cpython-314.pyc and /dev/null differ
diff --git a/tienda/__pycache__/models.cpython-314.pyc b/tienda/__pycache__/models.cpython-314.pyc
deleted file mode 100644
index 0a69a30..0000000
Binary files a/tienda/__pycache__/models.cpython-314.pyc and /dev/null differ
diff --git a/tienda/__pycache__/urls.cpython-314.pyc b/tienda/__pycache__/urls.cpython-314.pyc
deleted file mode 100644
index ab71d24..0000000
Binary files a/tienda/__pycache__/urls.cpython-314.pyc and /dev/null differ
diff --git a/tienda/__pycache__/vars.cpython-314.pyc b/tienda/__pycache__/vars.cpython-314.pyc
deleted file mode 100644
index c41a8a3..0000000
Binary files a/tienda/__pycache__/vars.cpython-314.pyc and /dev/null differ
diff --git a/tienda/__pycache__/views.cpython-314.pyc b/tienda/__pycache__/views.cpython-314.pyc
deleted file mode 100644
index bd15423..0000000
Binary files a/tienda/__pycache__/views.cpython-314.pyc and /dev/null differ
diff --git a/tienda/admin.py b/tienda/admin.py
index 685160b..c31c77f 100644
--- a/tienda/admin.py
+++ b/tienda/admin.py
@@ -1,12 +1,18 @@
from django.contrib import admin
-from .models import Category, Image, Product, Cart, CartItem, Order, OrderItem, OrderMessage, User, VerificationCode
+from .models import Category, Image, Product, Cart, CartItem, Order, OrderItem, OrderMessage, StockReservation, StockReservationItem, User, VerificationCode
# Register your models here.
admin.site.register(Category)
admin.site.register(Image)
-admin.site.register(Product)
admin.site.register(User)
admin.site.register(VerificationCode)
+
+
+@admin.register(Product)
+class ProductAdmin(admin.ModelAdmin):
+ list_display = ('id', 'name', 'price', 'stock', 'category', 'creator')
+ search_fields = ('name', 'creator__username', 'creator__email')
+ list_filter = ('category',)
class CartItemInline(admin.TabularInline):
model = CartItem
extra = 0
@@ -46,9 +52,9 @@ class OrderItemInline(admin.TabularInline):
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
- list_display = ('id', 'buyer', 'total', 'status', 'payment_method', 'payment_reference', 'created_at')
+ list_display = ('id', 'transaction_code', 'buyer', 'total', 'status', 'payment_method', 'payment_reference', 'created_at')
list_filter = ('status', 'payment_method', 'created_at')
- search_fields = ('buyer__username', 'buyer__email', 'payment_reference')
+ search_fields = ('buyer__username', 'buyer__email', 'payment_reference', 'transaction_code')
inlines = [OrderItemInline]
@@ -67,4 +73,17 @@ class OrderMessageAdmin(admin.ModelAdmin):
def message_preview(self, obj):
return obj.message[:50] + "..." if len(obj.message) > 50 else obj.message
- message_preview.short_description = 'Mensaje'
\ No newline at end of file
+ message_preview.short_description = 'Mensaje'
+
+
+class StockReservationItemInline(admin.TabularInline):
+ model = StockReservationItem
+ extra = 0
+
+
+@admin.register(StockReservation)
+class StockReservationAdmin(admin.ModelAdmin):
+ list_display = ('id', 'user', 'session_key', 'status', 'payment_method', 'expires_at', 'created_at')
+ list_filter = ('status', 'payment_method', 'created_at')
+ search_fields = ('user__username', 'user__email', 'session_key')
+ inlines = [StockReservationItemInline]
\ No newline at end of file
diff --git a/tienda/migrations/0003_order_transaction_code.py b/tienda/migrations/0003_order_transaction_code.py
new file mode 100644
index 0000000..d1b91d7
--- /dev/null
+++ b/tienda/migrations/0003_order_transaction_code.py
@@ -0,0 +1,41 @@
+# Generated by Django 6.0.1 on 2026-04-09
+
+from django.db import migrations, models
+from django.utils.crypto import get_random_string
+
+
+TRANSACTION_CODE_PREFIX = "comal-"
+TRANSACTION_CODE_LENGTH = 32
+TRANSACTION_CODE_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
+
+
+def populate_transaction_codes(apps, schema_editor):
+ Order = apps.get_model("tienda", "Order")
+
+ for order in Order.objects.filter(transaction_code__isnull=True):
+ while True:
+ code = f"{TRANSACTION_CODE_PREFIX}{get_random_string(TRANSACTION_CODE_LENGTH, TRANSACTION_CODE_ALPHABET)}"
+ if not Order.objects.filter(transaction_code=code).exists():
+ order.transaction_code = code
+ order.save(update_fields=["transaction_code"])
+ break
+
+
+def noop_reverse(apps, schema_editor):
+ pass
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("tienda", "0002_verificationcode_code_mode_and_more"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="order",
+ name="transaction_code",
+ field=models.CharField(blank=True, db_index=True, max_length=38, null=True, unique=True),
+ ),
+ migrations.RunPython(populate_transaction_codes, noop_reverse),
+ ]
diff --git a/tienda/migrations/0004_product_stock_stockreservation_stockreservationitem.py b/tienda/migrations/0004_product_stock_stockreservation_stockreservationitem.py
new file mode 100644
index 0000000..1782119
--- /dev/null
+++ b/tienda/migrations/0004_product_stock_stockreservation_stockreservationitem.py
@@ -0,0 +1,45 @@
+# Generated by Django 6.0.1 on 2026-04-09 06:55
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('tienda', '0003_order_transaction_code'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='product',
+ name='stock',
+ field=models.PositiveIntegerField(default=0),
+ ),
+ migrations.CreateModel(
+ name='StockReservation',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('session_key', models.CharField(blank=True, max_length=40, null=True)),
+ ('status', models.CharField(choices=[('active', 'Activa'), ('completed', 'Completada'), ('cancelled', 'Cancelada'), ('expired', 'Expirada')], default='active', max_length=20)),
+ ('payment_method', models.CharField(choices=[('stripe', 'Stripe'), ('paypal', 'PayPal')], max_length=20)),
+ ('expires_at', models.DateTimeField(db_index=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='stock_reservations', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='StockReservationItem',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('quantity', models.PositiveIntegerField(default=1)),
+ ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='stock_reservation_items', to='tienda.product')),
+ ('reservation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='tienda.stockreservation')),
+ ],
+ options={
+ 'unique_together': {('reservation', 'product')},
+ },
+ ),
+ ]
diff --git a/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc b/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc
deleted file mode 100644
index 851e7f4..0000000
Binary files a/tienda/migrations/__pycache__/0001_initial.cpython-314.pyc and /dev/null differ
diff --git a/tienda/migrations/__pycache__/0002_verificationcode_code_mode_and_more.cpython-314.pyc b/tienda/migrations/__pycache__/0002_verificationcode_code_mode_and_more.cpython-314.pyc
deleted file mode 100644
index d4fc2a6..0000000
Binary files a/tienda/migrations/__pycache__/0002_verificationcode_code_mode_and_more.cpython-314.pyc and /dev/null differ
diff --git a/tienda/migrations/__pycache__/__init__.cpython-314.pyc b/tienda/migrations/__pycache__/__init__.cpython-314.pyc
deleted file mode 100644
index ef4a82c..0000000
Binary files a/tienda/migrations/__pycache__/__init__.cpython-314.pyc and /dev/null differ
diff --git a/tienda/models.py b/tienda/models.py
index 0970157..ab57703 100644
--- a/tienda/models.py
+++ b/tienda/models.py
@@ -1,7 +1,17 @@
from django.db import models
from django.contrib.auth.models import User, AbstractUser
-from .vars import VAT_RATE
+from django.utils.crypto import get_random_string
+from .vars import VAT_RATE, TRANSACTION_CODE_PREFIX, TRANSACTION_CODE_LENGTH, TRANSACTION_CODE_ALPHABET
import random, string
+
+
+def generate_transaction_code() -> str:
+ while True:
+ code = f"{TRANSACTION_CODE_PREFIX}{get_random_string(TRANSACTION_CODE_LENGTH, TRANSACTION_CODE_ALPHABET)}"
+ if not Order.objects.filter(transaction_code=code).exists():
+ return code
+
+
class User(AbstractUser):
class RegisterStatus(models.TextChoices):
CONFIRMATION_REQUIRED = "CR", "Confirmation Required"
@@ -47,6 +57,7 @@ class Product(models.Model):
description = models.TextField(default = "")
briefdesc = models.TextField(default = "")
price = models.FloatField(default = 0)
+ stock = models.PositiveIntegerField(default=0)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
primary_image = models.ForeignKey(Image, on_delete=models.SET_NULL, null=True)
secondary_images = models.ManyToManyField(Image, related_name='products_secondary', blank=True)
@@ -64,6 +75,49 @@ class Product(models.Model):
return round(self.price * VAT_RATE, 2)
+class StockReservation(models.Model):
+ STATUS_ACTIVE = "active"
+ STATUS_COMPLETED = "completed"
+ STATUS_CANCELLED = "cancelled"
+ STATUS_EXPIRED = "expired"
+ STATUS_CHOICES = [
+ (STATUS_ACTIVE, "Activa"),
+ (STATUS_COMPLETED, "Completada"),
+ (STATUS_CANCELLED, "Cancelada"),
+ (STATUS_EXPIRED, "Expirada"),
+ ]
+
+ PAYMENT_STRIPE = "stripe"
+ PAYMENT_PAYPAL = "paypal"
+ PAYMENT_CHOICES = [
+ (PAYMENT_STRIPE, "Stripe"),
+ (PAYMENT_PAYPAL, "PayPal"),
+ ]
+
+ user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name="stock_reservations")
+ session_key = models.CharField(max_length=40, null=True, blank=True)
+ status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_ACTIVE)
+ payment_method = models.CharField(max_length=20, choices=PAYMENT_CHOICES)
+ expires_at = models.DateTimeField(db_index=True)
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ def __str__(self):
+ return f"Reserva {self.id} - {self.user or self.session_key} ({self.status})"
+
+
+class StockReservationItem(models.Model):
+ reservation = models.ForeignKey(StockReservation, on_delete=models.CASCADE, related_name="items")
+ product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="stock_reservation_items")
+ quantity = models.PositiveIntegerField(default=1)
+
+ class Meta:
+ unique_together = ("reservation", "product")
+
+ def __str__(self):
+ return f"{self.quantity}x {self.product.name} (reserva {self.reservation_id})"
+
+
class Cart(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
session_key = models.CharField(max_length=40, null=True, blank=True)
@@ -136,12 +190,18 @@ class Order(models.Model):
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_PAID)
payment_method = models.CharField(max_length=20, choices=PAYMENT_CHOICES, default=PAYMENT_MANUAL)
payment_reference = models.CharField(max_length=200, blank=True, default="")
+ transaction_code = models.CharField(max_length=38, unique=True, null=True, blank=True, db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"Pedido {self.id} - {self.buyer or self.session_key}"
+ def save(self, *args, **kwargs):
+ if not self.transaction_code:
+ self.transaction_code = generate_transaction_code()
+ super().save(*args, **kwargs)
+
def get_items_count(self):
return sum(item.quantity for item in self.items.all())
diff --git a/tienda/pdf.py b/tienda/pdf.py
new file mode 100644
index 0000000..c805a78
--- /dev/null
+++ b/tienda/pdf.py
@@ -0,0 +1,50 @@
+import os
+from django.conf import settings
+
+from fpdf import FPDF
+import string, random
+class Recibo(FPDF):
+ def header(self):
+ self.set_font('Arial', 'B', 15)
+ self.cell(0, 10, "Comercialmeria S.L")
+ self.cell(0, 10, 'RECIBO DE PAGO', ln=True, align='R')
+ self.ln(10)
+
+ def footer(self):
+ self.set_y(-15)
+ self.set_font('Arial', 'I', 8)
+ self.cell(0, 10, f'Pagina {self.page_no()}', align='C')
+
+def generar_recibo(cliente: str, total: float, objetos: list, metodo_pago: str, transaction_code: str):
+ pdf = Recibo()
+ font_path = "/fonts/Roboto-Regular.ttf"
+ pdf.add_font('Roboto', '', '/fonts/Roboto-Regular.ttf')
+ pdf.add_font('Roboto', 'B', '/fonts/Roboto-Bold.ttf')
+ pdf.add_page()
+ pdf.set_font('Roboto', size=12)
+
+ pdf.cell(0, 10, f"Cliente: {cliente}", ln=True)
+ pdf.cell(0, 10, f"ID de transaccion: {transaction_code}", ln=True)
+ pdf.cell(0, 10, f"")
+
+ DATA = []
+ DATA.append(
+ ("Cant.", "Nombre", "Precio Unit.", "Subtotal")
+ )
+ for i in objetos:
+ DATA.append(
+ (str(i["amount"]), str(i["product_name"]), str(i["price"]), str(float(i["price"])*int(i["amount"])))
+ )
+ pdf.cell(0, 10, "DETALLE DEL COBRO", ln=True, align='R')
+ pdf.ln(5)
+
+ with pdf.table() as table:
+ for data_row in DATA:
+ row = table.row()
+ for datum in data_row:
+ row.cell(datum)
+ pdf.ln(5)
+ pdf.set_font('Roboto', size=12)
+ pdf.cell(0, 10, f'TOTAL A PAGAR: {total} €', align="R")
+ return pdf.output(dest="S")
+
\ No newline at end of file
diff --git a/tienda/static/fonts/Roboto-Black.ttf b/tienda/static/fonts/Roboto-Black.ttf
new file mode 100644
index 0000000..f939fc7
Binary files /dev/null and b/tienda/static/fonts/Roboto-Black.ttf differ
diff --git a/tienda/static/fonts/Roboto-BlackItalic.ttf b/tienda/static/fonts/Roboto-BlackItalic.ttf
new file mode 100644
index 0000000..e2a2028
Binary files /dev/null and b/tienda/static/fonts/Roboto-BlackItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto-Bold.ttf b/tienda/static/fonts/Roboto-Bold.ttf
new file mode 100644
index 0000000..6516185
Binary files /dev/null and b/tienda/static/fonts/Roboto-Bold.ttf differ
diff --git a/tienda/static/fonts/Roboto-BoldItalic.ttf b/tienda/static/fonts/Roboto-BoldItalic.ttf
new file mode 100644
index 0000000..adc7d44
Binary files /dev/null and b/tienda/static/fonts/Roboto-BoldItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto-ExtraBold.ttf b/tienda/static/fonts/Roboto-ExtraBold.ttf
new file mode 100644
index 0000000..9dc48a1
Binary files /dev/null and b/tienda/static/fonts/Roboto-ExtraBold.ttf differ
diff --git a/tienda/static/fonts/Roboto-ExtraBoldItalic.ttf b/tienda/static/fonts/Roboto-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..f297672
Binary files /dev/null and b/tienda/static/fonts/Roboto-ExtraBoldItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto-ExtraLight.ttf b/tienda/static/fonts/Roboto-ExtraLight.ttf
new file mode 100644
index 0000000..5e517b3
Binary files /dev/null and b/tienda/static/fonts/Roboto-ExtraLight.ttf differ
diff --git a/tienda/static/fonts/Roboto-ExtraLightItalic.ttf b/tienda/static/fonts/Roboto-ExtraLightItalic.ttf
new file mode 100644
index 0000000..ff2c0d9
Binary files /dev/null and b/tienda/static/fonts/Roboto-ExtraLightItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto-Italic.ttf b/tienda/static/fonts/Roboto-Italic.ttf
new file mode 100644
index 0000000..01f2c5d
Binary files /dev/null and b/tienda/static/fonts/Roboto-Italic.ttf differ
diff --git a/tienda/static/fonts/Roboto-Light.ttf b/tienda/static/fonts/Roboto-Light.ttf
new file mode 100644
index 0000000..ee7268f
Binary files /dev/null and b/tienda/static/fonts/Roboto-Light.ttf differ
diff --git a/tienda/static/fonts/Roboto-LightItalic.ttf b/tienda/static/fonts/Roboto-LightItalic.ttf
new file mode 100644
index 0000000..cb70f96
Binary files /dev/null and b/tienda/static/fonts/Roboto-LightItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto-Medium.ttf b/tienda/static/fonts/Roboto-Medium.ttf
new file mode 100644
index 0000000..bc5b170
Binary files /dev/null and b/tienda/static/fonts/Roboto-Medium.ttf differ
diff --git a/tienda/static/fonts/Roboto-MediumItalic.ttf b/tienda/static/fonts/Roboto-MediumItalic.ttf
new file mode 100644
index 0000000..739dcf3
Binary files /dev/null and b/tienda/static/fonts/Roboto-MediumItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto-Regular.ttf b/tienda/static/fonts/Roboto-Regular.ttf
new file mode 100644
index 0000000..3db0d1f
Binary files /dev/null and b/tienda/static/fonts/Roboto-Regular.ttf differ
diff --git a/tienda/static/fonts/Roboto-SemiBold.ttf b/tienda/static/fonts/Roboto-SemiBold.ttf
new file mode 100644
index 0000000..7a8ef87
Binary files /dev/null and b/tienda/static/fonts/Roboto-SemiBold.ttf differ
diff --git a/tienda/static/fonts/Roboto-SemiBoldItalic.ttf b/tienda/static/fonts/Roboto-SemiBoldItalic.ttf
new file mode 100644
index 0000000..224bc96
Binary files /dev/null and b/tienda/static/fonts/Roboto-SemiBoldItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto-Thin.ttf b/tienda/static/fonts/Roboto-Thin.ttf
new file mode 100644
index 0000000..cdd56c5
Binary files /dev/null and b/tienda/static/fonts/Roboto-Thin.ttf differ
diff --git a/tienda/static/fonts/Roboto-ThinItalic.ttf b/tienda/static/fonts/Roboto-ThinItalic.ttf
new file mode 100644
index 0000000..ec4b537
Binary files /dev/null and b/tienda/static/fonts/Roboto-ThinItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-Black.ttf b/tienda/static/fonts/Roboto_Condensed-Black.ttf
new file mode 100644
index 0000000..222ede4
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-Black.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-BlackItalic.ttf b/tienda/static/fonts/Roboto_Condensed-BlackItalic.ttf
new file mode 100644
index 0000000..532c8ca
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-BlackItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-Bold.ttf b/tienda/static/fonts/Roboto_Condensed-Bold.ttf
new file mode 100644
index 0000000..d589f51
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-Bold.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-BoldItalic.ttf b/tienda/static/fonts/Roboto_Condensed-BoldItalic.ttf
new file mode 100644
index 0000000..08d7c05
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-BoldItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-ExtraBold.ttf b/tienda/static/fonts/Roboto_Condensed-ExtraBold.ttf
new file mode 100644
index 0000000..d4c604a
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-ExtraBold.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-ExtraBoldItalic.ttf b/tienda/static/fonts/Roboto_Condensed-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..036486f
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-ExtraBoldItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-ExtraLight.ttf b/tienda/static/fonts/Roboto_Condensed-ExtraLight.ttf
new file mode 100644
index 0000000..50a71d8
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-ExtraLight.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-ExtraLightItalic.ttf b/tienda/static/fonts/Roboto_Condensed-ExtraLightItalic.ttf
new file mode 100644
index 0000000..0e91dfd
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-ExtraLightItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-Italic.ttf b/tienda/static/fonts/Roboto_Condensed-Italic.ttf
new file mode 100644
index 0000000..86853c3
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-Italic.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-Light.ttf b/tienda/static/fonts/Roboto_Condensed-Light.ttf
new file mode 100644
index 0000000..edaf03e
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-Light.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-LightItalic.ttf b/tienda/static/fonts/Roboto_Condensed-LightItalic.ttf
new file mode 100644
index 0000000..1b6a511
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-LightItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-Medium.ttf b/tienda/static/fonts/Roboto_Condensed-Medium.ttf
new file mode 100644
index 0000000..dcad58b
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-Medium.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-MediumItalic.ttf b/tienda/static/fonts/Roboto_Condensed-MediumItalic.ttf
new file mode 100644
index 0000000..521057b
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-MediumItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-Regular.ttf b/tienda/static/fonts/Roboto_Condensed-Regular.ttf
new file mode 100644
index 0000000..0c6feda
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-Regular.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-SemiBold.ttf b/tienda/static/fonts/Roboto_Condensed-SemiBold.ttf
new file mode 100644
index 0000000..3f20051
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-SemiBold.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-SemiBoldItalic.ttf b/tienda/static/fonts/Roboto_Condensed-SemiBoldItalic.ttf
new file mode 100644
index 0000000..da9625c
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-SemiBoldItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-Thin.ttf b/tienda/static/fonts/Roboto_Condensed-Thin.ttf
new file mode 100644
index 0000000..5a81776
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-Thin.ttf differ
diff --git a/tienda/static/fonts/Roboto_Condensed-ThinItalic.ttf b/tienda/static/fonts/Roboto_Condensed-ThinItalic.ttf
new file mode 100644
index 0000000..4c58469
Binary files /dev/null and b/tienda/static/fonts/Roboto_Condensed-ThinItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-Black.ttf b/tienda/static/fonts/Roboto_SemiCondensed-Black.ttf
new file mode 100644
index 0000000..d321657
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-Black.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-BlackItalic.ttf b/tienda/static/fonts/Roboto_SemiCondensed-BlackItalic.ttf
new file mode 100644
index 0000000..3fddfd2
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-BlackItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-Bold.ttf b/tienda/static/fonts/Roboto_SemiCondensed-Bold.ttf
new file mode 100644
index 0000000..3b09bdc
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-Bold.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-BoldItalic.ttf b/tienda/static/fonts/Roboto_SemiCondensed-BoldItalic.ttf
new file mode 100644
index 0000000..58a292d
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-BoldItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-ExtraBold.ttf b/tienda/static/fonts/Roboto_SemiCondensed-ExtraBold.ttf
new file mode 100644
index 0000000..2ed59f6
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-ExtraBold.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-ExtraBoldItalic.ttf b/tienda/static/fonts/Roboto_SemiCondensed-ExtraBoldItalic.ttf
new file mode 100644
index 0000000..6a6ea25
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-ExtraBoldItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-ExtraLight.ttf b/tienda/static/fonts/Roboto_SemiCondensed-ExtraLight.ttf
new file mode 100644
index 0000000..d86e218
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-ExtraLight.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-ExtraLightItalic.ttf b/tienda/static/fonts/Roboto_SemiCondensed-ExtraLightItalic.ttf
new file mode 100644
index 0000000..44777e0
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-ExtraLightItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-Italic.ttf b/tienda/static/fonts/Roboto_SemiCondensed-Italic.ttf
new file mode 100644
index 0000000..fb944fb
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-Italic.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-Light.ttf b/tienda/static/fonts/Roboto_SemiCondensed-Light.ttf
new file mode 100644
index 0000000..aa2fd11
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-Light.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-LightItalic.ttf b/tienda/static/fonts/Roboto_SemiCondensed-LightItalic.ttf
new file mode 100644
index 0000000..16331bd
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-LightItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-Medium.ttf b/tienda/static/fonts/Roboto_SemiCondensed-Medium.ttf
new file mode 100644
index 0000000..54c1f07
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-Medium.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-MediumItalic.ttf b/tienda/static/fonts/Roboto_SemiCondensed-MediumItalic.ttf
new file mode 100644
index 0000000..f0d504b
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-MediumItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-Regular.ttf b/tienda/static/fonts/Roboto_SemiCondensed-Regular.ttf
new file mode 100644
index 0000000..462fe48
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-Regular.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-SemiBold.ttf b/tienda/static/fonts/Roboto_SemiCondensed-SemiBold.ttf
new file mode 100644
index 0000000..2abf7bc
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-SemiBold.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-SemiBoldItalic.ttf b/tienda/static/fonts/Roboto_SemiCondensed-SemiBoldItalic.ttf
new file mode 100644
index 0000000..62905c8
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-SemiBoldItalic.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-Thin.ttf b/tienda/static/fonts/Roboto_SemiCondensed-Thin.ttf
new file mode 100644
index 0000000..a51429a
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-Thin.ttf differ
diff --git a/tienda/static/fonts/Roboto_SemiCondensed-ThinItalic.ttf b/tienda/static/fonts/Roboto_SemiCondensed-ThinItalic.ttf
new file mode 100644
index 0000000..a1cd1cf
Binary files /dev/null and b/tienda/static/fonts/Roboto_SemiCondensed-ThinItalic.ttf differ
diff --git a/tienda/tasks.py b/tienda/tasks.py
index 8b76165..a547100 100644
--- a/tienda/tasks.py
+++ b/tienda/tasks.py
@@ -1,9 +1,11 @@
from celery import shared_task
from django.conf import settings
from django.template.loader import render_to_string
+from django.core.mail import EmailMessage
from .utilities import send_email, send_hemail
from .vars import login_message, verify_message
import random, string
+from . import pdf
from .models import User, VerificationCode
@shared_task
@@ -18,7 +20,8 @@ def enviar_correo_bienvenida(email_usuario: str, nombre_usuario: str):
send_hemail(email_usuario, "Inicio de Sesión correcto", html_content, "Has iniciado sesión...")
@shared_task
-def enviar_correo_confirmacion(usuario: User):
+def enviar_correo_confirmacion(id: int):
+ usuario = User.objects.get(id=id)
code = VerificationCode.objects.create(
user = usuario,
code_mode = VerificationCode.VerificationModes.VERIFY_ACCOUNT,
@@ -26,4 +29,54 @@ def enviar_correo_confirmacion(usuario: User):
)
message = verify_message.format(name = usuario.get_full_name(), protocol = settings.PROTOCOL, domain = settings.DOMAIN, code = code.code)
- email_result = send_email(usuario.email, "Verificación de cuenta", message)
\ No newline at end of file
+ email_result = send_email(usuario.email, "Verificación de cuenta", message)
+
+@shared_task
+def enviar_correo_recuperacion(email: str):
+ usuario = User.objects.get(email=email)
+ if usuario is not None:
+ ver_code = VerificationCode.objects.create(
+ code_mode = VerificationCode.VerificationModes.RESET_PASSWORD,
+ user = usuario,
+ code = ''.join(random.choices(string.digits, k=12))
+ )
+ ver_code.save()
+ html_content = render_to_string(
+ 'emails/reset_pass.html',
+ {
+ "name": usuario.get_full_name(),
+ "domain": settings.DOMAIN,
+ "protocol": settings.PROTOCOL,
+ "code": ver_code.code
+ },
+ using='jinja2'
+ )
+
+ send_hemail(email, "Reset de Contraseña", html_content, "Estas reseteando la contraseña...")
+
+
+# Purchased items should be a list of dictionary, the dictionary must follow this tags: amount, product name, price (each)
+@shared_task
+def process_purchase(user_id: int, purchased_items: list, payment_method: str, transaction_code: str):
+ user = User.objects.get(id=user_id)
+ total = 0
+ for i in purchased_items:
+ total += i["price"]*i["amount"]
+ pdf_data = pdf.generar_recibo(
+ user.get_full_name(),
+ total,
+ purchased_items,
+ payment_method,
+ transaction_code,
+ )
+
+ email = EmailMessage(
+ subject="Tu recibo de compra",
+ body = "Hola, adjunto encontrarás el recibo de tu reciente transacción",
+ from_email = settings.DEFAULT_FROM_EMAIL,
+ to = [user.email]
+ )
+
+ email.attach("recibo.pdf", pdf_data, "application/pdf")
+
+ email.send()
\ No newline at end of file
diff --git a/tienda/templates/tienda/base.html b/tienda/templates/tienda/base.html
index 40ff180..7a8150c 100644
--- a/tienda/templates/tienda/base.html
+++ b/tienda/templates/tienda/base.html
@@ -6,7 +6,8 @@
- Document
+
+ Comercialmeria
@@ -78,7 +79,7 @@
{% cache 500 sidebar request.user.username %}
-
+