From b9aa8478db993a87e2acc8558edd83d418546429 Mon Sep 17 00:00:00 2001 From: dmolinari Date: Thu, 29 Jan 2026 13:43:44 -0300 Subject: [PATCH] Init Commit --- .gitignore | 94 + Backend/Dockerfile.API | 31 + .../Controllers/AdminController.cs | 526 +++ .../Controllers/AdsV2Controller.cs | 816 ++++ .../Controllers/AuthController.cs | 485 ++ .../Controllers/AvisosLegacyController.cs | 79 + .../Controllers/ChatController.cs | 126 + .../OperacionesLegacyController.cs | 119 + .../Controllers/PaymentsController.cs | 145 + .../Controllers/ProfileController.cs | 74 + .../Controllers/SeedController.cs | 180 + .../Controllers/UsuariosLegacyController.cs | 33 + .../MotoresArgentinosV2.API.csproj | 24 + .../MotoresArgentinosV2.API.http | 6 + Backend/MotoresArgentinosV2.API/Program.cs | 216 + .../Properties/launchSettings.json | 23 + .../MotoresArgentinosV2.API/appsettings.json | 9 + .../MotoresArgentinosV2.Core/DTOs/AdDtos.cs | 120 + .../MotoresArgentinosV2.Core/DTOs/AuthDtos.cs | 50 + .../DTOs/AvisoWebDto.cs | 13 + .../DTOs/DatosAvisoDto.cs | 66 + .../DTOs/InsertarAvisoDto.cs | 70 + .../DTOs/PaymentDtos.cs | 38 + .../MotoresArgentinosV2.Core/DTOs/UserDtos.cs | 32 + .../DTOs/UsuariosLegacyDtos.cs | 41 + .../Entities/AppEntities.cs | 209 + .../Entities/MedioDePago.cs | 18 + .../Entities/Operacion.cs | 163 + .../Entities/RefreshToken.cs | 22 + .../Interfaces/IAdSyncService.cs | 8 + .../Interfaces/IAvisosLegacyService.cs | 28 + .../Interfaces/IEmailService.cs | 6 + .../Interfaces/IIdentityService.cs | 20 + .../Interfaces/ILegacyPaymentService.cs | 16 + .../Interfaces/INotificationService.cs | 14 + .../Interfaces/IOperacionesLegacyService.cs | 38 + .../Interfaces/IPasswordService.cs | 7 + .../Interfaces/IPaymentService.cs | 10 + .../Interfaces/ITokenService.cs | 15 + .../Interfaces/IUsuariosLegacyService.cs | 9 + .../Models/MailSettings.cs | 11 + .../MotoresArgentinosV2.Core.csproj | 9 + .../Data/AutosDbContext.cs | 79 + .../Data/InternetDbContext.cs | 36 + .../Data/MotoresV2DbContext.cs | 74 + .../MotoresArgentinosV2.Infrastructure.csproj | 32 + .../Services/AdExpirationService.cs | 448 ++ .../Services/AdSyncService.cs | 75 + .../Services/AvisosLegacyService.cs | 255 + .../Services/IdentityService.cs | 336 ++ .../Services/ImageStorageService.cs | 149 + .../Services/LegacyPaymentService.cs | 176 + .../Services/MercadoPagoService.cs | 392 ++ .../Services/NotificationService.cs | 210 + .../Services/OperacionesLegacyService.cs | 111 + .../Services/PasswordService.cs | 45 + .../Services/SmtpEmailService.cs | 70 + .../Services/TokenService.cs | 89 + .../Services/UsuariosLegacyService.cs | 61 + .../AdMigrator.cs | 386 ++ .../Models/LegacyAdData.cs | 30 + .../Models/LegacyMotoData.cs | 33 + .../MotoresArgentinosV2.MigrationTool.csproj | 16 + .../Program.cs | 211 + Frontend/.gitignore | 31 + Frontend/Dockerfile | 31 + Frontend/README.md | 73 + Frontend/eslint.config.js | 23 + Frontend/index.html | 14 + Frontend/nginx.conf | 34 + Frontend/package-lock.json | 4200 +++++++++++++++++ Frontend/package.json | 39 + Frontend/public/bg-car.jpg | Bin 0 -> 136082 bytes Frontend/public/logo-ma.svg | 64 + Frontend/public/placeholder-car.png | Bin 0 -> 20937 bytes Frontend/src/App.tsx | 336 ++ Frontend/src/components/AdDetailsModal.tsx | 124 + Frontend/src/components/AdStatusBadge.tsx | 26 + .../src/components/ChangePasswordModal.tsx | 141 + Frontend/src/components/ChatModal.tsx | 132 + Frontend/src/components/ConfigPanel.tsx | 196 + Frontend/src/components/ConfirmationModal.tsx | 85 + Frontend/src/components/CreditCardForm.tsx | 378 ++ Frontend/src/components/FormularioAviso.tsx | 1043 ++++ Frontend/src/components/LoginModal.tsx | 694 +++ Frontend/src/components/MercadoPagoLogo.tsx | 35 + Frontend/src/components/ModerationModal.tsx | 689 +++ Frontend/src/components/SearchableSelect.tsx | 98 + Frontend/src/components/UserModal.tsx | 151 + Frontend/src/components/VisualCreditCard.tsx | 117 + Frontend/src/constants/adStatuses.ts | 86 + Frontend/src/constants/vehicleOptions.ts | 76 + Frontend/src/context/AuthContext.tsx | 92 + Frontend/src/index.css | 127 + Frontend/src/main.tsx | 10 + Frontend/src/pages/AdminPage.tsx | 968 ++++ Frontend/src/pages/ExplorarPage.tsx | 346 ++ Frontend/src/pages/HomePage.tsx | 212 + Frontend/src/pages/MisAvisosPage.tsx | 607 +++ Frontend/src/pages/PerfilPage.tsx | 144 + Frontend/src/pages/PublicarAvisoPage.tsx | 266 ++ Frontend/src/pages/ResetPasswordPage.tsx | 141 + Frontend/src/pages/SeguridadPage.tsx | 29 + Frontend/src/pages/SuccessPage.tsx | 113 + Frontend/src/pages/VehiculoDetailPage.tsx | 281 ++ Frontend/src/pages/VerifyEmailPage.tsx | 57 + Frontend/src/services/admin.service.ts | 73 + Frontend/src/services/ads.v2.service.ts | 135 + Frontend/src/services/auth.service.ts | 143 + Frontend/src/services/avisos.service.ts | 35 + Frontend/src/services/axios.client.ts | 59 + Frontend/src/services/chat.service.ts | 37 + Frontend/src/services/operaciones.service.ts | 13 + Frontend/src/services/profile.service.ts | 13 + Frontend/src/services/usuarios.service.ts | 22 + Frontend/src/types/aviso.types.ts | 73 + Frontend/src/types/usuario.types.ts | 27 + Frontend/src/utils/app.utils.ts | 51 + Frontend/tailwind.config.js | 31 + Frontend/tsconfig.app.json | 28 + Frontend/tsconfig.json | 7 + Frontend/tsconfig.node.json | 26 + Frontend/vite.config.ts | 11 + MotoresArgentinosV2.sln | 69 + README.md | 111 + docker-compose.yml | 43 + 126 files changed, 20649 insertions(+) create mode 100644 .gitignore create mode 100644 Backend/Dockerfile.API create mode 100644 Backend/MotoresArgentinosV2.API/Controllers/AdminController.cs create mode 100644 Backend/MotoresArgentinosV2.API/Controllers/AdsV2Controller.cs create mode 100644 Backend/MotoresArgentinosV2.API/Controllers/AuthController.cs create mode 100644 Backend/MotoresArgentinosV2.API/Controllers/AvisosLegacyController.cs create mode 100644 Backend/MotoresArgentinosV2.API/Controllers/ChatController.cs create mode 100644 Backend/MotoresArgentinosV2.API/Controllers/OperacionesLegacyController.cs create mode 100644 Backend/MotoresArgentinosV2.API/Controllers/PaymentsController.cs create mode 100644 Backend/MotoresArgentinosV2.API/Controllers/ProfileController.cs create mode 100644 Backend/MotoresArgentinosV2.API/Controllers/SeedController.cs create mode 100644 Backend/MotoresArgentinosV2.API/Controllers/UsuariosLegacyController.cs create mode 100644 Backend/MotoresArgentinosV2.API/MotoresArgentinosV2.API.csproj create mode 100644 Backend/MotoresArgentinosV2.API/MotoresArgentinosV2.API.http create mode 100644 Backend/MotoresArgentinosV2.API/Program.cs create mode 100644 Backend/MotoresArgentinosV2.API/Properties/launchSettings.json create mode 100644 Backend/MotoresArgentinosV2.API/appsettings.json create mode 100644 Backend/MotoresArgentinosV2.Core/DTOs/AdDtos.cs create mode 100644 Backend/MotoresArgentinosV2.Core/DTOs/AuthDtos.cs create mode 100644 Backend/MotoresArgentinosV2.Core/DTOs/AvisoWebDto.cs create mode 100644 Backend/MotoresArgentinosV2.Core/DTOs/DatosAvisoDto.cs create mode 100644 Backend/MotoresArgentinosV2.Core/DTOs/InsertarAvisoDto.cs create mode 100644 Backend/MotoresArgentinosV2.Core/DTOs/PaymentDtos.cs create mode 100644 Backend/MotoresArgentinosV2.Core/DTOs/UserDtos.cs create mode 100644 Backend/MotoresArgentinosV2.Core/DTOs/UsuariosLegacyDtos.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Entities/AppEntities.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Entities/MedioDePago.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Entities/Operacion.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Entities/RefreshToken.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/IAdSyncService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/IAvisosLegacyService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/IEmailService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/IIdentityService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/ILegacyPaymentService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/INotificationService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/IOperacionesLegacyService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/IPasswordService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/IPaymentService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/ITokenService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Interfaces/IUsuariosLegacyService.cs create mode 100644 Backend/MotoresArgentinosV2.Core/Models/MailSettings.cs create mode 100644 Backend/MotoresArgentinosV2.Core/MotoresArgentinosV2.Core.csproj create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Data/AutosDbContext.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Data/InternetDbContext.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Data/MotoresV2DbContext.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/MotoresArgentinosV2.Infrastructure.csproj create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/AdExpirationService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/AdSyncService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/AvisosLegacyService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/IdentityService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/ImageStorageService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/LegacyPaymentService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/MercadoPagoService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/NotificationService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/OperacionesLegacyService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/PasswordService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/SmtpEmailService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/TokenService.cs create mode 100644 Backend/MotoresArgentinosV2.Infrastructure/Services/UsuariosLegacyService.cs create mode 100644 Backend/MotoresArgentinosV2.MigrationTool/AdMigrator.cs create mode 100644 Backend/MotoresArgentinosV2.MigrationTool/Models/LegacyAdData.cs create mode 100644 Backend/MotoresArgentinosV2.MigrationTool/Models/LegacyMotoData.cs create mode 100644 Backend/MotoresArgentinosV2.MigrationTool/MotoresArgentinosV2.MigrationTool.csproj create mode 100644 Backend/MotoresArgentinosV2.MigrationTool/Program.cs create mode 100644 Frontend/.gitignore create mode 100644 Frontend/Dockerfile create mode 100644 Frontend/README.md create mode 100644 Frontend/eslint.config.js create mode 100644 Frontend/index.html create mode 100644 Frontend/nginx.conf create mode 100644 Frontend/package-lock.json create mode 100644 Frontend/package.json create mode 100644 Frontend/public/bg-car.jpg create mode 100644 Frontend/public/logo-ma.svg create mode 100644 Frontend/public/placeholder-car.png create mode 100644 Frontend/src/App.tsx create mode 100644 Frontend/src/components/AdDetailsModal.tsx create mode 100644 Frontend/src/components/AdStatusBadge.tsx create mode 100644 Frontend/src/components/ChangePasswordModal.tsx create mode 100644 Frontend/src/components/ChatModal.tsx create mode 100644 Frontend/src/components/ConfigPanel.tsx create mode 100644 Frontend/src/components/ConfirmationModal.tsx create mode 100644 Frontend/src/components/CreditCardForm.tsx create mode 100644 Frontend/src/components/FormularioAviso.tsx create mode 100644 Frontend/src/components/LoginModal.tsx create mode 100644 Frontend/src/components/MercadoPagoLogo.tsx create mode 100644 Frontend/src/components/ModerationModal.tsx create mode 100644 Frontend/src/components/SearchableSelect.tsx create mode 100644 Frontend/src/components/UserModal.tsx create mode 100644 Frontend/src/components/VisualCreditCard.tsx create mode 100644 Frontend/src/constants/adStatuses.ts create mode 100644 Frontend/src/constants/vehicleOptions.ts create mode 100644 Frontend/src/context/AuthContext.tsx create mode 100644 Frontend/src/index.css create mode 100644 Frontend/src/main.tsx create mode 100644 Frontend/src/pages/AdminPage.tsx create mode 100644 Frontend/src/pages/ExplorarPage.tsx create mode 100644 Frontend/src/pages/HomePage.tsx create mode 100644 Frontend/src/pages/MisAvisosPage.tsx create mode 100644 Frontend/src/pages/PerfilPage.tsx create mode 100644 Frontend/src/pages/PublicarAvisoPage.tsx create mode 100644 Frontend/src/pages/ResetPasswordPage.tsx create mode 100644 Frontend/src/pages/SeguridadPage.tsx create mode 100644 Frontend/src/pages/SuccessPage.tsx create mode 100644 Frontend/src/pages/VehiculoDetailPage.tsx create mode 100644 Frontend/src/pages/VerifyEmailPage.tsx create mode 100644 Frontend/src/services/admin.service.ts create mode 100644 Frontend/src/services/ads.v2.service.ts create mode 100644 Frontend/src/services/auth.service.ts create mode 100644 Frontend/src/services/avisos.service.ts create mode 100644 Frontend/src/services/axios.client.ts create mode 100644 Frontend/src/services/chat.service.ts create mode 100644 Frontend/src/services/operaciones.service.ts create mode 100644 Frontend/src/services/profile.service.ts create mode 100644 Frontend/src/services/usuarios.service.ts create mode 100644 Frontend/src/types/aviso.types.ts create mode 100644 Frontend/src/types/usuario.types.ts create mode 100644 Frontend/src/utils/app.utils.ts create mode 100644 Frontend/tailwind.config.js create mode 100644 Frontend/tsconfig.app.json create mode 100644 Frontend/tsconfig.json create mode 100644 Frontend/tsconfig.node.json create mode 100644 Frontend/vite.config.ts create mode 100644 MotoresArgentinosV2.sln create mode 100644 README.md create mode 100644 docker-compose.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb7810c --- /dev/null +++ b/.gitignore @@ -0,0 +1,94 @@ +# ---------------------------------------------------------------------------- +# ## General / Sistema Operativo y Editores ## +# ---------------------------------------------------------------------------- + +# Archivos de caché del sistema operativo +.DS_Store +Thumbs.db + +# Archivos de configuración y caché de IDEs y editores +.idea/ +.vscode/ +*.suo +*.user +*.userosscache +*.sln.docstates + +# Archivos de log +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Archivos de variables de entorno locales +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# ---------------------------------------------------------------------------- +# ## Backend: .NET / C# (Carpeta ChatbotApi/) ## +# ---------------------------------------------------------------------------- + +# Carpetas de compilación y binarios. Se generan al compilar. +[Bb]in/ +[Oo]bj/ + +# Configuración de desarrollo que puede contener secretos. +# Es mejor usar "User Secrets" en desarrollo para las claves. +appsettings.Development.json + +# Archivos de publicación de Visual Studio +[Pp]roperties/[Pp]ublish[Pp]rofiles/ +*.pubxml +*.publish.xml + +# Directorio de paquetes de NuGet (formato antiguo) +[Pp]ackages/ + +# Archivos de caché de Visual Studio +.vs/ + +# Herramientas de .NET +.dotnet/ +tools/ + +# Archivos generados por Entity Framework +# Nota: La carpeta 'Migrations' SÍ se suele incluir en el repositorio, +# pero si tienes algún archivo temporal o generado dentro, puedes añadirlo aquí. + +# ---------------------------------------------------------------------------- +# ## Frontend: React / Vite / Node.js ## +# ---------------------------------------------------------------------------- + +# Dependencias (se instalan con 'npm install' o 'yarn') +node_modules/ + +# Carpetas de build (se generan con 'npm run build') +dist/ +build/ +.next/ +out/ + +# Archivos de caché de herramientas de frontend +.npm/ +.vite/ +.cache/ +.eslintcache + +# Reportes de cobertura de tests +coverage/ + +# Archivos de configuración de proxy de Create React App +.proxyrc.js + +# Backups de DB +.bak + +#Documentación +*.pdf +*.txt + +#Directorio de Imagenes +Backend/MotoresArgentinosV2.API/wwwroot \ No newline at end of file diff --git a/Backend/Dockerfile.API b/Backend/Dockerfile.API new file mode 100644 index 0000000..cbf8258 --- /dev/null +++ b/Backend/Dockerfile.API @@ -0,0 +1,31 @@ +# Etapa de construcción +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src + +# Copiar archivos .sln y .csproj para restaurar dependencias +COPY MotoresArgentinosV2.sln ./ +COPY Backend/MotoresArgentinosV2.API/*.csproj Backend/MotoresArgentinosV2.API/ +COPY Backend/MotoresArgentinosV2.Core/*.csproj Backend/MotoresArgentinosV2.Core/ +COPY Backend/MotoresArgentinosV2.Infrastructure/*.csproj Backend/MotoresArgentinosV2.Infrastructure/ +COPY Backend/MotoresArgentinosV2.MigrationTool/*.csproj Backend/MotoresArgentinosV2.MigrationTool/ + +RUN dotnet restore + +# Copiar el resto del código y construir +COPY . . +WORKDIR "/src/Backend/MotoresArgentinosV2.API" +RUN dotnet build "MotoresArgentinosV2.API.csproj" -c Release -o /app/build + +# Publicar +FROM build AS publish +RUN dotnet publish "MotoresArgentinosV2.API.csproj" -c Release -o /app/publish /p:UseAppHost=false + +# Etapa final: Ejecución +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final +WORKDIR /app +COPY --from=publish /app/publish . + +# Exponer el puerto configurado (ASP.NET 10 usa 8080 por defecto en contenedores) +EXPOSE 8080 + +ENTRYPOINT ["dotnet", "MotoresArgentinosV2.API.dll"] diff --git a/Backend/MotoresArgentinosV2.API/Controllers/AdminController.cs b/Backend/MotoresArgentinosV2.API/Controllers/AdminController.cs new file mode 100644 index 0000000..936bba9 --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Controllers/AdminController.cs @@ -0,0 +1,526 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MotoresArgentinosV2.Infrastructure.Data; +using MotoresArgentinosV2.Core.Entities; +using MotoresArgentinosV2.Core.Interfaces; + +using Microsoft.AspNetCore.Authorization; + +using MotoresArgentinosV2.Core.DTOs; + +namespace MotoresArgentinosV2.API.Controllers; + +[Authorize(Roles = "Admin")] +[ApiController] +[Route("api/[controller]")] +public class AdminController : ControllerBase +{ + private readonly MotoresV2DbContext _context; + private readonly IAdSyncService _syncService; + private readonly INotificationService _notificationService; + + public AdminController(MotoresV2DbContext context, IAdSyncService syncService, INotificationService notificationService) + { + _context = context; + _syncService = syncService; + _notificationService = notificationService; + } + + // --- MODERACIÓN --- + + // GESTIÓN GLOBAL DE AVISOS (Buscador Admin) + [HttpGet("ads")] + public async Task GetAllAds( + [FromQuery] string? q = null, + [FromQuery] int? statusId = null, + [FromQuery] int page = 1) + { + const int pageSize = 20; + var query = _context.Ads + .Include(a => a.Brand) + .Include(a => a.User) + .Include(a => a.Photos) // Para mostrar la miniatura + .AsNoTracking() // Optimización de lectura + .AsQueryable(); + + // Filtro por Texto (Marca, Modelo, Email Usuario, Nombre Usuario) + if (!string.IsNullOrEmpty(q)) + { + query = query.Where(a => + a.VersionName!.Contains(q) || + a.Brand.Name.Contains(q) || + a.User.Email.Contains(q) || + a.User.UserName.Contains(q) + ); + } + + // Filtro por Estado + if (statusId.HasValue) + { + query = query.Where(a => a.StatusID == statusId.Value); + } + + var total = await query.CountAsync(); + + var ads = await query + .OrderByDescending(a => a.CreatedAt) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .Select(a => new + { + a.AdID, + BrandName = a.Brand != null ? a.Brand.Name : null, + VersionName = a.VersionName, + StatusID = a.StatusID, + + CreatedAt = a.CreatedAt, + PublishedAt = a.PublishedAt, + ExpiresAt = a.ExpiresAt, + DeletedAt = a.DeletedAt, + Views = a.ViewsCounter, + LegacyID = a.LegacyAdID, + + // Datos de la transacción APROBADA más reciente + PaidAmount = _context.Transactions + .Where(t => t.AdID == a.AdID && t.Status == "APPROVED") + .OrderByDescending(t => t.CreatedAt) + .Select(t => (decimal?)t.Amount) // Cast a nullable para detectar si no hubo pago + .FirstOrDefault(), + + PaidDate = _context.Transactions + .Where(t => t.AdID == a.AdID && t.Status == "APPROVED") + .OrderByDescending(t => t.CreatedAt) + .Select(t => (DateTime?)t.CreatedAt) + .FirstOrDefault(), + + UserEmail = a.User.Email, + UserName = a.User.UserName, + Thumbnail = a.Photos.Where(p => p.IsCover).Select(p => p.FilePath).FirstOrDefault() + ?? a.Photos.Select(p => p.FilePath).FirstOrDefault() + }) + .ToListAsync(); + + return Ok(new { ads, total, page, pageSize }); + } + + [HttpGet("ads/pending")] + public async Task GetPendingAds() + { + var ads = await _context.Ads + .Include(a => a.User) + .Include(a => a.Photos) + .Where(a => a.StatusID == (int)AdStatusEnum.ModerationPending) + .OrderByDescending(a => a.CreatedAt) + .Select(a => new + { + a.AdID, + VersionName = a.Brand != null ? $"{a.Brand.Name} {a.VersionName}" : a.VersionName, + a.Price, + a.Currency, + a.CreatedAt, + UserID = a.UserID, + UserName = a.User.UserName, + Email = a.User.Email, + Thumbnail = a.Photos.Where(p => p.IsCover).Select(p => p.FilePath).FirstOrDefault() ?? a.Photos.Select(p => p.FilePath).FirstOrDefault() + }) + .ToListAsync(); + + return Ok(ads); + } + + [HttpPost("ads/{id}/approve")] + public async Task ApproveAd(int id) + { + var ad = await _context.Ads.Include(a => a.User).Include(a => a.Brand).FirstOrDefaultAsync(a => a.AdID == id); + if (ad == null) return NotFound(); + + ad.StatusID = (int)AdStatusEnum.Active; + ad.PublishedAt = DateTime.UtcNow; + ad.ExpiresAt = DateTime.UtcNow.AddDays(30); + + await _context.SaveChangesAsync(); + + // Audit Log + var adminId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + _context.AuditLogs.Add(new AuditLog + { + Action = "AD_APPROVED", + Entity = "Ad", + EntityID = id, + UserID = adminId, + Details = $"Aviso '{ad.Brand?.Name} {ad.VersionName}' aprobado por administrador." + }); + await _context.SaveChangesAsync(); + + // Sincronizar a Legacy + try + { + await _syncService.SyncAdToLegacyAsync(id); + var adTitle = $"{ad.Brand?.Name} {ad.VersionName}"; + await _notificationService.SendAdStatusChangedEmailAsync(ad.User?.Email ?? string.Empty, adTitle, "APROBADO"); + } + catch (Exception) + { + // Logueamos pero no bloqueamos la aprobación en V2 + } + + return Ok(new { message = "Aviso aprobado y publicado." }); + } + + [HttpPost("ads/{id}/reject")] + public async Task RejectAd(int id, [FromBody] string reason) + { + var ad = await _context.Ads.Include(a => a.User).Include(a => a.Brand).FirstOrDefaultAsync(a => a.AdID == id); + if (ad == null) return NotFound(); + + ad.StatusID = (int)AdStatusEnum.Rejected; + + await _context.SaveChangesAsync(); + + // Audit Log + var adminId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + _context.AuditLogs.Add(new AuditLog + { + Action = "AD_REJECTED", + Entity = "Ad", + EntityID = id, + UserID = adminId, + Details = $"Aviso '{ad.Brand?.Name} {ad.VersionName}' rechazado. Motivo: {reason}" + }); + await _context.SaveChangesAsync(); + + // Notificar rechazo + var adTitle = $"{ad.Brand?.Name} {ad.VersionName}"; + await _notificationService.SendAdStatusChangedEmailAsync(ad.User?.Email ?? string.Empty, adTitle, "RECHAZADO", reason); + + return Ok(new { message = "Aviso rechazado." }); + } + + // --- TRANSACCIONES --- + + [HttpGet("transactions")] + public async Task GetTransactions( + [FromQuery] string? status = null, + [FromQuery] string? userSearch = null, + [FromQuery] DateTime? fromDate = null, + [FromQuery] DateTime? toDate = null, + [FromQuery] int page = 1) + { + const int pageSize = 20; + + var query = from t in _context.Transactions + join a in _context.Ads on t.AdID equals a.AdID into adGroup + from ad in adGroup.DefaultIfEmpty() // Left Join con Ads + join u in _context.Users on ad.UserID equals u.UserID into userGroup + from user in userGroup.DefaultIfEmpty() // Left Join con Users + select new { t, ad, user }; + + // 1. Filtro por Estado + if (!string.IsNullOrEmpty(status)) + { + query = query.Where(x => x.t.Status == status); + } + + // 2. Filtro por Usuario (Búsqueda) + if (!string.IsNullOrEmpty(userSearch)) + { + // Solo buscamos si el usuario existe (no es nulo) + query = query.Where(x => + x.user != null && + (x.user.Email.Contains(userSearch) || x.user.UserName.Contains(userSearch)) + ); + } + + // 3. Filtro por Fechas + if (fromDate.HasValue) + { + query = query.Where(x => x.t.CreatedAt >= fromDate.Value); + } + + if (toDate.HasValue) + { + var toDateEndOfDay = toDate.Value.Date.AddHours(23).AddMinutes(59).AddSeconds(59); + query = query.Where(x => x.t.CreatedAt <= toDateEndOfDay); + } + + // 4. Paginación y Ejecución + var total = await query.CountAsync(); + + var txs = await query + .OrderByDescending(x => x.t.CreatedAt) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .Select(x => new + { + x.t.TransactionID, + x.t.OperationCode, + x.t.Amount, + x.t.Status, + x.t.CreatedAt, + x.t.UpdatedAt, + AdID = x.t.AdID, + + // LÓGICA DE PRIORIDAD: Snapshot > Relación Viva > Default + UserID = (x.ad != null) ? x.ad.UserID : 0, + + UserName = !string.IsNullOrEmpty(x.t.SnapshotUserName) + ? x.t.SnapshotUserName + : ((x.user != null) ? x.user.UserName : "ELIMINADO"), + + UserEmail = !string.IsNullOrEmpty(x.t.SnapshotUserEmail) + ? x.t.SnapshotUserEmail + : ((x.user != null) ? x.user.Email : "-"), + + AdTitle = !string.IsNullOrEmpty(x.t.SnapshotAdTitle) + ? x.t.SnapshotAdTitle + : ((x.ad != null && x.ad.Brand != null) ? $"{x.ad.Brand.Name} {x.ad.VersionName}" : "Vehículo Desconocido/Eliminado") + }) + .ToListAsync(); + + return Ok(new { transactions = txs, total, page, pageSize }); + } + + // --- USUARIOS --- + [HttpGet("users")] + public async Task GetUsers([FromQuery] string? q = null, [FromQuery] int page = 1) + { + const int pageSize = 20; + var query = _context.Users.AsQueryable(); + + if (!string.IsNullOrEmpty(q)) + { + query = query.Where(u => (u.Email ?? "").Contains(q) || + (u.UserName ?? "").Contains(q) || + (u.FirstName ?? "").Contains(q) || + (u.LastName ?? "").Contains(q)); + } + + var total = await query.CountAsync(); + var users = await query + .OrderByDescending(u => u.CreatedAt) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .Select(u => new + { + u.UserID, + u.UserName, + u.Email, + u.FirstName, + u.LastName, + u.UserType, + u.MigrationStatus, + u.IsBlocked, + u.CreatedAt + }) + .ToListAsync(); + + return Ok(new { users, total, page, pageSize }); + } + + [HttpGet("users/{id}")] + public async Task GetUserDetail(int id) + { + var user = await _context.Users + .Where(u => u.UserID == id) + .Select(u => new UserDetailDto + { + UserID = u.UserID, + UserName = u.UserName, + Email = u.Email, + FirstName = u.FirstName, + LastName = u.LastName, + PhoneNumber = u.PhoneNumber, + UserType = u.UserType, + IsBlocked = u.IsBlocked, + MigrationStatus = u.MigrationStatus, + IsEmailVerified = u.IsEmailVerified, + CreatedAt = u.CreatedAt + }) + .FirstOrDefaultAsync(); + + if (user == null) return NotFound(); + return Ok(user); + } + + [HttpPut("users/{id}")] + public async Task UpdateUser(int id, [FromBody] UserUpdateDto dto) + { + var user = await _context.Users.FindAsync(id); + if (user == null) return NotFound(); + + user.UserName = dto.UserName; + user.FirstName = dto.FirstName; + user.LastName = dto.LastName; + user.PhoneNumber = dto.PhoneNumber; + user.UserType = dto.UserType; + + await _context.SaveChangesAsync(); + + // Audit Log + var adminId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + _context.AuditLogs.Add(new AuditLog + { + Action = "USER_UPDATED", + Entity = "User", + EntityID = id, + UserID = adminId, + Details = $"Usuario '{user.UserName}' actualizado por administrador." + }); + await _context.SaveChangesAsync(); + + return Ok(new { message = "Usuario actualizado correctamente." }); + } + + [HttpGet("stats")] + public async Task GetStats() + { + var stats = new + { + // 1. Usamos AuditLogs para contar TODAS las creaciones históricas, + // incluso si el aviso ya fue borrado físicamente de la tabla Ads. + TotalAds = await _context.AuditLogs.CountAsync(l => l.Action == "AD_CREATED"), + + // 2. Avisos actualmente activos (Status = 4) + ActiveAds = await _context.Ads.CountAsync(a => a.StatusID == (int)AdStatusEnum.Active), + + // 3. Pendientes de moderación (Status = 3) + PendingAds = await _context.Ads.CountAsync(a => a.StatusID == (int)AdStatusEnum.ModerationPending), + + // 4. Total de Usuarios + TotalUsers = await _context.Users.CountAsync(), + + // 5. Mensajes NO LEÍDOS (IsRead = false) + UnreadMessages = await _context.ChatMessages.CountAsync(m => !m.IsRead) + }; + return Ok(stats); + } + + [HttpGet("audit")] + public async Task GetAuditLogs( + [FromQuery] string? actionType = null, + [FromQuery] string? entity = null, + [FromQuery] int? userId = null, + [FromQuery] DateTime? fromDate = null, + [FromQuery] DateTime? toDate = null, + [FromQuery] int page = 1) + { + const int pageSize = 50; + var query = _context.AuditLogs.AsQueryable(); + + if (!string.IsNullOrEmpty(actionType)) + query = query.Where(l => l.Action == actionType); + + if (!string.IsNullOrEmpty(entity)) + query = query.Where(l => l.Entity == entity); + + if (userId.HasValue) + query = query.Where(l => l.UserID == userId.Value); + + if (fromDate.HasValue) + query = query.Where(l => l.CreatedAt >= fromDate.Value); + + if (toDate.HasValue) + query = query.Where(l => l.CreatedAt <= toDate.Value); + + var total = await query.CountAsync(); + var logs = await query + .OrderByDescending(l => l.CreatedAt) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .Select(l => new + { + l.AuditLogID, + l.Action, + l.Entity, + l.EntityID, + l.UserID, + l.Details, + l.CreatedAt, + UserName = l.UserID == 0 ? "Sistema" : _context.Users.Where(u => u.UserID == l.UserID).Select(u => u.UserName).FirstOrDefault() ?? "Desconocido" + }) + .ToListAsync(); + + return Ok(new { logs, total, page, pageSize }); + } + + // BÚSQUEDA DE USUARIOS PARA ASIGNAR AVISO + [HttpGet("users/search")] + public async Task SearchUsers([FromQuery] string q) + { + if (string.IsNullOrEmpty(q)) return Ok(new List()); + + var users = await _context.Users + .Where(u => (u.Email ?? "").Contains(q) || (u.UserName ?? "").Contains(q) || (u.FirstName ?? "").Contains(q) || (u.LastName ?? "").Contains(q)) + .Take(10) + .Select(u => new { u.UserID, u.UserName, u.Email, u.FirstName, u.LastName, u.PhoneNumber, u.IsBlocked }) + .ToListAsync(); + + return Ok(users); + } + + // GESTIÓN DE BLOQUEO + [HttpPost("users/{id}/toggle-block")] + public async Task ToggleBlockUser(int id) + { + var user = await _context.Users.FindAsync(id); + if (user == null) return NotFound(); + + // Evitar autobloqueo + var currentAdminId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + if (user.UserID == currentAdminId) return BadRequest("No puedes bloquearte a ti mismo."); + + user.IsBlocked = !user.IsBlocked; + + // 📝 AUDITORÍA + _context.AuditLogs.Add(new AuditLog + { + Action = user.IsBlocked ? "USER_BLOCKED" : "USER_UNBLOCKED", + Entity = "User", + EntityID = user.UserID, + UserID = currentAdminId, + Details = $"Usuario '{user.UserName}' {(user.IsBlocked ? "bloqueado" : "desbloqueado")} por administrador." + }); + + await _context.SaveChangesAsync(); + + return Ok(new { message = user.IsBlocked ? "Usuario bloqueado" : "Usuario desbloqueado", isBlocked = user.IsBlocked }); + } + + // REPUBLICAR AVISO VENCIDO + [HttpPost("ads/{id}/republish")] + public async Task RepublishAd(int id) + { + var ad = await _context.Ads.Include(a => a.Brand).FirstOrDefaultAsync(a => a.AdID == id); + if (ad == null) return NotFound(); + + // Verificación de seguridad: solo se pueden republicar avisos vencidos. + if (ad.StatusID != (int)AdStatusEnum.Expired) + { + return BadRequest("Solo se pueden republicar avisos que se encuentren vencidos."); + } + + // Actualizamos el estado y las fechas para un nuevo ciclo de 30 días. + ad.StatusID = (int)AdStatusEnum.Active; + ad.PublishedAt = DateTime.UtcNow; // La nueva fecha de publicación es ahora. + ad.ExpiresAt = DateTime.UtcNow.AddDays(30); + ad.ExpirationWarningSent = false; // Reseteamos la advertencia de expiración. + + // Audit Log + var adminId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + _context.AuditLogs.Add(new AuditLog + { + Action = "AD_REPUBLISHED_BY_ADMIN", + Entity = "Ad", + EntityID = id, + UserID = adminId, + Details = $"Aviso ID {id} ('{ad.Brand?.Name} {ad.VersionName}') republicado por administrador." + }); + + await _context.SaveChangesAsync(); + + // Aquí podrías agregar una sincronización con el sistema legacy si fuera necesario. + // await _syncService.SyncAdToLegacyAsync(id); + + return Ok(new { message = "Aviso republicado y activado por 30 días." }); + } +} diff --git a/Backend/MotoresArgentinosV2.API/Controllers/AdsV2Controller.cs b/Backend/MotoresArgentinosV2.API/Controllers/AdsV2Controller.cs new file mode 100644 index 0000000..3e2cb5c --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Controllers/AdsV2Controller.cs @@ -0,0 +1,816 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MotoresArgentinosV2.Infrastructure.Data; +using MotoresArgentinosV2.Core.Entities; +using MotoresArgentinosV2.Infrastructure.Services; +using MotoresArgentinosV2.Core.Interfaces; +using MotoresArgentinosV2.Core.DTOs; +using Microsoft.AspNetCore.Authorization; + +namespace MotoresArgentinosV2.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class AdsV2Controller : ControllerBase +{ + private readonly MotoresV2DbContext _context; + private readonly IImageStorageService _imageService; + private readonly IIdentityService _identityService; + + public AdsV2Controller(MotoresV2DbContext context, IImageStorageService imageService, IIdentityService identityService) + { + _context = context; + _imageService = imageService; + _identityService = identityService; + } + + private int GetCurrentUserId() + { + return int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + } + + private bool IsUserAdmin() + { + return User.IsInRole("Admin"); + } + + [HttpGet] + public async Task GetAll( + [FromQuery] string? q, + [FromQuery] string? c, + [FromQuery] int? minPrice, + [FromQuery] int? maxPrice, + [FromQuery] string? currency, + [FromQuery] int? minYear, + [FromQuery] int? maxYear, + [FromQuery] int? userId, + [FromQuery] int? brandId, + [FromQuery] int? modelId, + [FromQuery] string? fuel, + [FromQuery] string? transmission, + [FromQuery] string? color, + [FromQuery] bool? isFeatured) + { + var query = _context.Ads + .Include(a => a.Photos) + .Include(a => a.Features) + .Include(a => a.Brand) + .Include(a => a.Model) + .AsQueryable(); + + if (isFeatured.HasValue) query = query.Where(a => a.IsFeatured == isFeatured.Value); + + if (brandId.HasValue) query = query.Where(a => a.BrandID == brandId.Value); + if (modelId.HasValue) query = query.Where(a => a.ModelID == modelId.Value); + + if (!string.IsNullOrEmpty(currency)) + { + query = query.Where(a => a.Currency == currency); + } + + if (minPrice.HasValue) query = query.Where(a => a.Price >= minPrice.Value); + if (maxPrice.HasValue) query = query.Where(a => a.Price <= maxPrice.Value); + + if (!string.IsNullOrEmpty(fuel)) + query = query.Where(a => a.FuelType == fuel || a.Features.Any(f => f.FeatureKey == "Combustible" && f.FeatureValue == fuel)); + + if (!string.IsNullOrEmpty(transmission)) + query = query.Where(a => a.Transmission == transmission || a.Features.Any(f => f.FeatureKey == "Transmision" && f.FeatureValue == transmission)); + + if (!string.IsNullOrEmpty(color)) + query = query.Where(a => a.Color == color || a.Features.Any(f => f.FeatureKey == "Color" && f.FeatureValue == color)); + + if (!string.IsNullOrEmpty(Request.Query["segment"])) + { + var segment = Request.Query["segment"].ToString(); + query = query.Where(a => a.Segment == segment); + } + if (!string.IsNullOrEmpty(Request.Query["location"])) + { + var loc = Request.Query["location"].ToString(); + query = query.Where(a => a.Location != null && a.Location.Contains(loc)); + } + if (!string.IsNullOrEmpty(Request.Query["condition"])) + { + var cond = Request.Query["condition"].ToString(); + query = query.Where(a => a.Condition == cond); + } + + if (!string.IsNullOrEmpty(Request.Query["steering"])) + { + var st = Request.Query["steering"].ToString(); + query = query.Where(a => a.Steering == st); + } + + if (int.TryParse(Request.Query["doorCount"], out int dc)) + { + query = query.Where(a => a.DoorCount == dc); + } + + if (!userId.HasValue) + { + var publicStatuses = new[] { + (int)AdStatusEnum.Active, + (int)AdStatusEnum.Sold, + (int)AdStatusEnum.Reserved + }; + query = query.Where(a => publicStatuses.Contains(a.StatusID)); + } + else + { + query = query.Where(a => a.UserID == userId.Value); + } + + // --- LÓGICA DE BÚSQUEDA POR PALABRAS --- + if (!string.IsNullOrEmpty(q)) + { + // 1. Dividimos el término de búsqueda en palabras individuales. + var keywords = q.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + + // 2. Por cada palabra, agregamos una condición 'Where'. + // Esto crea un AND implícito: el aviso debe coincidir con TODAS las palabras. + foreach (var keyword in keywords) + { + var lowerKeyword = keyword.ToLower(); + query = query.Where(a => + (a.Brand != null && a.Brand.Name.ToLower().Contains(lowerKeyword)) || + (a.Model != null && a.Model.Name.ToLower().Contains(lowerKeyword)) || + (a.VersionName != null && a.VersionName.ToLower().Contains(lowerKeyword)) + ); + } + } + + if (!string.IsNullOrEmpty(c)) + { + int typeId = c == "EAUTOS" ? 1 : (c == "EMOTOS" ? 2 : 0); + if (typeId > 0) query = query.Where(a => a.VehicleTypeID == typeId); + } + + if (minPrice.HasValue) query = query.Where(a => a.Price >= minPrice.Value); + if (maxPrice.HasValue) query = query.Where(a => a.Price <= maxPrice.Value); + if (minYear.HasValue) query = query.Where(a => a.Year >= minYear.Value); + if (maxYear.HasValue) query = query.Where(a => a.Year <= maxYear.Value); + + var results = await query.OrderByDescending(a => a.IsFeatured) + .ThenByDescending(a => a.CreatedAt) + .Select(a => new + { + id = a.AdID, + brandName = a.Brand != null ? a.Brand.Name : null, + versionName = a.VersionName, + price = a.Price, + currency = a.Currency, + year = a.Year, + km = a.KM, + image = a.Photos.OrderBy(p => p.SortOrder).Select(p => p.FilePath).FirstOrDefault(), + isFeatured = a.IsFeatured, + statusId = a.StatusID, + viewsCounter = a.ViewsCounter, + + createdAt = a.CreatedAt, + location = a.Location, + fuelType = a.FuelType, + color = a.Color, + segment = a.Segment, + condition = a.Condition, + doorCount = a.DoorCount, + transmission = a.Transmission, + steering = a.Steering + }) + .ToListAsync(); + + return Ok(results); + } + + [HttpGet("search-suggestions")] + public async Task GetSearchSuggestions([FromQuery] string term) + { + if (string.IsNullOrEmpty(term) || term.Length < 2) + { + return Ok(new List()); + } + + // Buscar en Marcas + var brands = await _context.Brands + .Where(b => b.Name.Contains(term)) + .Select(b => b.Name) + .ToListAsync(); + + // Buscar en Modelos + var models = await _context.Models + .Where(m => m.Name.Contains(term)) + .Select(m => m.Name) + .ToListAsync(); + + // Combinar, eliminar duplicados y tomar los primeros 5 + var suggestions = brands.Concat(models) + .Distinct() + .OrderBy(s => s) + .Take(5) + .ToList(); + + return Ok(suggestions); + } + + [HttpGet("{id}")] + public async Task GetById(int id) + { + var ad = await _context.Ads + .Include(a => a.Photos) + .Include(a => a.Features) + .Include(a => a.Brand) + .Include(a => a.Model) + .Include(a => a.User) + .FirstOrDefaultAsync(a => a.AdID == id); + + if (ad == null) return NotFound(); + + // 1. SEGURIDAD Y CONTROL DE ACCESO + // Obtenemos datos del usuario actual + var userIdStr = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + int currentUserId = int.TryParse(userIdStr, out int uid) ? uid : 0; + bool isAdmin = User.IsInRole("Admin"); + bool isOwner = ad.UserID == currentUserId; + + // Definimos qué estados son visibles para todo el mundo + var publicStatuses = new[] { + (int)AdStatusEnum.Active, + (int)AdStatusEnum.Reserved, + (int)AdStatusEnum.Sold + }; + + bool isPublic = publicStatuses.Contains(ad.StatusID); + + // REGLA A: Si está ELIMINADO (9), nadie lo ve por URL pública (solo admin podría en dashboard) + if (ad.StatusID == (int)AdStatusEnum.Deleted && !isAdmin) + { + return NotFound(); + } + + // REGLA B: Si NO es público (ej: Vencido, Borrador, Pausado) y NO es dueño ni admin -> 404 + if (!isPublic && !isOwner && !isAdmin) + { + return NotFound(); + } + + // 2. LÓGICA DE CONTEO + try + { + // REGLA: Solo contamos si NO es el dueño (isOwner ya calculado arriba) + if (!isOwner) + { + var userIp = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "0.0.0.0"; + var cutoffDate = DateTime.UtcNow.AddHours(-24); + + // Verificamos si esta IP ya vio este aviso en las últimas 24hs + var alreadyViewed = await _context.AdViewLogs + .AnyAsync(v => v.AdID == id && v.IPAddress == userIp && v.ViewDate > cutoffDate); + + if (!alreadyViewed) + { + // A. Registrar Log + _context.AdViewLogs.Add(new AdViewLog + { + AdID = id, + IPAddress = userIp, + ViewDate = DateTime.UtcNow + }); + + // B. Incrementar Contador + await _context.Ads + .Where(a => a.AdID == id) + .ExecuteUpdateAsync(s => s.SetProperty(a => a.ViewsCounter, a => a.ViewsCounter + 1)); + + await _context.SaveChangesAsync(); + } + } + } + catch (Exception) + { + // Ignoramos errores de conteo para no bloquear la visualización + } + + // 3. RESPUESTA + return Ok(new + { + adID = ad.AdID, + userID = ad.UserID, + ownerUserType = ad.User?.UserType, + vehicleTypeID = ad.VehicleTypeID, + brandID = ad.BrandID, + modelID = ad.ModelID, + versionName = ad.VersionName, + brandName = ad.Brand?.Name, + + year = ad.Year, + km = ad.KM, + price = ad.Price, + currency = ad.Currency, + description = ad.Description, + isFeatured = ad.IsFeatured, + statusID = ad.StatusID, + createdAt = ad.CreatedAt, + publishedAt = ad.PublishedAt, + expiresAt = ad.ExpiresAt, + legacyAdID = ad.LegacyAdID, + viewsCounter = ad.ViewsCounter, + fuelType = ad.FuelType, + color = ad.Color, + segment = ad.Segment, + location = ad.Location, + condition = ad.Condition, + doorCount = ad.DoorCount, + transmission = ad.Transmission, + steering = ad.Steering, + contactPhone = ad.ContactPhone, + contactEmail = ad.ContactEmail, + displayContactInfo = ad.DisplayContactInfo, + photos = ad.Photos.Select(p => new { p.PhotoID, p.FilePath, p.IsCover, p.SortOrder }), + features = ad.Features.Select(f => new { f.FeatureKey, f.FeatureValue }), + brand = ad.Brand != null ? new { id = ad.Brand.BrandID, name = ad.Brand.Name } : null, + model = ad.Model != null ? new { id = ad.Model.ModelID, name = ad.Model.Name } : null + }); + } + + [HttpPost] + public async Task Create([FromBody] CreateAdRequestDto request) + { + var currentUserId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + var userRole = User.FindFirst(System.Security.Claims.ClaimTypes.Role)?.Value; + bool isAdmin = userRole == "Admin"; + + int finalUserId = currentUserId; + + if (isAdmin) + { + if (request.TargetUserID.HasValue) + { + finalUserId = request.TargetUserID.Value; + } + else if (!string.IsNullOrEmpty(request.GhostUserEmail)) + { + // Pasamos nombre y apellido por separado + var ghost = await _identityService.CreateGhostUserAsync( + request.GhostUserEmail, + request.GhostFirstName ?? "Usuario", + request.GhostLastName ?? "", + request.GhostUserPhone ?? "" + ); + finalUserId = ghost.UserID; + } + } + + // 🟢 LÓGICA DE MODELO DINÁMICO + int finalModelId = request.ModelID; + + // Si el front manda 0 (modelo nuevo escrito a mano) + if (finalModelId == 0 && !string.IsNullOrEmpty(request.VersionName)) + { + // Intentamos extraer el nombre del modelo desde el VersionName + var brand = await _context.Brands.FindAsync(request.BrandID); + string modelNameCandidate = request.VersionName; + + // Si el nombre de versión empieza con la marca (ej: "Fiat 600"), quitamos la marca + if (brand != null && modelNameCandidate.StartsWith(brand.Name, StringComparison.OrdinalIgnoreCase)) + { + modelNameCandidate = modelNameCandidate.Substring(brand.Name.Length).Trim(); + } + + // Si quedó vacío o es muy genérico, usar un default + if (string.IsNullOrWhiteSpace(modelNameCandidate)) modelNameCandidate = "Modelo Desconocido"; + + // Opcional: Tomar solo la primera palabra como nombre del modelo para agrupar mejor + // var firstWord = modelNameCandidate.Split(' ')[0]; + // if (firstWord.Length > 2) modelNameCandidate = firstWord; + + // Buscar si ya existe este modelo para esta marca + var existingModel = await _context.Models + .FirstOrDefaultAsync(m => m.BrandID == request.BrandID && m.Name == modelNameCandidate); + + if (existingModel != null) + { + finalModelId = existingModel.ModelID; + } + else + { + // Crear Nuevo Modelo + var newModel = new Model + { + BrandID = request.BrandID, + Name = modelNameCandidate + }; + _context.Models.Add(newModel); + await _context.SaveChangesAsync(); // Guardar para obtener ID + finalModelId = newModel.ModelID; + } + } + + var ad = new Ad + { + UserID = finalUserId, + VehicleTypeID = request.VehicleTypeID, + BrandID = request.BrandID, + ModelID = finalModelId, // Usamos el ID resuelto + VersionName = request.VersionName, + Year = request.Year, + KM = request.KM, + Price = request.Price, + Currency = request.Currency, + Description = request.Description, + IsFeatured = request.IsFeatured, + + FuelType = request.FuelType, + Color = request.Color, + Segment = request.Segment, + Location = request.Location, + Condition = request.Condition, + DoorCount = request.DoorCount, + Transmission = request.Transmission, + Steering = request.Steering, + + ContactPhone = request.ContactPhone, + ContactEmail = request.ContactEmail, + DisplayContactInfo = request.DisplayContactInfo, + + CreatedAt = DateTime.UtcNow + }; + + if (isAdmin) + { + ad.StatusID = (int)AdStatusEnum.Active; + ad.PublishedAt = DateTime.UtcNow; + ad.ExpiresAt = DateTime.UtcNow.AddDays(30); + } + else + { + ad.StatusID = (int)AdStatusEnum.Draft; + } + + _context.Ads.Add(ad); + await _context.SaveChangesAsync(); + + // 📝 AUDITORÍA + var brandName = (await _context.Brands.FindAsync(ad.BrandID))?.Name ?? ""; + _context.AuditLogs.Add(new AuditLog + { + Action = "AD_CREATED", + Entity = "Ad", + EntityID = ad.AdID, + UserID = currentUserId, + Details = $"Aviso '{brandName} {ad.VersionName}' creado. Estado inicial: {ad.StatusID}" + }); + await _context.SaveChangesAsync(); + + return CreatedAtAction(nameof(GetById), new { id = ad.AdID }, ad); + } + + [HttpGet("brands/{vehicleTypeId}")] + public async Task GetBrands(int vehicleTypeId) + { + var brands = await _context.Set() + .Where(b => b.VehicleTypeID == vehicleTypeId) + .Select(b => new { id = b.BrandID, name = b.Name }) + .ToListAsync(); + return Ok(brands); + } + + [HttpGet("models/{brandId}")] + public async Task GetModels(int brandId) + { + var models = await _context.Set() + .Where(m => m.BrandID == brandId) + .Select(m => new { id = m.ModelID, name = m.Name }) + .ToListAsync(); + return Ok(models); + } + + [HttpGet("models/search")] + public async Task SearchModels([FromQuery] int brandId, [FromQuery] string query) + { + if (string.IsNullOrEmpty(query) || query.Length < 2) return Ok(new List()); + + var models = await _context.Models + .Where(m => m.BrandID == brandId && m.Name.Contains(query)) + .OrderBy(m => m.Name) + .Take(20) + .Select(m => new { id = m.ModelID, name = m.Name }) + .ToListAsync(); + + return Ok(models); + } + + [HttpPost("{id}/upload-photos")] + public async Task UploadPhotos(int id, [FromForm] IFormFileCollection files) + { + if (files == null || files.Count == 0) return BadRequest("No se recibieron archivos en la petición."); + + var ad = await _context.Ads.Include(a => a.Photos).FirstOrDefaultAsync(a => a.AdID == id); + if (ad == null) return NotFound("Aviso no encontrado."); + + // 🛡️ SEGURIDAD: Verificar propiedad o ser admin + var currentUserId = GetCurrentUserId(); + if (ad.UserID != currentUserId && !IsUserAdmin()) return Forbid(); + + int currentCount = ad.Photos.Count; + int availableSlots = 5 - currentCount; + + if (availableSlots <= 0) + { + return BadRequest($"Límite de 5 fotos alcanzado. Espacios disponibles: 0. Fotos actuales: {currentCount}"); + } + + var filesToProcess = files.Take(availableSlots).ToList(); + var uploadedCount = 0; + var errors = new List(); + + foreach (var file in filesToProcess) + { + if (file.Length > 0) + { + try + { + var relativePath = await _imageService.SaveAdImageAsync(id, file); + + _context.AdPhotos.Add(new AdPhoto + { + AdID = id, + FilePath = relativePath, + IsCover = !_context.AdPhotos.Any(p => p.AdID == id) && uploadedCount == 0, + SortOrder = currentCount + uploadedCount + }); + + uploadedCount++; + } + catch (Exception ex) + { + errors.Add($"{file.FileName}: {ex.Message}"); + } + } + } + + if (uploadedCount > 0) + { + await _context.SaveChangesAsync(); + return Ok(new + { + message = $"{uploadedCount} fotos procesadas.", + errors = errors.Any() ? errors : null + }); + } + + return BadRequest(new + { + message = "No se pudieron procesar las imágenes.", + details = errors + }); + } + + [HttpDelete("photos/{photoId}")] + public async Task DeletePhoto(int photoId) + { + var photo = await _context.AdPhotos.Include(p => p.Ad).FirstOrDefaultAsync(p => p.PhotoID == photoId); + if (photo == null) return NotFound(); + + // 🛡️ SEGURIDAD: Verificar propiedad o ser admin + var currentUserId = GetCurrentUserId(); + if (photo.Ad.UserID != currentUserId && !IsUserAdmin()) return Forbid(); + + // Eliminar del disco + _imageService.DeleteAdImage(photo.FilePath); + + // Eliminar de la base de datos + _context.AdPhotos.Remove(photo); + await _context.SaveChangesAsync(); + + return NoContent(); + } + + [HttpPut("{id}")] + [Authorize] + public async Task Update(int id, [FromBody] CreateAdRequestDto updatedAdDto) + { + var ad = await _context.Ads.FindAsync(id); + if (ad == null) return NotFound(); + + var userId = GetCurrentUserId(); + + if (ad.UserID != userId && !IsUserAdmin()) + { + return Forbid(); + } + + // Si está en moderación, no se puede editar (salvo Admin) + if (!IsUserAdmin() && ad.StatusID == (int)AdStatusEnum.ModerationPending) + { + return BadRequest("No puedes editar un aviso que está en proceso de revisión."); + } + + // --- Lógica de Modelo Dinámico (Si edita a un modelo nuevo) --- + int finalModelId = updatedAdDto.ModelID; + if (finalModelId == 0 && !string.IsNullOrEmpty(updatedAdDto.VersionName)) + { + var brand = await _context.Brands.FindAsync(updatedAdDto.BrandID); + string modelNameCandidate = updatedAdDto.VersionName; + if (brand != null && modelNameCandidate.StartsWith(brand.Name, StringComparison.OrdinalIgnoreCase)) + { + modelNameCandidate = modelNameCandidate.Substring(brand.Name.Length).Trim(); + } + if (string.IsNullOrWhiteSpace(modelNameCandidate)) modelNameCandidate = "Modelo Desconocido"; + + var existingModel = await _context.Models + .FirstOrDefaultAsync(m => m.BrandID == updatedAdDto.BrandID && m.Name == modelNameCandidate); + + if (existingModel != null) + { + finalModelId = existingModel.ModelID; + } + else + { + var newModel = new Model { BrandID = updatedAdDto.BrandID, Name = modelNameCandidate }; + _context.Models.Add(newModel); + await _context.SaveChangesAsync(); + finalModelId = newModel.ModelID; + } + } + + // --- Mapeo manual desde el DTO a la Entidad --- + ad.BrandID = updatedAdDto.BrandID; + ad.ModelID = finalModelId; // Usar el ID resuelto + ad.VersionName = updatedAdDto.VersionName; + ad.Year = updatedAdDto.Year; + ad.KM = updatedAdDto.KM; + ad.Price = updatedAdDto.Price; + ad.Currency = updatedAdDto.Currency; + ad.Description = updatedAdDto.Description; + + ad.FuelType = updatedAdDto.FuelType; + ad.Color = updatedAdDto.Color; + ad.Segment = updatedAdDto.Segment; + ad.Location = updatedAdDto.Location; + ad.Condition = updatedAdDto.Condition; + ad.DoorCount = updatedAdDto.DoorCount; + ad.Transmission = updatedAdDto.Transmission; + ad.Steering = updatedAdDto.Steering; + ad.ContactPhone = updatedAdDto.ContactPhone; + ad.ContactEmail = updatedAdDto.ContactEmail; + ad.DisplayContactInfo = updatedAdDto.DisplayContactInfo; + // Nota: IsFeatured y otros campos sensibles se manejan por separado (pago/admin) + + // 📝 AUDITORÍA + var adBrandName = (await _context.Brands.FindAsync(ad.BrandID))?.Name ?? ""; + _context.AuditLogs.Add(new AuditLog + { + Action = "AD_UPDATED", + Entity = "Ad", + EntityID = ad.AdID, + UserID = userId, + Details = $"Aviso ID {ad.AdID} ({adBrandName} {ad.VersionName}) actualizado por el propietario/admin." + }); + + await _context.SaveChangesAsync(); + return Ok(ad); + } + + [HttpDelete("{id}")] + [Authorize] + public async Task Delete(int id) + { + var ad = await _context.Ads.FindAsync(id); + if (ad == null) return NotFound(); + + var userId = GetCurrentUserId(); + + if (ad.UserID != userId && !IsUserAdmin()) + { + return Forbid(); + } + + ad.StatusID = (int)AdStatusEnum.Deleted; + ad.DeletedAt = DateTime.UtcNow; + + // 📝 AUDITORÍA + var delBrandName = (await _context.Brands.FindAsync(ad.BrandID))?.Name ?? ""; + _context.AuditLogs.Add(new AuditLog + { + Action = "AD_DELETED_SOFT", + Entity = "Ad", + EntityID = ad.AdID, + UserID = userId, + Details = $"Aviso ID {ad.AdID} ({delBrandName} {ad.VersionName}) marcado como eliminado (borrado lógico)." + }); + + await _context.SaveChangesAsync(); + return NoContent(); + } + + [HttpPost("favorites")] + public async Task AddFavorite([FromBody] Favorite fav) + { + if (await _context.Favorites.AnyAsync(f => f.UserID == fav.UserID && f.AdID == fav.AdID)) + return BadRequest("Ya es favorito"); + + _context.Favorites.Add(fav); + await _context.SaveChangesAsync(); + return Ok(); + } + + [HttpDelete("favorites")] + public async Task RemoveFavorite([FromQuery] int userId, [FromQuery] int adId) + { + var fav = await _context.Favorites.FindAsync(userId, adId); + if (fav == null) return NotFound(); + + _context.Favorites.Remove(fav); + await _context.SaveChangesAsync(); + return NoContent(); + } + + [HttpGet("favorites/{userId}")] + public async Task GetFavorites(int userId) + { + var ads = await _context.Favorites + .Where(f => f.UserID == userId) + .Join(_context.Ads, f => f.AdID, a => a.AdID, (f, a) => a) + .Include(a => a.Photos) + .Select(a => new + { + id = a.AdID, + BrandName = a.Brand != null ? a.Brand.Name : null, + VersionName = a.VersionName, + price = a.Price, + currency = a.Currency, + year = a.Year, + km = a.KM, + image = a.Photos.OrderBy(p => p.SortOrder).Select(p => p.FilePath).FirstOrDefault() + }) + .ToListAsync(); + + return Ok(ads); + } + + [HttpPatch("{id}/status")] + public async Task ChangeStatus(int id, [FromBody] int newStatus) + { + var ad = await _context.Ads.FindAsync(id); + if (ad == null) return NotFound(); + + var userId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + var userRole = User.FindFirst(System.Security.Claims.ClaimTypes.Role)?.Value; + bool isAdmin = userRole == "Admin"; + + if (ad.UserID != userId && !isAdmin) return Forbid(); + + // 🛡️ VALIDACIONES DE ESTADO + if (!isAdmin) + { + // 1. No activar borradores sin pagar + if (ad.StatusID == (int)AdStatusEnum.Draft || ad.StatusID == (int)AdStatusEnum.PaymentPending) + { + return BadRequest("Debes completar el pago para activar este aviso."); + } + + // 2. NUEVO: No tocar si está en moderación + if (ad.StatusID == (int)AdStatusEnum.ModerationPending) + { + return BadRequest("El aviso está en revisión. Espera la aprobación del administrador."); + } + } + + // Validar estados destino permitidos para el usuario + var allowedUserStatuses = new[] { 4, 6, 7, 9, 10 }; + if (!isAdmin && !allowedUserStatuses.Contains(newStatus)) + { + return BadRequest("Estado destino no permitido."); + } + + int oldStatus = ad.StatusID; + ad.StatusID = newStatus; + + // 📝 AUDITORÍA + var statusBrandName = (await _context.Brands.FindAsync(ad.BrandID))?.Name ?? ""; + _context.AuditLogs.Add(new AuditLog + { + Action = "AD_STATUS_CHANGED", + Entity = "Ad", + EntityID = ad.AdID, + UserID = userId, + Details = $"Estado de aviso ({statusBrandName} {ad.VersionName}) cambiado de {oldStatus} a {newStatus}." + }); + + await _context.SaveChangesAsync(); + + return Ok(new { message = "Estado actualizado" }); + } + + [HttpGet("payment-methods")] + public async Task GetPaymentMethods() + { + var methods = await _context.PaymentMethods + .OrderBy(p => p.PaymentMethodID) + .Select(p => new { id = p.PaymentMethodID, mediodepago = p.Name }) + .ToListAsync(); + + return Ok(methods); + } +} \ No newline at end of file diff --git a/Backend/MotoresArgentinosV2.API/Controllers/AuthController.cs b/Backend/MotoresArgentinosV2.API/Controllers/AuthController.cs new file mode 100644 index 0000000..f4b34f7 --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Controllers/AuthController.cs @@ -0,0 +1,485 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MotoresArgentinosV2.Core.Interfaces; +using MotoresArgentinosV2.Infrastructure.Data; +using MotoresArgentinosV2.Core.Entities; +using MotoresArgentinosV2.Core.DTOs; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.RateLimiting; + +namespace MotoresArgentinosV2.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +// CORRECCIÓN: Se quitó [EnableRateLimiting("AuthPolicy")] de aquí para no bloquear /me ni /logout +public class AuthController : ControllerBase +{ + private readonly IIdentityService _identityService; + private readonly ITokenService _tokenService; + private readonly INotificationService _notificationService; + private readonly MotoresV2DbContext _context; + + public AuthController(IIdentityService identityService, ITokenService tokenService, INotificationService notificationService, MotoresV2DbContext context) + { + _identityService = identityService; + _tokenService = tokenService; + _notificationService = notificationService; + _context = context; + } + + // Helper privado para cookies + private void SetTokenCookie(string token, string cookieName) + { + var cookieOptions = new CookieOptions + { + HttpOnly = true, // Seguridad: JS no puede leer esto + Expires = DateTime.UtcNow.AddDays(7), + Secure = true, // Solo HTTPS (localhost con https cuenta) + SameSite = SameSiteMode.Strict, + IsEssential = true + }; + Response.Cookies.Append(cookieName, token, cookieOptions); + } + + [HttpPost("login")] + [EnableRateLimiting("AuthPolicy")] // PROTEGIDO (5 intentos/min) + public async Task Login([FromBody] LoginRequest request) + { + var (user, message) = await _identityService.AuthenticateAsync(request.Username, request.Password); + + if (user == null) + { + if (message == "EMAIL_NOT_VERIFIED") + return Unauthorized(new { message = "Debes verificar tu email antes de ingresar." }); + + if (message == "USER_BLOCKED") + return Unauthorized(new { message = "Tu usuario ha sido bloqueado por un administrador." }); + + return Unauthorized(new { message = "Credenciales inválidas" }); + } + + if (message == "FORCE_PASSWORD_CHANGE") + { + return Ok(new { status = "MIGRATION_REQUIRED", username = user.UserName }); + } + + // Lógica MFA (Si aplica) + if (user.IsMFAEnabled) + { + return Ok(new { status = "TOTP_REQUIRED", username = user.UserName }); + } + if (user.UserType == 3 && string.IsNullOrEmpty(user.MFASecret)) // Admin forzar setup + { + var secret = _tokenService.GenerateBase32Secret(); + user.MFASecret = secret; + await _context.SaveChangesAsync(); + var qrUri = _tokenService.GetQrCodeUri(user.Email, secret); + return Ok(new { status = "MFA_SETUP_REQUIRED", username = user.UserName, qrUri, secret }); + } + + // --- LOGIN EXITOSO --- + + // 1. Generar Tokens + var jwtToken = _tokenService.GenerateJwtToken(user); + var refreshToken = _tokenService.GenerateRefreshToken(HttpContext.Connection.RemoteIpAddress?.ToString() ?? "0.0.0.0"); + + // 2. Guardar RefreshToken en DB + // Asegúrate de que la propiedad RefreshTokens exista en User (Paso 2 abajo) + user.RefreshTokens.Add(refreshToken); + await _context.SaveChangesAsync(); + + // 3. Setear Cookies + SetTokenCookie(jwtToken, "accessToken"); + SetTokenCookie(refreshToken.Token, "refreshToken"); + + // 4. Audit Log + _context.AuditLogs.Add(new AuditLog + { + Action = "LOGIN_SUCCESS", + Entity = "User", + EntityID = user.UserID, + UserID = user.UserID, + Details = "Login con Cookies exitoso" + }); + await _context.SaveChangesAsync(); + + // 5. Retornar User (Sin el token, porque va en cookie) + return Ok(new + { + status = "SUCCESS", + recommendMfa = !user.IsMFAEnabled, + user = new + { + id = user.UserID, + username = user.UserName, + email = user.Email, + firstName = user.FirstName, + lastName = user.LastName, + userType = user.UserType, + isMFAEnabled = user.IsMFAEnabled + } + }); + } + + [HttpPost("refresh-token")] + // NO PROTEGIDO ESTRICTAMENTE (Usa límite global) + public async Task RefreshToken() + { + var refreshToken = Request.Cookies["refreshToken"]; + if (string.IsNullOrEmpty(refreshToken)) + return Unauthorized(new { message = "Token no proporcionado" }); + + var user = await _context.Users + .Include(u => u.RefreshTokens) + .FirstOrDefaultAsync(u => u.RefreshTokens.Any(t => t.Token == refreshToken)); + + if (user == null) return Unauthorized(new { message = "Usuario no encontrado" }); + + var storedToken = user.RefreshTokens.SingleOrDefault(x => x.Token == refreshToken); + + if (storedToken == null || !storedToken.IsActive) + return Unauthorized(new { message = "Token inválido o expirado" }); + + // Rotar Refresh Token + var newRefreshToken = _tokenService.GenerateRefreshToken(HttpContext.Connection.RemoteIpAddress?.ToString() ?? "0.0.0.0"); + + storedToken.Revoked = DateTime.UtcNow; + storedToken.RevokedByIp = HttpContext.Connection.RemoteIpAddress?.ToString(); + storedToken.ReplacedByToken = newRefreshToken.Token; + + user.RefreshTokens.Add(newRefreshToken); + await _context.SaveChangesAsync(); + + // Nuevo JWT + var newJwtToken = _tokenService.GenerateJwtToken(user); + + // Actualizar Cookies + SetTokenCookie(newJwtToken, "accessToken"); + SetTokenCookie(newRefreshToken.Token, "refreshToken"); + + return Ok(new { message = "Token renovado" }); + } + + [HttpPost("logout")] + // NO PROTEGIDO ESTRICTAMENTE + public IActionResult Logout() + { + Response.Cookies.Delete("accessToken"); + Response.Cookies.Delete("refreshToken"); + return Ok(new { message = "Sesión cerrada" }); + } + + [HttpGet("me")] + // 1. Sin [Authorize] para controlar nosotros la respuesta + public async Task GetMe() + { + // 2. Verificamos si existe la cookie de acceso + var hasToken = Request.Cookies.ContainsKey("accessToken"); + + // 3. Si NO tiene cookie, es un visitante anónimo. + // Devolvemos 200 OK con null para no ensuciar la consola con 401. + if (!hasToken) + { + return Ok(null); + } + + // 4. Si TIENE cookie, verificamos si el Middleware pudo leer el usuario. + var userIdStr = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + + // Si tiene cookie pero userIdStr es null, significa que el token expiró o es inválido. + // Aquí SI devolvemos 401 para disparar el "Auto Refresh" del frontend. + if (string.IsNullOrEmpty(userIdStr)) + { + return Unauthorized(); + } + + // 5. Todo válido, buscamos datos + var userId = int.Parse(userIdStr); + var user = await _context.Users.FindAsync(userId); + + if (user == null) return Unauthorized(); // Usuario borrado de DB + + return Ok(new + { + id = user.UserID, + username = user.UserName, + email = user.Email, + firstName = user.FirstName, + lastName = user.LastName, + userType = user.UserType, + phoneNumber = user.PhoneNumber, + isMFAEnabled = user.IsMFAEnabled + }); + } + + // Permite a un usuario logueado iniciar el proceso de MFA voluntariamente + [Authorize] + [HttpPost("init-mfa")] + public async Task InitMFA() + { + var userIdStr = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + if (string.IsNullOrEmpty(userIdStr)) return Unauthorized(); + + var user = await _context.Users.FindAsync(int.Parse(userIdStr)); + if (user == null) return NotFound(); + + // Generar secreto si no tiene + if (string.IsNullOrEmpty(user.MFASecret)) + { + user.MFASecret = _tokenService.GenerateBase32Secret(); + await _context.SaveChangesAsync(); + } + + // 📝 AUDITORÍA + _context.AuditLogs.Add(new AuditLog + { + Action = "MFA_INITIATED", + Entity = "User", + EntityID = user.UserID, + UserID = user.UserID, + Details = "Proceso de configuración de MFA (TOTP) iniciado." + }); + await _context.SaveChangesAsync(); + + var qrUri = _tokenService.GetQrCodeUri(user.Email, user.MFASecret); + + return Ok(new + { + qrUri = qrUri, + secret = user.MFASecret + }); + } + + [HttpPost("verify-mfa")] + [EnableRateLimiting("AuthPolicy")] + public async Task VerifyMFA([FromBody] MFARequest request) + { + var user = await _context.Users.FirstOrDefaultAsync(u => u.UserName == request.Username); + if (user == null || string.IsNullOrEmpty(user.MFASecret)) + { + return Unauthorized(new { message = "Sesión de autenticación inválida" }); + } + + bool isValid = _tokenService.ValidateTOTP(user.MFASecret, request.Code); + if (!isValid) + { + return Unauthorized(new { message = "Código de verificación inválido" }); + } + + if (!user.IsMFAEnabled) + { + user.IsMFAEnabled = true; + await _context.SaveChangesAsync(); + } + + var token = _tokenService.GenerateJwtToken(user); + var refreshToken = _tokenService.GenerateRefreshToken(HttpContext.Connection.RemoteIpAddress?.ToString() ?? "0.0.0.0"); + + // Cargar colección explícitamente para evitar errores de EF + await _context.Entry(user).Collection(u => u.RefreshTokens).LoadAsync(); + + user.RefreshTokens.Add(refreshToken); + await _context.SaveChangesAsync(); + + // Setear Cookies Seguras + SetTokenCookie(token, "accessToken"); + SetTokenCookie(refreshToken.Token, "refreshToken"); + + _context.AuditLogs.Add(new AuditLog + { + Action = "LOGIN_TOTP_SUCCESS", + Entity = "User", + EntityID = user.UserID, + UserID = user.UserID, + Details = "Login con TOTP (Authenticator) exitoso" + }); + await _context.SaveChangesAsync(); + + return Ok(new + { + status = "SUCCESS", + // user: retornamos datos para el frontend, pero NO el token + user = new + { + id = user.UserID, + username = user.UserName, + email = user.Email, + firstName = user.FirstName, + lastName = user.LastName, + userType = user.UserType, + isMFAEnabled = user.IsMFAEnabled + } + }); + } + + [Authorize] + [HttpPost("disable-mfa")] + public async Task DisableMFA() + { + var userIdStr = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + if (string.IsNullOrEmpty(userIdStr)) return Unauthorized(); + + var user = await _context.Users.FindAsync(int.Parse(userIdStr)); + if (user == null) return NotFound(); + + // Desactivamos MFA y limpiamos el secreto por seguridad + user.IsMFAEnabled = false; + user.MFASecret = null; + + // 📝 AUDITORÍA + _context.AuditLogs.Add(new AuditLog + { + Action = "MFA_DISABLED", + Entity = "User", + EntityID = user.UserID, + UserID = user.UserID, + Details = "Autenticación de dos factores desactivada por el usuario." + }); + + await _context.SaveChangesAsync(); + + return Ok(new { message = "MFA Desactivado correctamente." }); + } + + [HttpPost("migrate-password")] + [EnableRateLimiting("AuthPolicy")] + public async Task MigratePassword([FromBody] MigrateRequest request) + { + var success = await _identityService.MigratePasswordAsync(request.Username, request.NewPassword); + + if (!success) + { + return BadRequest(new { message = "No se pudo actualizar la contraseña" }); + } + + return Ok(new { message = "Contraseña actualizada con éxito. Ya puede iniciar sesión." }); + } + + [HttpPost("register")] + [EnableRateLimiting("AuthPolicy")] // PROTEGIDO + public async Task Register([FromBody] RegisterRequest request) + { + var (success, message) = await _identityService.RegisterUserAsync(request); + if (!success) return BadRequest(new { message }); + + // 📝 AUDITORÍA + _context.AuditLogs.Add(new AuditLog + { + Action = "USER_REGISTERED", + Entity = "User", + EntityID = 0, + UserID = 0, + Details = $"Nuevo registro de usuario: {request.Email}" + }); + await _context.SaveChangesAsync(); + + return Ok(new { message }); + } + + [HttpPost("verify-email")] + [EnableRateLimiting("AuthPolicy")] // PROTEGIDO + public async Task VerifyEmail([FromBody] VerifyEmailRequest request) + { + var (success, message) = await _identityService.VerifyEmailAsync(request.Token); + if (!success) return BadRequest(new { message }); + + // 📝 AUDITORÍA + _context.AuditLogs.Add(new AuditLog + { + Action = "EMAIL_VERIFIED", + Entity = "User", + EntityID = 0, + UserID = 0, + Details = "Correo electrónico verificado con éxito vía token." + }); + await _context.SaveChangesAsync(); + + return Ok(new { message }); + } + + [HttpPost("resend-verification")] + [EnableRateLimiting("AuthPolicy")] // PROTEGIDO + public async Task ResendVerification([FromBody] ResendVerificationRequest request) + { + var (success, message) = await _identityService.ResendVerificationEmailAsync(request.Email); + if (!success) return BadRequest(new { message }); + return Ok(new { message }); + } + + [HttpPost("forgot-password")] + [EnableRateLimiting("AuthPolicy")] // PROTEGIDO + public async Task ForgotPassword([FromBody] ForgotPasswordRequest request) + { + var (success, message) = await _identityService.ForgotPasswordAsync(request.Email); + + if (!success) + { + // Si falló por Rate Limit, devolvemos BadRequest o 429 Too Many Requests + return BadRequest(new { message }); + } + + return Ok(new { message }); + } + + [HttpPost("reset-password")] + [EnableRateLimiting("AuthPolicy")] // PROTEGIDO + public async Task ResetPassword([FromBody] ResetPasswordRequest request) + { + var (success, message) = await _identityService.ResetPasswordAsync(request.Token, request.NewPassword); + if (!success) return BadRequest(new { message }); + + // 📝 AUDITORÍA + _context.AuditLogs.Add(new AuditLog + { + Action = "PASSWORD_RESET_SUCCESS", + Entity = "User", + EntityID = 0, // No tenemos el ID directo aquí sin buscarlo + UserID = 0, + Details = "Contraseña restablecida vía token de recuperación." + }); + await _context.SaveChangesAsync(); + + return Ok(new { message }); + } + + [Authorize] + [HttpPost("change-password")] + [EnableRateLimiting("AuthPolicy")] // PROTEGIDO + public async Task ChangePassword([FromBody] ChangePasswordRequest request) + { + var userId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + var (success, message) = await _identityService.ChangePasswordAsync(userId, request.CurrentPassword, request.NewPassword); + + if (!success) return BadRequest(new { message }); + + // Enviar notificación por mail + try + { + var user = await _context.Users.FindAsync(userId); + if (user != null) + { + await _notificationService.SendSecurityAlertEmailAsync(user.Email, "Cambio de contraseña"); + } + } + catch (Exception) { /* No bloqueamos el éxito si falla el mail */ } + + // 📝 AUDITORÍA + _context.AuditLogs.Add(new AuditLog + { + Action = "PASSWORD_CHANGED", + Entity = "User", + EntityID = userId, + UserID = userId, + Details = "Contraseña cambiada por el usuario desde la configuración." + }); + await _context.SaveChangesAsync(); + + return Ok(new { message }); + } +} + +public class ChangePasswordRequest +{ + public string CurrentPassword { get; set; } = string.Empty; + public string NewPassword { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Backend/MotoresArgentinosV2.API/Controllers/AvisosLegacyController.cs b/Backend/MotoresArgentinosV2.API/Controllers/AvisosLegacyController.cs new file mode 100644 index 0000000..eaafd27 --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Controllers/AvisosLegacyController.cs @@ -0,0 +1,79 @@ +using Microsoft.AspNetCore.Mvc; +using MotoresArgentinosV2.Core.DTOs; +using MotoresArgentinosV2.Core.Interfaces; + +namespace MotoresArgentinosV2.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class AvisosLegacyController : ControllerBase +{ + private readonly IAvisosLegacyService _avisosService; + private readonly ILogger _logger; + + public AvisosLegacyController(IAvisosLegacyService avisosService, ILogger logger) + { + _avisosService = avisosService; + _logger = logger; + } + + /// + /// Obtiene las tarifas y configuración de avisos según la tarea y paquete + /// + /// Tipo de tarea (ej: EMOTORES, EAUTOS) + /// ID del paquete (opcional, default 0) + [HttpGet("configuracion")] + public async Task>> GetConfiguracion([FromQuery] string tarea, [FromQuery] int paquete = 0) + { + try + { + if (string.IsNullOrEmpty(tarea)) + { + return BadRequest("El parámetro 'tarea' es obligatorio."); + } + + var resultados = await _avisosService.ObtenerDatosAvisosAsync(tarea, paquete); + return Ok(resultados); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener configuración de avisos"); + return StatusCode(500, "Ocurrió un error interno al procesar la solicitud."); + } + } + + /// + /// Crea un nuevo aviso en el sistema legacy + /// + [HttpPost] + public async Task> CrearAviso([FromBody] InsertarAvisoDto dto) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + var resultado = await _avisosService.InsertarAvisoAsync(dto); + + if (resultado) + { + return CreatedAtAction(nameof(CrearAviso), true); + } + + return BadRequest("No se pudo crear el aviso."); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al crear aviso para cliente {IdCliente}", dto.IdCliente); + return StatusCode(500, "Ocurrió un error interno al crear el aviso."); + } + } + [HttpGet("cliente/{nroDoc}")] + public async Task>> GetAvisosPorCliente(string nroDoc) + { + var avisos = await _avisosService.ObtenerAvisosPorClienteAsync(nroDoc); + return Ok(avisos); + } +} diff --git a/Backend/MotoresArgentinosV2.API/Controllers/ChatController.cs b/Backend/MotoresArgentinosV2.API/Controllers/ChatController.cs new file mode 100644 index 0000000..f0100de --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Controllers/ChatController.cs @@ -0,0 +1,126 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MotoresArgentinosV2.Infrastructure.Data; +using MotoresArgentinosV2.Core.Entities; +using MotoresArgentinosV2.Core.Interfaces; +using Microsoft.AspNetCore.Authorization; + +namespace MotoresArgentinosV2.API.Controllers; + +[Authorize] +[ApiController] +[Route("api/[controller]")] +public class ChatController : ControllerBase +{ + private readonly MotoresV2DbContext _context; + private readonly INotificationService _notificationService; + + public ChatController(MotoresV2DbContext context, INotificationService notificationService) + { + _context = context; + _notificationService = notificationService; + } + + [HttpPost("send")] + public async Task SendMessage([FromBody] ChatMessage msg) + { + // 1. Guardar Mensaje + msg.SentAt = DateTime.UtcNow; + msg.IsRead = false; + + _context.ChatMessages.Add(msg); + await _context.SaveChangesAsync(); + + // 2. Notificar por Email (Con manejo de errores silencioso) + try + { + var receiver = await _context.Users.FindAsync(msg.ReceiverID); + var sender = await _context.Users.FindAsync(msg.SenderID); + + if (receiver != null && !string.IsNullOrEmpty(receiver.Email)) + { + // LÓGICA DE NOMBRE DE REMITENTE + string senderDisplayName; + + if (sender != null && sender.UserType == 3) // 3 = ADMIN + { + // Caso: Moderador escribe a Usuario + senderDisplayName = "Un moderador de Motores Argentinos"; + } + else + { + // Caso: Usuario responde a Moderador + string name = sender?.UserName ?? "Un usuario"; + senderDisplayName = $"El usuario {name}"; + } + + await _notificationService.SendChatNotificationEmailAsync( + receiver.Email, + senderDisplayName, // Pasamos el nombre formateado + msg.MessageText, + msg.AdID); + } + } + catch (Exception ex) + { + // Loguear error sin romper el flujo del chat + Console.WriteLine($"Error enviando notificación de chat: {ex.Message}"); + } + + return Ok(msg); + } + + [HttpGet("inbox/{userId}")] + public async Task GetInbox(int userId) + { + // Obtener todas las conversaciones donde el usuario es remitente o destinatario + var messages = await _context.ChatMessages + .Where(m => m.SenderID == userId || m.ReceiverID == userId) + .OrderByDescending(m => m.SentAt) + .ToListAsync(); + + return Ok(messages); + } + + [HttpGet("conversation/{adId}/{user1Id}/{user2Id}")] + public async Task GetConversation(int adId, int user1Id, int user2Id) + { + var messages = await _context.ChatMessages + .Where(m => m.AdID == adId && + ((m.SenderID == user1Id && m.ReceiverID == user2Id) || + (m.SenderID == user2Id && m.ReceiverID == user1Id))) + .OrderBy(m => m.SentAt) + .ToListAsync(); + + return Ok(messages); + } + + [HttpPost("mark-read/{messageId}")] + public async Task MarkRead(int messageId) + { + var msg = await _context.ChatMessages.FindAsync(messageId); + if (msg == null) return NotFound(); + + msg.IsRead = true; + await _context.SaveChangesAsync(); + return Ok(); + } + + [HttpGet("unread-count/{userId}")] + public async Task GetUnreadCount(int userId) + { + // Seguridad: Asegurarse de que el usuario que consulta es el dueño de los mensajes o un admin. + var currentUserId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + var isAdmin = User.IsInRole("Admin"); + + if (currentUserId != userId && !isAdmin) + { + return Forbid(); + } + + var count = await _context.ChatMessages + .CountAsync(m => m.ReceiverID == userId && !m.IsRead); + + return Ok(new { count }); + } +} diff --git a/Backend/MotoresArgentinosV2.API/Controllers/OperacionesLegacyController.cs b/Backend/MotoresArgentinosV2.API/Controllers/OperacionesLegacyController.cs new file mode 100644 index 0000000..f0eddce --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Controllers/OperacionesLegacyController.cs @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Mvc; +using MotoresArgentinosV2.Core.Entities; +using MotoresArgentinosV2.Core.Interfaces; + +namespace MotoresArgentinosV2.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class OperacionesLegacyController : ControllerBase +{ + private readonly IOperacionesLegacyService _operacionesService; + private readonly ILogger _logger; + + public OperacionesLegacyController(IOperacionesLegacyService operacionesService, ILogger logger) + { + _operacionesService = operacionesService; + _logger = logger; + } + + /// + /// Obtiene los medios de pago disponibles + /// + [HttpGet("medios-pago")] + public async Task>> GetMediosDePago() + { + try + { + var medios = await _operacionesService.ObtenerMediosDePagoAsync(); + return Ok(medios); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener medios de pago"); + return StatusCode(500, "Ocurrió un error interno al obtener medios de pago"); + } + } + + /// + /// Busca una operación por su número de operación + /// + [HttpGet("{noperacion}")] + public async Task>> GetOperacion(string noperacion) + { + try + { + var operaciones = await _operacionesService.ObtenerOperacionesPorNumeroAsync(noperacion); + + if (operaciones == null || !operaciones.Any()) + { + return NotFound($"No se encontraron operaciones con el número {noperacion}"); + } + + return Ok(operaciones); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al obtener operación {Noperacion}", noperacion); + return StatusCode(500, "Ocurrió un error interno al buscar la operación"); + } + } + + /// + /// Obtiene operaciones realizadas en un rango de fechas + /// + [HttpGet("buscar")] + public async Task>> GetOperacionesPorFecha([FromQuery] DateTime fechaInicio, [FromQuery] DateTime fechaFin) + { + try + { + if (fechaInicio > fechaFin) + { + return BadRequest("La fecha de inicio no puede ser mayor a la fecha de fin."); + } + + var operaciones = await _operacionesService.ObtenerOperacionesPorFechasAsync(fechaInicio, fechaFin); + return Ok(operaciones); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al buscar operaciones por fecha"); + return StatusCode(500, "Ocurrió un error interno al buscar operaciones."); + } + } + + /// + /// Registra una nueva operación de pago + /// + [HttpPost] + public async Task CrearOperacion([FromBody] Operacion operacion) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + // Validar campos mínimos necesarios si es necesario + if (string.IsNullOrEmpty(operacion.Noperacion)) + { + return BadRequest("El número de operación es obligatorio."); + } + + var resultado = await _operacionesService.InsertarOperacionAsync(operacion); + + if (resultado) + { + return CreatedAtAction(nameof(GetOperacion), new { noperacion = operacion.Noperacion }, operacion); + } + + return BadRequest("No se pudo registrar la operación."); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error al crear operación {Noperacion}", operacion.Noperacion); + return StatusCode(500, "Ocurrió un error interno al registrar la operación."); + } + } +} diff --git a/Backend/MotoresArgentinosV2.API/Controllers/PaymentsController.cs b/Backend/MotoresArgentinosV2.API/Controllers/PaymentsController.cs new file mode 100644 index 0000000..2a62472 --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Controllers/PaymentsController.cs @@ -0,0 +1,145 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using MotoresArgentinosV2.Core.DTOs; +using MotoresArgentinosV2.Core.Interfaces; +using Microsoft.AspNetCore.RateLimiting; +using System.Security.Cryptography; +using System.Text; + +namespace MotoresArgentinosV2.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +[Authorize] +public class PaymentsController : ControllerBase +{ + private readonly IPaymentService _paymentService; + private readonly IConfiguration _config; + + public PaymentsController(IPaymentService paymentService, IConfiguration config) + { + _paymentService = paymentService; + _config = config; + } + + [HttpPost("process")] + [EnableRateLimiting("AuthPolicy")] + public async Task ProcessPayment([FromBody] CreatePaymentRequestDto request) + { + try + { + var userId = int.Parse(User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "0"); + + var result = await _paymentService.ProcessPaymentAsync(request, userId); + + if (result.Status == "approved") + { + return Ok(new { status = "approved", id = result.PaymentId }); + } + else if (result.Status == "in_process") + { + return Ok(new { status = "in_process", message = "El pago está siendo revisado." }); + } + else + { + return BadRequest(new { status = "rejected", detail = result.StatusDetail }); + } + } + catch (Exception ex) + { + return StatusCode(500, new { message = ex.Message }); + } + } + + // --- MÉTODO WEBHOOK ACTUALIZADO CON VALIDACIÓN DE FIRMA --- + [HttpPost("webhook")] + [AllowAnonymous] + public async Task MercadoPagoWebhook([FromQuery] string? topic, [FromQuery] string? id) + { + // 1. EXTRACCIÓN DE DATOS DE LA PETICIÓN + Request.Headers.TryGetValue("x-request-id", out var xRequestId); + Request.Headers.TryGetValue("x-signature", out var xSignature); + + var resourceId = id; + var resourceTopic = topic; + + if (string.IsNullOrEmpty(resourceId)) + { + try + { + Request.EnableBuffering(); + using var reader = new System.IO.StreamReader(Request.Body, leaveOpen: true); + var body = await reader.ReadToEndAsync(); + Request.Body.Position = 0; + + var json = System.Text.Json.JsonDocument.Parse(body); + if (json.RootElement.TryGetProperty("type", out var typeProp)) + { + resourceTopic = typeProp.GetString(); + } + if (json.RootElement.TryGetProperty("data", out var dataProp) && dataProp.TryGetProperty("id", out var idProp)) + { + resourceId = idProp.GetString(); + } + } + catch { /* Ignorar errores de lectura */ } + } + + if (string.IsNullOrEmpty(resourceId) || resourceTopic != "payment") + { + return Ok(); + } + + // 2. VALIDACIÓN DE LA FIRMA + var secret = _config["MercadoPago:WebhookSecret"]; + if (!string.IsNullOrEmpty(secret)) + { + var signatureParts = xSignature.ToString().Split(',') + .Select(part => part.Trim().Split('=')) + .ToDictionary(split => split[0], split => split.Length > 1 ? split[1] : ""); + + if (!signatureParts.TryGetValue("ts", out var ts) || !signatureParts.TryGetValue("v1", out var hash)) + { + return Unauthorized("Invalid signature header."); + } + + // Según la documentación de MP, el data.id debe estar en minúsculas para la firma. + var manifest = $"id:{resourceId.ToLower()};request-id:{xRequestId};ts:{ts};"; + + using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret)); + var computedHashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(manifest)); + var computedHash = Convert.ToHexString(computedHashBytes).ToLower(); + + if (computedHash != hash) + { + return Unauthorized("Signature mismatch."); + } + } + + // 3. PROCESAMIENTO (SOLO SI LA FIRMA ES VÁLIDA) + try + { + await _paymentService.ProcessWebhookAsync(resourceTopic, resourceId); + return Ok(); + } + catch (Exception ex) + { + Console.WriteLine($"Error procesando webhook validado: {ex.Message}"); + return StatusCode(500, "Internal server error processing webhook."); + } + } + + [HttpPost("check-status/{adId}")] + public async Task CheckStatus(int adId) + { + try + { + var result = await _paymentService.CheckPaymentStatusAsync(adId); + return Ok(result); + } + catch (Exception ex) + { + return BadRequest(new { message = ex.Message }); + } + } +} \ No newline at end of file diff --git a/Backend/MotoresArgentinosV2.API/Controllers/ProfileController.cs b/Backend/MotoresArgentinosV2.API/Controllers/ProfileController.cs new file mode 100644 index 0000000..c3ed282 --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Controllers/ProfileController.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MotoresArgentinosV2.Core.DTOs; +using MotoresArgentinosV2.Core.Entities; +using MotoresArgentinosV2.Infrastructure.Data; +using Microsoft.AspNetCore.Authorization; +using System.Security.Claims; + +namespace MotoresArgentinosV2.API.Controllers; + +[Authorize] +[ApiController] +[Route("api/[controller]")] +public class ProfileController : ControllerBase +{ + private readonly MotoresV2DbContext _context; + + public ProfileController(MotoresV2DbContext context) + { + _context = context; + } + + [HttpGet] + public async Task GetProfile() + { + var userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "0"); + var user = await _context.Users + .Where(u => u.UserID == userId) + .Select(u => new + { + u.UserID, + u.UserName, + u.Email, + u.FirstName, + u.LastName, + u.PhoneNumber, + u.UserType, + u.CreatedAt, + u.IsEmailVerified + }) + .FirstOrDefaultAsync(); + + if (user == null) return NotFound(); + return Ok(user); + } + + [HttpPut] + public async Task UpdateProfile([FromBody] ProfileUpdateDto dto) + { + var userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "0"); + var user = await _context.Users.FindAsync(userId); + + if (user == null) return NotFound(); + + user.FirstName = dto.FirstName; + user.LastName = dto.LastName; + user.PhoneNumber = dto.PhoneNumber; + + await _context.SaveChangesAsync(); + + // Audit Log + _context.AuditLogs.Add(new AuditLog + { + Action = "PROFILE_UPDATED", + Entity = "User", + EntityID = userId, + UserID = userId, + Details = "Usuario actualizó su perfil personal." + }); + await _context.SaveChangesAsync(); + + return Ok(new { message = "Perfil actualizado con éxito." }); + } +} diff --git a/Backend/MotoresArgentinosV2.API/Controllers/SeedController.cs b/Backend/MotoresArgentinosV2.API/Controllers/SeedController.cs new file mode 100644 index 0000000..b8ee48b --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Controllers/SeedController.cs @@ -0,0 +1,180 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MotoresArgentinosV2.Infrastructure.Data; +using MotoresArgentinosV2.Core.Entities; +using MotoresArgentinosV2.Core.Interfaces; + +namespace MotoresArgentinosV2.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class SeedController : ControllerBase +{ + private readonly MotoresV2DbContext _context; + private readonly IPasswordService _passwordService; + + public SeedController(MotoresV2DbContext context, IPasswordService passwordService) + { + _context = context; + _passwordService = passwordService; + } + + [HttpPost("database")] + public async Task SeedDatabase() + { + // 1. Asegurar Marcas y Modelos + if (!await _context.Brands.AnyAsync()) + { + var toyota = new Brand { VehicleTypeID = 1, Name = "Toyota" }; + var ford = new Brand { VehicleTypeID = 1, Name = "Ford" }; + var vw = new Brand { VehicleTypeID = 1, Name = "Volkswagen" }; + var honda = new Brand { VehicleTypeID = 2, Name = "Honda" }; + var yamaha = new Brand { VehicleTypeID = 2, Name = "Yamaha" }; + + _context.Brands.AddRange(toyota, ford, vw, honda, yamaha); + await _context.SaveChangesAsync(); + + _context.Models.AddRange( + new Model { BrandID = toyota.BrandID, Name = "Corolla" }, + new Model { BrandID = toyota.BrandID, Name = "Hilux" }, + new Model { BrandID = ford.BrandID, Name = "Ranger" }, + new Model { BrandID = vw.BrandID, Name = "Amarok" }, + new Model { BrandID = honda.BrandID, Name = "Wave 110" }, + new Model { BrandID = yamaha.BrandID, Name = "FZ FI" } + ); + await _context.SaveChangesAsync(); + } + + // 2. Crear Usuarios de Prueba + var testUser = await _context.Users.FirstOrDefaultAsync(u => u.UserName == "testuser"); + if (testUser == null) + { + testUser = new User + { + UserName = "testuser", + Email = "test@motores.com.ar", + PasswordHash = _passwordService.HashPassword("test123"), + FirstName = "Usuario", + LastName = "Prueba", + MigrationStatus = 1, + UserType = 1 + }; + _context.Users.Add(testUser); + } + + var adminUser = await _context.Users.FirstOrDefaultAsync(u => u.UserName == "admin"); + if (adminUser == null) + { + adminUser = new User + { + UserName = "admin", + Email = "admin@motoresargentinos.com.ar", + PasswordHash = _passwordService.HashPassword("admin123"), + FirstName = "Admin", + LastName = "Motores", + MigrationStatus = 1, + UserType = 3 // ADMIN + }; + _context.Users.Add(adminUser); + } + + await _context.SaveChangesAsync(); + + // 3. Crear Avisos de Prueba + if (!await _context.Ads.AnyAsync()) + { + var brands = await _context.Brands.ToListAsync(); + var models = await _context.Models.ToListAsync(); + + var ad1 = new Ad + { + UserID = testUser.UserID, + VehicleTypeID = 1, + BrandID = brands.First(b => b.Name == "Toyota").BrandID, + ModelID = models.First(m => m.Name == "Corolla").ModelID, + VersionName = "Toyota Corolla 1.8 XLI", + Year = 2022, + KM = 15000, + Price = 25000, + Currency = "USD", + Description = "Excelente estado, único dueño. Service al día.", + StatusID = 4, // Activo + IsFeatured = true, + CreatedAt = DateTime.UtcNow, + PublishedAt = DateTime.UtcNow + }; + + var ad2 = new Ad + { + UserID = testUser.UserID, + VehicleTypeID = 2, + BrandID = brands.First(b => b.Name == "Honda").BrandID, + ModelID = models.First(m => m.Name == "Wave 110").ModelID, + VersionName = "Honda Wave 110 S", + Year = 2023, + KM = 2500, + Price = 1800, + Currency = "USD", + Description = "Impecable, como nueva. Muy económica.", + StatusID = 4, // Activo + CreatedAt = DateTime.UtcNow, + PublishedAt = DateTime.UtcNow + }; + + var ad3 = new Ad + { + UserID = testUser.UserID, + VehicleTypeID = 1, + BrandID = brands.First(b => b.Name == "Ford").BrandID, + ModelID = models.First(m => m.Name == "Ranger").ModelID, + VersionName = "Ford Ranger Limited 4x4", + Year = 2021, + KM = 35000, + Price = 42000, + Currency = "USD", + Description = "Camioneta impecable, lista para transferir.", + StatusID = 3, // Moderacion Pendiente + CreatedAt = DateTime.UtcNow + }; + + _context.Ads.AddRange(ad1, ad2, ad3); + await _context.SaveChangesAsync(); + + // Agregar fotos + _context.AdPhotos.AddRange( + new AdPhoto { AdID = ad1.AdID, FilePath = "https://images.unsplash.com/photo-1621007947382-bb3c3994e3fb?auto=format&fit=crop&q=80&w=1200", IsCover = true }, + new AdPhoto { AdID = ad1.AdID, FilePath = "https://images.unsplash.com/photo-1590362891991-f776e933a68e?auto=format&fit=crop&q=80&w=1200" }, + new AdPhoto { AdID = ad1.AdID, FilePath = "https://images.unsplash.com/photo-1549317661-bd32c8ce0db2?auto=format&fit=crop&q=80&w=1200" }, + new AdPhoto { AdID = ad2.AdID, FilePath = "https://images.unsplash.com/photo-1558981403-c5f91cbba527?auto=format&fit=crop&q=80&w=800", IsCover = true }, + new AdPhoto { AdID = ad3.AdID, FilePath = "https://images.unsplash.com/photo-1533473359331-0135ef1b58bf?auto=format&fit=crop&q=80&w=1200", IsCover = true } + ); + + // Agregar Características Técnicas + _context.AdFeatures.AddRange( + new AdFeature { AdID = ad1.AdID, FeatureKey = "Combustible", FeatureValue = "Nafta" }, + new AdFeature { AdID = ad1.AdID, FeatureKey = "Transmision", FeatureValue = "Automática" }, + new AdFeature { AdID = ad1.AdID, FeatureKey = "Color", FeatureValue = "Blanco" }, + new AdFeature { AdID = ad2.AdID, FeatureKey = "Combustible", FeatureValue = "Nafta" }, + new AdFeature { AdID = ad2.AdID, FeatureKey = "Color", FeatureValue = "Rojo" } + ); + + await _context.SaveChangesAsync(); + } + + return Ok("Database seeded successfully with Features and Multiple Photos"); + } + + [HttpPost("reset")] + public async Task ResetDatabase() + { + _context.ChangeTracker.Clear(); + await _context.Database.ExecuteSqlRawAsync("DELETE FROM Transactions"); + await _context.Database.ExecuteSqlRawAsync("DELETE FROM AdPhotos"); + await _context.Database.ExecuteSqlRawAsync("DELETE FROM AdFeatures"); + await _context.Database.ExecuteSqlRawAsync("DELETE FROM Ads"); + await _context.Database.ExecuteSqlRawAsync("DELETE FROM Models"); + await _context.Database.ExecuteSqlRawAsync("DELETE FROM Brands"); + + return await SeedDatabase(); + } +} diff --git a/Backend/MotoresArgentinosV2.API/Controllers/UsuariosLegacyController.cs b/Backend/MotoresArgentinosV2.API/Controllers/UsuariosLegacyController.cs new file mode 100644 index 0000000..c52cd0e --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Controllers/UsuariosLegacyController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; +using MotoresArgentinosV2.Core.DTOs; +using MotoresArgentinosV2.Core.Interfaces; + +namespace MotoresArgentinosV2.API.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class UsuariosLegacyController : ControllerBase +{ + private readonly IUsuariosLegacyService _usuariosService; + + public UsuariosLegacyController(IUsuariosLegacyService usuariosService) + { + _usuariosService = usuariosService; + } + + [HttpGet("particular/{usuario}")] + public async Task> GetParticular(string usuario) + { + var datos = await _usuariosService.ObtenerParticularPorUsuarioAsync(usuario); + if (datos == null) return NotFound("Usuario particular no encontrado en legacy."); + return Ok(datos); + } + + [HttpGet("agencia/{usuario}")] + public async Task> GetAgencia(string usuario) + { + var datos = await _usuariosService.ObtenerAgenciaPorUsuarioAsync(usuario); + if (datos == null) return NotFound("Agencia no encontrada en legacy."); + return Ok(datos); + } +} diff --git a/Backend/MotoresArgentinosV2.API/MotoresArgentinosV2.API.csproj b/Backend/MotoresArgentinosV2.API/MotoresArgentinosV2.API.csproj new file mode 100644 index 0000000..8d3dc43 --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/MotoresArgentinosV2.API.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + enable + enable + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/Backend/MotoresArgentinosV2.API/MotoresArgentinosV2.API.http b/Backend/MotoresArgentinosV2.API/MotoresArgentinosV2.API.http new file mode 100644 index 0000000..6e87d3d --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/MotoresArgentinosV2.API.http @@ -0,0 +1,6 @@ +@MotoresArgentinosV2.API_HostAddress = http://localhost:5262 + +GET {{MotoresArgentinosV2.API_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Backend/MotoresArgentinosV2.API/Program.cs b/Backend/MotoresArgentinosV2.API/Program.cs new file mode 100644 index 0000000..53f508c --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Program.cs @@ -0,0 +1,216 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using Microsoft.AspNetCore.RateLimiting; // Rate Limit +using System.Threading.RateLimiting; // Rate Limit +using System.Text; +using MotoresArgentinosV2.Core.Interfaces; +using MotoresArgentinosV2.Infrastructure.Data; +using MotoresArgentinosV2.Infrastructure.Services; +using MotoresArgentinosV2.Core.Models; +using DotNetEnv; +using Microsoft.AspNetCore.HttpOverrides; + +// 🔒 ENV VARS +Env.Load(); + +var builder = WebApplication.CreateBuilder(args); + +// Forzar a la configuración a leer las variables +builder.Configuration.AddEnvironmentVariables(); + +// 🔒 KESTREL HARDENING +builder.WebHost.ConfigureKestrel(options => options.AddServerHeader = false); + +// LOGGING +builder.Logging.ClearProviders(); +builder.Logging.AddConsole(); +builder.Logging.AddDebug(); + +// 🔒 CORS POLICY +var frontendUrls = (builder.Configuration["AppSettings:FrontendUrl"] ?? "http://localhost:5173").Split(','); +builder.Services.AddCors(options => +{ + options.AddPolicy("AllowSpecificOrigin", + policy => policy.WithOrigins(frontendUrls) + .AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials()); +}); + +// FORWARDED HEADERS (CRÍTICO PARA DOCKER/NGINX) +// Por defecto, .NET solo confía en localhost. En Docker, Nginx tiene otra IP. +// Debemos limpiar las redes conocidas para que confíe en el proxy interno de Docker. +builder.Services.Configure(options => +{ + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + options.KnownIPNetworks.Clear(); + options.KnownProxies.Clear(); +}); + +// 🔒 RATE LIMITING +builder.Services.AddRateLimiter(options => +{ + options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; + + options.GlobalLimiter = PartitionedRateLimiter.Create(context => + { + // En producción detrás de Nginx, RemoteIpAddress será la IP real del usuario. + // Si por alguna razón falla (ej: conexión directa local), usamos "unknown". + var remoteIp = context.Connection.RemoteIpAddress?.ToString() ?? "unknown"; + + // Si es Loopback (localhost), sin límites (útil para dev) + if (System.Net.IPAddress.IsLoopback(context.Connection.RemoteIpAddress!)) + { + return RateLimitPartition.GetNoLimiter("loopback"); + } + + return RateLimitPartition.GetFixedWindowLimiter( + partitionKey: remoteIp, // Clave correcta: IP del usuario + factory: _ => new FixedWindowRateLimiterOptions + { + AutoReplenishment = true, + PermitLimit = 100, + QueueLimit = 2, + Window = TimeSpan.FromMinutes(1) + }); + }); + + options.AddPolicy("AuthPolicy", context => + { + // 🟢 FIX: Si es localhost, SIN LÍMITES + var remoteIp = context.Connection.RemoteIpAddress; + if (System.Net.IPAddress.IsLoopback(remoteIp!)) + { + return RateLimitPartition.GetNoLimiter("loopback_auth"); + } + + return RateLimitPartition.GetFixedWindowLimiter( + partitionKey: remoteIp?.ToString() ?? "unknown", + factory: _ => new FixedWindowRateLimiterOptions + { + AutoReplenishment = true, + PermitLimit = 5, // 5 intentos por minuto para IPs externas + QueueLimit = 0, + Window = TimeSpan.FromMinutes(1) + }); + }); +}); + +// DB CONTEXTS +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("Internet"))); +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("Autos"))); +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("MotoresV2"), + sqlOptions => sqlOptions.EnableRetryOnFailure())); + +// SERVICIOS +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.Configure(builder.Configuration.GetSection("SmtpSettings")); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddHostedService(); + +// 🔒 JWT AUTH +var jwtKey = builder.Configuration["Jwt:Key"] ?? throw new InvalidOperationException("JWT Key Missing"); +builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(options => + { + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = builder.Configuration["Jwt:Issuer"], + ValidAudience = builder.Configuration["Jwt:Audience"], + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey)) + }; + // 🟢 LEER TOKEN DESDE COOKIE + options.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + // Buscar el token en la cookie llamada "accessToken" + var accessToken = context.Request.Cookies["accessToken"]; + if (!string.IsNullOrEmpty(accessToken)) + { + context.Token = accessToken; + } + return Task.CompletedTask; + } + }; + }); + +builder.Services.AddAuthorization(); +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// USAR EL MIDDLEWARE AL PRINCIPIO +// Debe ser lo primero para que el RateLimiter y los Logs vean la IP real +app.UseForwardedHeaders(); + +// 🔒 HEADERS DE SEGURIDAD MIDDLEWARE +app.Use(async (context, next) => +{ + context.Response.Headers.Append("X-Frame-Options", "DENY"); + context.Response.Headers.Append("X-Content-Type-Options", "nosniff"); + context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin"); + context.Response.Headers.Append("X-XSS-Protection", "1; mode=block"); + + // CSP adaptada para permitir pagos en Payway y WebSockets de Vite + string csp = "default-src 'self'; " + + "img-src 'self' data: https: blob:; " + + "script-src 'self' 'unsafe-inline'; " + + "style-src 'self' 'unsafe-inline'; " + + "connect-src 'self' https: ws: wss:; " + + "object-src 'none'; " + + "base-uri 'self'; " + + "form-action 'self' https://developers-ventasonline.payway.com.ar; " + + "frame-ancestors 'none';"; + context.Response.Headers.Append("Content-Security-Policy", csp); + + context.Response.Headers.Remove("Server"); + context.Response.Headers.Remove("X-Powered-By"); + await next(); +}); + +// PIPELINE +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} +else +{ + // 🔒 HSTS en Producción + app.UseHsts(); +} + +app.UseHttpsRedirection(); +app.UseStaticFiles(); + +// 🔒 APLICAR CORS & RATE LIMIT +app.UseCors("AllowSpecificOrigin"); +app.UseRateLimiter(); + +app.UseAuthentication(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/Backend/MotoresArgentinosV2.API/Properties/launchSettings.json b/Backend/MotoresArgentinosV2.API/Properties/launchSettings.json new file mode 100644 index 0000000..c717a13 --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5262", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7251;http://localhost:5262", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/Backend/MotoresArgentinosV2.API/appsettings.json b/Backend/MotoresArgentinosV2.API/appsettings.json new file mode 100644 index 0000000..ec04bc1 --- /dev/null +++ b/Backend/MotoresArgentinosV2.API/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} \ No newline at end of file diff --git a/Backend/MotoresArgentinosV2.Core/DTOs/AdDtos.cs b/Backend/MotoresArgentinosV2.Core/DTOs/AdDtos.cs new file mode 100644 index 0000000..8603c53 --- /dev/null +++ b/Backend/MotoresArgentinosV2.Core/DTOs/AdDtos.cs @@ -0,0 +1,120 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.Json.Serialization; + +namespace MotoresArgentinosV2.Core.DTOs; + +public class CreateAdRequestDto +{ + [Required] + [Range(1, 3, ErrorMessage = "Tipo de vehículo inválido")] + [JsonPropertyName("vehicleTypeID")] + public int VehicleTypeID { get; set; } + + [Required] + [JsonPropertyName("brandID")] + public int BrandID { get; set; } + + [JsonPropertyName("modelID")] + public int ModelID { get; set; } + + [Required] + [StringLength(100, MinimumLength = 1, ErrorMessage = "La versión debe tener entre 1 y 100 caracteres.")] + [RegularExpression(@"^[a-zA-Z0-9\s\-\.\(\),/áéíóúÁÉÍÓÚñÑ]+$", ErrorMessage = "Caracteres no permitidos en el nombre de versión.")] + [JsonPropertyName("versionName")] + public string VersionName { get; set; } = string.Empty; + + [Range(1900, 2100)] + [JsonPropertyName("year")] + public int Year { get; set; } + + [Range(0, 2000000)] + [JsonPropertyName("km")] + public int KM { get; set; } + + [Range(0, 999999999)] + [JsonPropertyName("price")] + public decimal Price { get; set; } + + [Required] + [StringLength(3, MinimumLength = 3)] + [RegularExpression(@"^(ARS|USD)$", ErrorMessage = "Moneda inválida.")] + [JsonPropertyName("currency")] + public string Currency { get; set; } = "ARS"; + + [StringLength(1000, ErrorMessage = "La descripción no puede superar los 1000 caracteres.")] + [RegularExpression(@"^(?!.* + + +
+ + + diff --git a/Frontend/nginx.conf b/Frontend/nginx.conf new file mode 100644 index 0000000..4744cab --- /dev/null +++ b/Frontend/nginx.conf @@ -0,0 +1,34 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } + + # Proxy interno al Backend (Nombre del servicio en docker-compose) + location /api/ { + proxy_pass http://motores-backend:8080/api/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Proxy para las imágenes servidas por el backend (fotos de vehículos) + location /uploads/ { + proxy_pass http://motores-backend:8080/uploads/; + proxy_set_header Host $host; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} diff --git a/Frontend/package-lock.json b/Frontend/package-lock.json new file mode 100644 index 0000000..4cf841a --- /dev/null +++ b/Frontend/package-lock.json @@ -0,0 +1,4200 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@mercadopago/sdk-react": "^1.0.7", + "@tailwindcss/vite": "^4.1.18", + "axios": "^1.13.2", + "qrcode.react": "^4.2.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-icons": "^5.5.0", + "react-router-dom": "^7.12.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mercadopago/sdk-js": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@mercadopago/sdk-js/-/sdk-js-0.0.3.tgz", + "integrity": "sha512-kO48DNLHdfFAp3on12nuKdqNlEmw1x3+nM6wLd04BdWOXoFcAhkNMQV3AyUIanXdO/bB/dENakdacLT29297EQ==", + "license": "Apache-2.0" + }, + "node_modules/@mercadopago/sdk-react": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@mercadopago/sdk-react/-/sdk-react-1.0.7.tgz", + "integrity": "sha512-babuAgsi1pQU+jfIQX1Sl0efp2no64OU/9yYK+RDz0NvSyARe+/D9ih9GyDNwVJJZuAA2lS4GF06oSxDFS5HYg==", + "license": "Apache-2.0", + "dependencies": { + "@mercadopago/sdk-js": "^0.0.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.7.tgz", + "integrity": "sha512-+054pVMzVTmRQV8BhpGv3UyfZ2Llgl8rdpDTon+cUH9+na0ncBVXj3wTUKh14+Kiz18ziM3b4ikpP5/Pc0rQEQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", + "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.52.0.tgz", + "integrity": "sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/type-utils": "8.52.0", + "@typescript-eslint/utils": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.52.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.52.0.tgz", + "integrity": "sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.52.0.tgz", + "integrity": "sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.52.0", + "@typescript-eslint/types": "^8.52.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.52.0.tgz", + "integrity": "sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.52.0.tgz", + "integrity": "sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.52.0.tgz", + "integrity": "sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/utils": "8.52.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.52.0.tgz", + "integrity": "sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.52.0.tgz", + "integrity": "sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.52.0", + "@typescript-eslint/tsconfig-utils": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/visitor-keys": "8.52.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.52.0.tgz", + "integrity": "sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.52.0", + "@typescript-eslint/types": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.52.0.tgz", + "integrity": "sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.52.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode.react": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz", + "integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", + "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz", + "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==", + "license": "MIT", + "dependencies": { + "react-router": "7.12.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.52.0.tgz", + "integrity": "sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.52.0", + "@typescript-eslint/parser": "8.52.0", + "@typescript-eslint/typescript-estree": "8.52.0", + "@typescript-eslint/utils": "8.52.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/Frontend/package.json b/Frontend/package.json new file mode 100644 index 0000000..85e32c5 --- /dev/null +++ b/Frontend/package.json @@ -0,0 +1,39 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@mercadopago/sdk-react": "^1.0.7", + "@tailwindcss/vite": "^4.1.18", + "axios": "^1.13.2", + "qrcode.react": "^4.2.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-icons": "^5.5.0", + "react-router-dom": "^7.12.0" + }, + "devDependencies": { + "@eslint/js": "^9.39.1", + "@types/node": "^24.10.1", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.1", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.1", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.4.24", + "globals": "^16.5.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "typescript": "~5.9.3", + "typescript-eslint": "^8.46.4", + "vite": "^7.2.4" + } +} diff --git a/Frontend/public/bg-car.jpg b/Frontend/public/bg-car.jpg new file mode 100644 index 0000000000000000000000000000000000000000..582846c05ad62981c57f8fded7331a8ee6677ec3 GIT binary patch literal 136082 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:cehK-;HV3G?>1_|7uJ${ZUS?LjUND!bDF=sfWja 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%0AoApPRg%SS9k`2$SF0ZseAylB&HE}S^``O4g* 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 A!T + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Frontend/public/placeholder-car.png b/Frontend/public/placeholder-car.png new file mode 100644 index 0000000000000000000000000000000000000000..1649377c0f52ebcd58c1f0bf914e3fe77596b65c GIT binary patch literal 20937 zcmcdy1ydYNm_`yLxCUKhkzm0gI6)V8C&1$F?m-u4aVHQgI4tfi!Gde>;O@@x-Cww> zo2ssv>6w20>33QtLRnE7;{(YDI5;>A8IXi39Nas}w*vPL@vY|i$AkLY3Eo9jS`4mo zoNWK?2GtRy;{peV+52CCA9N@&eXAr@mRFa2eSN*Ty_uYxtgfz(j)?|?!TkLE?Ck6e z3=BNmO+IjND$+6%U)4RAj%I2w5Wb?n6LmlqW)xLIo`EB$B%%BfatgO3#j4x1T|lgJ zINFW$`Hk@Z(?K{Xy-krw`>Q~2*hWqxhRkNhcmDu~h4u4ancRkvW2aof`D3yGQ@?ND zejPWTK0c=D#H!_<+8ml%dfEL=J~ufvXxNgnXM;m@1;8D(iSkw~J5QxYw`-CK*z~KI zvkXd9_;ttC4}3nWF|jyuqPAgPw1%bLScBw|LGw&S`pct9bu&D1Do&}97_49Bc`o~14S%j<5~R%*ngY@psB{EH zF;=L;d&iy2)0yb3MtdDo@gZ6xZHt_=6@pjFgWpKt$_~qaj^U4NqJB6@Nj|%vHZd(q zYw7y*@K&2NRAY=DJKZoQwnXN%^&W__z?nd1zXLiEXGe-^NkC8F+uTBl?xKF;GF%#+ z+FZdf`PBZcVNsexS^+h>2ghvD9FU%8BHm9_?w$2^NuIakWW<$WR<{T)lH5l$iI^WD zvoQ~gz(U4|mISUHjwv$HlMuyq4*3Z4u3U-!B9iGJk*DDV9t%|vDb7fq%-k4c4JkVpBdh0MUv?lsULhymoyKh@H8@= zSQ3mfRm>3WED{UVZ%692SlmCjyXO)e1A8d>e ze4vBLec9!@nI_RbiZCoVw24}j_skeKkr|GZcu&M=C^jMq=8Lebbf+!XuWqpIYl^vhBMkVF21#9stUpe(+=$$t znP!f+Wv8Mpx^ynYR}EbH5l^n$-Q~h0bs{McQQkO_kl=ukdI=~AS%4NE93*2 z=kmYAv_R~DJ0M*?me$Ft&ZP8;P&dz?`NKo+nC*ZUFx@!a!VwYY;o-QYUy3M=MPy{G25mRN-%8T_(P31qHlLbD)VSN%-L0`Z``c(M` zr7NeZm6pA}rzc&CC;A_G{>cvshcjhPeXs$69+*uZ2LOi<*olTmMLm9f9#&5+7Ioqv zTJ^n~%?K{Ai(Y&$kOg~gi;+7fQ0(U|B*2wAU6Lm@y|oklgQsPhIJimdd!*fZZy*zw z5u6bTn})zQ*%ORaww)bp%z5Rf`9OHBcMTzaIiS)oCZrI=VEBjHk)Y=yx7Qw@?{MpZ z{s1D9g%2M*^yLCtHRs(<`WMmZ=x}w(@Ovxy^cwENJP{?dAsM+`)h;-OulJ8GEQ@nW8boK!CckcRwN#xaQ zqL6e?C-pkx!TaS{hE(1)5?W+PP**H0m1&$~xG@yPjfB4hJiq@~>U$#-z)fWZU_?+p zY9cWkBc?WtiP-ytzxUH<{`z|D4Tl|o%jW|e+R^H*73_~Aw)l76VK5Q1V`Ca_E zck@QL)O_kkI6g4^VZPxCR6LiT3ryF>{RtMo{$|wGePQnfuvgg(SJvs76g5N{F96LS zMi{4uD(ZX2A~Xg8PE9#GzT0yl&74LP10+mUwIF97JD1JKE`XDGzs?-UKI#`5T%?dH zGUBRv>!e+E{Mn{NO2Kn3OYGPdH($pT=A|$ez0PPC&n=>cH$| zxfN9nuPGM3Qj|y-(nZGy*JrW2iY~OmUSwA3@6aQx zj?REfGhggjo93SjFndYYDuF$fZ#D25;5^4Gk~tB-ndgJS;#~>>t6p?qZsjXt`j1gDxJzwVSC8@TVvMe z)K07`w}0wU(Ba%4GoG5K_DP+6o}LF~Z$r%tY$%lyKe z)vGgZ>u$ykPkJ_)ld#fEf=ee3<0XO%&_u;SLyg$0Q=-PzRd8d?X&cOyJnM2x8Pb#} zL0JfD_gf;ycM645yAzU+I8*Sapdw=W6%kAY3{7t|67bI zj9a^FB*DfxZ~W~l^~ibt(9%HHBQeX`-Mv_S1tKC&jU!o$q~aSO?0Jvp-k!$qJwtH!2Y3vwy(Yd&PUaudW7O0Ll48_E4)iM9xAnv8IFzEzkjNJfqBH3sbRZ> zTPA-cZ@Bf?o&>>Y6DRr@Rpk+EI3GKaeG!GTlBFa-A$^wlBL&7d`8TuJQ5&0VMGz>e z7hb=+Ip*@GLSFB9?mj=F7j?)`wQ@x(_lmwEo|}kjICIZcAL7;}R}>gcgxVMnkZTn) zK-&|(-_1fjzN$W@p*LubQq>_@GKiOnZHof`6MFQ1SNG6d(WoYUgkQ{Z_owOt@^}&A z)b8(i<&Se+1v0Nc%%KPESPVMvqC*--RA8uaMEJ(^wZ4c^b~78p zRD9^nNBbr~!a*;Bz13hHux6BA!ohS0ui-4E32Iq>xch{OMJYv5DMtwN-XrlXNu8t! zZEeA)1M}$nRcnWYoNEgwu26JFNc(Si^f^vB^7KiquETrIXojRe;6(U4Uovq$Sp9~1 zr%DMqcsDnVX_^A1sc3Eck~0!{0EdWpq!yh?qdf<_T*+X9?uKBBYr7))IiN8O0`_NE z+2T+{+=6`CG>IFc)b=|pO8<(GwBd5vZgQ0&UEekyot59wg~BK6U6RpZXu>HvG?|`Q zG`2-F`%m4RLJP5XvYcyNV*Nfs-iXbhlh`1!#B#3yLzogQG0j zDQTP3Wu)P!nhBB(N9uolPF{p!fZ$D>lGWA#Vde|6X^p+eziepRQl+x6cnUquAd$&q*22-$gmUSn^F+fu>*3-fe#h>@@ecKPVy#^_O1AzNxmTwx_u zko|IykiT&tWZVb3(>9N}S^xxi8ySd@s3p6m|*TA^q6>BMV21kM4>-OIyHU^T@becSg*$H15qw>){;w+<# zMD6r`Tgy!EH7K}LO$#03GMj=aly?;UqN!Z7p%n~EFMJ5BK{2eY+V84+_CmICAO#~% zFVW%S_?*|^6H-m?sb@6D{2^f}w08elBANtmz=FYnuli#)TYSEn01+In#FfWSqQz|H);02J7AQ^ zjtu)`ZPb#?W%YhE-+^Kwo(+i#xk6}lrB&0v-<0dIyf+~owh$`!l9Zl_yO+8aTF{6g zclz+E5HN_(%9c>oT-a7VZ*E?bjV+wo-a@>%9LFW=dY*EG_+wJ%Vle zh`hRQT{~3|#Q&5=*02s|q{30(jzMvZhpeAsUF+qypMj$p>T1PHDkDO@fw((Z4Qv=i zB-A^@V%PxWi{R1yELwFux0fbcN5od-PQkWgR^^>qJ8$(Omky{&rTEBd#NM;Mp`}B} zvW;?!*O_E0!5hL!u@vIt#QwlE{VUm>_G`r|95-3HOzjK(EHW>PY;r;rh$szhSh9|g z-fB0E4d^EHmxwOIFL1D3;>)YLwxzf{;~0XBAu_r4M|c8F5wC4vP;b{Zf=u{U$Rh+N zcX!ldu?)M0jP8iK$b}HRS%k4}(4S4Zfm$YRm4@ynSPr%Kqqi;jz{8A-L>E_T)ywMJ z<@cp2>fRbn1XTmR6{U$GRQp!X%||1-<8YExGJ{9*su6>{7-t;VG5*N6{B+_jc)I;G zVS3=w*X)^H9Tnmt-ZK+Q`q3&?OkMG>wM?gVRgeio7O8OaeZm%$&^CC`&(m{7z3e4t zbLca<`?;H9Sf6FRIG+BA-R^8kpnDzTLtMo_@VDc)0d|ceQCgFf>8+Jro_bsJeYV~K5>fmi=d)Z!6 zfJkFBgv#8Q&OY_4r`z(C*a|m|n2jQ~i5QtCaB-H4;YKLw>`>=gZlU*9y1->FLyNaP z*<5egTgsRBrDPtb@H8LaU>?LwjYQix(D>;lE2b4LJ^^DP)FoGTOqUlU3q0nC0mb2P zQG524Hrw%F+?>YH~>N>WRwph>~)(7Mqur>VUH@y&h90CjI#$ zz+ETc1otdJB%3(qo3>m{KA_mCkRBD#%Q4aHkT}QyreKEqIE5?Vct)GJ@sfgaf=LDz zH-8xs(2kNko+Jcydq1^v)V^Tn@f=HOv-h8h=hA2RkDzrb08B(u=HR1*yb+#QP>BhK zb__HLdHQCnZWIH4t!GAY4Eg)(fc%?DR5!*!Z02ni7Yha^7vM?L6-i^*Z4Hj&%JN zI(?j#rWe)H(gTW1`UkBazXN$#mKWX#N)@#mgzF0 zSqQL_nqFl6`7Az*c3M$C3`QnIxl@HMu9Os&at=Ca$84w752IouMN5yPM`6mT$Ni}6 z88sLT+*Yk^;hYS8Z#)-yD(v$$^{s-oPC zWDQ$_4R;+YjTj8fW1cK5`!-~6Rk$hgrLy$fK%hA~N-(Rh<2)yhoy*a+2q%opJVxFS zh>Aj$t%_zfcJT)7ULrvNsEv_V!@ha4n{LeAvJ*)M{*qTv2^k+no%WWRU1)io}-76FkCF9*s{k;JrPHQ}iM62V40clMc zjKV{ie8mpZwpon{SoTV_#`o+<6k@fWpHSs2#3iS!*mQfTIpplWyjk51Jw~D_u8?ZZ zuH|og`%e8OYV{qGpmAGDB~OU z|M@Xv-@1>rw@rv!r7roKwx>CH2bu>by}fz+ar1?FO1|W8l61%H*;PT>1N-CoaU2(1 z>JCH-?9b6458VjN2Nr{cB$GLrdFh_e0i=wCIh)QN5h;rE?2u+I`iwg4JYcvfOsc31 z=Z^EF-m_$4BNg+e6?SMNPIJ5iHwg&QaTb2eV0g?>mlfDIqi!cducn`;ZnM@LTT1*F zO3|*w2S~3YZ%$e*GMIEpG8o)r?g*|Eb-dG7{rJmJV$W;ze5hax%0zxwHPf;&IF7L4 z6Yknu44dU0I>;w(AX_@JDJp}mpVxf@&%(w+nmo7IQ8rA5kL!Qz{F0G$T#S`h2U2-? zsPP29%QiL((c#Srpb%d094Jt%z)Qw=FdiaFFOp)8uUshMm}7(DWC$r?BE|<{%@=N% zH>%+A>3*)2Dmt*i9S_wYor}Op-0tF{gn!xgivF|kwx?++ zyAArs=jWv|t@|?R4@7Wrv_YMF6CApo01E*Ql^D-H7vr5dSeaoT{%QD%3c8P)s@<;^ zyyn6BpHnCM1!v@z^+lPLJ};m(6`^!ZlegfWtI)6opo#YKgmHg-E^eQzU} z&%4Z{rC35GV&YdZj> zZKN;0;0KkI*AsoA)KQ;u=PDPcBeQ*rG9$r@UtE2pq;vOGXf)^%hAn5|D8r>6s&w$t zGIx1Q4LQHYtT&L0Mut`HNCx#+OZYNtL{z9)iP1AGltWSHbgE?3Nv)OAn!8XulW5xR zX9{}bFEeE3bPRD4uawGhQgYc47&>UJ_;|)zDX5mA4A!PPAMA=eV{srGQ!_4p%>q#c zqK`o^VanAkQ>Y_K2vgT5EY1>?B$kp3r1{DjCE{115j<4mq-+D}lQFY^~7Uw>;a=z)w9wPzgVyCn{SD7mA8QrG`o@S)UjIf z^fW1Ewqm4w3j>0{Udy|LrN?UPv|1^;B50sCt{qXAZAn*JpQ@k2q0K!Jim;6-Kgsu2 z9-4CS_meLEp0tI=v8w5o?2SZQX}TgDefDc~RJ@oH9KZMq7JP2!EN%{#m@g|I7&2Zt zI=dlvt`&b#FhcfNb%1IkeynppvzNnH$H=7mwi4}sSTD)X^U+n4c9J}2JUg$-W=u=*&_uTuifxf`i{0rIYs<9!Vw}px<%f)+L6PpUY_#6&) z1#LUkQVbbsGZ5_$`(RGv-?3!6e^eASFGHDZ_G-_EL@r^uKKdq1mCZnAW91C-=(Wc# z4T8t{mc;i8Qnvk*#5JbUpCe|fGkm`R*2lQ$Bn+8KfFA|EVqCV<>%PrLS&foA| zNZO(g6Uf{yT_;i-r?1x-!d2hjoo~kEFK;i+%wF3)j3-L);Or+QhWsuLg|*2;ig^)d z7E$dX(8W*k+t&mK6!7i~ghk49Cq7cdr?M(`r__S^)A#Jt{q#s1-4JF*jA3h3DFYQu zk9yrkU=h(hCNb_iZp|GA)*p@kexWY%T+|N`0vo5CW%rW~nc4zqe8NSW$p2R+UR5S- zMgK4s)*o`XJRd;I7QO4y3*hZQPO1x`lukX3^B<&J>sJgpaVIccV+rT8!n60RKdSJ^ zTN>YIdt#0e0y#vLvqdN(Gsdy7Oay6#IqM`Vi7AozQXp{gF-eOSi&3xrk*<52sA%SLt^Hn7a>|-PEf-=&0Q**UDdp1TYcAi{ULImIMNUcTp{$) zAp$A~*Jo%a^fl8H{_jLDI%tf*G7Cp98XIDH@hwx52$g)#$?D$zm|H4^=su)WT%NEV zOf03WPu2aNbOM%PEU}rv5mUd@hX!#KsY6HSd9xKR5DrB)$p1iZbsC^wrQ_EClYU%9%YSPljI0Ls^twu*m85KulN!heKb z!8QA`QOh_^iL>3Vx6<;9I94N!kgm-al&@6IxwH7d(~YmVF)26Frun{@L8PZF;7DjR z1pT`2{oqA=7n z;&Xm3f#^OL35E-ts44$#+5>6jxh7(4R{>+Yim$}>>gpJqIfrGhMT$ldBvnzo|6s;Q z;-{0nBHs@W*P;srQ&%jwhfp9TLjVykSi-*e(27OT;ol^mP|ri8Hf;$(xfXZsy1Uv#D&ZD>fv_| zqyaoKe#08HrwK(IGWd#`ITST+DKN?S`FCuMGz0s;>E1e`wuz|HxHZ#TuLAYrp6I6~pb@bIt9FmwN%5MllWjaZS4T{00RM+pmTgvt@3C>U6`i-$u7uUcSzu$G; zZjtp<%p1$F&sY!uh7m=4yUz#R+-T}+P+ioh%>pmZuJ@c`-n`u({YjamvK0DQ!&LUC z57Pac_{}nRGuA#L&-TE1^>xp`;4m@N4V^2p3|gr>%vubp+5XfFKSQsiCk%ON;s+4O zk+6*-gIKD7xCv!9QnT_`(%N=Nm>^)8+~>$90=i-SnY7#a*H=$=O~rgaxcA}FTzAvE zAYr%tZ%K`M$%u#MV1>2YL0?VW2*In?)mus%Yy7R3 zGNzyFz*hOo(_zx~_tPcq3n=kF#*yGzkS ze=ARPU5d%)e8W-bkA%xAn&|ZRsamHt!2)!Erj&5sm4I0pbP<9na z`N>VTWo5HvPd{Y-`2w6f0!{2Po+PJADwOl)(%9<4sfTPYC%z?w0gx>6k40Bd*4My= zc|H-U%i)a;8Px?ki@vmpj61l%Prn?~LYHNQbn&pXO8?OhbDv0A^q~OrPXjAHTa4w~ zeQ|ytBTgjn)V;dc#tWsf63q9$0Wf&G-)b{%#R0bf!m*sI78@;FCj)+2NV@43!Iv?I zX>{TNH^S2iS;-0P1M2rUp90oZlL5h4-sCvM#KbCudFQkLBx9r-yseIoy6%)6X)?r2fwZhuyv$~;B1&i0 zAoclzqX!jev=L*nxk6CfS?Lv73O$a}Ij6eCW7MsWk*M5kBSD3R*Vijw_# zq`sV<`0c)ll;eBVcw2mS=*EhsBXtS_QY?J- zhzx_52^{E|b$o%w-ofc!mM>dMo2w-lcbc22ksZH(hYpeIKeZz+-VxqjdD`@w9pV`P zf(a-o%h;p(Lp1>O5Gk9DGzMej4ZymYYjczs}o( zmA0tOOU=zZSa? zLn#ULuxw6(C2Rv!HT~c>W6wawrkk;wopWhoV))e3~P2scj@%Zi$Obc*ch4?_eD~ya`^UG|q2F;?V{NK;3GC9_?77=T#qSxzLmya^{8A=X zV$lx4R56*Wmsl&a7Pu%qO-szaLKnvl_&8>A|mCzz+6veXyQstRg;bog{DC5 zv6rUymyH!&_Oj5A*Aw!8)3E>^UvZp-2{vu>N&<(u`G1gaFh=8W<%@c+MJj*a7NC0Y z2Vi)JT8L)Ju3|IVWQT@8D40sg7rhOCIx%Lo2JPNkX`!tna<0HnThhy`TE_kA5|W#p zw{0h{Ayy0#{(x5FL+FHT{9%`+xn~3=Sw0{07xchZY8d9-`>6TbQ=)Pc2TTYZUsDJ; zEoLrT89LxDHVz57Nes1?9+-lFKN(x==vQJd_Z@f3Pg@)z*t}Cc@-)#Ex^;P(5{&4* zCwWe`#;m9VM&?z!;BKz?Sy$BK;PD1vuEsNO5~k$>L(_T;K@Zobq+c@LX~#%QO@sQB z(_Y!kp!ppBU)@DdC?zNv?IU`Y*XWs>Q(8pAT?&akjD$5~Z2bfg?z{xu`{Y%R=0O4M zn;D2SSgqN)=PsvPzv9oNL@b6mw}-hc#AClkaEowcNTF{C@!|d+lBM^urK2d7?mLEn zihF3pc)Qwm!I61?UdvkcT=3b+sFr#bw>#L=lRX^U9F$j(xMPZ0(AY0)k8NIZ9~Giw zchabgKD}tVP450-BbpTSrvY!~kJo){{D2o_bF<;#19zq_vEZ~Q&BPk?(b~#aRRMsQ zrvZHb8nK~V(wXM0gFtgHLs$mL+woaJlH|7vO_NER`{de`iyw$lj8h3OV+bq)AR5z4 z^koGMSM|GDpa!gKQP}q(o%cFra;uDjx5%R6xhBAH%6?;Co}~MJ+>L0;I+%s5pcXw#E0Mjci$vFX7j zrK0;n^_?@My|`=>XL%h%zVdx4MLJG2B3!#Ni3ctSQCdo9t#B>Zhuy6qc8|EKXh=8$ z0C466I9J+sjjR1@G~wPZ1h<>6jmC>iK7i)lV@AwOJCx6F8P8#%4`BJHeGd1*BgSsfk z;7BDe8FxGi7)fpX?9SZz1(U~^%T+trQCPV`oau`^Q3kdWnm~NB`Bh-~i)~8=*|mRP zbT!umyP!kKK>2%VBC^Ym{hC>4-ljc^g7`@c@q!3;oMbv3?NwAHNINSU0;-PKY+S=v zT*aU$u!eqgGxWtsVZ-<-Mww!q-JyY%dQ3;25NRG|)=i;1chdERK4f$HZzKB zd_HG**qZ723t1MxEQ zu%+mS3=yJTXu9O@+MYJToqoB=a|my_;>+N_E@2&gEOtSXYYJ|2mUM~e?*VJ7lYl2A z&YnUJXVco9w1-7MaKd;3*bzUj;2|SufK-ZdBTc5SLba@Y&HuNfNqfgHymelD7@iq63^L^D0tELW~gSn|XdsW=t=EUr24#fet|}41Yvit`xFGSwAX@aQwiI0W$X&ujZ*bXe!#4P; z2&8D`u@I!Xq|w}avJMBGBkReMhuVoGK#^_f%3f`}^!D^hK)7onl76!osk!w5!2v3N z%g{nH?c{0FbbU?d;|6)1%sw=)!G_SZhY;2tIusD z(erC2|IWtJ{~UFTJq|>U)*2&Pw=l|b%7Yf4M#inQOGSr2bGd#?5Faf$Lqq`c_*>g= zEy6$M=q|Acv2Am_oROA@9u99!zrRbA0iAl z4Ul6I%}QlD3>xvJN^|?CBd0i-9u1^ubxWL=Hm@fEe`Nhhtc^%{1FSsC&VR1LP0i~V z@+fw0LQlO7NfDyfwshl4dt2b1h=}LH67TZ^cg`+BGZ)@grg2zf^{AcuYU2@_>-~hm zRL1E?&zpHoteb^p?%Qws572KzbnYrLFJwQC)&-?&>RBt&`B+Tu{DJt0!dq&&IpR4( zLVS7Qiv!SKQ*-yWzO8s+uU;3i%VZ z%|l|TPf*p+kV>Ryu6C}P4pW|~TPl|y*HW!l5dn}uhLgb#hBNcu+~3`SQ^Y565ZZ2R zBiJJI!{R7h8baXaK`G9ym>{i|&%eJAd4n|XBWeI#ymj9LYTu-Pi19~uILPj7nC}N^ zyz8?C6)wS2l6(9f*MiCFpLZkt)z@k$H~}Ahd+axln+D1n*?@w4)q&Z46Ke`(62cgG zB3Z=lxH0q^aJeBLX`^sjL4A@es%_*w&AKKY5GoSuipvVC92-bjw%C}fW>l2@L~nX% z2syQNG(5fAUmv+N(+g?__h>Iqz3H>Y(9r#j*nhg*rAqYN2JPIj1zs7r?jO(`Y$C7c zQ1B*m1{F7;ExLMF#3(8*+co0vs;Y|gB`^5C*!$K6eiMa`&Wm&BVk92X!yx38jQJ0V zBvt;OsmC}eYS0Q$Vp8p9hb*Z3rp6MDKUPB)G$K~|7r7n}YS7;GqRzHJTZwQp+R{vr zAvgcjG%%!2F!7vr6D8mIMva0$_&g5HKCia+(wu-ngl8<9&$h95K7+H-zEp{R z*9Yeq99mI4hY}^@lC7L#eWPRFd(JR6;jhVOW$#$UhS-~TBD{U0Xs!phhg8TlU9?QO z>)QF)e1rRv2?={ z-7|vG=758|yVCOy^?SfG6Ba>I{K1uMui#v1-`SGS^rHS>?0=n1q7P7?dHmffaOo24 zutvcDBjP}SF4{k1l56^9nXLo*< z9H?Jcr_uUoFz`}JtM3;w-}e^&16geKnaz0Rq$@LnqDGegwR*yEIB(ZgUV}fmS#~$nZ8c`g%EE%%K6|b$#Cyg2wW-Fy z!N{Ak^xah5atUDDy%3_%I z=aRe5YXJj&a$x$f!mM;(bJX-Lb~(ojNnMgb%yu%9A2#W52Q4Cxne*NW295lxisOUm zZoaX8O9^EL?Rt&&8e4PE4()k*Ite1jrJ}*iq=Y!WjhXgeQ+*Gwj@C%`>y*STAN~v+ zjl4=>O5};QjP1$$IE+RszBVy^umh}9Fp?ufu+)SzBOaD3McD`CEOb+9`P;BYkM6JY zW*Y1YQ>CXkuG*PL@V0&7y$%JxiMc65SM`daCp0EGQ`aWb?FG%LYOpC9T}K!bT*6{X zvd2^{Tf>F-k0s9?_hhgRmn~^F%~&}T|3HMOPO{~Cri?-)aZwz|VszRV(+ zJMR$t{-lg=5mbyGUkO7@bLEK^DTgPHGY9Epa(C-Hs$}-ZgWQgb%EGABeHM>CEA}eI;kvOEH2Dq#AJ8*>N?Ixdpm5ICvBnej2soz<;0O%VZj?7LrDK ztd=J1Z*FjRwa^=22Y7nBK=9yAYBBV4MB}Pvcj#Nzh${EH&6YUE-9KC!GCDPrZqoh zp|G{T7f4F_G_`+Hpl8aR-J43Gp(}Nn(7-0rjy2Yzb)n35$Sqkzql=cI6N}S#W7QyG z)1~HdF8B<+L{{`JN3nI<%ah~g z_iwym=Aj^zkK_S~|NaUJW{_Kz&W_jpDSezTU(h>)Ks(;`(VM$v;*F)}Nb7V zmnv2HrN>9U>>D)K;l|(JmrZ|Amv()BG~2|{kV9`me;R+_93`XJNHF%SSEWiGqmz&%C)w8lmMLPBG5 z2M6Vy657bUC~aHeJ=7XmXxFNLA#&kLgZmc4HchMaa)-b5d5|YHG`{%cU!@|seSCU! z&?kc_TXwA23X*9>ZcG0=KB>`N?>_Sg)xTVqiC+7z1^ut$g2v(bsKAf!nYh@UZ98@` zn#)_nF|o45LH0^A0%PvvaG1iPJ+jeXsxZf&rSOU4suQIRbcrKA%1IVr^A%|Yql^5Z z-(|L^3Q8^vUmY4&Q;|F&trcGdrBPKDQ-MP|N>bUVvFcycY^8-qFOU{VQnN;DM2gy7 zSHb&X=hA`*@UeecYipi5Cx8Q)Ca^qHq^I99N8K?w{ze*}5&~nn@W7>SipPP%#G_5- z)2}GNM2tpRD(zn2YVjjn?Tn53JHLct_=Jfb*6e;!39WOGa*Ws`?vNLa9rF$@F#_m- zJIZ@9(C;#U3SPiH<4@9=CSf@EYlvU*air@R%8np48UWt4daF%=%GT{1K?q?o*NQ>{9|OY=I~hqcB`2%}{VDjzzsI!E8z$widT}Uqr)}k1 zlUqTJUttfTkfDDBT;yFP0y`diZ5y8fDxpYA+f;iv=0y9+ZTh;s{`~{`!59yyrud0z zvs>)X`FvEsm!QgC6rRyi$`*4Gu^Igs+R`vkElk|v0;P90Y2gw9Z;^RKiut`X+H4XUQq2r>FY@G%V{{tGEeWik#_W`YyKrxbo4@u{`DI=b+cqg~ z0l_ziSBh{*P z<1E**-7ZDdU}5aX%BiP&3|`^&g~*G=YxRC{F|CZv_17R#xalkY{A}6fz~fL@P5J=h z-ARyxW!V)<5Q-FxQy5!FY;^{aExsoKv}rL7K$j`hl z`Zne9wFiO2L0_skgf1v|NUox-2tk~M7_@6~qTA4O2^Ei{{{HnWGO@XHkOh^ffq(-6 z(=Ct-PC(5Vr9$YC&Z~YD%&7Y%+{R0y-ohMl%uoDAfIasRhf=mlu7Vq;m4^s?(`?jB z=Ep>|ma<)V(0H@?ZyUomSXiBbB)oq`-V;}-TAfI{=B`u(G@|nh@nRw9G;lxS_P$n!7djoKO`DmQzohL7{Xdd9eWt#H_Js78oBS{!e zzt+Qf+49tVihE^_v(>$S6rTp>BX27w?jTc_ZNCB7wD0i2NXaGdj)q$Il_ahtjJn%# z_A%pK@)|8r>2%@(- z1_v!dW$y$(`{iTXoopHwn=f$9ek%Svmy#2C1m$HBs&%t}lGNJAp%i<>RthgNnEILu!{j1zApIoD+BmRdEuC9bV>Y|N4oA0ph1 z)N@9;cNg^QPl$Nly+i|0^Vg>A|LZ|Uc1e!=)xe)E`6O&aYB_!8BO2@S3LYOaUWgNj zQ`n~#*z*+>@}K3h@yna`4&ZnBw^h;zvF#r?0|xyomi_?a z5n*3(tF9FRsMdBZ(hpB<^G_E~t-CHhd1H&F6VIwWkwRsh?E^TweM zuYu^B1kM+NjoPr=4vMcdDXs<;0s4lS-V$hwUO`d-$JX5MlL+A|#AQ=txWrr;3%OsDbm>(5gFd5N>Jm(Q5^=WvXiXPh(vksaQF6d>o2crnE`eeui=YA%e+#4e-!mAS?+q+i4`*3 zLCKU*8AM-~ave`3@u?N%lh~xW(i}!%z7Mi_eCgRj41pee&Itopje@;v^rA;Ht0*{) zke!bEgsmLI;T9cV@x$nLUSz42niPNtX1K;7cy`>%EZd3v_xj>4hcaJfPNcEz55 zGo1D;DyJ2)^4{&%9!lcB$5#>g+(1rrIHVWX|IYlsIyw5n1Q+pz$GYFI@~r<;tg*ug zJe5r@_h4>fdiH8*qF;|PEurS4YZps?WebD`GmG80D^hCj;e z&>X#Zw0^)r`awypLE%(dNcn*HfBa>jXU77ku})DKbSPTCo2p6vF0nDhBQ6oMU)wH% zx`i`Z2YY6Oz5TUk7NLw- zT#h~zAN-%MO1>?*4K%dXg9LB9|1Pu|9-=52S4bEx84q<jx~+FQnM}p=4%(_y()=DU&f8$i z(_=FNratKRIJI>6kYbSAOAVT5`|F+5L8#MCc1c;1~4KhT@%_qSw_7&)ck76$!h`)Mtk6-T6-{G-yh zDs8^ilS909{QgPHR+)D!24pP1QQv<62=_*)dx)3CPLWe`80nYUhA@c@naEe5MYM)5nl`_JFV<<# z(7MyD6$YzTmRiW{XgSVSDD359d4E?16+HL%W7`NoX$Qg z4!U`QYDc{4P0tyG$sWkDP>-@wQ<(8aJ6nMaC%weA*?Xg_(T}YZox1?jM z;o~z4EsCs2P6%A+1HtMM-EHC}|V8gm? zHTB3v+iMy4qke2%K6>7fyXc&`9hFvaXx+#t%LQ=yzxp)TaK20YHzEZ z#kK7@9+}4frCywNRA4+^jR=%o6e8>Bg=zU)n=#n%sVWCUSlWX543+dJ3$r!N&b6%$R~{2t1^! zN*NY_C%*fW_lCI8JiSa9<|D#p4A(pck)D@zrsIViLQ+uLIga9v>b2$kz<+ctZXMC< zds88SB}o?ICl3NH1K!!jaMZ6Pj=zB_3(9n{iV^FZTO({B59^B0TZrJ%orlHTqZ>Ow zQ2uupz=Qcs2=~VjFjrP0uK5gS%}CQvRtQL5`(AIAhLg;mCS{%6CKnQ(75#-Q2ztz_oyJ7JMXeaU{1Zn^5&ZS zdUcR;+Goa&WBtU{HcRae*y6M5)|f{9p|T~nLmQW$cDbHKxDxP|0xM~m?ut(KGDFD| zINsfH`jCeRu9Y8tQ|E_d_9)GD?4MDh4q|99*uZ0Lvc;#mNAl;N7RGOjHmyy&(;$^$ zt~n~H=GSA6j&Oh12I% z=h1qo(<}fqawzcCNk&!e_Ry2f%(;>JvW3ij)W!3F+%#Rdjnh+nTUm_`DuHZDg(C2| zrsJqLv72#9l*5@j_bzx-w;6=|??wEDan}%BS`*E>u;ulL_H$)QF-2L#!n~t7pP{YF z&3(evFjzoCC`0?*l2j)WwjZ?HqT}yP@v|b}ft0wy^n?O!zSeG6rbhkm!ZBWZps51# z+{w5X)e6BId;~@;gi?Xh03^1}ja;>boHZ#K-k0(B`nMtwULV>|E{EIHR|nq`8*@#N zr%{A$!tLQ-Z|z%8*QG8bKQPE_64)FFQG23oyDqdyKN`dAGqK@gjxhMnrLYUlm->#?6oSd-J7TpNbXx>?^LYIKtnKs%< zKhn0*V9$8ruP8uOP*SehuXZ&n!oHk=d2v}JP-OvU=YPo*M`l|c_+pLHxX{|+pVUMi z&(ZsJ*&{=y%87+)#ue*3&`XZ6a~-Kd3iXE6&ljjkQmA^nGYPjEF&UO~gz zRLCjg+>e;k>xx0}vG8RJrj=jWo|i%WiZ~xHlVDtCF^IIPkI{(5nBuuNse0iv+xQoa zkjJgxP6!uhGtJy1Z&#GDNm8u#5wcwPC*ZxTsp=y8wV3wt@PH4u7Eb0{j!pXjn+T=uPT4OwgcIYrlmX3G3JD_7Fk@G3&%8t^vMl-k^#S8XY=!{cCpJ#W@-= zXP*#YkS`OeSu{@($~>To!upSoUX;)w^88Zb5!?4tIs@JZ0ZEJ3so5=PeNSFKmH^H0 zi{(P>N$E@ASHNda_ROaody(=XimzNWw*%M5G9ulwH1hVdZP0U9-JDO@)wYB~okK9E0b(r* z14*<9PBQ8aE%rNX@oPGSYgUaNfzIpqogyuZIFIr3={WUFI_8!>d+S`2y^4#PMMKUj zdjt&Ksi8?fWMK9C&1Yuxqi$`w@*}JxatT>-<&k^QRuknOG>ne(u#$ z9?iFfu)J4H3Y?7_xh{l+%v`TNqPGDxX5W0-Z(!4uUP_*8XNB~BJikc3byMnNx0gGJ zh10VXD|uolZ$BTcx~J=SO6PHe1d!GwPb}AP%f1o^#}z7V7FU5+&EAXqvsSR3P14!L zpC2jrA|b8H%_-_z7LCYO?-XD&<5X*Gb3tvTO90ab*ZYBz*~VYXLzK(L-`JC+YhLcU zMlx`q+wDloa#3Sk<*BpkxhpDu4sx?`9Aj}c50f1;SE>u{eFGaP35M68roLHj@47M5 zXMeq$MOr9dGFsZRwC(RIxWBl$uwjrFc@!8K*m(JsaSkLwlvQ^{&7P^eOTHnDf3}XFZr}kZ}uQ% zYqmqcH@##n-euvAAo}3PI?1qK3LnsCJ<+fQV47sB;%=cZf&ZUp6 z(s{;fqjKsReVDa!r!skA+7z`r7B}$OM>@iN?Or$UhNpQWGvj#>5HH&e8`+63#ow}( zxWCl$8L+2fJ2?s&sTs4*sIB$?&mp_MsdP}$SxVmT}3W=ussHy)Hr$Bd)un}9AJuG3d)9N?s2Ei zo=nG0#6203WX4Vh#t4)bE^iF7RS66}QCHAgei319Y|CEDosQjMf%^D1D~ygA_&uZe z#8-|jro~QnI8XARs#i%ko2VnRkiAhX+K_ET!e7tAva6=k<)yL zHd`^+^(oHHYbJ@-o?UVY2o|30)6wW&h5SDd`TsrIhS^k|%|@U&q(*`No6^yGi1?~u G74|=TX#J@G literal 0 HcmV?d00001 diff --git a/Frontend/src/App.tsx b/Frontend/src/App.tsx new file mode 100644 index 0000000..8dbc045 --- /dev/null +++ b/Frontend/src/App.tsx @@ -0,0 +1,336 @@ +import { useState, useEffect } from 'react'; +import { BrowserRouter as Router, Routes, Route, Link, Navigate, useNavigate, useLocation } from 'react-router-dom'; +import HomePage from './pages/HomePage'; +import ExplorarPage from './pages/ExplorarPage'; +import VehiculoDetailPage from './pages/VehiculoDetailPage'; +import PublicarAvisoPage from './pages/PublicarAvisoPage'; +import MisAvisosPage from './pages/MisAvisosPage'; +import SuccessPage from './pages/SuccessPage'; +import AdminPage from './pages/AdminPage'; +import LoginModal from './components/LoginModal'; +import { type UserSession } from './services/auth.service'; +import VerifyEmailPage from './pages/VerifyEmailPage'; +import ResetPasswordPage from './pages/ResetPasswordPage'; +import PerfilPage from './pages/PerfilPage'; +import SeguridadPage from './pages/SeguridadPage'; +import { FaHome, FaSearch, FaCar, FaUser, FaShieldAlt } from 'react-icons/fa'; +import { initMercadoPago } from '@mercadopago/sdk-react'; +import { AuthProvider, useAuth } from './context/AuthContext'; + +function AdminGuard({ children }: { children: React.ReactNode }) { + const { user, loading } = useAuth(); + if (loading) return

