first commit

This commit is contained in:
2026-02-15 09:23:44 +01:00
commit 5a22d3abae
276 changed files with 231906 additions and 0 deletions
+247
View File
@@ -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>
+109
View File
@@ -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 %}
+172
View File
@@ -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 %}
+73
View File
@@ -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 %}
+235
View File
@@ -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 %}
+42
View File
@@ -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 %}
+50
View File
@@ -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 %}
{% 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 %}
+115
View File
@@ -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 %}
+58
View File
@@ -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 %}
+57
View File
@@ -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 %}
+104
View File
@@ -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 %}