Proceso basico de pedidos

This commit is contained in:
devRaGonSa 2025-04-30 14:54:44 +02:00
parent 065effae3d
commit ab74433b70
14 changed files with 742 additions and 80 deletions

View File

@ -21,6 +21,41 @@ namespace WebVentaCoche.Controllers
_context = context; _context = context;
} }
//GET:/Account/Details/{id}
[HttpGet]
public async Task<IActionResult> Details(string id)
{
if (string.IsNullOrEmpty(id))
return BadRequest();
var user = await _userHelper.GetUserByIdAsync(id);
if (user == null)
return NotFound();
var addresses = await _context.Addresses.Where(a => a.UserId == id).ToListAsync();
var vm = new UserDetailsViewModel
{
Id = user.Id,
Name = user.Name,
Surname = user.Surname,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
UserType = user.UserType,
Addresses = addresses.Select(a => new AddressViewModel
{
Id = a.Id,
Street = a.Street,
City = a.City,
State = a.State,
ZipCode = a.ZipCode,
Country = a.Country
}).ToList()
};
return View(vm);
}
//GET:/Account/Settings //GET:/Account/Settings
[HttpGet] [HttpGet]
public async Task<IActionResult> Settings() public async Task<IActionResult> Settings()
@ -29,9 +64,7 @@ namespace WebVentaCoche.Controllers
var user = await _userHelper.GetUserByIdAsync(userId); var user = await _userHelper.GetUserByIdAsync(userId);
if (user == null) return NotFound(); if (user == null) return NotFound();
var addresses = await _context.Addresses var addresses = await _context.Addresses.Where(a => a.UserId == userId).ToListAsync();
.Where(a => a.UserId == userId)
.ToListAsync();
var vm = new UserDetailsViewModel var vm = new UserDetailsViewModel
{ {

View File

@ -4,6 +4,8 @@ using WebVentaCoche.DataBase;
using WebVentaCoche.Helpers; using WebVentaCoche.Helpers;
using WebVentaCoche.Models; using WebVentaCoche.Models;
using System.Linq; using System.Linq;
using System.Security.Claims;
using WebVentaCoche.Enums;
namespace WebVentaCoche.Controllers namespace WebVentaCoche.Controllers
{ {
@ -16,8 +18,7 @@ namespace WebVentaCoche.Controllers
_context = context; _context = context;
} }
// GET: /Cart/Index //GET:/Cart/Index
// Muestra la lista de productos del carrito y el total.
public IActionResult Index() public IActionResult Index()
{ {
// Diccionario de IDs -> Cantidades desde sesión (o cualquier fuente) // Diccionario de IDs -> Cantidades desde sesión (o cualquier fuente)
@ -45,8 +46,63 @@ namespace WebVentaCoche.Controllers
return View(viewModel); return View(viewModel);
} }
// POST: /Cart/AddProductToCart // POST: /Cart/Purchase
// Añade el producto al carrito y devuelve un JSON con el nuevo contador [HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Purchase()
{
// 1) Obtén el carrito de la sesión
var items = CartSessionHelper.GetCartItems(HttpContext.Session);
if (!items.Any())
{
TempData["Error"] = "El carrito está vacío.";
return RedirectToAction("Index");
}
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier)!;
var addr = await _context.Addresses.Where(a => a.UserId == userId).FirstOrDefaultAsync();
if (addr == null)
{
TempData["Error"] = "No tienes ninguna dirección guardada. Primero añade una dirección en tu perfil.";
return RedirectToAction("Addresses", "Account");
}
var shippingAddress = $"{addr.Street}, {addr.City}, {addr.State}, {addr.ZipCode}, {addr.Country}";
var order = new Order
{
UserId = userId,
OrderDate = DateTime.UtcNow,
Status = OrderStatus.Pending,
ShippingAddress = shippingAddress
};
decimal total = 0m;
foreach (var (productId, quantity) in items)
{
var product = await _context.Products.FindAsync(productId);
if (product == null) continue;
var detail = new OrderDetail
{
ProductId = product.Id,
Quantity = quantity,
UnitPrice = product.Price
};
total += detail.Subtotal;
order.OrderDetails.Add(detail);
}
order.TotalAmount = total;
_context.Orders.Add(order);
await _context.SaveChangesAsync();
CartSessionHelper.ClearCart(HttpContext.Session);
return RedirectToAction("Details", "Order", new { id = order.Id });
}
//POST:/Cart/AddProductToCart
[HttpPost] [HttpPost]
public IActionResult AddProductToCart(int id) public IActionResult AddProductToCart(int id)
{ {
@ -66,8 +122,7 @@ namespace WebVentaCoche.Controllers
return Json(new { success = true, cartCount = count }); return Json(new { success = true, cartCount = count });
} }
// POST: /Cart/Remove //POST:/Cart/Remove
// Elimina un producto según su ID
[HttpPost] [HttpPost]
public IActionResult Remove(int id) public IActionResult Remove(int id)
{ {
@ -75,8 +130,7 @@ namespace WebVentaCoche.Controllers
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
// POST: /Cart/Clear //POST:/Cart/Clear
// Vacía todo el carrito (la sesión)
[HttpPost] [HttpPost]
public IActionResult Clear() public IActionResult Clear()
{ {
@ -84,8 +138,7 @@ namespace WebVentaCoche.Controllers
return RedirectToAction("Index"); return RedirectToAction("Index");
} }
// GET: /Cart/Checkout //GET:/Cart/Checkout
// (Opcional) Muestra un formulario o datos para la compra/pago.
public IActionResult Checkout() public IActionResult Checkout()
{ {
return View(); return View();

View File

@ -1,12 +1,13 @@
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using WebVentaCoche.DataBase; using WebVentaCoche.DataBase;
using WebVentaCoche.Models; using WebVentaCoche.Models;
namespace WebVentaCoche.Controllers namespace WebVentaCoche.Controllers
{ {
[Authorize] // Requiere autenticación para acceder al controlador [Authorize]
public class OrderController : Controller public class OrderController : Controller
{ {
private readonly ApplicationDbContext _context; private readonly ApplicationDbContext _context;
@ -19,28 +20,31 @@ namespace WebVentaCoche.Controllers
//GET:Order/Index //GET:Order/Index
public async Task<IActionResult> Index() public async Task<IActionResult> Index()
{ {
var orders = await _context.Orders var orders = await _context.Orders.Include(o => o.User).ToListAsync();
.Include(o => o.User) // Cargar la relación con el usuario
.ToListAsync();
return View(orders); return View(orders);
} }
//GET:/Order/MyOrders
[Authorize]
public async Task<IActionResult> UserOrders()
{
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier)!;
var myOrders = await _context.Orders.Where(o => o.UserId == userId).Include(o => o.OrderDetails).ThenInclude(d => d.Product).OrderByDescending(o => o.OrderDate).ToListAsync();
//GET:Order/Details/5 return View(myOrders);
}
//GET:Order/Details/{id}
[HttpGet]
public async Task<IActionResult> Details(int? id) public async Task<IActionResult> Details(int? id)
{ {
if (id == null) if (id == null)
{
return NotFound(); return NotFound();
}
var order = await _context.Orders var userId = User.FindFirstValue(ClaimTypes.NameIdentifier)!;
.Include(o => o.User) // Cargar la relación con el usuario
.FirstOrDefaultAsync(o => o.Id == id); var order = await _context.Orders.Include(o => o.OrderDetails).ThenInclude(d => d.Product).FirstOrDefaultAsync(o => o.Id == id && o.UserId == userId);
if (order == null) if (order == null)
{
return NotFound(); return NotFound();
}
return View(order); return View(order);
} }

View File

@ -2,10 +2,10 @@
{ {
public enum OrderStatus public enum OrderStatus
{ {
Pending, // Orden pendiente Pending,
Processing, // Orden en proceso Processing,
Shipped, // Orden enviada Shipped,
Delivered, // Orden entregada Delivered,
Cancelled // Orden cancelada Cancelled
} }
} }

View File

@ -0,0 +1,478 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using WebVentaCoche.DataBase;
#nullable disable
namespace WebVentaCoche.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20250430123732_AddOrderAndDetails")]
partial class AddOrderAndDetails
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(450)");
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(450)");
b.Property<string>("Name")
.HasColumnType("nvarchar(450)");
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("WebVentaCoche.Models.Address", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("City")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Country")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("State")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("nvarchar(50)");
b.Property<string>("Street")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("ZipCode")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("nvarchar(20)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Addresses");
});
modelBuilder.Entity("WebVentaCoche.Models.Order", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<DateTime>("OrderDate")
.HasColumnType("datetime2");
b.Property<DateTime?>("ShippedDate")
.HasColumnType("datetime2");
b.Property<string>("ShippingAddress")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<int>("Status")
.HasColumnType("int");
b.Property<decimal>("TotalAmount")
.HasColumnType("decimal(18,2)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Orders");
});
modelBuilder.Entity("WebVentaCoche.Models.OrderDetail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<int>("OrderId")
.HasColumnType("int");
b.Property<int>("ProductId")
.HasColumnType("int");
b.Property<int>("Quantity")
.HasColumnType("int");
b.Property<decimal>("UnitPrice")
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.HasIndex("OrderId");
b.HasIndex("ProductId");
b.ToTable("OrderDetails");
});
modelBuilder.Entity("WebVentaCoche.Models.Product", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("ImagePath")
.HasColumnType("nvarchar(max)");
b.Property<string>("LongDescription")
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<decimal>("Price")
.HasColumnType("decimal(18,2)");
b.Property<string>("ShortDescription")
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.ToTable("Products");
});
modelBuilder.Entity("WebVentaCoche.Models.User", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<string>("Surname")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("nvarchar(256)");
b.Property<int>("UserType")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("WebVentaCoche.Models.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("WebVentaCoche.Models.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebVentaCoche.Models.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("WebVentaCoche.Models.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("WebVentaCoche.Models.Address", b =>
{
b.HasOne("WebVentaCoche.Models.User", "User")
.WithMany("Addresses")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebVentaCoche.Models.Order", b =>
{
b.HasOne("WebVentaCoche.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("WebVentaCoche.Models.OrderDetail", b =>
{
b.HasOne("WebVentaCoche.Models.Order", "Order")
.WithMany("OrderDetails")
.HasForeignKey("OrderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("WebVentaCoche.Models.Product", "Product")
.WithMany()
.HasForeignKey("ProductId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Order");
b.Navigation("Product");
});
modelBuilder.Entity("WebVentaCoche.Models.Order", b =>
{
b.Navigation("OrderDetails");
});
modelBuilder.Entity("WebVentaCoche.Models.User", b =>
{
b.Navigation("Addresses");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace WebVentaCoche.Migrations
{
/// <inheritdoc />
public partial class AddOrderAndDetails : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@ -9,27 +9,19 @@ namespace WebVentaCoche.Models
{ {
[Key] [Key]
public int Id { get; set; } public int Id { get; set; }
[Required] [Required]
public string UserId { get; set; } public string UserId { get; set; }
[ForeignKey("UserId")] [ForeignKey("UserId")]
public virtual User User { get; set; } public virtual User User { get; set; }
[Required] [Required]
public DateTime OrderDate { get; set; } // Fecha y hora en que se realizó la orden public DateTime OrderDate { get; set; }
public DateTime? ShippedDate { get; set; }
public DateTime? ShippedDate { get; set; } // Fecha de envío (opcional)
[Required] [Required]
public decimal TotalAmount { get; set; } // Monto total de la orden public decimal TotalAmount { get; set; }
[Required] [Required]
public OrderStatus Status { get; set; } // Estado de la orden (Pendiente, Enviado, Completado, Cancelado) public OrderStatus Status { get; set; }
[Required] [Required]
public string ShippingAddress { get; set; } // Dirección de envío del pedido public string ShippingAddress { get; set; }
public virtual List<OrderDetail> OrderDetails { get; set; } = new List<OrderDetail>();
public virtual List<OrderDetail> OrderDetails { get; set; } // Relación con los detalles de la orden
} }
} }

View File

@ -7,23 +7,15 @@ namespace WebVentaCoche.Models
{ {
[Key] [Key]
public int Id { get; set; } public int Id { get; set; }
[Required]
public int OrderId { get; set; } public int OrderId { get; set; }
[ForeignKey("OrderId")] [ForeignKey("OrderId")]
public virtual Order Order { get; set; } // Relación con la orden public virtual Order Order { get; set; }
[Required]
public int ProductId { get; set; } public int ProductId { get; set; }
[ForeignKey("ProductId")] [ForeignKey("ProductId")]
public virtual Product Product { get; set; } // Relación con el producto public virtual Product Product { get; set; }
public int Quantity { get; set; }
[Required] public decimal UnitPrice { get; set; }
public int Quantity { get; set; } // Cantidad del producto en la orden public decimal Subtotal => Quantity * UnitPrice;
[Required]
public decimal UnitPrice { get; set; } // Precio unitario del producto en el momento de la compra
} }
} }

View File

@ -12,7 +12,6 @@ namespace WebVentaCoche.Services
public EmailService(IConfiguration configuration) public EmailService(IConfiguration configuration)
{ {
// Leer configuraciones desde appsettings.json
_apiKey = configuration["SendGrid:ApiKey"]; _apiKey = configuration["SendGrid:ApiKey"];
_fromEmail = configuration["SendGrid:FromEmail"]; _fromEmail = configuration["SendGrid:FromEmail"];
_fromName = configuration["SendGrid:FromName"]; _fromName = configuration["SendGrid:FromName"];
@ -20,23 +19,17 @@ namespace WebVentaCoche.Services
public async Task SendEmailAsync(string to, string subject, string htmlContent) public async Task SendEmailAsync(string to, string subject, string htmlContent)
{ {
// Crear cliente SendGrid
var client = new SendGridClient(_apiKey); var client = new SendGridClient(_apiKey);
// Configurar remitente y destinatario
var from = new EmailAddress(_fromEmail, _fromName); var from = new EmailAddress(_fromEmail, _fromName);
var toEmail = new EmailAddress(to); var toEmail = new EmailAddress(to);
// Convertir el contenido HTML a texto plano eliminando etiquetas
var plainTextContent = System.Text.RegularExpressions.Regex.Replace(htmlContent, "<[^>]+>", string.Empty); var plainTextContent = System.Text.RegularExpressions.Regex.Replace(htmlContent, "<[^>]+>", string.Empty);
// Crear el mensaje
var message = MailHelper.CreateSingleEmail(from, toEmail, subject, plainTextContent, htmlContent); var message = MailHelper.CreateSingleEmail(from, toEmail, subject, plainTextContent, htmlContent);
// Enviar el mensaje
var response = await client.SendEmailAsync(message); var response = await client.SendEmailAsync(message);
// Manejar errores en caso de que el envío falle
if (response.StatusCode == System.Net.HttpStatusCode.BadRequest) if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
{ {
throw new Exception("Error: Solicitud inválida al enviar el correo."); throw new Exception("Error: Solicitud inválida al enviar el correo.");

View File

@ -0,0 +1,10 @@
namespace WebVentaCoche.ViewModels
{
public class OrderSummaryViewModel
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public decimal TotalAmount { get; set; }
public string Status { get; set; } = null!;
}
}

View File

@ -88,9 +88,14 @@
</button> </button>
</form> </form>
<a class="btn btn-primary ms-2" href="#"> <form asp-action="Purchase" asp-controller="Cart" method="post" class="d-inline">
Proceder al Pago @Html.AntiForgeryToken()
</a> <button type="submit" class="btn btn-primary ms-2">
Proceder al Pago
</button>
</form>
</div> </div>
</div> </div>
} }

View File

@ -1,20 +1,60 @@
@model WebVentaCoche.Models.Order @model WebVentaCoche.Models.Order
@{ @{
ViewData["Title"] = "Detalles de la Orden"; ViewData["Title"] = "Detalle de Pedido";
} }
<div class="container mt-5"> <div class="container mt-4">
<h2 class="mb-4">Detalles de la Orden</h2> <h2 class="mb-4">Pedido #@Model.Id</h2>
<div class="card shadow">
<div class="card-body"> <div class="row mb-4">
<h5 class="card-title">Orden #@Model.Id</h5> <div class="col-md-6">
<p class="card-text"><strong>Fecha del Pedido:</strong> @Model.OrderDate.ToString("dd/MM/yyyy")</p> <p><strong>Fecha del pedido:</strong> @Model.OrderDate.ToString("g")</p>
<p class="card-text"><strong>Cliente:</strong> @Model.User.Name @Model.User.Surname</p> <p><strong>Estado:</strong> @Model.Status</p>
<p class="card-text"><strong>Estado:</strong> @Model.Status</p> </div>
<p class="card-text"><strong>Total:</strong> @Model.TotalAmount.ToString("C")</p> <div class="col-md-6">
<p class="card-text"><strong>Dirección de Envío:</strong> @Model.ShippingAddress</p> <p><strong>Dirección de envío:</strong><br />@Model.ShippingAddress</p>
<a href="@Url.Action("Index", "Order")" class="btn btn-primary mt-3">Volver a la Lista</a> <p>
<strong>Fecha de envío:</strong> @(Model.ShippedDate.HasValue ? Model.ShippedDate.Value.ToString("g") : "Pendiente")
</p>
</div> </div>
</div> </div>
<h4 class="mb-3">Productos del Pedido</h4>
<table class="table table-striped align-middle">
<thead class="table-dark">
<tr>
<th style="width:100px;">Imagen</th>
<th>Nombre</th>
<th>Cantidad</th>
<th>Precio unidad</th>
<th>Subtotal</th>
</tr>
</thead>
<tbody>
@foreach (var detalle in Model.OrderDetails)
{
<tr>
<td>
@if (!string.IsNullOrEmpty(detalle.Product.ImagePath))
{
<img src="@detalle.Product.ImagePath" alt="@detalle.Product.Name" class="img-fluid rounded" style="max-height:80px;" />
}
else
{
<img src="/images/default.png" alt="Sin imagen" class="img-fluid rounded" style="max-height:80px;" />
}
</td>
<td>@detalle.Product.Name</td>
<td>@detalle.Quantity</td>
<td>@detalle.UnitPrice.ToString("0.00") €</td>
<td>@detalle.Subtotal.ToString("0.00") €</td>
</tr>
}
</tbody>
</table>
<div class="text-end mt-4">
<h4>Total del pedido: @Model.TotalAmount.ToString("0.00") €</h4>
</div>
</div> </div>

View File

@ -0,0 +1,40 @@
@model IEnumerable<WebVentaCoche.Models.Order>
@{
ViewData["Title"] = "Mis Pedidos";
}
<div class="container mt-4">
<h2 class="mb-4 text-center">Mis Pedidos</h2>
@if (!Model.Any())
{
<p class="text-center">Aún no has realizado ningún pedido.</p>
}
else
{
<div class="row">
@foreach (var order in Model)
{
<div class="col-md-4 mb-4">
<div class="card h-100 shadow-sm">
<div class="card-body d-flex flex-column">
<h5 class="card-title">Pedido #@order.Id</h5>
<p class="card-text mb-1"><strong>Fecha:</strong> @order.OrderDate.ToString("g")</p>
<p class="card-text mb-1"><strong>Total:</strong> @order.TotalAmount.ToString("0.00") €</p>
<p class="card-text mb-1"><strong>Estado:</strong> @order.Status</p>
<p class="card-text mt-auto"><strong>Envío:</strong><br />@order.ShippingAddress</p>
</div>
<div class="card-footer bg-transparent border-0 text-end">
<a asp-action="Details"
asp-route-id="@order.Id"
class="btn btn-sm btn-primary">
Ver detalles
</a>
</div>
</div>
</div>
}
</div>
}
</div>

View File

@ -76,7 +76,7 @@
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown"> <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
<li> <a class="dropdown-item" asp-controller="Account" asp-action="Details" asp-route-id="@userId">Detalles Cuenta</a></li> <li> <a class="dropdown-item" asp-controller="Account" asp-action="Details" asp-route-id="@userId">Detalles Cuenta</a></li>
<li><a class="dropdown-item" asp-controller="Account" asp-action="Settings">Configuración</a></li> <li><a class="dropdown-item" asp-controller="Account" asp-action="Settings">Configuración</a></li>
<li><a class="dropdown-item" href="@Url.Action("Index", "Order")">Pedidos</a></li> <li><a class="dropdown-item" asp-controller="Order" asp-action="UserOrders">Mis Pedidos</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li> <li>
<form method="post" asp-action="Logout" asp-controller="User" class="d-inline"> <form method="post" asp-action="Logout" asp-controller="User" class="d-inline">