; + if (!user || user.userType !== 3) return ; + return <>{children}; +} + +// COMPONENTE NAVBAR CON DROPDOWN +function Navbar() { + const { user, logout, login, unreadCount } = useAuth(); + const [showLoginModal, setShowLoginModal] = useState(false); + const [showUserMenu, setShowUserMenu] = useState(false); + const [showMobileMenu, setShowMobileMenu] = useState(false); + const isAdmin = user?.userType === 3; + const navigate = useNavigate(); + const location = useLocation(); + + const handleLogout = () => { + logout(); + setShowUserMenu(false); + setShowMobileMenu(false); + navigate('/'); + }; + + const handleMobileNavClick = (path: string) => { + setShowMobileMenu(false); + navigate(path); + }; + + const handleLoginSuccess = (userSession: UserSession) => { + login(userSession); + setShowLoginModal(false); + }; + + const getLinkClass = (path: string) => { + const isActive = location.pathname === path; + return `transition-all duration-300 font-bold tracking-widest text-s hover:text-white ${isActive ? 'text-blue-400 drop-shadow-[0_0_8px_rgba(59,130,246,0.5)]' : 'text-gray-300'}`; + }; + + return ( + <> + + + {/* Menú móvil overlay MODERNIZADO */} + {showMobileMenu && ( +
+
+ + + + + + + + + {isAdmin && ( + + )} +
+ +
+

