first commit
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
{% load static %}
|
||||
{% load compress %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="{% static 'css/custom.css' %}">
|
||||
<style>
|
||||
.search-suggestions-container {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.search-suggestions {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-top: none;
|
||||
border-radius: 0 0 4px 4px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.search-suggestions.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.search-suggestion-item {
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.search-suggestion-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.search-suggestion-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.suggestion-name {
|
||||
font-weight: 500;
|
||||
color: #212529;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.suggestion-price {
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.search-highlight {
|
||||
font-weight: 600;
|
||||
color: #0d6efd;
|
||||
}
|
||||
</style>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md header">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="{% url 'home' %}">
|
||||
<img src="{% static 'img/logo.png' %}" alt="Logo" style="height: 40px;">
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent" aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarContent">
|
||||
<span class="navbar-text fw-semibold me-3">Comercialmeria</span>
|
||||
<!-- Barra de búsqueda con sugerencias -->
|
||||
<form class="search-suggestions-container" method="GET" action="{% url 'search' %}" role="search" id="searchForm">
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="search" name="q" id="searchInput" placeholder="Buscar productos..." aria-label="Buscar" autocomplete="off">
|
||||
<button class="btn btn-outline-primary" type="submit">🔍</button>
|
||||
</div>
|
||||
<div class="search-suggestions" id="searchSuggestions"></div>
|
||||
</form>
|
||||
|
||||
<div class="navbar-nav ms-auto d-flex align-items-md-center gap-2 flex-wrap">
|
||||
<a href="{% url 'view_cart' %}" class="nav-link position-relative btn btn-outline-primary btn-sm">
|
||||
🛒 Carrito
|
||||
{% if cart_count > 0 %}
|
||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="font-size: 0.65rem;">
|
||||
{{ cart_count }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<a href="{% url 'mis_productos' %}" class="nav-link btn btn-outline-secondary btn-sm">Panel Vendedor</a>
|
||||
<span class="nav-text d-none d-md-inline text-muted">{{ user.first_name|default:user.username }}</span>
|
||||
<a href="{% url 'logout' %}" class="nav-link btn btn-primary btn-sm">Cerrar Sesión</a>
|
||||
{% else %}
|
||||
<a href="{% url 'login' %}" class="nav-link btn btn-primary btn-sm">Iniciar Sesión</a>
|
||||
<a href="{% url 'register' %}" class="nav-link btn btn-outline-primary btn-sm">Registrarse</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid">
|
||||
<!-- Mensajes -->
|
||||
{% if messages %}
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Contenido-->
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
<!-- Footer-->
|
||||
<div id="footer" class="row pt-2 pb-2 mt-5">
|
||||
<div class="col-md-12 grid">
|
||||
<p class="text-center">Enlace 1</p>
|
||||
<p class="text-center">Enlace 2</p>
|
||||
<p class="text-center">Enlace 3</p>
|
||||
<p class="text-center">Enlace 4</p>
|
||||
<p class="text-center">Enlace 5</p>
|
||||
<p class="text-center">Enlace 6</p>
|
||||
<p class="text-center">Enlace 7</p>
|
||||
<p class="text-center">Enlace 8</p>
|
||||
<p class="text-center">Enlace 9</p>
|
||||
<p class="text-center">Enlace 10</p>
|
||||
<p class="text-center">Enlace 11</p>
|
||||
<p class="text-center">Enlace 12</p>
|
||||
<p class="text-center">Enlace 13</p>
|
||||
<p class="text-center">Enlace 14</p>
|
||||
<p class="text-center">Enlace 15</p>
|
||||
<p class="text-center">Enlace 16</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Sistema de sugerencias de búsqueda en tiempo real
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const searchSuggestions = document.getElementById('searchSuggestions');
|
||||
const searchForm = document.getElementById('searchForm');
|
||||
let searchTimeout;
|
||||
|
||||
// Escuchar cambios en el input
|
||||
searchInput.addEventListener('input', function() {
|
||||
clearTimeout(searchTimeout);
|
||||
const query = this.value.trim();
|
||||
|
||||
if (query.length < 2) {
|
||||
searchSuggestions.classList.remove('show');
|
||||
return;
|
||||
}
|
||||
|
||||
// Debounce: esperar 300ms después de dejar de escribir
|
||||
searchTimeout = setTimeout(() => {
|
||||
fetchSuggestions(query);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Función para obtener sugerencias del servidor
|
||||
function fetchSuggestions(query) {
|
||||
fetch(`{% url 'search_suggestions' %}?q=${encodeURIComponent(query)}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
displaySuggestions(data.suggestions, query);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching suggestions:', error);
|
||||
searchSuggestions.classList.remove('show');
|
||||
});
|
||||
}
|
||||
|
||||
// Función para mostrar las sugerencias
|
||||
function displaySuggestions(suggestions, query) {
|
||||
if (suggestions.length === 0) {
|
||||
searchSuggestions.innerHTML = '<div class="search-suggestion-item text-muted">No se encontraron productos</div>';
|
||||
searchSuggestions.classList.add('show');
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '';
|
||||
suggestions.forEach(suggestion => {
|
||||
// Resaltar la coincidencia en el nombre
|
||||
const highlightedName = highlightMatch(suggestion.name, query);
|
||||
const priceWithVAT = (suggestion.price * 1.21).toFixed(2);
|
||||
html += `
|
||||
<a href="/tienda/producto/${suggestion.id}" class="search-suggestion-item text-decoration-none">
|
||||
<span class="suggestion-name">${highlightedName}</span>
|
||||
<span class="suggestion-price">€${priceWithVAT}</span>
|
||||
</a>
|
||||
`;
|
||||
});
|
||||
|
||||
searchSuggestions.innerHTML = html;
|
||||
searchSuggestions.classList.add('show');
|
||||
}
|
||||
|
||||
// Función para resaltar el texto que coincide
|
||||
function highlightMatch(text, query) {
|
||||
const regex = new RegExp(`(${query})`, 'gi');
|
||||
return text.replace(regex, '<span class="search-highlight">$1</span>');
|
||||
}
|
||||
|
||||
// Cerrar sugerencias cuando se hace clic fuera
|
||||
document.addEventListener('click', function(event) {
|
||||
if (!searchForm.contains(event.target)) {
|
||||
searchSuggestions.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Cerrar sugerencias al enviar el formulario
|
||||
searchForm.addEventListener('submit', function() {
|
||||
searchSuggestions.classList.remove('show');
|
||||
});
|
||||
|
||||
// Mostrar sugerencias al hacer clic en el input (si hay texto)
|
||||
searchInput.addEventListener('focus', function() {
|
||||
if (this.value.trim().length >= 2 && searchSuggestions.innerHTML) {
|
||||
searchSuggestions.classList.add('show');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,109 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
{% load vat_filters %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h2>Carrito de Compras</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if cart_items %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Producto</th>
|
||||
<th>Precio (sin IVA)</th>
|
||||
<th>Cantidad</th>
|
||||
<th>Subtotal (con IVA)</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in cart_items %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
{% if item.product.primary_image %}
|
||||
<img src="/media/{{ item.product.primary_image.image }}" alt="{{ item.product.name }}" style="width: 50px; height: 50px; object-fit: cover;" class="me-3">
|
||||
{% endif %}
|
||||
<a href="{% url 'producto' item.product.id %}">{{ item.product.name }}</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ item.product.price|format_price }} €</td>
|
||||
<td>
|
||||
<form method="post" action="{% url 'update_cart_item' item.id %}" class="d-flex align-items-center" style="max-width: 150px;">
|
||||
{% csrf_token %}
|
||||
<input type="number" name="quantity" value="{{ item.quantity }}" min="1" class="form-control form-control-sm me-2" style="width: 70px;">
|
||||
<button type="submit" class="btn btn-sm btn-primary">Actualizar</button>
|
||||
</form>
|
||||
</td>
|
||||
<td class="price">{{ item.get_subtotal_with_vat|format_price }} €</td>
|
||||
<td>
|
||||
<form method="post" action="{% url 'remove_from_cart' item.id %}" style="display: inline;">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm btn-danger">Eliminar</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Resumen del Pedido</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Subtotal ({{ cart.get_items_count }} producto{{ cart.get_items_count|pluralize }})</span>
|
||||
<span class="price">{{ cart.get_total|format_price }} €</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>IVA (21%)</span>
|
||||
<span class="price text-success">{{ cart.get_vat_amount|format_price }} €</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<span>Envío</span>
|
||||
<span>Gratis</span>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<strong>Total (con IVA)</strong>
|
||||
<strong class="price">{{ cart.get_total_with_vat|format_price }} €</strong>
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{% url 'checkout' %}" class="btn btn-primary btn-lg">Proceder al Pago</a>
|
||||
<a href="{% url 'index' %}" class="btn btn-outline-secondary">Continuar Comprando</a>
|
||||
<form method="post" action="{% url 'clear_cart' %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-outline-danger w-100">Vaciar Carrito</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-5">
|
||||
<h4>Tu carrito está vacío</h4>
|
||||
<p class="text-muted">¡Agrega algunos productos para empezar!</p>
|
||||
<a href="{% url 'index' %}" class="btn btn-primary mt-3">Ver Productos</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,172 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
{% load vat_filters %}
|
||||
|
||||
{% block head %}
|
||||
<script src="https://js.stripe.com/v3/"></script>
|
||||
<script src="{% static 'js/checkout.js' %}"></script>
|
||||
<script defer src="https://use.fontawesome.com/releases/v6.4.0/js/all.js"></script>
|
||||
<style>
|
||||
.payment-methods {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.payment-btn {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
padding: 12px 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.payment-section {
|
||||
background-color: #f8f9fa;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.payment-section h3 {
|
||||
margin-bottom: 20px;
|
||||
color: #212529;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-2">
|
||||
<div class="col-md-12">
|
||||
<!-- Token CSRF para requests AJAX -->
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1>Checkout</h1>
|
||||
<a href="{% url 'view_cart' %}" class="btn btn-outline-secondary">← Volver al carrito</a>
|
||||
</div>
|
||||
|
||||
{% if cart_items %}
|
||||
<div class="table-responsive mb-4">
|
||||
<table class="table table-striped align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Producto</th>
|
||||
<th class="text-end">Precio (sin IVA)</th>
|
||||
<th class="text-end">Cantidad</th>
|
||||
<th class="text-end">Subtotal (con IVA)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in cart_items %}
|
||||
<tr>
|
||||
<td>{{ item.product.name }}</td>
|
||||
<td class="text-end">{{ item.product.price|format_price }}€</td>
|
||||
<td class="text-end">{{ item.quantity }}</td>
|
||||
<td class="text-end">{{ item.get_subtotal_with_vat|format_price }}€</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th colspan="2" class="text-end">Subtotal:</th>
|
||||
<th colspan="2" class="text-end">{{ cart.get_total|format_price }}€</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2" class="text-end">IVA (21%):</th>
|
||||
<th colspan="2" class="text-end text-success">+{{ cart.get_vat_amount|format_price }}€</th>
|
||||
</tr>
|
||||
<tr style="background-color: #f8f9fa;">
|
||||
<th colspan="2" class="text-end" style="font-size: 1.1rem;">Total:</th>
|
||||
<th colspan="2" class="text-end" style="font-size: 1.1rem;">{{ cart.get_total_with_vat|format_price }}€</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="payment-section">
|
||||
<h3>Selecciona tu método de pago</h3>
|
||||
<div class="payment-methods">
|
||||
<button
|
||||
id="checkout-button"
|
||||
class="btn btn-primary payment-btn"
|
||||
data-config-url="/tienda/config/"
|
||||
data-session-url="/tienda/create-checkout-session/">
|
||||
💳 Pagar con Stripe
|
||||
</button>
|
||||
|
||||
<button
|
||||
id="paypal-button"
|
||||
class="btn btn-warning payment-btn"
|
||||
data-payment-url="{% url 'create_paypal_payment' %}">
|
||||
🅿️ Pagar con PayPal
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">Tu carrito está vacío.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Manejo del botón de PayPal
|
||||
document.getElementById('paypal-button').addEventListener('click', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const button = this;
|
||||
const originalText = button.innerHTML;
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Procesando...';
|
||||
|
||||
try {
|
||||
// Obtener CSRF token de múltiples formas
|
||||
let csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value;
|
||||
if (!csrfToken) {
|
||||
csrfToken = document.querySelector('input[name="csrfmiddlewaretoken"]')?.value;
|
||||
}
|
||||
if (!csrfToken) {
|
||||
csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken='))?.split('=')[1];
|
||||
}
|
||||
|
||||
console.log('CSRF Token encontrado:', csrfToken ? 'Sí' : 'No');
|
||||
|
||||
const response = await fetch(button.dataset.paymentUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken || '',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Response status:', response.status);
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Response data:', data);
|
||||
|
||||
if (response.ok && data.redirect) {
|
||||
// Redirigir a PayPal
|
||||
console.log('Redirigiendo a:', data.redirect);
|
||||
window.location.href = data.redirect;
|
||||
} else if (data.error) {
|
||||
console.error('Error en respuesta:', data.error);
|
||||
alert('Error: ' + data.error);
|
||||
button.disabled = false;
|
||||
button.innerHTML = originalText;
|
||||
} else {
|
||||
console.error('Respuesta inesperada:', data);
|
||||
alert('Error inesperado al procesar el pago');
|
||||
button.disabled = false;
|
||||
button.innerHTML = originalText;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error en fetch:', error);
|
||||
alert('Error al procesar el pago con PayPal: ' + error.message);
|
||||
button.disabled = false;
|
||||
button.innerHTML = originalText;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,14 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12 text-center">
|
||||
<div class="alert alert-warning p-5">
|
||||
<h2 class="mb-3">Pago cancelado</h2>
|
||||
<p class="mb-4">No se realizó ningún cargo. Puedes intentarlo de nuevo.</p>
|
||||
<a href="{% url 'checkout' %}" class="btn btn-primary">Volver al checkout</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,14 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12 text-center">
|
||||
<div class="alert alert-success p-5">
|
||||
<h2 class="mb-3">¡Pago completado!</h2>
|
||||
<p class="mb-4">Tu pedido ha sido procesado correctamente.</p>
|
||||
<a href="{% url 'index' %}" class="btn btn-primary">Volver a la tienda</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,92 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4 mb-5">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Crear Nuevo Producto</h2>
|
||||
<a href="{% url 'mis_productos' %}" class="btn btn-outline-secondary">← Volver a Mis Productos</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Nombre del producto -->
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nombre del Producto <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="name" name="name" required maxlength="200"
|
||||
placeholder="Ej: iPhone 15 Pro Max">
|
||||
</div>
|
||||
|
||||
<!-- Descripción breve -->
|
||||
<div class="mb-3">
|
||||
<label for="briefdesc" class="form-label">Descripción Breve</label>
|
||||
<input type="text" class="form-control" id="briefdesc" name="briefdesc" maxlength="250"
|
||||
placeholder="Una descripción corta para mostrar en las tarjetas de producto">
|
||||
<div class="form-text">Opcional. Se mostrará en las vistas de listado de productos.</div>
|
||||
</div>
|
||||
|
||||
<!-- Descripción completa -->
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Descripción Completa <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="description" name="description" rows="5" required
|
||||
placeholder="Describe tu producto en detalle..."></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Precio -->
|
||||
<div class="mb-3">
|
||||
<label for="price" class="form-label">Precio <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">€</span>
|
||||
<input type="number" class="form-control" id="price" name="price" required
|
||||
min="0" step="0.01" placeholder="0.00">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Categoría -->
|
||||
<div class="mb-3">
|
||||
<label for="category" class="form-label">Categoría <span class="text-danger">*</span></label>
|
||||
<select class="form-select" id="category" name="category" required>
|
||||
<option value="" selected disabled>Selecciona una categoría</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}">{{ category.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Imagen principal -->
|
||||
<div class="mb-3">
|
||||
<label for="primary_image" class="form-label">Imagen Principal</label>
|
||||
<input type="file" class="form-control" id="primary_image" name="primary_image"
|
||||
accept="image/*">
|
||||
<div class="form-text">Opcional. Esta será la imagen destacada del producto.</div>
|
||||
</div>
|
||||
|
||||
<!-- Imágenes secundarias -->
|
||||
<div class="mb-4">
|
||||
<label for="secondary_images" class="form-label">Imágenes Secundarias</label>
|
||||
<input type="file" class="form-control" id="secondary_images" name="secondary_images"
|
||||
accept="image/*" multiple>
|
||||
<div class="form-text">Opcional. Puedes seleccionar múltiples imágenes adicionales.</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones -->
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="{% url 'mis_productos' %}" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary">Crear Producto</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<small class="text-muted">
|
||||
<span class="text-danger">*</span> Campos obligatorios
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,73 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h2>Mis Direcciones de Entrega</h2>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'portal_usuario' %}">Portal de Usuario</a></li>
|
||||
<li class="breadcrumb-item active">Direcciones</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menú de navegación del portal -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'portal_usuario' %}" class="btn btn-outline-primary">Inicio</a>
|
||||
<a href="{% url 'editar_perfil' %}" class="btn btn-outline-primary">Mi Perfil</a>
|
||||
<a href="{% url 'direcciones_usuario' %}" class="btn btn-primary">Direcciones</a>
|
||||
<a href="{% url 'mensajes_comprador' %}" class="btn btn-outline-primary">Mensajes</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<a href="{% url 'crear_direccion' %}" class="btn btn-success mb-3">➕ Nueva Dirección</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lista de direcciones -->
|
||||
<div class="row">
|
||||
{% if direcciones %}
|
||||
{% for direccion in direcciones %}
|
||||
<div class="col-md-6 col-lg-4 mb-3">
|
||||
<div class="card {% if direccion.is_default %}border-primary{% endif %}">
|
||||
<div class="card-body">
|
||||
{% if direccion.is_default %}
|
||||
<span class="badge bg-primary mb-2">Predeterminada</span>
|
||||
{% endif %}
|
||||
<h5 class="card-title">{{ direccion.full_name }}</h5>
|
||||
<p class="card-text">
|
||||
{{ direccion.address_line_1 }}<br>
|
||||
{% if direccion.address_line_2 %}{{ direccion.address_line_2 }}<br>{% endif %}
|
||||
{{ direccion.postal_code }}, {{ direccion.city }}<br>
|
||||
{{ direccion.country }}<br>
|
||||
📞 {{ direccion.phone }}
|
||||
</p>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="{% url 'editar_direccion' direccion.id %}" class="btn btn-outline-primary">Editar</a>
|
||||
<form method="POST" action="{% url 'eliminar_direccion' direccion.id %}" style="display: inline;" onsubmit="return confirm('¿Estás seguro de eliminar esta dirección?');">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-outline-danger">Eliminar</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<p class="mb-0">No tienes direcciones guardadas. <a href="{% url 'crear_direccion' %}">Crea tu primera dirección</a></p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,70 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h2>{% if direccion %}Editar Dirección{% else %}Nueva Dirección{% endif %}</h2>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'portal_usuario' %}">Portal de Usuario</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'direcciones_usuario' %}">Direcciones</a></li>
|
||||
<li class="breadcrumb-item active">{% if direccion %}Editar{% else %}Nueva{% endif %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-8 col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="full_name" class="form-label">Nombre Completo *</label>
|
||||
<input type="text" class="form-control" id="full_name" name="full_name" value="{{ direccion.full_name|default:'' }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="address_line_1" class="form-label">Dirección *</label>
|
||||
<input type="text" class="form-control" id="address_line_1" name="address_line_1" value="{{ direccion.address_line_1|default:'' }}" placeholder="Calle, número, piso, puerta" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="address_line_2" class="form-label">Dirección (línea 2)</label>
|
||||
<input type="text" class="form-control" id="address_line_2" name="address_line_2" value="{{ direccion.address_line_2|default:'' }}" placeholder="Edificio, bloque, etc. (opcional)">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="city" class="form-label">Ciudad *</label>
|
||||
<input type="text" class="form-control" id="city" name="city" value="{{ direccion.city|default:'' }}" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="postal_code" class="form-label">Código Postal *</label>
|
||||
<input type="text" class="form-control" id="postal_code" name="postal_code" value="{{ direccion.postal_code|default:'' }}" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="country" class="form-label">País *</label>
|
||||
<input type="text" class="form-control" id="country" name="country" value="{{ direccion.country|default:'España' }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">Teléfono *</label>
|
||||
<input type="tel" class="form-control" id="phone" name="phone" value="{{ direccion.phone|default:'' }}" placeholder="+34 600 000 000" required>
|
||||
</div>
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="is_default" name="is_default" {% if direccion.is_default %}checked{% endif %}>
|
||||
<label class="form-check-label" for="is_default">
|
||||
Establecer como dirección predeterminada
|
||||
</label>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">{% if direccion %}Actualizar{% else %}Crear{% endif %} Dirección</button>
|
||||
<a href="{% url 'direcciones_usuario' %}" class="btn btn-secondary">Cancelar</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,91 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h2>Mi Perfil</h2>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'portal_usuario' %}">Portal de Usuario</a></li>
|
||||
<li class="breadcrumb-item active">Mi Perfil</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menú de navegación del portal -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'portal_usuario' %}" class="btn btn-outline-primary">Inicio</a>
|
||||
<a href="{% url 'editar_perfil' %}" class="btn btn-primary">Mi Perfil</a>
|
||||
<a href="{% url 'direcciones_usuario' %}" class="btn btn-outline-primary">Direcciones</a>
|
||||
<a href="{% url 'mensajes_comprador' %}" class="btn btn-outline-primary">Mensajes</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<!-- Formulario de edición de perfil -->
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Información Personal</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="first_name" class="form-label">Nombre</label>
|
||||
<input type="text" class="form-control" id="first_name" name="first_name" value="{{ user.first_name }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="last_name" class="form-label">Apellidos</label>
|
||||
<input type="text" class="form-control" id="last_name" name="last_name" value="{{ user.last_name }}">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label">Correo Electrónico</label>
|
||||
<input type="email" class="form-control" id="email" name="email" value="{{ user.email }}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label">Nombre de Usuario</label>
|
||||
<input type="text" class="form-control" id="username" value="{{ user.username }}" disabled>
|
||||
<small class="text-muted">El nombre de usuario no se puede cambiar</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Formulario de cambio de contraseña -->
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Cambiar Contraseña</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{% url 'cambiar_contrasena' %}">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="current_password" class="form-label">Contraseña Actual</label>
|
||||
<input type="password" class="form-control" id="current_password" name="current_password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="new_password" class="form-label">Nueva Contraseña</label>
|
||||
<input type="password" class="form-control" id="new_password" name="new_password" required>
|
||||
<small class="text-muted">Mínimo 8 caracteres</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="confirm_password" class="form-label">Confirmar Nueva Contraseña</label>
|
||||
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-warning">Cambiar Contraseña</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,92 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4 mb-5">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Editar Producto</h2>
|
||||
<a href="{% url 'mis_productos' %}" class="btn btn-outline-secondary">← Volver a Mis Productos</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<form method="POST" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Nombre del producto -->
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Nombre del Producto <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="name" name="name" required maxlength="200"
|
||||
value="{{ producto.name }}" placeholder="Ej: iPhone 15 Pro Max">
|
||||
</div>
|
||||
|
||||
<!-- Descripción breve -->
|
||||
<div class="mb-3">
|
||||
<label for="briefdesc" class="form-label">Descripción Breve</label>
|
||||
<input type="text" class="form-control" id="briefdesc" name="briefdesc" maxlength="250"
|
||||
value="{{ producto.briefdesc }}" placeholder="Una descripción corta para mostrar en las tarjetas de producto">
|
||||
<div class="form-text">Opcional. Se mostrará en las vistas de listado de productos.</div>
|
||||
</div>
|
||||
|
||||
<!-- Descripción completa -->
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label">Descripción Completa <span class="text-danger">*</span></label>
|
||||
<textarea class="form-control" id="description" name="description" rows="5" required
|
||||
placeholder="Describe tu producto en detalle...">{{ producto.description }}</textarea>
|
||||
</div>
|
||||
|
||||
<!-- Precio -->
|
||||
<div class="mb-3">
|
||||
<label for="price" class="form-label">Precio <span class="text-danger">*</span></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">€</span>
|
||||
<input type="number" class="form-control" id="price" name="price" required
|
||||
min="0" step="0.01" value="{{ producto.price }}" placeholder="0.00">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Categoría -->
|
||||
<div class="mb-3">
|
||||
<label for="category" class="form-label">Categoría <span class="text-danger">*</span></label>
|
||||
<select class="form-select" id="category" name="category" required>
|
||||
<option value="" disabled>Selecciona una categoría</option>
|
||||
{% for category in categories %}
|
||||
<option value="{{ category.id }}" {% if producto.category.id == category.id %}selected{% endif %}>{{ category.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Imagen principal -->
|
||||
<div class="mb-3">
|
||||
<label for="primary_image" class="form-label">Imagen Principal</label>
|
||||
<input type="file" class="form-control" id="primary_image" name="primary_image"
|
||||
accept="image/*">
|
||||
<div class="form-text">Opcional. Si subes una nueva, reemplazará la actual.</div>
|
||||
</div>
|
||||
|
||||
<!-- Imágenes secundarias -->
|
||||
<div class="mb-4">
|
||||
<label for="secondary_images" class="form-label">Imágenes Secundarias</label>
|
||||
<input type="file" class="form-control" id="secondary_images" name="secondary_images"
|
||||
accept="image/*" multiple>
|
||||
<div class="form-text">Opcional. Si subes nuevas, se reemplazarán las actuales.</div>
|
||||
</div>
|
||||
|
||||
<!-- Botones -->
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="{% url 'mis_productos' %}" class="btn btn-secondary">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary">Guardar Cambios</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<small class="text-muted">
|
||||
<span class="text-danger">*</span> Campos obligatorios
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,235 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
{% load vat_filters %}
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
.hero-section {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 80px 20px;
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.hero-section h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hero-section p {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 30px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hero-buttons .btn {
|
||||
padding: 12px 30px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.featured-section {
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.featured-section h2 {
|
||||
margin-bottom: 40px;
|
||||
font-size: 2rem;
|
||||
text-align: center;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.categories-section {
|
||||
background-color: #f8f9fa;
|
||||
padding: 40px 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.categories-section h2 {
|
||||
margin-bottom: 30px;
|
||||
font-size: 1.8rem;
|
||||
text-align: center;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.category-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.category-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.category-card h3 {
|
||||
color: #667eea;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.category-card a {
|
||||
display: inline-block;
|
||||
margin-top: 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.stats-section {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 1.1rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Hero Section -->
|
||||
<div class="hero-section">
|
||||
<h1>Bienvenido a Comercialmeria</h1>
|
||||
<p>De almerienses, para Almerienses</p>
|
||||
<div class="hero-buttons">
|
||||
<a href="{% url 'productos' %}" class="btn btn-light btn-lg">
|
||||
🛍️ Explorar Productos
|
||||
</a>
|
||||
<a href="{% url 'register' %}" class="btn btn-outline-light btn-lg">
|
||||
📝 Registrarse
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Estadísticas -->
|
||||
<div class="row stats-section mb-5">
|
||||
<div class="col-md-6 stat-item">
|
||||
<div class="stat-number">{{ total_products }}</div>
|
||||
<div class="stat-label">Producto{{ total_products|pluralize }}</div>
|
||||
</div>
|
||||
<div class="col-md-6 stat-item">
|
||||
<div class="stat-number">{{ total_sellers }}</div>
|
||||
<div class="stat-label">Usuario{{ total_sellers|pluralize }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Productos Destacados -->
|
||||
{% if featured_products %}
|
||||
<div class="featured-section">
|
||||
<h2>✨ Productos Destacados</h2>
|
||||
<div class="row g-4">
|
||||
{% for product in featured_products %}
|
||||
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card product-card h-100">
|
||||
{% if product.primary_image %}
|
||||
<img src="{{ product.primary_image.image.url }}" class="card-img-top" alt="{{ product.name }}" style="height: 250px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="card-img-top bg-light d-flex align-items-center justify-content-center" style="height: 250px;">
|
||||
<span class="text-muted">Sin imagen</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title">{{ product.name }}</h5>
|
||||
|
||||
{% if product.briefdesc %}
|
||||
<p class="card-text text-muted small">{{ product.briefdesc|truncatewords:10 }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-auto">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<div class="price-info">
|
||||
<div class="small text-muted">Sin IVA: €{{ product.price|format_price }}</div>
|
||||
<span class="h5 mb-0 text-primary">IVA incl: €{{ product.get_price_with_vat|format_price }}</span>
|
||||
</div>
|
||||
{% if product.creator %}
|
||||
<small class="text-muted">{{ product.creator.first_name|default:product.creator.username }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="gap-2 d-flex">
|
||||
<a href="{% url 'producto' product.id %}" class="btn btn-primary btn-sm flex-grow-1">
|
||||
Ver detalles
|
||||
</a>
|
||||
<a href="{% url 'add_to_cart' product.id %}" class="btn btn-outline-primary btn-sm">
|
||||
🛒
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Categorías -->
|
||||
{% if categories %}
|
||||
<div class="categories-section">
|
||||
<h2>📂 Categorías Populares</h2>
|
||||
<div class="row g-3">
|
||||
{% for category in categories %}
|
||||
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="category-card">
|
||||
<h3>{{ category.name }}</h3>
|
||||
<p class="text-muted">Explora esta categoría</p>
|
||||
<a href="{% url 'categoria' category.id %}" class="btn btn-sm btn-outline-primary">
|
||||
Ver más →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Call to Action -->
|
||||
<div class="row mt-5 mb-5">
|
||||
<div class="col-md-12">
|
||||
<div style="background-color: #f8f9fa; padding: 40px; border-radius: 8px; text-align: center;">
|
||||
<h2>¿Quieres vender con nosotros?</h2>
|
||||
<p style="font-size: 1.1rem; color: #666; margin-bottom: 20px;">
|
||||
Únete a nuestra comunidad de vendedores y comienza a ganar dinero hoy
|
||||
</p>
|
||||
<a href="{% url 'register' %}" class="btn btn-primary btn-lg">
|
||||
Crear Cuenta
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,42 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load vat_filters %}
|
||||
{% block content %}
|
||||
<div class="row mt-2">
|
||||
<div class="col-md-2 d-none d-lg-block">
|
||||
<h5 class="categorias-titulo">Categorias</h5>
|
||||
<ul class="list-group categorias-lista">
|
||||
{% if categories %}
|
||||
{% for category in categories %}
|
||||
<li class="list-group-item categoria-item">
|
||||
<a href="{% url 'categoria' category.id %}">{{ category.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-12 col-md-10 grid">
|
||||
{% if products %}
|
||||
{% for producto in products %}
|
||||
<div class="card card-producto mt-5" style="width: 18rem;">
|
||||
<img src="/media/{{ producto.primary_image.image}}" class="card-img-top" alt="{{ producto.name }}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ producto.name }}</h5>
|
||||
<div class="card-text price-info mb-3">
|
||||
<small class="text-muted d-block">Sin IVA: €{{ producto.price|format_price }}</small>
|
||||
<p class="price mb-0">IVA incl: €{{ producto.get_price_with_vat|format_price }}</p>
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{% url 'producto' producto.id %}" class="btn btn-primary">Ver más</a>
|
||||
<form method="post" action="{% url 'add_to_cart' producto.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="quantity" value="1">
|
||||
<button type="submit" class="btn btn-outline-primary w-100">🛒 Agregar al Carrito</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,50 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="text-center mb-0">Iniciar Sesión</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="{% url 'login' %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="loginEmail" class="form-label">Correo Electrónico</label>
|
||||
<input type="email" class="form-control" id="loginEmail" name="email" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="loginPassword" class="form-label">Contraseña</label>
|
||||
<input type="password" class="form-control" id="loginPassword" name="password" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="rememberMe" name="remember">
|
||||
<label class="form-check-label" for="rememberMe">
|
||||
Recordarme
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary">Iniciar Sesión</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-center">
|
||||
<a href="#" class="text-decoration-none">¿Olvidaste tu contraseña?</a>
|
||||
</div>
|
||||
|
||||
<hr class="my-3">
|
||||
|
||||
<div class="text-center">
|
||||
<p class="mb-0">¿No tienes cuenta? <a href="{% url 'register' %}">Regístrate aquí</a></p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,86 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h2>Mis Mensajes</h2>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'portal_usuario' %}">Portal de Usuario</a></li>
|
||||
<li class="breadcrumb-item active">Mensajes</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menú de navegación del portal -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'portal_usuario' %}" class="btn btn-outline-primary">Inicio</a>
|
||||
<a href="{% url 'editar_perfil' %}" class="btn btn-outline-primary">Mi Perfil</a>
|
||||
<a href="{% url 'direcciones_usuario' %}" class="btn btn-outline-primary">Direcciones</a>
|
||||
<a href="{% url 'mensajes_comprador' %}" class="btn btn-primary">Mensajes</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
{% if order_items %}
|
||||
{% for order_item in order_items %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h5 class="mb-0">Pedido #{{ order_item.order.id }} - {{ order_item.product_name }}</h5>
|
||||
<small class="text-muted">
|
||||
Vendedor: {{ order_item.seller.first_name|default:order_item.seller.username }}
|
||||
| Estado: <span class="badge bg-info">{{ order_item.get_status_display }}</span>
|
||||
</small>
|
||||
</div>
|
||||
<span class="text-muted">{{ order_item.created_at|date:"d/m/Y" }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if order_item.messages.all %}
|
||||
<h6 class="mb-3">Mensajes:</h6>
|
||||
<div class="list-group mb-3">
|
||||
{% for message in order_item.messages.all %}
|
||||
<div class="list-group-item {% if message.sender == user %}bg-light{% endif %}">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">
|
||||
{% if message.sender == user %}
|
||||
Tú
|
||||
{% else %}
|
||||
{{ message.sender.first_name|default:message.sender.username }} (Vendedor)
|
||||
{% endif %}
|
||||
</h6>
|
||||
<small class="text-muted">{{ message.created_at|date:"d/m/Y H:i" }}</small>
|
||||
</div>
|
||||
<p class="mb-0">{{ message.message }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted">No hay mensajes para este pedido.</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p class="mb-1"><strong>Producto:</strong> {{ order_item.product_name }}</p>
|
||||
<p class="mb-1"><strong>Cantidad:</strong> {{ order_item.quantity }}</p>
|
||||
<p class="mb-1"><strong>Precio Total:</strong> {{ order_item.total_price }}€</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
<p class="mb-0">No tienes mensajes de vendedores. Los mensajes relacionados con tus pedidos aparecerán aquí.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,82 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
{% load vat_filters %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h1>Mis Productos</h1>
|
||||
<p class="text-muted mb-0">Productos que has creado: <strong>{{ total_productos }}</strong></p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'pedidos_vendedor' %}" class="btn btn-outline-primary">📦 Pedidos Asignados</a>
|
||||
<a href="{% url 'crear_producto' %}" class="btn btn-primary">
|
||||
➕ Crear Nuevo Producto
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if productos %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 90px;">Imagen</th>
|
||||
<th>Producto</th>
|
||||
<th>Categoría</th>
|
||||
<th class="text-end">Precio (sin IVA)</th>
|
||||
<th class="text-end">Precio (con IVA)</th>
|
||||
<th class="text-end">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for producto in productos %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if producto.primary_image %}
|
||||
<img src="{{ producto.primary_image.image.url }}" alt="{{ producto.name }}" class="rounded" style="width: 70px; height: 70px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="bg-secondary d-flex align-items-center justify-content-center rounded" style="width: 70px; height: 70px;">
|
||||
<span class="text-white small">Sin</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-semibold">{{ producto.name }}</div>
|
||||
<div class="text-muted small">{{ producto.briefdesc|truncatewords:12 }}</div>
|
||||
</td>
|
||||
<td>{{ producto.category.name }}</td>
|
||||
<td class="text-end">{{ producto.price|format_price }}€</td>
|
||||
<td class="text-end text-success"><strong>{{ producto.get_price_with_vat|format_price }}€</strong></td>
|
||||
<td class="text-end">
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="{% url 'editar_producto' producto.id %}" class="btn btn-outline-primary btn-sm">Editar</a>
|
||||
<form method="POST" action="{% url 'borrar_producto' producto.id %}" onsubmit="return confirm('¿Seguro que quieres borrar este producto?');">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm">Borrar</button>
|
||||
</form>
|
||||
<a href="{% url 'producto' producto.id %}" class="btn btn-primary btn-sm">Ver</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row mt-5">
|
||||
<div class="col-12 text-center">
|
||||
<div class="alert alert-info d-inline-block p-5">
|
||||
<h3>No has creado ningún producto todavía</h3>
|
||||
<p class="mb-3">Empieza a crear productos para verlos aquí.</p>
|
||||
<a href="{% url 'crear_producto' %}" class="btn btn-primary">Crear Mi Primer Producto</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,101 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h1>Pedidos Asignados</h1>
|
||||
<p class="text-muted mb-0">Pedidos pendientes para enviar: <strong>{{ total_pedidos }}</strong></p>
|
||||
</div>
|
||||
<a href="{% url 'mis_productos' %}" class="btn btn-outline-secondary">↩️ Volver a Mis Productos</a>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if pedidos %}
|
||||
<div class="row">
|
||||
{% for item in pedidos %}
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>Pedido #{{ item.order.id }}</strong> - {{ item.product_name }}
|
||||
</div>
|
||||
<span class="badge
|
||||
{% if item.status == 'pending' %}bg-warning
|
||||
{% elif item.status == 'processing' %}bg-info
|
||||
{% elif item.status == 'shipped' %}bg-success
|
||||
{% endif %}">
|
||||
{{ item.get_status_display }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h6>Información del Pedido</h6>
|
||||
<ul class="list-unstyled">
|
||||
<li><strong>Comprador:</strong> {{ item.order.buyer.username|default:"Invitado" }}</li>
|
||||
<li><strong>Email:</strong> {{ item.order.buyer.email|default:"N/A" }}</li>
|
||||
<li><strong>Cantidad:</strong> {{ item.quantity }}</li>
|
||||
<li><strong>Precio total:</strong> {{ item.total_price }}€</li>
|
||||
<li><strong>Fecha:</strong> {{ item.created_at|date:"d/m/Y H:i" }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Cambiar Estado</h6>
|
||||
<form method="POST" action="{% url 'cambiar_estado_pedido' item.id %}" class="mb-3">
|
||||
{% csrf_token %}
|
||||
<div class="input-group">
|
||||
<select name="estado" class="form-select" required>
|
||||
<option value="">Seleccionar estado...</option>
|
||||
<option value="pending" {% if item.status == 'pending' %}selected{% endif %}>Pendiente</option>
|
||||
<option value="processing" {% if item.status == 'processing' %}selected{% endif %}>En preparación</option>
|
||||
<option value="shipped" {% if item.status == 'shipped' %}selected{% endif %}>Enviado</option>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary">Actualizar</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h6>Enviar Mensaje al Comprador</h6>
|
||||
<form method="POST" action="{% url 'enviar_mensaje_pedido' item.id %}">
|
||||
{% csrf_token %}
|
||||
<div class="mb-2">
|
||||
<textarea name="mensaje" class="form-control" rows="3" placeholder="Escribe tu mensaje aquí..." required></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success btn-sm">📧 Enviar Mensaje</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if item.messages.all %}
|
||||
<hr>
|
||||
<h6>Historial de Mensajes</h6>
|
||||
<div class="messages-container" style="max-height: 200px; overflow-y: auto;">
|
||||
{% for msg in item.messages.all %}
|
||||
<div class="alert alert-light py-2 mb-2">
|
||||
<small class="text-muted">{{ msg.created_at|date:"d/m/Y H:i" }} - <strong>{{ msg.sender.username }}</strong></small>
|
||||
<p class="mb-0">{{ msg.message }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row mt-5">
|
||||
<div class="col-12 text-center">
|
||||
<div class="alert alert-info d-inline-block p-5">
|
||||
<h3>No tienes pedidos asignados</h3>
|
||||
<p class="mb-3">Cuando alguien compre tus productos, aparecerán aquí.</p>
|
||||
<a href="{% url 'mis_productos' %}" class="btn btn-primary">Ir a Mis Productos</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,115 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h2>Portal de Usuario</h2>
|
||||
<p class="text-muted">Bienvenido, {{ user.first_name|default:user.username }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Menú de navegación del portal -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'portal_usuario' %}" class="btn btn-primary">Inicio</a>
|
||||
<a href="{% url 'editar_perfil' %}" class="btn btn-outline-primary">Mi Perfil</a>
|
||||
<a href="{% url 'direcciones_usuario' %}" class="btn btn-outline-primary">Direcciones</a>
|
||||
<a href="{% url 'mensajes_comprador' %}" class="btn btn-outline-primary">Mensajes</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tarjetas de estadísticas -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">📦 Mis Pedidos</h5>
|
||||
<p class="display-4">{{ total_orders }}</p>
|
||||
<p class="text-muted">pedidos realizados</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">📍 Direcciones</h5>
|
||||
<p class="display-4">{{ total_addresses }}</p>
|
||||
<p class="text-muted">direcciones guardadas</p>
|
||||
<a href="{% url 'direcciones_usuario' %}" class="btn btn-sm btn-primary">Gestionar</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">✉️ Mensajes</h5>
|
||||
<p class="display-4">{{ recent_messages|length }}</p>
|
||||
<p class="text-muted">mensajes recientes</p>
|
||||
<a href="{% url 'mensajes_comprador' %}" class="btn btn-sm btn-primary">Ver todos</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pedidos recientes -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h4>Pedidos Recientes</h4>
|
||||
{% if recent_orders %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pedido #</th>
|
||||
<th>Fecha</th>
|
||||
<th>Total</th>
|
||||
<th>Estado</th>
|
||||
<th>Método de Pago</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in recent_orders %}
|
||||
<tr>
|
||||
<td>{{ order.id }}</td>
|
||||
<td>{{ order.created_at|date:"d/m/Y H:i" }}</td>
|
||||
<td>{{ order.total }}€</td>
|
||||
<td><span class="badge bg-success">{{ order.get_status_display }}</span></td>
|
||||
<td>{{ order.get_payment_method_display }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-muted">No has realizado ningún pedido todavía.</p>
|
||||
<a href="{% url 'index' %}" class="btn btn-primary">Ir a la tienda</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mensajes recientes -->
|
||||
{% if recent_messages %}
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h4>Mensajes Recientes de Vendedores</h4>
|
||||
<div class="list-group">
|
||||
{% for message in recent_messages %}
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h6 class="mb-1">{{ message.sender.first_name|default:message.sender.username }}</h6>
|
||||
<small class="text-muted">{{ message.created_at|date:"d/m/Y H:i" }}</small>
|
||||
</div>
|
||||
<p class="mb-1">{{ message.message|truncatewords:20 }}</p>
|
||||
<small class="text-muted">Pedido #{{ message.order_item.order.id }}</small>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<a href="{% url 'mensajes_comprador' %}" class="btn btn-link">Ver todos los mensajes →</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,58 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load vat_filters %}
|
||||
{% block content %}
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-md-4">
|
||||
<div id="carouselProducto" class="carousel slide">
|
||||
<div class="carousel-inner">
|
||||
<div class="carousel-item active">
|
||||
<img src="/media/{{ product.primary_image.image }}" class="d-block w-100">
|
||||
</div>
|
||||
{% for image in product.secondary_images.all %}
|
||||
<div class="carousel-item">
|
||||
<img src="/media/{{ image.image }}" class="d-block w-100">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#carouselProducto" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Anterior</span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#carouselProducto" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Siguiente</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1"></div>
|
||||
<div class="col-md-5">
|
||||
<h1>{{product.name}}</h1>
|
||||
<div class="price-section mb-4">
|
||||
<div class="small text-muted mb-2">Precio sin IVA (21%):</div>
|
||||
<div class="price text-muted mb-3" style="text-decoration: line-through;">€{{ product.price|format_price }}</div>
|
||||
|
||||
<div class="small text-primary font-weight-bold mb-2">Precio total (IVA incluido):</div>
|
||||
<span class="price" style="font-size: 2rem; color: #28a745;">€{{ product.get_price_with_vat|format_price }}</span>
|
||||
<div class="small text-success mt-2">IVA: €{{ product.get_vat_amount|format_price }}</div>
|
||||
</div>
|
||||
<div id="descripcion">
|
||||
{{ product.briefdesc }}
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'add_to_cart' product.id %}" class="mt-4">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="quantity" class="form-label">Cantidad:</label>
|
||||
<input type="number" name="quantity" id="quantity" value="1" min="1" class="form-control" style="max-width: 100px;">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-lg">🛒 Agregar al Carrito</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-2 pb-2">
|
||||
<div class="col-md-12">
|
||||
{{ product.description }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,57 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6 col-lg-5">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="text-center mb-0">Crear Cuenta</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="{% url 'register' %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="registerName" class="form-label">Nombre Completo</label>
|
||||
<input type="text" class="form-control" id="registerName" name="name" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="registerEmail" class="form-label">Correo Electrónico</label>
|
||||
<input type="email" class="form-control" id="registerEmail" name="email" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="registerPassword" class="form-label">Contraseña</label>
|
||||
<input type="password" class="form-control" id="registerPassword" name="password" required>
|
||||
<div class="form-text">La contraseña debe tener al menos 8 caracteres.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="registerPasswordConfirm" class="form-label">Confirmar Contraseña</label>
|
||||
<input type="password" class="form-control" id="registerPasswordConfirm" name="password_confirm" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3 form-check">
|
||||
<input type="checkbox" class="form-check-input" id="acceptTerms" name="terms" required>
|
||||
<label class="form-check-label" for="acceptTerms">
|
||||
Acepto los <a href="#" target="_blank">términos y condiciones</a>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-primary">Crear Cuenta</button>
|
||||
</div>
|
||||
|
||||
<hr class="my-3">
|
||||
|
||||
<div class="text-center">
|
||||
<p class="mb-0">¿Ya tienes cuenta? <a href="{% url 'login' %}">Inicia sesión aquí</a></p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,104 @@
|
||||
{% extends "tienda/base.html" %}
|
||||
{% load static %}
|
||||
{% load vat_filters %}
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
.search-container {
|
||||
margin: 30px 0;
|
||||
}
|
||||
.search-title {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.product-card {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
.product-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
.no-results {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
.no-results i {
|
||||
font-size: 4rem;
|
||||
color: #ccc;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="search-container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="search-title">
|
||||
{% if query %}
|
||||
<h2>Resultados de búsqueda para: <strong>"{{ query }}"</strong></h2>
|
||||
<p class="text-muted">Se encontraron {{ products.count }} resultado(s)</p>
|
||||
{% else %}
|
||||
<h2>Búsqueda de productos</h2>
|
||||
<p class="text-muted">Ingresa un término de búsqueda para encontrar productos</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if products %}
|
||||
<div class="row g-4">
|
||||
{% for product in products %}
|
||||
<div class="col-12 col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card product-card h-100">
|
||||
{% if product.primary_image %}
|
||||
<img src="{{ product.primary_image.image.url }}" class="card-img-top" alt="{{ product.name }}" style="height: 250px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="card-img-top bg-light d-flex align-items-center justify-content-center" style="height: 250px;">
|
||||
<span class="text-muted">Sin imagen</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title">{{ product.name }}</h5>
|
||||
|
||||
{% if product.briefdesc %}
|
||||
<p class="card-text text-muted small">{{ product.briefdesc|truncatewords:15 }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-auto">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="price-info">
|
||||
<small class="text-muted d-block">€{{ product.price|format_price }}</small>
|
||||
<span class="h5 mb-0 text-primary">€{{ product.get_price_with_vat|format_price }}</span>
|
||||
</div>
|
||||
{% if product.creator %}
|
||||
<small class="text-muted">{{ product.creator.first_name|default:product.creator.username }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mt-3 gap-2 d-flex">
|
||||
<a href="{% url 'producto' product.id %}" class="btn btn-primary btn-sm flex-grow-1">
|
||||
Ver detalles
|
||||
</a>
|
||||
<a href="{% url 'add_to_cart' product.id %}" class="btn btn-outline-primary btn-sm">
|
||||
🛒
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elif query %}
|
||||
<div class="no-results">
|
||||
<h3 class="text-muted">No se encontraron productos</h3>
|
||||
<p class="text-muted">Intenta con otros términos de búsqueda o <a href="{% url 'index' %}">vuelve al inicio</a></p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="no-results">
|
||||
<h3 class="text-muted">Realiza una búsqueda</h3>
|
||||
<p class="text-muted">Usa la barra de búsqueda superior para encontrar productos</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user