Motores Argentinos v2.0

+
+
+ )} + + {showLoginModal && ( +
+
+ setShowLoginModal(false)} + /> +
+
+ )} + + ); +} + +function FooterLegal() { + const currentYear = new Date().getFullYear(); + const baseEdition = 5858; + const baseDate = new Date('2026-01-21T00:00:00'); + const today = new Date(); + today.setHours(0, 0, 0, 0); + baseDate.setHours(0, 0, 0, 0); + const diffTime = today.getTime() - baseDate.getTime(); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + const currentEdition = baseEdition + (diffDays > 0 ? diffDays : 0); + + return ( +
+
+
+

© {currentYear} MotoresArgentinos. Todos los derechos reservados. Edición número: {currentEdition}.

+

Registro DNDA Nº: RL-2024-70042723-APN-DNDA#MJ - Propietario: Publiéxito S.A.

+

Director: Leonardo Mario Forclaz - 46 N 423 - La Plata - Pcia. de Bs. As.

+
+
+
+ ); +} + +function MainLayout() { + const { loading } = useAuth(); + + if (loading) { + return ( +
+
+
+ ); + } + + return ( +
+ +
+
+
+
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + } /> + +
+
+ +
+ ); +} + +function App() { + useEffect(() => { + const mpPublicKey = import.meta.env.VITE_MP_PUBLIC_KEY; + if (mpPublicKey) initMercadoPago(mpPublicKey); + }, []); + + return ( + + + + + + ); +} + +export default App; \ No newline at end of file diff --git a/Frontend/src/components/AdDetailsModal.tsx b/Frontend/src/components/AdDetailsModal.tsx new file mode 100644 index 0000000..8a7d016 --- /dev/null +++ b/Frontend/src/components/AdDetailsModal.tsx @@ -0,0 +1,124 @@ +import { parseUTCDate, formatCurrency } from '../utils/app.utils'; +import { STATUS_CONFIG } from '../constants/adStatuses'; + +interface Props { + ad: any; + onClose: () => void; +} + +export default function AdDetailsModal({ ad, onClose }: Props) { + if (!ad) return null; + + const status = STATUS_CONFIG[ad.statusID] || { label: 'Desconocido', color: 'text-gray-400', bg: 'bg-gray-500/10', border: 'border-white/10' }; + + // Helper para fecha de pago + const paymentDate = ad.paidDate + ? parseUTCDate(ad.paidDate).toLocaleDateString('es-AR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) + : '-'; + + return ( +
+
+ +
+ + {/* Header */} +
+
+
+ + {status.label} + + ID #{ad.adID} +
+

{ad.title}

+
+ +
+ + {/* Contenido con Scroll Interno */} +
+
+ {/* Columna 1: Datos Financieros */} +
+
+
+

💰 Transacción

+
+
+ Monto Abonado + + {ad.paidAmount ? formatCurrency(ad.paidAmount, 'ARS') : Sin pago registrado} + +
+
+ Fecha de Pago + {paymentDate} +
+
+
+ + {/* Métricas */} +
+

Métricas

+
+
+ {ad.views} + Visitas +
+
+
+ {ad.legacyID || '-'} + ID Operación +
+
+
+
+ + {/* Columna 2: Línea de Tiempo */} +
+

Ciclo de Vida

+
+ + + new Date()} /> + {ad.deletedAt && } +
+
+
+
+ + {/* Footer */} +
+
+
+ 👤 +
+
+ {ad.userName} + {ad.userEmail} +
+
+ + + Ver en Web ↗ + +
+
+
+ ); +} + +function TimelineItem({ date, label, active, highlight, future, color = 'text-gray-400' }: any) { + if (!date && !future) return null; + + return ( +
+
+

+ {date ? parseUTCDate(date).toLocaleDateString('es-AR', { day: '2-digit', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : '-'} +

+

{label}

+
+ ); +} \ No newline at end of file diff --git a/Frontend/src/components/AdStatusBadge.tsx b/Frontend/src/components/AdStatusBadge.tsx new file mode 100644 index 0000000..e202b3c --- /dev/null +++ b/Frontend/src/components/AdStatusBadge.tsx @@ -0,0 +1,26 @@ +import { AD_STATUSES, STATUS_CONFIG } from '../constants/adStatuses'; + +interface Props { + statusId: number; + className?: string; +} + +export default function AdStatusBadge({ statusId, className = '' }: Props) { + // Si está activo, no mostramos badge en las tarjetas públicas (Home/Explorar) para mantener limpieza. + if (statusId === AD_STATUSES.ACTIVE) return null; + + const config = STATUS_CONFIG[statusId]; + if (!config) return null; + + return ( +
+ {config.icon} + {config.label} +
+ ); +} \ No newline at end of file diff --git a/Frontend/src/components/ChangePasswordModal.tsx b/Frontend/src/components/ChangePasswordModal.tsx new file mode 100644 index 0000000..74cdda7 --- /dev/null +++ b/Frontend/src/components/ChangePasswordModal.tsx @@ -0,0 +1,141 @@ +import { useState } from 'react'; +import { AuthService } from '../services/auth.service'; + +interface Props { + onClose: () => void; +} + +// --- ICONOS SVG (Reutilizados) --- +const EyeIcon = () => (); +const EyeSlashIcon = () => (); +const CheckCircleIcon = () => (); +const XCircleIcon = () => (); +const NeutralCircleIcon = () => (); + +export default function ChangePasswordModal({ onClose }: Props) { + const [currentPass, setCurrentPass] = useState(''); + const [newPass, setNewPass] = useState(''); + const [confirmPass, setConfirmPass] = useState(''); + + // Visibilidad + const [showCurrent, setShowCurrent] = useState(false); + const [showNew, setShowNew] = useState(false); + const [showConfirm, setShowConfirm] = useState(false); + + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(false); + + // Validaciones + const validations = { + length: newPass.length >= 8, + upper: /[A-Z]/.test(newPass), + number: /\d/.test(newPass), + special: /[\W_]/.test(newPass), + match: newPass.length > 0 && newPass === confirmPass + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + + if (!validations.length || !validations.upper || !validations.number || !validations.special || !validations.match) { + setError('La nueva contraseña no cumple con los requisitos.'); + return; + } + + setLoading(true); + try { + await AuthService.changePassword(currentPass, newPass); + setSuccess(true); + setTimeout(() => onClose(), 2000); // Cerrar automáticamente tras éxito + } catch (err: any) { + setError(err.response?.data?.message || 'Error al cambiar contraseña'); + } finally { + setLoading(false); + } + }; + + const RequirementItem = ({ isValid, text }: { isValid: boolean, text: string }) => { + const isNeutral = newPass.length === 0; + return ( +
  • + {isNeutral ? : isValid ? : } + {text} +
  • + ); + }; + + return ( +
    +
    + + +

    Cambiar Contraseña

    +

    Seguridad de la cuenta

    + + {error && ( +
    + {error} +
    + )} + + {success ? ( +
    +
    +

    ¡Contraseña Actualizada!

    +
    + ) : ( +
    + {/* Contraseña Actual */} +
    + setCurrentPass(e.target.value)} + className="w-full bg-white/5 border border-white/10 rounded-xl pl-4 pr-10 py-3 text-sm text-white outline-none focus:border-blue-500 placeholder:text-gray-600" + placeholder="Contraseña Actual" /> + +
    + +
    + + {/* Nueva Contraseña */} +
    + setNewPass(e.target.value)} + className="w-full bg-white/5 border border-white/10 rounded-xl pl-4 pr-10 py-3 text-sm text-white outline-none focus:border-green-500 placeholder:text-gray-600" + placeholder="Nueva Contraseña" /> + +
    + + {/* Confirmar Nueva */} +
    + setConfirmPass(e.target.value)} + className="w-full bg-white/5 border border-white/10 rounded-xl pl-4 pr-10 py-3 text-sm text-white outline-none focus:border-green-500 placeholder:text-gray-600" + placeholder="Repetir Nueva Contraseña" /> + +
    + + {/* Validaciones Visuales */} +
    +
      + + + + + +
    +
    + + +
    + )} +
    +
    + ); +} \ No newline at end of file diff --git a/Frontend/src/components/ChatModal.tsx b/Frontend/src/components/ChatModal.tsx new file mode 100644 index 0000000..4b07c76 --- /dev/null +++ b/Frontend/src/components/ChatModal.tsx @@ -0,0 +1,132 @@ +import { useState, useEffect, useRef } from 'react'; +import { ChatService, type ChatMessage } from '../services/chat.service'; +import { parseUTCDate } from '../utils/app.utils'; + +interface ChatModalProps { + isOpen: boolean; + onClose: () => void; + adId: number; + adTitle: string; + sellerId: number; + currentUserId: number; +} + +export default function ChatModal({ isOpen, onClose, adId, adTitle, sellerId, currentUserId }: ChatModalProps) { + const [messages, setMessages] = useState([]); + const [newMessage, setNewMessage] = useState(''); + const [loading, setLoading] = useState(false); + const scrollRef = useRef(null); + + useEffect(() => { + if (isOpen) { + loadMessages(); + const interval = setInterval(loadMessages, 5000); + return () => clearInterval(interval); + } + }, [isOpen]); + + useEffect(() => { + if (scrollRef.current) { + scrollRef.current.scrollTop = scrollRef.current.scrollHeight; + } + }, [messages]); + + const loadMessages = async () => { + try { + const data = await ChatService.getConversation(adId, currentUserId, sellerId); + setMessages(data); + } catch (err) { + console.error(err); + } + }; + + const handleSend = async (e: React.FormEvent) => { + e.preventDefault(); + if (!newMessage.trim()) return; + + setLoading(true); + try { + await ChatService.sendMessage({ + adID: adId, + senderID: currentUserId, + receiverID: sellerId, + messageText: newMessage + }); + setNewMessage(''); + loadMessages(); + } catch (err) { + alert('Error al enviar mensaje'); + } finally { + setLoading(false); + } + }; + + if (!isOpen) return null; + + return ( +
    +
    + + {/* Header Neutro */} +
    +
    + {/* Ícono Genérico de Mensaje */} +
    + 💬 +
    +
    +

    Mensajes del Aviso

    +

    {adTitle}

    +
    +
    + +
    + + {/* Cuerpo de Mensajes */} +
    + {messages.length === 0 ? ( +
    + 📝 +

    No hay mensajes previos

    +
    + ) : ( + messages.map((m, idx) => { + const isMine = m.senderID === currentUserId; + return ( +
    +
    +

    {m.messageText}

    + + {parseUTCDate(m.sentAt!).toLocaleTimeString('es-AR', { timeZone: 'America/Argentina/Buenos_Aires', hour: '2-digit', minute: '2-digit', hour12: false })} + +
    +
    + ); + }) + )} +
    + + {/* Input area */} +
    + setNewMessage(e.target.value)} + placeholder="Escribe aquí..." + className="flex-1 bg-black/30 border border-white/10 rounded-xl px-4 py-3 text-sm text-white focus:outline-none focus:border-blue-500 transition-all placeholder:text-gray-600" + /> + +
    +
    +
    + ); +} \ No newline at end of file diff --git a/Frontend/src/components/ConfigPanel.tsx b/Frontend/src/components/ConfigPanel.tsx new file mode 100644 index 0000000..8ecd643 --- /dev/null +++ b/Frontend/src/components/ConfigPanel.tsx @@ -0,0 +1,196 @@ +import { useState } from 'react'; +import { AuthService, type UserSession } from '../services/auth.service'; +import { QRCodeSVG } from 'qrcode.react'; +import ChangePasswordModal from './ChangePasswordModal'; +import { useAuth } from '../context/AuthContext'; + +// Iconos +const CopyIcon = () => (); +const CheckIcon = () => (); + +export default function ConfigPanel({ user }: { user: UserSession }) { + const { refreshSession } = useAuth(); // Para actualizar si isMFAEnabled cambia en el contexto + const [showPasswordModal, setShowPasswordModal] = useState(false); + + // Estados MFA + const [mfaStep, setMfaStep] = useState<'IDLE' | 'QR'>('IDLE'); + const [qrUri, setQrUri] = useState(''); + const [secretKey, setSecretKey] = useState(''); // El código manual + const [mfaCode, setMfaCode] = useState(''); + const [msgMfa, setMsgMfa] = useState({ text: '', type: '' }); // type: 'success' | 'error' + const [copied, setCopied] = useState(false); + const [loading, setLoading] = useState(false); + const isMfaActive = (user as any).isMFAEnabled; + + const handleInitMfa = async () => { + if (isMfaActive) { + if (!window.confirm("Al reconfigurar, el código anterior dejará de funcionar en tu otro dispositivo. ¿Continuar?")) return; + } + + setLoading(true); + setMsgMfa({ text: '', type: '' }); + try { + const data = await AuthService.initMFA(); + setQrUri(data.qrUri); + setSecretKey(data.secret); + setMfaStep('QR'); + } catch { + setMsgMfa({ text: "Error iniciando configuración.", type: 'error' }); + } finally { + setLoading(false); + } + }; + + const handleVerifyMfa = async () => { + setLoading(true); + try { + await AuthService.verifyMFA(user.username, mfaCode); + setMsgMfa({ text: "¡MFA Activado correctamente!", type: 'success' }); + setMfaStep('IDLE'); + setMfaCode(''); + await refreshSession(); // Actualizar estado global + } catch { + setMsgMfa({ text: "Código incorrecto. Intenta nuevamente.", type: 'error' }); + } finally { + setLoading(false); + } + }; + + const handleDisableMfa = async () => { + if (!window.confirm("¿Seguro que deseas desactivar la protección de dos factores? Tu cuenta será menos segura.")) return; + + setLoading(true); + try { + await AuthService.disableMFA(); + setMsgMfa({ text: "MFA Desactivado.", type: 'success' }); + await refreshSession(); + } catch { + setMsgMfa({ text: "Error al desactivar MFA.", type: 'error' }); + } finally { + setLoading(false); + } + }; + + const copyToClipboard = () => { + navigator.clipboard.writeText(secretKey); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; + + return ( + <> +
    + + {/* SECCIÓN CONTRASEÑA */} +
    +
    +
    + 🔑 +
    +

    Contraseña

    +

    + Mantén tu cuenta segura actualizando tu contraseña periódicamente. +

    + +
    + + {/* SECCIÓN MFA */} +
    + + {/* Indicador de Estado */} +
    + {isMfaActive ? 'Protegido' : 'No Activo'} +
    + +
    + 🛡️ +
    +

    Doble Factor (2FA)

    + + {mfaStep === 'IDLE' ? ( +
    +

    + {isMfaActive + ? "Tu cuenta está protegida. Se solicita un código cada vez que inicias sesión en un dispositivo nuevo." + : "Añade una capa extra de seguridad. Requerirá un código de tu celular al iniciar sesión."} +

    + +
    + {isMfaActive ? ( +
    + + +
    + ) : ( + + )} + + {msgMfa.text && ( +

    + {msgMfa.text} +

    + )} +
    +
    + ) : ( +
    +
    + +
    + +

    1. Escanea el código

    +

    Usa Google Authenticator o Authy en tu celular.

    + + {/* CÓDIGO MANUAL */} +
    +

    O ingresa el código manual

    +
    + {secretKey} + +
    +
    + +

    2. Ingresa el token

    + setMfaCode(e.target.value.replace(/\D/g, ''))} + className="w-full bg-black/20 border border-white/10 rounded-xl px-4 py-3 text-center text-2xl font-black text-white mb-4 tracking-[0.4em] outline-none focus:border-blue-500 transition-colors placeholder:opacity-20" + /> + +
    + + +
    +
    + )} +
    +
    + + {showPasswordModal && ( + setShowPasswordModal(false)} /> + )} + + ); +} \ No newline at end of file diff --git a/Frontend/src/components/ConfirmationModal.tsx b/Frontend/src/components/ConfirmationModal.tsx new file mode 100644 index 0000000..3c36b03 --- /dev/null +++ b/Frontend/src/components/ConfirmationModal.tsx @@ -0,0 +1,85 @@ +import { useEffect } from 'react'; + +interface Props { + isOpen: boolean; + title: string; + message: React.ReactNode; + onConfirm: () => void; + onCancel: () => void; + confirmText?: string; + cancelText?: string; + isDanger?: boolean; // Para pintar el botón de rojo si es eliminar +} + +export default function ConfirmationModal({ + isOpen, + title, + message, + onConfirm, + onCancel, + confirmText = "Confirmar", + cancelText = "Cancelar", + isDanger = false +}: Props) { + + // Cerrar con tecla ESC + useEffect(() => { + const handleEsc = (e: KeyboardEvent) => { + if (e.key === 'Escape') onCancel(); + }; + if (isOpen) window.addEventListener('keydown', handleEsc); + return () => window.removeEventListener('keydown', handleEsc); + }, [isOpen, onCancel]); + + if (!isOpen) return null; + + return ( +
    + {/* Backdrop */} +
    + + {/* Modal Content */} +
    + + {/* Icono decorativo según tipo */} +
    + {isDanger ? '⚠️' : 'ℹ️'} +
    + +

    + {title} +

    + +
    + {message} +
    + +
    + + + +
    + +
    +
    + ); +} \ No newline at end of file diff --git a/Frontend/src/components/CreditCardForm.tsx b/Frontend/src/components/CreditCardForm.tsx new file mode 100644 index 0000000..14f34f8 --- /dev/null +++ b/Frontend/src/components/CreditCardForm.tsx @@ -0,0 +1,378 @@ +import { useState, useEffect } from 'react'; +import VisualCreditCard from './VisualCreditCard'; + +declare global { + interface Window { + MercadoPago: any; + } +} + +interface Props { + amount: number; + onPaymentSuccess: (details: any) => Promise; + onPaymentError: (error: string) => void; + onCancel: () => void; +} + +interface IdentificationType { + id: string; + name: string; +} + +const FALLBACK_DOC_TYPES = [ + { id: 'DNI', name: 'DNI' }, { id: 'CUIT', name: 'CUIT' }, { id: 'CUIL', name: 'CUIL' } +]; + +export default function CreditCardForm({ amount, onPaymentSuccess, onPaymentError, onCancel }: Props) { + const [loading, setLoading] = useState(false); + const [docTypes, setDocTypes] = useState(FALLBACK_DOC_TYPES); + const [mpInstance, setMpInstance] = useState(null); + const [paymentMethod, setPaymentMethod] = useState<{ id: string, name: string, thumbnail: string } | null>(null); + const [isCvvFocused, setIsCvvFocused] = useState(false); + + const [formData, setFormData] = useState({ + cardNumber: '', + cardholderName: '', + cardExpiration: '', + securityCode: '', + identificationType: 'DNI', + identificationNumber: '', + email: '' + }); + + // --- PATRÓN SINGLETON PARA MERCADOPAGO --- + // El script de seguridad de MP (Armor) lanza errores de IndexedDB si se reinicializa múltiples veces. + useEffect(() => { + const publicKey = import.meta.env.VITE_MP_PUBLIC_KEY; + if (window.MercadoPago) { + if (!(window as any).mpInstanceGlobal) { + try { + (window as any).mpInstanceGlobal = new window.MercadoPago(publicKey); + } catch (e) { + console.error("Error al crear instancia de MP:", e); + } + } + setMpInstance((window as any).mpInstanceGlobal); + } + }, []); + + useEffect(() => { + if (mpInstance) { + mpInstance.getIdentificationTypes() + .then((types: IdentificationType[]) => { + if (types?.length > 0) setDocTypes(types); + }) + .catch(() => console.warn("Usando fallback de tipos de documento.")); + } + }, [mpInstance]); + + const handleBinLookup = async (bin: string) => { + if (bin.length < 6 || !mpInstance) { + setPaymentMethod(null); + return; + } + try { + const response = await mpInstance.getPaymentMethods({ bin }); + if (response && response.results && response.results.length > 0) { + setPaymentMethod(response.results[0]); + } + } catch (error) { console.error(error); } + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + let finalValue = value; + + if (name === 'cardNumber') { + const cleanValue = value.replace(/[^\d]/g, ''); + finalValue = cleanValue.replace(/(.{4})/g, '$1 ').trim(); + + if (cleanValue.length >= 6) { + handleBinLookup(cleanValue.substring(0, 6)); + } else { + setPaymentMethod(null); + } + } else if (name === 'cardExpiration') { + const cleanValue = value.replace(/[^\d]/g, ''); + if (cleanValue.length <= 2) { + finalValue = cleanValue; + } else { + finalValue = `${cleanValue.slice(0, 2)}/${cleanValue.slice(2, 4)}`; + } + } + + setFormData(prev => ({ ...prev, [name]: finalValue })); + }; + + const handlePaste = (e: React.ClipboardEvent) => { + e.preventDefault(); + alert("Por seguridad, ingresa los datos manualmente."); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!mpInstance || !paymentMethod) { + onPaymentError("Verifica los datos de la tarjeta."); + return; + } + setLoading(true); + + const [expMonth, expYear] = formData.cardExpiration.split('/'); + + try { + const tokenResponse = await mpInstance.createCardToken({ + cardNumber: formData.cardNumber.replace(/\s/g, ''), + cardholderName: formData.cardholderName, + cardExpirationMonth: expMonth, + cardExpirationYear: `20${expYear}`, + securityCode: formData.securityCode, + identificationType: formData.identificationType, + identificationNumber: formData.identificationNumber, + }); + + if (!tokenResponse?.id) throw new Error("Datos de tarjeta inválidos."); + + // 1. Intentamos obtener el issuer desde la respuesta del token + let finalIssuerId = tokenResponse.issuer_id; + + // 2. Si no viene en el token, intentamos buscarlo explícitamente + if (!finalIssuerId && mpInstance) { + try { + const bin = formData.cardNumber.replace(/\s/g, '').substring(0, 6); + const issuers = await mpInstance.getIssuers({ paymentMethodId: paymentMethod.id, bin }); + + if (issuers && issuers.length > 0) { + finalIssuerId = issuers[0].id; // Tomamos el primero encontrado + } + } catch (issuerErr) { + console.warn("No se pudo obtener issuer explícito", issuerErr); + } + } + + const issuerIdToSend = finalIssuerId ? String(finalIssuerId) : ""; + + await onPaymentSuccess({ + token: tokenResponse.id, + transactionAmount: amount, + paymentMethodId: paymentMethod.id, + installments: 1, + issuerId: issuerIdToSend, + payerEmail: formData.email + }); + + } catch (err: any) { + let msg = "Error procesando el pago."; + if (err.cause?.[0]?.code) { + const code = err.cause[0].code; + if (code === "205") msg = "Revisá el número de tarjeta."; + else if (['208', '209'].includes(code)) msg = "Revisá la fecha de vencimiento."; + else if (['212', '213', '214'].includes(code)) msg = "Documento inválido."; + else if (code === "221") msg = "Nombre y apellido requeridos."; + else if (code === "224") msg = "Código de seguridad (CVV) inválido."; + } + onPaymentError(msg); + } finally { + setLoading(false); + } + }; + + const [expMonth, expYear] = formData.cardExpiration.split('/'); + + // 🟢 VALIDACIÓN EN TIEMPO REAL + // Verifica que todos los campos tengan contenido válido antes de habilitar el botón + const isFormValid = + formData.cardNumber.replace(/\s/g, '').length >= 15 && // Tarjetas suelen ser 15 o 16 dígitos + formData.cardholderName.trim().length > 2 && + formData.cardExpiration.length === 5 && // Formato MM/AA completo + formData.securityCode.length >= 3 && // CVV mínimo + formData.identificationNumber.trim().length >= 6 && // DNI razonable + /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email) && // Regex básico de email + paymentMethod !== null; // Tarjeta reconocida + + return ( +
    + + {/* --- COLUMNA IZQUIERDA: FORMULARIO --- */} +
    + +
    +

    + 💳 Datos de Facturación +

    + +
    + {/* Número */} +
    +
    + + {/* Logo en móvil: al lado del label para no tapar el número */} + {paymentMethod && ( +
    + card brand +
    + )} +
    +
    + + {/* Logo en desktop: dentro del input */} +
    + {paymentMethod ? ( + card brand + ) : ( +
    + )} +
    +
    +
    + + {/* Nombre */} +
    + + +
    + + {/* Grid Vencimiento + CVV */} +
    +
    + + +
    +
    + +
    + setIsCvvFocused(true)} + onBlur={() => setIsCvvFocused(false)} + /> +
    +
    +
    + + {/* DNI - FILA COMPLETA */} +
    + +
    +
    + + +
    + +
    +
    + + {/* EMAIL - FILA COMPLETA */} +
    + + +
    +
    +
    +
    + + {/* --- COLUMNA DERECHA: VISUAL + BOTONES --- */} +
    + + {/* Tarjeta Visual */} +
    +
    +
    + +
    + +
    +
    + +
    +

    + Revisa que los datos coincidan exactamente con tu tarjeta física para evitar rechazos. +

    +
    +
    + + {/* BOTONES DE ACCIÓN */} +
    + + + +
    + +
    + +
    + ); +} diff --git a/Frontend/src/components/FormularioAviso.tsx b/Frontend/src/components/FormularioAviso.tsx new file mode 100644 index 0000000..6dbf692 --- /dev/null +++ b/Frontend/src/components/FormularioAviso.tsx @@ -0,0 +1,1043 @@ +import { useState, useEffect, useRef } from 'react'; +import { AdsV2Service } from '../services/ads.v2.service'; +import { AuthService } from '../services/auth.service'; +import type { DatosAvisoDto } from '../types/aviso.types'; +import SearchableSelect from './SearchableSelect'; +import { AdminService } from '../services/admin.service'; +import CreditCardForm from './CreditCardForm'; +import MercadoPagoLogo from './MercadoPagoLogo'; +import ConfirmationModal from './ConfirmationModal'; +import { + VEHICLE_TYPES, + AUTO_SEGMENTS, + MOTO_SEGMENTS, + AUTO_TRANSMISSIONS, + MOTO_TRANSMISSIONS, + FUEL_TYPES, + VEHICLE_CONDITIONS, + STEERING_TYPES +} from '../constants/vehicleOptions'; +import { useNavigate } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; + +interface Props { + plan: DatosAvisoDto; + onCancel: () => void; + onSuccess: (adId: number, isAdminAction?: boolean) => void; + editId?: number | null; +} + +type PhotoSource = File | { id: number; path: string }; + +export default function FormularioAviso({ plan, onCancel, onSuccess, editId }: Props) { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [photos, setPhotos] = useState([]); + const navigate = useNavigate(); + + // Estados Maestros + const [brands, setBrands] = useState<{ id: number, name: string }[]>([]); + + // Estados Autocomplete Modelos + const [modelSearch, setModelSearch] = useState(''); + const [modelSuggestions, setModelSuggestions] = useState<{ id: number, name: string }[]>([]); + const [showSuggestions, setShowSuggestions] = useState(false); + const modelWrapperRef = useRef(null); + + // Estados de Pago (Mercado Pago) + const [showPaymentForm, setShowPaymentForm] = useState(false); + const [currentAdId, setCurrentAdId] = useState(editId || null); + + // ADMIN STATE + const { user } = useAuth(); + const isAdmin = user?.userType === 3; + + // Buscador de Usuarios para Admin + const [userSearch, setUserSearch] = useState(''); + const [userSuggestions, setUserSuggestions] = useState<{ id: number, name: string, email: string }[]>([]); + const [targetUser, setTargetUser] = useState<{ id: number, name: string, email: string } | null>(null); + const [showUserSuggestions, setShowUserSuggestions] = useState(false); + const userWrapperRef = useRef(null); + + const [ghostUser, setGhostUser] = useState({ email: '', firstName: '', lastName: '', phone: '' }); + + // Estado para el modal de coincidencia de usuario + const [userMatchModal, setUserMatchModal] = useState<{ isOpen: boolean; user: any | null }>({ + isOpen: false, + user: null + }); + + // Estado del vehículo + const [vehicleData, setVehicleData] = useState({ + brandId: '', + modelId: '', + modelName: '', + year: new Date().getFullYear(), + km: 0, + price: 0, + currency: 'ARS', + description: '', + fuelType: '', + color: '', + segment: '', + location: '', + condition: '', + doorCount: undefined as number | undefined, + transmission: '', + steering: '' + }); + + // Contacto + const [contactData, setContactData] = useState({ + phone: (user as any)?.phoneNumber || (user as any)?.phone || '', + email: user?.email || '', + displayInfo: true + }); + + // Sincronizar contacto cuando el usuario cargue + useEffect(() => { + if (user && !editId && !targetUser && !ghostUser.email) { + setContactData(prev => ({ + ...prev, + email: user.email || prev.email, + phone: (user as any).phone || (user as any).phoneNumber || prev.phone + })); + } + }, [user, editId]); + + // --- EFECTOS DE CARGA --- + + // 1. Cargar Marcas + useEffect(() => { + const vehicleTypeId = plan.idRubro; + AdsV2Service.getBrands(vehicleTypeId).then(setBrands).catch(console.error); + }, [plan.idRubro]); + + // 2. Autocomplete Modelos + useEffect(() => { + if (vehicleData.brandId && modelSearch.length >= 2) { + const timer = setTimeout(() => { + AdsV2Service.searchModels(Number(vehicleData.brandId), modelSearch) + .then(setModelSuggestions) + .catch(console.error); + }, 300); + return () => clearTimeout(timer); + } else { + setModelSuggestions([]); + } + }, [modelSearch, vehicleData.brandId]); + + // 3. Autocomplete Usuarios (Admin) + useEffect(() => { + if (isAdmin && userSearch.length >= 3) { + const timer = setTimeout(async () => { + try { + const data = await AdminService.searchUsers(userSearch); + const users = data.map((u: any) => ({ + id: u.userID, + name: `${u.firstName || ''} ${u.lastName || ''} (${u.userName})`.trim(), + email: u.email, + phone: u.phoneNumber + })); + setUserSuggestions(users); + } catch (e) { console.error(e); } + }, 300); + return () => clearTimeout(timer); + } else { + setUserSuggestions([]); + } + }, [userSearch, isAdmin]); + + // 4. Cargar datos si es edición + useEffect(() => { + if (editId) { + cargarDatosAviso(editId); + } + }, [editId]); + + // 5. CERRAR SUGERENCIAS AL CLICKEAR FUERA + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (modelWrapperRef.current && !modelWrapperRef.current.contains(event.target as Node)) { + setShowSuggestions(false); + } + if (userWrapperRef.current && !userWrapperRef.current.contains(event.target as Node)) { + setShowUserSuggestions(false); + } + } + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + const cargarDatosAviso = async (id: number) => { + try { + const ad = await AdsV2Service.getById(id); + setVehicleData({ + brandId: ad.brandID?.toString() || '', + modelId: ad.modelID?.toString() || '', + modelName: ad.versionName || '', + year: ad.year, + km: ad.km, + price: ad.price, + currency: ad.currency, + description: ad.description || '', + fuelType: (ad.fuelType || ad.FuelType) || '', + color: (ad.color || ad.Color) || '', + segment: (ad.segment || ad.Segment) || '', + location: (ad.location || ad.Location) || '', + condition: (ad.condition || ad.Condition) || '', + doorCount: ad.doorCount || ad.DoorCount, + transmission: (ad.transmission || ad.Transmission) || '', + steering: (ad.steering || ad.Steering) || '' + }); + setModelSearch(ad.versionName || ''); + setContactData({ + phone: ad.contactPhone || '', + email: ad.contactEmail || '', + displayInfo: ad.displayContactInfo + }); + if (ad.photos && ad.photos.length > 0) { + const existingPhotos = ad.photos.map((p: any) => ({ + id: p.photoID, + path: p.filePath + })); + setPhotos(existingPhotos); + } + } catch (err) { + console.error("Error al cargar datos del aviso", err); + } + }; + + // --- HANDLERS --- + + const handleNumberInput = (field: string, value: string) => { + if (value === '') { + setVehicleData(prev => ({ ...prev, [field]: '' })); + return; + } + const num = parseInt(value); + setVehicleData(prev => ({ ...prev, [field]: isNaN(num) ? '' : num })); + }; + + const handleFloatInput = (field: string, value: string) => { + if (value === '') { + setVehicleData(prev => ({ ...prev, [field]: '' })); + return; + } + const num = parseFloat(value); + setVehicleData(prev => ({ ...prev, [field]: isNaN(num) ? '' : num })); + }; + + const handlePhotoChange = (e: React.ChangeEvent) => { + if (e.target.files) { + const newFiles = Array.from(e.target.files); + + if (photos.length + newFiles.length > 5) { + alert("Máximo 5 fotos permitidas."); + return; + } + + const MAX_SIZE = 3 * 1024 * 1024; + const oversizedFiles = newFiles.filter(file => file.size > MAX_SIZE); + + if (oversizedFiles.length > 0) { + alert(`Algunas imágenes superan el límite de 3MB:\n${oversizedFiles.map(f => f.name).join('\n')}`); + return; + } + + const invalidTypes = newFiles.filter(file => !['image/jpeg', 'image/png', 'image/webp'].includes(file.type)); + if (invalidTypes.length > 0) { + alert("Solo se permiten archivos JPG, PNG o WEBP."); + return; + } + + setPhotos([...photos, ...newFiles]); + } + }; + + const selectModel = (id: number, name: string) => { + setVehicleData({ ...vehicleData, modelId: id.toString(), modelName: name }); + setModelSearch(name); + setShowSuggestions(false); + }; + + const selectUser = (u: { id: number, name: string, email: string, phone?: string }) => { + setTargetUser(u); + setUserSearch(u.name); + setShowUserSuggestions(false); + + // Actualizamos los datos de contacto con los del usuario seleccionado + setContactData(prev => ({ + ...prev, + email: u.email, + // Si u.phone viene undefined (porque el backend no lo manda o es null), usamos string vacío + phone: u.phone || '' + })); + }; + + // Sincronizar datos de usuario fantasma con datos de contacto + useEffect(() => { + if (!targetUser && (ghostUser.email || ghostUser.phone)) { + setContactData(prev => ({ + ...prev, + email: ghostUser.email, + phone: ghostUser.phone + })); + } + }, [ghostUser, targetUser]); + + // Detección automática de usuario existente al escribir en "Crear Nuevo" + useEffect(() => { + if (!isAdmin || targetUser || !ghostUser.email || ghostUser.email.length < 5) return; + + const timer = setTimeout(async () => { + if (ghostUser.email.includes('@')) { + try { + const results = await AdminService.searchUsers(ghostUser.email); + const exactMatch = results.find((u: any) => u.email.toLowerCase() === ghostUser.email.toLowerCase()); + + if (exactMatch) { + setUserMatchModal({ + isOpen: true, + user: exactMatch + }); + } + } catch (err) { + console.error("Error verificando existencia de usuario", err); + } + } + }, 800); + + return () => clearTimeout(timer); + }, [ghostUser.email, isAdmin, targetUser]); + + const handleConfirmUserMatch = () => { + if (userMatchModal.user) { + const u = userMatchModal.user; + // Mapeamos al formato que espera selectUser + const userToSelect = { + id: u.userID, + name: `${u.firstName || ''} ${u.lastName || ''} (${u.userName})`.trim(), + email: u.email, + phone: u.phoneNumber + }; + + // 1. Asignamos el usuario existente + selectUser(userToSelect); + + // 2. Limpiamos los campos de "Crear Nuevo" + setGhostUser({ email: '', firstName: '', lastName: '', phone: '' }); + } + // Cerramos modal + setUserMatchModal({ isOpen: false, user: null }); + }; + + const handleCancelUserMatch = () => { + // Si cancela, limpiamos el email (y resto de campos) para evitar el error de duplicado al intentar guardar + setGhostUser({ email: '', firstName: '', lastName: '', phone: '' }); + setUserMatchModal({ isOpen: false, user: null }); + }; + + // --- SUBMIT INICIAL --- + const handleFormSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(null); + + try { + const userSession = AuthService.getCurrentUser(); + if (!userSession) throw new Error("Usuario no autenticado"); + + if (!vehicleData.brandId || (!vehicleData.modelId && !vehicleData.modelName)) { + throw new Error("Seleccione una marca y escriba un modelo válido."); + } + + if (vehicleData.year < 1900 || vehicleData.year > new Date().getFullYear() + 1) { + throw new Error("El año ingresado no es válido."); + } + if (vehicleData.price <= 0) { + throw new Error("El precio debe ser mayor a 0."); + } + + // Si oculta datos, debe escribir descripción + if (!contactData.displayInfo && vehicleData.description.length < 10) { + throw new Error("Si ocultas tus datos de contacto, debes detallar un medio de comunicación en la descripción."); + } + + const finalVersionName = modelSearch; + + const adPayload = { + userID: userSession.id, + vehicleTypeID: plan.idRubro, + brandID: Number(vehicleData.brandId), + modelID: vehicleData.modelId ? Number(vehicleData.modelId) : 0, + versionName: finalVersionName, + year: Number(vehicleData.year) || 0, + km: Number(vehicleData.km) || 0, + price: Number(vehicleData.price) || 0, + currency: vehicleData.currency, + description: vehicleData.description, + + isFeatured: plan.paquete === 1, + + contactPhone: contactData.phone, + contactEmail: contactData.email, + displayContactInfo: contactData.displayInfo, + + fuelType: vehicleData.fuelType, + color: vehicleData.color, + segment: vehicleData.segment, + location: vehicleData.location, + condition: vehicleData.condition, + doorCount: vehicleData.doorCount, + transmission: vehicleData.transmission, + steering: vehicleData.steering, + + targetUserID: targetUser?.id, + ghostUserEmail: !targetUser && ghostUser.email ? ghostUser.email : undefined, + ghostFirstName: !targetUser && ghostUser.firstName ? ghostUser.firstName : undefined, + ghostLastName: !targetUser && ghostUser.lastName ? ghostUser.lastName : undefined, + ghostUserPhone: !targetUser && ghostUser.phone ? ghostUser.phone : undefined + }; + + let adId = currentAdId; + + const newPhotos = photos.filter(p => p instanceof File) as File[]; + + if (adId) { + await AdsV2Service.update(adId, adPayload); + if (newPhotos.length > 0) { + await AdsV2Service.uploadPhotos(adId, newPhotos); + } + + const currentAd = await AdsV2Service.getById(adId); + + // Si es Admin O el aviso ya estaba pago/aprobado + if (isAdmin || currentAd.statusID >= 3) { + alert("Cambios guardados."); + // Si es Admin, pasamos TRUE. Si es usuario editando un aviso ya activo, pasamos FALSE (para que diga "Guardado") + onSuccess(adId!, isAdmin); + return; + } + } else { + const response = await AdsV2Service.createDraft(adPayload); + adId = response.adID; + setCurrentAdId(adId); + + if (photos.length > 0 && adId) { + const allFiles = photos as File[]; + await AdsV2Service.uploadPhotos(adId, allFiles); + } + } + + if (isAdmin) { + onSuccess(adId!, true); + return; + } + + // Validación específica para Admin creando usuario fantasma + if (isAdmin && !targetUser && !editId) { + if (!ghostUser.email || !ghostUser.firstName || !ghostUser.lastName || !ghostUser.phone) { + setError("Para crear un nuevo cliente, debes completar Nombre, Apellido, Email y Teléfono."); + setLoading(false); + return; + } + // Validar formato de email básico + if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(ghostUser.email)) { + setError("El email del nuevo cliente no es válido."); + setLoading(false); + return; + } + } + + setLoading(false); + setShowPaymentForm(true); + + } catch (err: any) { + console.error("Error al guardar aviso:", err.response?.data || err); + const backendError = err.response?.data; + let msg = "Error al procesar el aviso."; + + if (backendError?.errors) { + // Extraer el primer error de validación de .NET + const firstKey = Object.keys(backendError.errors)[0]; + msg = backendError.errors[firstKey][0]; + } else if (backendError?.message) { + msg = backendError.message; + } else if (err.message) { + msg = err.message; + } + + setError(msg); + setLoading(false); + } + }; + + const handlePaymentSuccess = async (paymentData: any) => { + try { + const finalPayload = { ...paymentData, adId: currentAdId }; + const response = await AdsV2Service.processPayment(finalPayload); + + if (response.status === 'approved') { + onSuccess(currentAdId!); + } else { + navigate(`/pago-confirmado?status=${response.status}&adId=${currentAdId}`); + } + + } catch (err: any) { + console.error("Error completo:", err.response?.data); + const detail = err.response?.data?.detail; + const backendMessage = err.response?.data?.message; + let userMessage = "El pago no pudo ser procesado. Intenta nuevamente."; + + const MP_ERRORS: Record = { + 'cc_rejected_bad_filled_card_number': 'Revisá el número de tarjeta.', + 'cc_rejected_bad_filled_date': 'Revisá la fecha de vencimiento.', + 'cc_rejected_bad_filled_other': 'Revisá los datos de la tarjeta.', + 'cc_rejected_bad_filled_security_code': 'Revisá el código de seguridad (CVV).', + 'cc_rejected_blacklist': 'No pudimos procesar tu pago.', + 'cc_rejected_call_for_authorize': 'Debes autorizar el pago llamando a tu banco.', + 'cc_rejected_card_disabled': 'Llamá a tu banco para activar tu tarjeta.', + 'cc_rejected_card_error': 'No pudimos procesar tu pago.', + 'cc_rejected_duplicated_payment': 'Ya hiciste un pago por ese valor. Revisa tus movimientos.', + 'cc_rejected_high_risk': 'Tu pago fue rechazado por motivos de seguridad.', + 'cc_rejected_insufficient_amount': 'Tu tarjeta no tiene fondos suficientes.', + 'cc_rejected_invalid_installments': 'La tarjeta no procesa pagos en la cantidad de cuotas seleccionada.', + 'cc_rejected_max_attempts': 'Llegaste al límite de intentos permitidos.', + 'cc_rejected_other_reason': 'La tarjeta rechazó el pago.' + }; + + if (detail && MP_ERRORS[detail]) { + userMessage = MP_ERRORS[detail]; + } else if (backendMessage) { + userMessage = backendMessage; + } else if (err.response?.data?.errors) { + const firstKey = Object.keys(err.response.data.errors)[0]; + userMessage = `${err.response.data.errors[firstKey][0]}`; + } + + setError(userMessage); + } + }; + + if (showPaymentForm && currentAdId) { + const rawMonto = plan.importeTotsiniva > 0 + ? plan.importeTotsiniva * 1.105 + : plan.importeSiniva * 1.105; + + const monto = Math.round(rawMonto); + + return ( +
    + {/* HEADER DEL PAGO */} +
    + + {/* Tarjeta contenedora con gradiente sutil hacia abajo */} +
    + + {/* Fondo decorativo "Atmósfera MP" (Brillo azul en la parte superior) */} +
    + +

    + Finalizar Compra +

    +

    + Completa los datos para publicar tu aviso. +

    + +
    +
    + +
    + Total a pagar + + ${monto.toLocaleString('es-AR')} + +
    + + {/* Separador vertical (solo desktop) */} +
    + {/* Separador horizontal (solo mobile) */} +
    + + {/* Contenedor del Logo con Fondo Sólido */} +
    + +
    +
    + + + Procesado de forma segura por Mercado Pago + +
    +
    +
    + + {error && ( +
    + {error} +
    + )} + + setError(msg)} + onCancel={() => setShowPaymentForm(false)} + /> +
    + ); + } + + const buttonText = loading + ? 'Procesando...' + : (isAdmin + ? (editId ? 'Guardar Cambios' : 'Publicar Directamente') + : (editId ? 'Guardar Cambios' : 'Ir a Pagar') + ); + + return ( +
    + + {/* --- SECCIÓN ADMIN --- */} + {isAdmin && !editId && ( +
    +

    Panel Administrativo

    +
    +
    + + { + setUserSearch(e.target.value); + setShowUserSuggestions(true); + if (e.target.value === '') setTargetUser(null); + }} + onFocus={() => setShowUserSuggestions(true)} + /> + {showUserSuggestions && userSuggestions.length > 0 && ( +
      + {userSuggestions.map(u => ( +
    • selectUser(u)} + className="px-4 py-3 hover:bg-amber-600/20 cursor-pointer text-sm transition-colors border-b border-white/5 last:border-0 flex justify-between" + > + {u.name} + {u.email} +
    • + ))} +
    + )} + {targetUser && ( +
    + Asignado a: {targetUser.name} (ID: {targetUser.id}) + +
    + )} +
    +
    +
    + O crear nuevo +
    +
    +
    +
    + setGhostUser({ ...ghostUser, email: e.target.value })} + className="bg-black/20 border border-white/10 rounded-xl px-4 py-2 text-sm text-white focus:border-amber-500/50 outline-none" + /> + setGhostUser({ ...ghostUser, firstName: e.target.value })} + className="bg-black/20 border border-white/10 rounded-xl px-4 py-2 text-sm text-white focus:border-amber-500/50 outline-none" + /> + setGhostUser({ ...ghostUser, lastName: e.target.value })} + className="bg-black/20 border border-white/10 rounded-xl px-4 py-2 text-sm text-white focus:border-amber-500/50 outline-none" + /> + setGhostUser({ ...ghostUser, phone: e.target.value })} + className="bg-black/20 border border-white/10 rounded-xl px-4 py-2 text-sm text-white focus:border-amber-500/50 outline-none" + /> +
    +
    +
    +
    + )} + +
    +
    +

    + 1 + Datos del Vehículo +

    +
    +
    + + { + setVehicleData({ ...vehicleData, brandId: val, modelId: '', modelName: '' }); + setModelSearch(''); + setModelSuggestions([]); + }} + placeholder="Buscar Marca..." + /> +
    +
    + + { + const val = e.target.value; + setModelSearch(val); + setShowSuggestions(true); + setVehicleData(prev => ({ + ...prev, + modelId: '', + modelName: val + })); + }} + onFocus={() => setShowSuggestions(true)} + className="w-full bg-white/5 border border-white/10 rounded-xl px-4 py-3 text-white outline-none focus:border-blue-500 disabled:opacity-50" + /> + {showSuggestions && modelSuggestions.length > 0 && ( +
      + {modelSuggestions.map(m => ( +
    • selectModel(m.id, m.name)} + className="px-4 py-3 hover:bg-blue-600 cursor-pointer text-sm transition-colors border-b border-white/5 last:border-0" + > + {m.name} +
    • + ))} +
    + )} +
    + {/* INPUTS NUMÉRICOS SEGUROS */} +
    + + handleNumberInput('year', e.target.value)} /> +
    +
    + + handleNumberInput('km', e.target.value)} /> +
    +
    + +
    + + handleFloatInput('price', e.target.value)} /> +
    +
    + +
    +
    + + +
    +
    + + +
    +
    + + setVehicleData({ ...vehicleData, color: e.target.value })} /> +
    + {plan.idRubro !== VEHICLE_TYPES.MOTOS && ( +
    + + handleNumberInput('doorCount', e.target.value)} /> +
    + )} +
    + + +
    +
    + + setVehicleData({ ...vehicleData, location: e.target.value })} /> +
    +
    + + +
    + {plan.idRubro !== VEHICLE_TYPES.MOTOS && ( +
    + + +
    + )} +
    +
    +
    + +
    + {/* Fondo decorativo sutil */} +
    + +

    + 2 + Multimedia y Detalles +

    + +
    + + {/* 1. DESCRIPCIÓN (Ancho completo) */} +
    +
    + + 900 ? 'text-amber-500' : 'text-gray-600'}`}> + {vehicleData.description.length}/1000 + +
    + +
    +