- {listing.currency} {listing.price.toLocaleString()}
+
+ {/* Content */}
+
+
+
+ {listing.categoryName || 'Clasificado'}
+
+
+
+
+ {new Date(listing.createdAt).toLocaleDateString()}
+
-
+
+
{listing.title}
-
-
-
Ver detalles
+
+
+
+
Importe
+
+ {listing.currency}
+ {listing.price.toLocaleString('es-AR')}
+
+
+
+
diff --git a/frontend/public-web/src/components/ProtectedRoute.tsx b/frontend/public-web/src/components/ProtectedRoute.tsx
new file mode 100644
index 0000000..09ccc22
--- /dev/null
+++ b/frontend/public-web/src/components/ProtectedRoute.tsx
@@ -0,0 +1,14 @@
+import { Navigate, Outlet } from 'react-router-dom';
+import { usePublicAuthStore } from '../store/publicAuthStore';
+
+export const ProtectedRoute = () => {
+ const user = usePublicAuthStore((state) => state.user);
+ const token = localStorage.getItem('public_token');
+
+ // Si no hay usuario o no hay token, al login
+ if (!user || !token) {
+ return
;
+ }
+
+ return
;
+};
\ No newline at end of file
diff --git a/frontend/public-web/src/components/SEO.tsx b/frontend/public-web/src/components/SEO.tsx
new file mode 100644
index 0000000..8e79a5c
--- /dev/null
+++ b/frontend/public-web/src/components/SEO.tsx
@@ -0,0 +1,72 @@
+import { Helmet } from 'react-helmet-async';
+
+interface SEOProps {
+ title: string;
+ description: string;
+ keywords?: string[];
+ image?: string;
+ url?: string;
+ type?: 'website' | 'article' | 'product';
+ author?: string;
+ publishedTime?: string;
+ modifiedTime?: string;
+}
+
+export default function SEO({
+ title,
+ description,
+ keywords = [],
+ image = '/og-default.jpg',
+ url = window.location.href,
+ type = 'website',
+ author,
+ publishedTime,
+ modifiedTime
+}: SEOProps) {
+ const siteName = 'Diario El Día - Clasificados';
+ const fullTitle = `${title} | ${siteName}`;
+
+ return (
+
+ {/* Primary Meta Tags */}
+ {fullTitle}
+
+
+ {keywords.length > 0 && }
+ {author && }
+
+ {/* Open Graph / Facebook */}
+
+
+
+
+
+
+
+
+ {/* Twitter */}
+
+
+
+
+
+
+ {/* Article specific */}
+ {type === 'article' && publishedTime && (
+
+ )}
+ {type === 'article' && modifiedTime && (
+
+ )}
+ {type === 'article' && author && (
+
+ )}
+
+ {/* Additional SEO */}
+
+
+
+
+
+ );
+}
diff --git a/frontend/public-web/src/components/SchemaMarkup.tsx b/frontend/public-web/src/components/SchemaMarkup.tsx
new file mode 100644
index 0000000..0c22865
--- /dev/null
+++ b/frontend/public-web/src/components/SchemaMarkup.tsx
@@ -0,0 +1,138 @@
+import { Helmet } from 'react-helmet-async';
+
+interface ListingSchemaProps {
+ listing: {
+ id: number;
+ title: string;
+ description: string;
+ price: number;
+ currency?: string;
+ category: string;
+ images?: string[];
+ seller?: {
+ name: string;
+ telephone?: string;
+ };
+ location?: string;
+ datePosted?: string;
+ };
+}
+
+// Schema.org markup para avisos clasificados (Product/Offer)
+export function ListingSchema({ listing }: ListingSchemaProps) {
+ const schema = {
+ '@context': 'https://schema.org',
+ '@type': 'Product',
+ name: listing.title,
+ description: listing.description,
+ category: listing.category,
+ image: listing.images || [],
+ offers: {
+ '@type': 'Offer',
+ price: listing.price,
+ priceCurrency: listing.currency || 'ARS',
+ availability: 'https://schema.org/InStock',
+ url: `${window.location.origin}/avisos/${listing.id}`,
+ seller: listing.seller ? {
+ '@type': 'Organization',
+ name: listing.seller.name,
+ telephone: listing.seller.telephone
+ } : undefined
+ },
+ ...(listing.datePosted && { datePublished: listing.datePosted }),
+ ...(listing.location && {
+ availableAtOrFrom: {
+ '@type': 'Place',
+ address: {
+ '@type': 'PostalAddress',
+ addressLocality: listing.location
+ }
+ }
+ })
+ };
+
+ return (
+
+
+
+ );
+}
+
+// Schema.org para la organización (footer del sitio)
+export function OrganizationSchema() {
+ const schema = {
+ '@context': 'https://schema.org',
+ '@type': 'Organization',
+ name: 'Diario El Día',
+ url: window.location.origin,
+ logo: `${window.location.origin}/logo.png`,
+ sameAs: [
+ 'https://www.facebook.com/diarioeldia',
+ 'https://twitter.com/diarioeldia',
+ 'https://www.instagram.com/diarioeldia'
+ ],
+ contactPoint: {
+ '@type': 'ContactPoint',
+ contactType: 'customer service',
+ telephone: '+54-11-1234-5678',
+ availableLanguage: 'Spanish'
+ }
+ };
+
+ return (
+
+
+
+ );
+}
+
+// Schema.org para breadcrumbs de navegación
+export function BreadcrumbSchema({ items }: { items: Array<{ name: string; url: string }> }) {
+ const schema = {
+ '@context': 'https://schema.org',
+ '@type': 'BreadcrumbList',
+ itemListElement: items.map((item, index) => ({
+ '@type': 'ListItem',
+ position: index + 1,
+ name: item.name,
+ item: item.url
+ }))
+ };
+
+ return (
+
+
+
+ );
+}
+
+// Schema.org para formulario de búsqueda
+export function WebsiteSearchSchema() {
+ const schema = {
+ '@context': 'https://schema.org',
+ '@type': 'WebSite',
+ url: window.location.origin,
+ potentialAction: {
+ '@type': 'SearchAction',
+ target: {
+ '@type': 'EntryPoint',
+ urlTemplate: `${window.location.origin}/buscar?q={search_term_string}`
+ },
+ 'query-input': 'required name=search_term_string'
+ }
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/frontend/public-web/src/components/SearchBar.tsx b/frontend/public-web/src/components/SearchBar.tsx
index 98e2fef..059a57d 100644
--- a/frontend/public-web/src/components/SearchBar.tsx
+++ b/frontend/public-web/src/components/SearchBar.tsx
@@ -16,19 +16,19 @@ export default function SearchBar({ onSearch }: SearchBarProps) {
};
return (
-
);
diff --git a/frontend/publish-wizard/src/components/StepWrapper.tsx b/frontend/public-web/src/components/StepWrapper.tsx
similarity index 100%
rename from frontend/publish-wizard/src/components/StepWrapper.tsx
rename to frontend/public-web/src/components/StepWrapper.tsx
diff --git a/frontend/public-web/src/hooks/useDebounce.ts b/frontend/public-web/src/hooks/useDebounce.ts
new file mode 100644
index 0000000..49a26ca
--- /dev/null
+++ b/frontend/public-web/src/hooks/useDebounce.ts
@@ -0,0 +1,18 @@
+import { useState, useEffect } from 'react';
+
+// Hook para debounce (evita llamadas excesivas al escribir)
+export function useDebounce
(value: T, delay: number): T {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+}
diff --git a/frontend/public-web/src/hooks/useLazyImage.ts b/frontend/public-web/src/hooks/useLazyImage.ts
new file mode 100644
index 0000000..7b039e2
--- /dev/null
+++ b/frontend/public-web/src/hooks/useLazyImage.ts
@@ -0,0 +1,66 @@
+import { useState, useEffect, useRef } from 'react';
+
+interface UseLazyImageProps {
+ src: string;
+ placeholder?: string;
+ threshold?: number;
+}
+
+// Hook personalizado para lazy loading de imágenes
+export function useLazyImage({ src, placeholder = '', threshold = 0.25 }: UseLazyImageProps) {
+ const [imageSrc, setImageSrc] = useState(placeholder);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isError, setIsError] = useState(false);
+ const imgRef = useRef(null);
+
+ useEffect(() => {
+ let observer: IntersectionObserver;
+ let didCancel = false;
+
+ const currentImg = imgRef.current;
+ if (currentImg && imageSrc === placeholder) {
+ // Crear observer que detecta cuando la imagen entra en viewport
+ observer = new IntersectionObserver(
+ entries => {
+ entries.forEach(entry => {
+ if (!didCancel && entry.isIntersecting) {
+ setIsLoading(true);
+ setImageSrc(src);
+ }
+ });
+ },
+ {
+ threshold,
+ rootMargin: '50px' // Empezar a cargar 50px antes de que sea visible
+ }
+ );
+
+ observer.observe(currentImg);
+ }
+
+ return () => {
+ didCancel = true;
+ if (observer && currentImg) {
+ observer.unobserve(currentImg);
+ }
+ };
+ }, [src, imageSrc, placeholder, threshold]);
+
+ const handleLoad = () => {
+ setIsLoading(false);
+ };
+
+ const handleError = () => {
+ setIsLoading(false);
+ setIsError(true);
+ };
+
+ return {
+ imgRef,
+ imageSrc,
+ isLoading,
+ isError,
+ handleLoad,
+ handleError
+ };
+}
diff --git a/frontend/public-web/src/main.tsx b/frontend/public-web/src/main.tsx
index bef5202..9f7ef0b 100644
--- a/frontend/public-web/src/main.tsx
+++ b/frontend/public-web/src/main.tsx
@@ -1,10 +1,13 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
+import { HelmetProvider } from 'react-helmet-async'
import './index.css'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
-
+
+
+
,
-)
+)
\ No newline at end of file
diff --git a/frontend/public-web/src/pages/HomePage.tsx b/frontend/public-web/src/pages/HomePage.tsx
index e3500b5..f1f3f2c 100644
--- a/frontend/public-web/src/pages/HomePage.tsx
+++ b/frontend/public-web/src/pages/HomePage.tsx
@@ -1,21 +1,20 @@
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import SearchBar from '../components/SearchBar';
import ListingCard from '../components/ListingCard';
import { publicService } from '../services/publicService';
-import type { Listing, Category } from '../types';
-import { Filter, X } from 'lucide-react';
+import type { Listing } from '../types';
+import { Filter, X, Grid, List as ListIcon, TrendingUp, Search, Zap } from 'lucide-react';
import { processCategoriesForSelect, type FlatCategory } from '../utils/categoryTreeUtils';
+import { motion, AnimatePresence } from 'framer-motion';
+import SEO from '../components/SEO';
+import { WebsiteSearchSchema, OrganizationSchema } from '../components/SchemaMarkup';
export default function HomePage() {
const [listings, setListings] = useState([]);
-
- // Usamos FlatCategory para el renderizado
const [flatCategories, setFlatCategories] = useState([]);
- const [rawCategories, setRawCategories] = useState([]); // Guardamos raw para los botones del home
-
const [loading, setLoading] = useState(true);
- // Estado de Búsqueda
+ // Search State
const [searchText, setSearchText] = useState('');
const [selectedCatId, setSelectedCatId] = useState(null);
const [dynamicFilters, setDynamicFilters] = useState>({});
@@ -32,36 +31,38 @@ export default function HomePage() {
publicService.getCategories()
]);
setListings(latestListings);
- setRawCategories(cats);
-
- // Procesamos el árbol para el select
- const processed = processCategoriesForSelect(cats);
- setFlatCategories(processed);
-
- } catch (e) { console.error(e); }
- finally { setLoading(false); }
+ setFlatCategories(processCategoriesForSelect(cats));
+ } catch (e) {
+ console.error(e);
+ } finally {
+ setLoading(false);
+ }
};
- const performSearch = async () => {
+ const performSearch = useCallback(async () => {
setLoading(true);
try {
- const response = await import('../services/api').then(m => m.default.post('/listings/search', {
+ const { default: api } = await import('../services/api');
+ const response = await api.post('/listings/search', {
query: searchText,
categoryId: selectedCatId,
- filters: dynamicFilters
- }));
+ attributes: dynamicFilters
+ });
setListings(response.data);
- } catch (e) { console.error(e); }
- finally { setLoading(false); }
- };
+ } catch (e) {
+ console.error(e);
+ } finally {
+ setLoading(false);
+ }
+ }, [searchText, selectedCatId, dynamicFilters]);
useEffect(() => {
- if (searchText || selectedCatId || Object.keys(dynamicFilters).length > 0) {
+ if (selectedCatId || Object.keys(dynamicFilters).length > 0 || searchText) {
performSearch();
}
- }, [selectedCatId, dynamicFilters]);
+ }, [selectedCatId, dynamicFilters, searchText, performSearch]);
- const handleSearchText = (q: string) => {
+ const handleSearchSubmit = (q: string) => {
setSearchText(q);
performSearch();
};
@@ -70,98 +71,289 @@ export default function HomePage() {
setDynamicFilters({});
setSelectedCatId(null);
setSearchText('');
- publicService.getLatestListings().then(setListings); // Reset list
- }
-
- // Para los botones del Home, solo mostramos los Raíz
- const rootCategories = rawCategories.filter(c => !c.parentId);
+ loadInitialData();
+ };
return (
-
- {/* Hero */}
-
-
-
-
Encuentra tu próximo objetivo
-
-
-
+
+
+
+
+
+ {/* --- HERO SECTION REFINADO --- */}
+
+
+ {/* Capas de fondo: Orbes de luz y textura */}
+
+
+
+
+ {/* Badge superior estilizado */}
+
+
+
+ Marketplace Oficial Diario El Día
+
+
+
+
+ Tu próximo
+
+ gran hallazgo
+
+
+
+
+ Explora miles de clasificados verificados con la confianza de siempre, ahora en una experiencia digital Premium.
+
+
+
+ {/* Buscador con efecto Glassmorphism */}
+
+
+
+
+
+ {/* Sugerencias rápidas */}
+
+ Tendencias:
+ {['Departamentos', 'Camionetas', 'Motos', 'Servicios'].map((tag) => (
+
+ ))}
+
+
+
+
+ {/* Degradado hacia la sección blanca de abajo */}
+
-
+ {/* Main Content Layout */}
+
+
- {/* SIDEBAR DE FILTROS */}
-
-
-
-
- Filtros
-
- {(selectedCatId || Object.keys(dynamicFilters).length > 0) && (
-
- )}
-
-
- {/* Filtro Categoría (MEJORADO) */}
-
-
-
- {/* LISTADO */}
-
-
- {loading ? 'Cargando...' : selectedCatId
- ? `${listings.length} Resultados en ${flatCategories.find(c => c.id === selectedCatId)?.name}`
- : 'Resultados Recientes'}
-
-
- {!loading && listings.length === 0 && (
-
- No se encontraron avisos con esos criterios.
+
+
+
+
- )}
-
- {listings.map(listing => (
-
- ))}
-
+ {/* Grid Container */}
+
+
+ {loading ? (
+
+ {[1, 2, 3, 4, 5, 6].map(i => (
+
+ ))}
+
+ ) : listings.length === 0 ? (
+
+
+
+
+ Sin resultados
+
+ No hay avisos que coincidan con los criterios aplicados actualmente.
+
+
+
+ ) : (
+
+ {listings.map((listing, idx) => (
+
+
+
+ ))}
+
+ )}
+
+
+
diff --git a/frontend/public-web/src/pages/ListingDetailPage.tsx b/frontend/public-web/src/pages/ListingDetailPage.tsx
index 4aca27f..f78f5b0 100644
--- a/frontend/public-web/src/pages/ListingDetailPage.tsx
+++ b/frontend/public-web/src/pages/ListingDetailPage.tsx
@@ -1,8 +1,15 @@
import { useEffect, useState } from 'react';
-import { useParams } from 'react-router-dom';
+import { useParams, Link } from 'react-router-dom';
import { publicService } from '../services/publicService';
import type { ListingDetail } from '../types';
-import { Calendar, Tag, ChevronLeft } from 'lucide-react';
+import {
+ Tag, ChevronLeft, Share2, MessageCircle,
+ Info, Calendar, Eye, ShieldCheck, Heart
+} from 'lucide-react';
+import { motion, AnimatePresence } from 'framer-motion';
+import LazyImage from '../components/LazyImage';
+import SEO from '../components/SEO';
+import { ListingSchema } from '../components/SchemaMarkup';
export default function ListingDetailPage() {
const { id } = useParams();
@@ -15,102 +22,189 @@ export default function ListingDetailPage() {
publicService.getListingDetail(parseInt(id))
.then(data => {
setDetail(data);
- if (data.images.length > 0) {
- setActiveImage(data.images[0].url);
- }
+ if (data.images.length > 0) setActiveImage(data.images[0].url);
})
- .catch(err => console.error(err))
.finally(() => setLoading(false));
}
}, [id]);
- if (loading) return
Cargando...
;
- if (!detail) return
Aviso no encontrado.
;
+ if (loading) return (
+
+ );
+
+ if (!detail) return
No encontrado
;
const { listing, attributes, images } = detail;
- const baseUrl = import.meta.env.VITE_BASE_URL; // Ajustar puerto según backend launchSettings
+ const baseUrl = import.meta.env.VITE_BASE_URL;
return (
-
-
- Volver al listado
-
+
+ {/* --- SEO Y METADATOS (Invisibles) --- */}
+
-
- {/* Galería de Fotos */}
-
-
- {activeImage ? (
-

- ) : (
-
Sin imágenes
- )}
+ {/* RE-INTEGRADO: Vital para que Google muestre el precio en las búsquedas */}
+
`${baseUrl}${i.url}`)
+ }} />
+
+ {/* --- NAVEGACIÓN SUPERIOR --- */}
+
+
+
+
Volver al listado
+
+
+
+
- {images.length > 1 && (
-
- {images.map(img => (
-
- ))}
-
- )}
+
- {/* Información Principal */}
-
-
-
-
- Clasificado #{listing.id}
-
-
-
- {new Date(listing.createdAt).toLocaleDateString()}
-
-
+
+
-
{listing.title}
-
- {listing.currency} {listing.price.toLocaleString()}
-
-
-
-
-
-
- {/* Atributos Dinámicos */}
-
-
-
- Características
-
-
- {attributes.map(attr => (
-
-
{attr.attributeName}
-
{attr.value}
+ {/* --- COLUMNA IZQUIERDA: VISUALES --- */}
+
+
+
+
+
+
+
+
+
+
+ Verificado
- ))}
- {attributes.length === 0 &&
Sin características adicionales.
}
+
+
+ {images.length > 1 && (
+
+ {images.map(img => (
+
+ ))}
+
+ )}
+
+
+
+
+
+
Reseña del Anunciante
+
+
+ "{listing.description || 'Sin descripción detallada.'}"
+
-
-
Descripción
-
- {listing.description || "Sin descripción proporcionada."}
-
-
+ {/* --- COLUMNA DERECHA: INFO Y ACCIÓN (TODO DENTRO DE UNA CAJA STICKY) --- */}
+
diff --git a/frontend/public-web/src/pages/LoginPage.tsx b/frontend/public-web/src/pages/LoginPage.tsx
new file mode 100644
index 0000000..387e878
--- /dev/null
+++ b/frontend/public-web/src/pages/LoginPage.tsx
@@ -0,0 +1,224 @@
+import { useState } from 'react';
+import { publicAuthService } from '../services/authService';
+import { usePublicAuthStore } from '../store/publicAuthStore';
+import { useNavigate } from 'react-router-dom';
+import { motion } from 'framer-motion';
+import { Mail, Lock, User, ArrowRight, ShieldCheck } from 'lucide-react';
+
+export default function LoginPage() {
+ const setUser = usePublicAuthStore(state => state.setUser);
+ const [isLogin, setIsLogin] = useState(true);
+ const [formData, setFormData] = useState({ username: '', email: '', password: '' });
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState('');
+ const [showMfa, setShowMfa] = useState(false);
+ const [mfaCode, setMfaCode] = useState('');
+
+ const navigate = useNavigate();
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+ setError('');
+
+ try {
+ if (isLogin) {
+ const res = await publicAuthService.login(formData.username, formData.password);
+ if (res.success) {
+ if (res.requiresMfa) {
+ setShowMfa(true);
+ } else {
+ setUser({ username: formData.username });
+ navigate('/');
+ }
+ } else {
+ setError(res.errorMessage || 'Error al iniciar sesión');
+ }
+ } else {
+ const res = await publicAuthService.register(formData.username, formData.email, formData.password);
+ if (res.success) {
+ setIsLogin(true);
+ setError('Registro exitoso. Por favor inicie sesión.');
+ } else {
+ setError(res.errorMessage || 'Error al registrarse');
+ }
+ }
+ } catch {
+ setError('Error de conexión');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (showMfa) {
+ return (
+
+
+
+
+
+
+
Verificación MFA
+
Ingrese el código de su aplicación de autenticación
+
+
+
+ setMfaCode(e.target.value)}
+ className="w-full text-center text-4xl font-black tracking-[0.5em] py-6 bg-slate-50 border-none rounded-3xl focus:ring-4 focus:ring-blue-100 outline-none transition-all"
+ maxLength={6}
+ />
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {/* Lado Izquierdo: Visual & Branding */}
+
+
+
+
+
+
+
+
+ Vende y compra con total confianza.
+
+
+ Únete a la comunidad de clasificados más grande de la región. Seguridad verificada y contacto directo.
+
+
+
+
+
+ 25k+
+ Avisos activos
+
+
+ 10k+
+ Usuarios verificados
+
+
+
+
+ {/* Lado Derecho: Formulario */}
+
+
+
+
+ {isLogin ? '¡Hola de nuevo!' : 'Crea tu cuenta'}
+
+
+ {isLogin ? 'Accede a tu panel personal' : 'Registrarse es gratis y rápido'}
+
+
+
+
+
+
+ {/* Divisor */}
+
+
+ {/* Google Login Placeholder Button */}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/public-web/src/pages/ProfilePage.tsx b/frontend/public-web/src/pages/ProfilePage.tsx
new file mode 100644
index 0000000..d259358
--- /dev/null
+++ b/frontend/public-web/src/pages/ProfilePage.tsx
@@ -0,0 +1,478 @@
+import { useEffect, useState } from 'react';
+import { publicAuthService } from '../services/authService';
+import { useNavigate } from 'react-router-dom';
+import { motion, AnimatePresence } from 'framer-motion';
+import type { Listing } from '../types';
+import {
+ User, Package, Settings, ChevronRight,
+ Clock, Eye, ShieldCheck, QrCode, Lock,
+ Bell, RefreshCcw
+} from 'lucide-react';
+import { usePublicAuthStore } from '../store/publicAuthStore';
+import api from '../services/api';
+import LazyImage from '../components/LazyImage';
+import { useWizardStore } from '../store/wizardStore';
+import clsx from 'clsx';
+
+interface MfaData {
+ qrCodeUri: string;
+ secret: string;
+}
+
+type TabType = 'listings' | 'security' | 'settings';
+
+export default function ProfilePage() {
+ const { user, logout } = usePublicAuthStore();
+ const [activeTab, setActiveTab] = useState
('listings');
+ const [listings, setListings] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [mfaSetupData, setMfaSetupData] = useState(null);
+ const [mfaCode, setMfaCode] = useState('');
+ const baseUrl = import.meta.env.VITE_BASE_URL;
+ const setRepublishData = useWizardStore(state => state.setRepublishData);
+ const [republishTarget, setRepublishTarget] = useState(null);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (!user) {
+ navigate('/login');
+ return;
+ }
+ loadMyData();
+ }, [navigate, user]);
+
+ const handleConfirmRepublish = async () => {
+ if (!republishTarget) return;
+ try {
+ // Buscamos el detalle técnico (Modelo, KM, Puertas, etc.)
+ const res = await api.get(`/listings/${republishTarget.id}`);
+
+ // Cargamos el Store con toda la data
+ setRepublishData(res.data);
+
+ // Navegamos al Wizard
+ navigate('/publicar');
+ } catch (error) {
+ alert("Error al recuperar datos técnicos del aviso");
+ } finally {
+ setRepublishTarget(null);
+ }
+ };
+
+ const loadMyData = async () => {
+ try {
+ const response = await api.get('/listings/my');
+ setListings(response.data);
+ } catch (error) {
+ console.error("Error al cargar datos del perfil", error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleSetupMfa = async () => {
+ try {
+ const data = await publicAuthService.setupMfa();
+ setMfaSetupData(data);
+ } catch (error) {
+ console.error("Error al configurar MFA", error);
+ }
+ };
+
+ const handleVerifyAndEnableMfa = async () => {
+ if (mfaCode.length !== 6) return;
+ try {
+ const res = await publicAuthService.verifyMfa(mfaCode);
+ if (res.success) {
+ alert("¡MFA activado con éxito!");
+ setMfaSetupData(null);
+ setMfaCode('');
+ }
+ } catch (error) {
+ alert("Error al verificar código.");
+ }
+ };
+
+ const handleUpdateOverlay = async (id: number, status: string | null) => {
+ try {
+ await api.patch(`/listings/${id}/overlay`, JSON.stringify(status), {
+ headers: { 'Content-Type': 'application/json' }
+ });
+ loadMyData();
+ } catch (error) {
+ alert("Error al actualizar estado");
+ }
+ };
+
+ if (loading) return (
+
+ );
+
+ return (
+
+
+ {/* 1. HEADER REFINADO (Más bajo y elegante) */}
+
+ {/* Luces de fondo más tenues */}
+
+
+
+
+
+
+ {/* Avatar más integrado */}
+
+
+
+
+
+ {user?.username}
+
+
+ Verificado
+
+
+
+
+
+
+ {listings.filter(l => l.status === 'Published').length} ACTIVAS
+
+
+
+
+ Miembro desde 2025
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 2. CONTENIDO (Sidebar + Main) */}
+
+
+
+ {/* SIDEBAR MÁS ESTILIZADO */}
+
+
+ {/* CONTENIDO DINÁMICO */}
+
+
+
+ {activeTab === 'listings' && (
+
+ Historial de anuncios
+ {listings.length === 0 ? (
+
+
+
Sin actividad reciente
+
+ ) : (
+
+ {listings.map(item => (
+
+
+ {/* Contenedor de imagen con tamaño fijo y aspecto cuadrado */}
+
+ {item.mainImageUrl ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ {item.title}
+
+
+
+ {new Date(item.createdAt).toLocaleDateString()}
+
+
+ {item.viewCount} vistas
+
+
+
+
+
${item.price.toLocaleString()}
+
+ {/* BADGE DINÁMICO DE ESTADO */}
+
+ {item.status === 'Published' ? 'Publicado' :
+ item.status === 'Pending' ? 'En Revisión' :
+ 'Borrador'}
+
+
+
+
+
+
+ {/* PANEL DE GESTIÓN */}
+
+
+ {['Vendido', 'Reservado', 'Alquilado'].map(status => (
+
+ ))}
+
+
+
+
+
+
+ ))}
+
+ )}
+
+ )}
+
+ {/* TAB: SEGURIDAD / MFA */}
+ {activeTab === 'security' && (
+
+ Protección de Cuenta
+ Gestiona la seguridad de tu acceso y la verificación en dos pasos.
+
+
+ {/* Card MFA */}
+
+
+
+
+
+
+
Doble Factor de Autenticación (TOTP)
+
Añade una capa extra de seguridad usando Google Authenticator o similares.
+
+ {!mfaSetupData ? (
+
+ ) : (
+
+
1. Escanea este código
+
+
}`})
+
+
2. Introduce el código de 6 dígitos
+
+ setMfaCode(e.target.value)}
+ placeholder="000000"
+ className="flex-1 bg-slate-50 border-none rounded-xl text-center font-mono font-bold text-xl py-3 focus:ring-2 focus:ring-blue-500 transition-all"
+ />
+
+
+
+ )}
+
+
+
+
+ {/* Card Password Change */}
+
+
+
+
+
+
+
Cambiar Contraseña
+
Último cambio: Hace 3 meses
+
+
+
+
+
+
+ )}
+
+ {/* TAB: AJUSTES */}
+ {activeTab === 'settings' && (
+
+ Ajustes de cuenta
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ {/* MODAL DE CONFIRMACIÓN */}
+
+ {republishTarget && (
+
+
setRepublishTarget(null)}
+ className="absolute inset-0 bg-slate-900/60 backdrop-blur-sm"
+ />
+
+
+
+
+ Republicar Aviso
+
+ Se creará una nueva publicación basada en {republishTarget.title}. Podrás editar los datos antes de realizar el pago.
+
+
+
+
+
+
+
+ )}
+
+
+ );
+}
+
+// COMPONENTES AUXILIARES PARA LIMPIEZA
+function SidebarItem({ icon, label, active, onClick }: { icon: any, label: string, active: boolean, onClick: () => void }) {
+ return (
+
+ );
+}
+
+function InputGroup({ label, value, disabled, placeholder }: any) {
+ return (
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/public-web/src/pages/PublishFeedback.tsx b/frontend/public-web/src/pages/PublishFeedback.tsx
new file mode 100644
index 0000000..3a50a20
--- /dev/null
+++ b/frontend/public-web/src/pages/PublishFeedback.tsx
@@ -0,0 +1,54 @@
+import { Link, useSearchParams } from 'react-router-dom';
+import { CheckCircle2, XCircle, ArrowRight } from 'lucide-react';
+import { motion } from 'framer-motion';
+
+export default function PublishFeedback() {
+ const [searchParams] = useSearchParams();
+ const status = searchParams.get('status'); // Viene de Mercado Pago
+
+ const isSuccess = status === 'approved';
+
+ return (
+
+
+ {isSuccess ? (
+ <>
+
+
+
+ ¡Publicado!
+
+ Tu pago fue aprobado. Tu aviso ya está en proceso de revisión/publicación.
+
+ >
+ ) : (
+ <>
+
+
+
+ Hubo un problema
+
+ No pudimos procesar el pago. Por favor, intenta de nuevo desde tu perfil.
+
+ >
+ )}
+
+
+
+ Ir a mis publicaciones
+
+
+ Volver al inicio
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/public-web/src/pages/PublishPage.tsx b/frontend/public-web/src/pages/PublishPage.tsx
new file mode 100644
index 0000000..ede4b78
--- /dev/null
+++ b/frontend/public-web/src/pages/PublishPage.tsx
@@ -0,0 +1,60 @@
+import { useEffect, useState } from 'react';
+import { useWizardStore } from '../store/wizardStore';
+import CategorySelection from './Steps/CategorySelection';
+import OperationSelection from './Steps/OperationSelection';
+import AttributeForm from './Steps/AttributeForm';
+import TextEditorStep from './Steps/TextEditorStep';
+import PhotoUploadStep from './Steps/PhotoUploadStep';
+import SummaryStep from './Steps/SummaryStep';
+import { wizardService } from '../services/wizardService';
+import type { AttributeDefinition } from '../types';
+import SEO from '../components/SEO';
+
+export default function PublishPage() {
+ const { step, selectedCategory } = useWizardStore();
+ const [definitions, setDefinitions] = useState([]);
+
+ useEffect(() => {
+ // CAMBIO: Agregamos el guard aquí también
+ if (selectedCategory?.id) {
+ wizardService.getAttributes(selectedCategory.id)
+ .then(setDefinitions)
+ .catch(err => console.error("Error en PublishPage:", err));
+ }
+ }, [selectedCategory?.id]);
+
+ return (
+
+
+
+ {/* Header del Wizard interno */}
+
+
+
+ Paso {step} de 6
+
+
+ {[1, 2, 3, 4, 5, 6].map((i) => (
+
+ ))}
+
+
+
+
+
+ {step === 1 && }
+ {step === 2 && }
+ {step === 3 && }
+ {step === 4 && }
+ {step === 5 && }
+ {step === 6 && }
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/public-web/src/pages/Steps/AttributeForm.tsx b/frontend/public-web/src/pages/Steps/AttributeForm.tsx
new file mode 100644
index 0000000..b304c6b
--- /dev/null
+++ b/frontend/public-web/src/pages/Steps/AttributeForm.tsx
@@ -0,0 +1,134 @@
+import { useEffect, useState } from 'react';
+import { wizardService } from '../../services/wizardService';
+import { useWizardStore } from '../../store/wizardStore';
+import type { AttributeDefinition } from '../../types';
+import { StepWrapper } from '../../components/StepWrapper';
+import { ChevronRight, Info, Tag, PenTool, DollarSign } from 'lucide-react';
+
+export default function AttributeForm() {
+ const { selectedCategory, selectedOperation, attributes, setAttribute, setStep } = useWizardStore();
+ const [definitions, setDefinitions] = useState([]);
+
+ useEffect(() => {
+ // CAMBIO: Verificamos específicamente que exista el ID para evitar el GET /undefined
+ if (selectedCategory?.id) {
+ wizardService.getAttributes(selectedCategory.id)
+ .then(setDefinitions)
+ .catch(err => console.error("Error al obtener atributos:", err));
+ }
+ }, [selectedCategory?.id]);
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ setStep(4);
+ };
+
+ return (
+
+ {/* Breadcrumbs */}
+
+
+
+
+
+
+
+
+
+
+ Características
+ del Anuncio
+
+
Completa los datos técnicos para mejorar la visibilidad.
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/publish-wizard/src/pages/Steps/CategorySelection.tsx b/frontend/public-web/src/pages/Steps/CategorySelection.tsx
similarity index 100%
rename from frontend/publish-wizard/src/pages/Steps/CategorySelection.tsx
rename to frontend/public-web/src/pages/Steps/CategorySelection.tsx
diff --git a/frontend/publish-wizard/src/pages/Steps/OperationSelection.tsx b/frontend/public-web/src/pages/Steps/OperationSelection.tsx
similarity index 100%
rename from frontend/publish-wizard/src/pages/Steps/OperationSelection.tsx
rename to frontend/public-web/src/pages/Steps/OperationSelection.tsx
diff --git a/frontend/public-web/src/pages/Steps/PhotoUploadStep.tsx b/frontend/public-web/src/pages/Steps/PhotoUploadStep.tsx
new file mode 100644
index 0000000..f0e8a8b
--- /dev/null
+++ b/frontend/public-web/src/pages/Steps/PhotoUploadStep.tsx
@@ -0,0 +1,107 @@
+import { useCallback } from 'react';
+import { useDropzone } from 'react-dropzone';
+import { StepWrapper } from '../../components/StepWrapper';
+import { useWizardStore } from '../../store/wizardStore';
+import { Upload, Plus, Trash2, ArrowLeft, ChevronRight } from 'lucide-react';
+import clsx from 'clsx';
+
+export default function PhotoUploadStep() {
+ const { setStep, photos, existingImages, removePhoto, removeExistingImage, addPhoto } = useWizardStore();
+ const baseUrl = import.meta.env.VITE_BASE_URL;
+
+ const onDrop = useCallback((acceptedFiles: File[]) => {
+ acceptedFiles.forEach(file => addPhoto(file));
+ }, [addPhoto]);
+
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
+ onDrop,
+ accept: { 'image/*': [] },
+ maxFiles: 10
+ });
+
+ return (
+
+
+
+ Galería de
+ Imágenes
+
+
Gestiona las fotos de tu aviso. La primera será la portada.
+
+
+
+
+ {/* GRILLA DE FOTOS (EXISTENTES + NUEVAS) */}
+ {(existingImages.length > 0 || photos.length > 0) && (
+
+
+ {/* Renderizado de Fotos Heredadas */}
+ {existingImages.map((img, idx) => (
+
+

+
+
+
+ {idx === 0 && (
+
Portada
+ )}
+
+ ))}
+
+ {/* Renderizado de Fotos Nuevas (Files) */}
+ {photos.map((file, idx) => (
+
+
})
+
+
+
+ {existingImages.length === 0 && idx === 0 && (
+
Portada
+ )}
+
+ ))}
+
+ {/* BOTÓN "AÑADIR MÁS" dentro de la grilla si ya hay fotos */}
+
+
+ )}
+
+ {/* ÁREA DE CARGA INICIAL (Solo si está todo vacío) */}
+ {existingImages.length === 0 && photos.length === 0 && (
+
+ )}
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/public-web/src/pages/Steps/SummaryStep.tsx b/frontend/public-web/src/pages/Steps/SummaryStep.tsx
new file mode 100644
index 0000000..4b6a59c
--- /dev/null
+++ b/frontend/public-web/src/pages/Steps/SummaryStep.tsx
@@ -0,0 +1,291 @@
+import { useState } from 'react';
+import { useWizardStore } from '../../store/wizardStore';
+import { wizardService } from '../../services/wizardService';
+import { StepWrapper } from '../../components/StepWrapper';
+import type { AttributeDefinition } from '../../types';
+import {
+ CreditCard,
+ Wallet,
+ ChevronRight,
+ ArrowLeft,
+ Tag,
+ FileText,
+ Clock
+} from 'lucide-react';
+import { initMercadoPago, Wallet as MPWallet } from '@mercadopago/sdk-react';
+import clsx from 'clsx';
+import api from '../../services/api';
+
+// Se inicializa con la Public Key desde env
+initMercadoPago(import.meta.env.VITE_MP_PUBLIC_KEY);
+
+export default function SummaryStep({ definitions }: { definitions: AttributeDefinition[] }) {
+ const { selectedCategory, selectedOperation, attributes, photos, setStep, reset } = useWizardStore();
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [preferenceId, setPreferenceId] = useState(null);
+ const [createdId, setCreatedId] = useState(null);
+ const [paymentMethod, setPaymentMethod] = useState<'mercadopago' | 'stripe'>('mercadopago');
+
+ const adFee = parseFloat(attributes['adFee'] || "0");
+ const listingPrice = parseFloat(attributes['price'] || "0");
+
+ const handlePublish = async () => {
+ if (!selectedCategory || !selectedOperation) return;
+
+ setIsSubmitting(true);
+ try {
+ const attributePayload: Record = {};
+ const { existingImages } = useWizardStore.getState();
+
+ definitions.forEach(def => {
+ const val = attributes[def.name.toLowerCase()] || attributes[def.name];
+ if (val) {
+ attributePayload[def.id] = val.toString();
+ }
+ });
+
+ const payload = {
+ categoryId: selectedCategory.id,
+ operationId: selectedOperation.id,
+ title: attributes['title'],
+ description: attributes['description'],
+ price: parseFloat(attributes['price'] || "0"),
+ adFee: adFee,
+ currency: 'ARS',
+ status: 'Pending',
+ origin: 'Web',
+ attributes: attributePayload,
+ imagesToClone: existingImages.map(img => img.url),
+ printText: attributes['description'],
+ printDaysCount: parseInt(attributes['days'] || "3"),
+ isBold: false,
+ isFrame: false,
+ printFontSize: 'normal',
+ printAlignment: 'left'
+ };
+
+ const result = await wizardService.createListing(payload);
+ setCreatedId(result.id);
+
+ if (photos.length > 0) {
+ for (const photo of photos) {
+ await wizardService.uploadImage(result.id, photo);
+ }
+ }
+
+ const resp = await fetch(`${import.meta.env.VITE_API_URL}/payments/create-preference/${result.id}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(adFee)
+ });
+ const data = await resp.json();
+
+ if (data.id) {
+ setPreferenceId(data.id);
+ }
+ } catch (error) {
+ console.error(error);
+ alert('Error al publicar');
+ } finally {
+ setIsSubmitting(false);
+ }
+ };
+
+ const urlParams = new URLSearchParams(window.location.search);
+ const paymentStatus = urlParams.get('status');
+
+ if (paymentStatus === 'approved') {
+ return (
+
+
+
+
+
+
¡Pago Recibido!
+
+ Tu aviso ha sido enviado a nuestro equipo de **Moderación**. Una vez aprobado, se publicará automáticamente en el portal y en la edición impresa.
+
+
+
+
+
+
+ );
+ }
+
+ if (preferenceId) {
+ return (
+
+
+
+
+
+
Finalizar Publicación
+
+ {/* LÓGICA DE SIMULACIÓN */}
+ {preferenceId === "MOCK_PREFERENCE_ID_12345" ? (
+
+
+ Modo Simulación Activo: Token de MP no configurado.
+
+
+ Estás en el entorno de desarrollo. Haz clic abajo para simular un pago aprobado y finalizar el proceso.
+
+
+
+ ) : (
+ // LÓGICA REAL
+ <>
+
+ Haz clic abajo para completar el pago de ${adFee.toLocaleString()} de forma segura con Mercado Pago.
+
+
+
+
+ >
+ )}
+
+
+ );
+ }
+
+ return (
+
+
+ Paso Final
+
+ Resumen de
+ tu publicación
+
+
+
+
+
+ {/* RESUMEN DEL AVISO */}
+
+
+
+
+
+
{attributes['title']}
+
{selectedCategory?.name} / {selectedOperation?.name}
+
+
+
+
Precio Producto
+
${listingPrice.toLocaleString()}
+
+
+
+
+ {definitions.map(def => {
+ const val = attributes[def.name.toLowerCase()] || attributes[def.name];
+ return val && (
+
+ {def.name}
+ {val}
+
+ );
+ })}
+
+
+
+ {/* --- BLOQUE DE COSTO (TOTAL A PAGAR) --- */}
+
+
+
+
Total a Pagar
+
+ $
+
+ {adFee.toLocaleString('es-AR', { minimumFractionDigits: 2 })}
+
+
+
+
+
+
+
+
+
+ {/* MÉTODO DE PAGO */}
+
+ Selecciona Método de Pago
+
+
+
+
+
+
+
+ {/* ACCIÓN PRINCIPAL */}
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/public-web/src/pages/Steps/TextEditorStep.tsx b/frontend/public-web/src/pages/Steps/TextEditorStep.tsx
new file mode 100644
index 0000000..a058a51
--- /dev/null
+++ b/frontend/public-web/src/pages/Steps/TextEditorStep.tsx
@@ -0,0 +1,233 @@
+import { useState, useEffect } from 'react';
+import { useWizardStore } from '../../store/wizardStore';
+import { StepWrapper } from '../../components/StepWrapper';
+import { useDebounce } from '../../hooks/useDebounce';
+import { Eye, FileText, Calendar, ChevronRight, ArrowLeft } from 'lucide-react';
+import clsx from 'clsx';
+import api from '../../services/api';
+
+interface PricingResult {
+ totalPrice: number;
+ baseCost: number;
+ extraCost: number;
+ wordCount: number;
+ specialCharCount: number;
+ details: string;
+}
+
+export default function TextEditorStep() {
+ const { selectedCategory, attributes, setAttribute, setStep } = useWizardStore();
+ const [text, setText] = useState(attributes['description'] || '');
+ const [days, setDays] = useState(parseInt(attributes['days'] as string) || 3);
+ const [pricing, setPricing] = useState({
+ totalPrice: 0,
+ baseCost: 0,
+ extraCost: 0,
+ wordCount: 0,
+ specialCharCount: 0,
+ details: ''
+ });
+ const [loadingPrice, setLoadingPrice] = useState(false);
+
+ const debouncedText = useDebounce(text, 800);
+
+ useEffect(() => {
+ if (!selectedCategory || debouncedText.length === 0) {
+ setPricing({ totalPrice: 0, baseCost: 0, extraCost: 0, wordCount: 0, specialCharCount: 0, details: '' });
+ return;
+ }
+
+ const calculatePrice = async () => {
+ setLoadingPrice(true);
+ try {
+ const response = await api.post('/pricing/calculate', {
+ categoryId: selectedCategory.id,
+ text: debouncedText,
+ days: days,
+ isBold: false,
+ isFrame: false,
+ startDate: new Date().toISOString()
+ });
+ setPricing(response.data);
+ } catch (error) {
+ console.error('Error al calcular precio:', error);
+ } finally {
+ setLoadingPrice(false);
+ }
+ };
+
+ calculatePrice();
+ }, [debouncedText, days, selectedCategory]);
+
+ const handleContinue = () => {
+ if (text.trim().length === 0) {
+ alert('⚠️ Debes escribir el texto del aviso');
+ return;
+ }
+ setAttribute('description', text);
+ setAttribute('days', days.toString());
+ setAttribute('adFee', pricing.totalPrice.toString());
+ setStep(5); // Siguiente paso
+ };
+
+ return (
+
+ {/* Header & Breadcrumb */}
+
+
+ {selectedCategory?.name}
+
+ Redacción
+
+
+ Escribe tu
+ Aviso Clasificado
+
+
+
+
+
+ {/* BLOQUE 1: EL EDITOR */}
+
+
+
+
+
+
Contenido del Aviso
+
+
+
+
+ {/* BLOQUE 2: CONFIGURACIÓN DE DÍAS */}
+
+
+
+
+
+
+
Duración de la oferta
+
¿Cuántos días se publicará?
+
+
+
+
+
+ setDays(parseInt(e.target.value) || 1)}
+ className="w-16 text-center bg-transparent border-none outline-none font-black text-xl text-blue-600"
+ />
+
+
+
+
+ {/* BLOQUE 3: PREVISUALIZACIÓN */}
+
+
+
+
+
+
+
+
Vista Previa Real
+
+
+
+ {text ? (
+
+ {text}
+
+ ) : (
+
+ El texto aparecerá aquí...
+
+ )}
+
+
+
+ {/* BLOQUE 4: COSTO FINAL */}
+
+
+
+
+
+
Total a Pagar
+
+ $
+
+ {pricing.totalPrice.toLocaleString('es-AR', { minimumFractionDigits: 2 })}
+
+
+
+
+
+
+ Tarifa base
+ ${pricing.baseCost.toLocaleString()}
+
+ {pricing.extraCost > 0 && (
+
+ Recargo texto
+ +${pricing.extraCost.toLocaleString()}
+
+ )}
+
+
+ * {pricing.details || "Calculando tasas e impuestos..."}
+
+
+
+
+
+ {/* BOTONES DE NAVEGACIÓN */}
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/public-web/src/services/api.ts b/frontend/public-web/src/services/api.ts
index f62cb5a..a9d9624 100644
--- a/frontend/public-web/src/services/api.ts
+++ b/frontend/public-web/src/services/api.ts
@@ -1,7 +1,18 @@
import axios from 'axios';
const api = axios.create({
- baseURL: import.meta.env.VITE_API_URL, // Usa la variable de entorno
+ baseURL: import.meta.env.VITE_API_URL,
+});
+
+api.interceptors.request.use(config => {
+ const token = localStorage.getItem('public_token');
+
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+}, error => {
+ return Promise.reject(error);
});
export default api;
\ No newline at end of file
diff --git a/frontend/public-web/src/services/authService.ts b/frontend/public-web/src/services/authService.ts
new file mode 100644
index 0000000..66e9c8d
--- /dev/null
+++ b/frontend/public-web/src/services/authService.ts
@@ -0,0 +1,67 @@
+const API_URL = `${import.meta.env.VITE_API_URL}/auth`;
+
+export const publicAuthService = {
+ login: async (username: string, password: string) => {
+ const response = await fetch(`${API_URL}/login`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username, password })
+ });
+ const data = await response.json();
+ if (response.ok && data.token) {
+ localStorage.setItem('public_token', data.token);
+ localStorage.setItem('public_user', JSON.stringify({ username }));
+ }
+ return data;
+ },
+
+ register: async (username: string, email: string, password: string) => {
+ const response = await fetch(`${API_URL}/register`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username, email, password })
+ });
+ return await response.json();
+ },
+
+ googleLogin: async (idToken: string) => {
+ const response = await fetch(`${API_URL}/google-login`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(idToken)
+ });
+ const data = await response.json();
+ if (response.ok && data.token) {
+ localStorage.setItem('public_token', data.token);
+ }
+ return data;
+ },
+
+ setupMfa: async () => {
+ const token = localStorage.getItem('public_token');
+ const response = await fetch(`${API_URL}/mfa/setup`, {
+ headers: { 'Authorization': `Bearer ${token}` }
+ });
+ return await response.json();
+ },
+
+ verifyMfa: async (code: string) => {
+ const token = localStorage.getItem('public_token');
+ const response = await fetch(`${API_URL}/mfa/verify`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`
+ },
+ body: JSON.stringify(code)
+ });
+ return await response.json();
+ },
+
+ logout: () => {
+ localStorage.removeItem('public_token');
+ localStorage.removeItem('public_user');
+ },
+
+ isAuthenticated: () => !!localStorage.getItem('public_token')
+};
diff --git a/frontend/publish-wizard/src/services/wizardService.ts b/frontend/public-web/src/services/wizardService.ts
similarity index 85%
rename from frontend/publish-wizard/src/services/wizardService.ts
rename to frontend/public-web/src/services/wizardService.ts
index 425d047..211c091 100644
--- a/frontend/publish-wizard/src/services/wizardService.ts
+++ b/frontend/public-web/src/services/wizardService.ts
@@ -1,5 +1,10 @@
import api from './api';
-import type { Category, Operation, AttributeDefinition } from '../types'; // ID: type-import-fix
+import type {
+ Category,
+ Operation,
+ AttributeDefinition,
+ CreateListingDto
+} from '../types';
export const wizardService = {
getCategories: async (): Promise => {
@@ -17,7 +22,7 @@ export const wizardService = {
return response.data;
},
- createListing: async (data: any): Promise<{ id: number }> => {
+ createListing: async (data: CreateListingDto): Promise<{ id: number }> => {
const response = await api.post<{ id: number }>('/listings', data);
return response.data;
},
@@ -30,4 +35,4 @@ export const wizardService = {
});
return response.data;
}
-};
+};
\ No newline at end of file
diff --git a/frontend/public-web/src/store/publicAuthStore.ts b/frontend/public-web/src/store/publicAuthStore.ts
new file mode 100644
index 0000000..02d42bc
--- /dev/null
+++ b/frontend/public-web/src/store/publicAuthStore.ts
@@ -0,0 +1,27 @@
+import { create } from 'zustand';
+
+interface User {
+ username: string;
+ email: string;
+}
+
+interface PublicAuthState {
+ user: User | null;
+ setUser: (user: User | null) => void;
+ logout: () => void;
+}
+
+export const usePublicAuthStore = create((set) => ({
+ // Inicializamos con lo que haya en localStorage
+ user: localStorage.getItem('public_user')
+ ? JSON.parse(localStorage.getItem('public_user')!)
+ : null,
+
+ setUser: (user) => set({ user }),
+
+ logout: () => {
+ localStorage.removeItem('public_token');
+ localStorage.removeItem('public_user');
+ set({ user: null });
+ }
+}));
\ No newline at end of file
diff --git a/frontend/public-web/src/store/wizardStore.ts b/frontend/public-web/src/store/wizardStore.ts
new file mode 100644
index 0000000..fa83e7e
--- /dev/null
+++ b/frontend/public-web/src/store/wizardStore.ts
@@ -0,0 +1,117 @@
+import { create } from 'zustand';
+import { persist, createJSONStorage } from 'zustand/middleware';
+import type { Category, Operation, Listing } from '../types';
+
+interface WizardState {
+ step: number;
+ selectedCategory: Category | null;
+ selectedOperation: Operation | null;
+ attributes: Record;
+ photos: File[];
+ setStep: (step: number) => void;
+ setCategory: (category: Category) => void;
+ setOperation: (operation: Operation) => void;
+ setAttribute: (key: string, value: string) => void;
+ addPhoto: (file: File) => void;
+ removePhoto: (index: number) => void;
+ setRepublishData: (listing: Listing) => void;
+ reset: () => void;
+ existingImages: any[];
+ removeExistingImage: (index: number) => void;
+}
+
+export const useWizardStore = create()(
+ persist(
+ (set) => ({
+ step: 1,
+ selectedCategory: null,
+ selectedOperation: null,
+ attributes: {},
+ photos: [],
+ existingImages: [],
+
+ setStep: (step) => set({ step }),
+
+ setCategory: (category) => set({ selectedCategory: category, step: 2 }),
+
+ setOperation: (operation) => set({ selectedOperation: operation, step: 3 }),
+
+ setAttribute: (key, value) => set((state) => ({
+ attributes: { ...state.attributes, [key]: value }
+ })),
+
+ addPhoto: (file) => set((state) => ({ photos: [...state.photos, file] })),
+
+ removePhoto: (index) => set((state) => ({ photos: state.photos.filter((_, i) => i !== index) })),
+
+ // REPUBLICACIÓN
+ setRepublishData: (data: any) => {
+ const listing = data.listing || data.Listing || data;
+
+ // Extraemos el ID con soporte para ambos casos
+ const catId = listing.categoryId ?? listing.CategoryId;
+
+ if (!catId) {
+ console.error("Error crítico: No se encontró CategoryId en:", data);
+ return;
+ }
+
+ // 1. Mapeamos campos estáticos (siempre en minúsculas para el form)
+ const mappedAttributes: Record = {
+ title: listing.title || listing.Title || '',
+ description: listing.description || listing.Description || '',
+ price: (listing.price || listing.Price || '0').toString(),
+ };
+
+ // 2. Mapeamos atributos dinámicos
+ const attributesArray = data.attributes || data.Attributes || [];
+ if (Array.isArray(attributesArray)) {
+ attributesArray.forEach((attr: any) => {
+ // Normalizamos la clave del atributo a minúsculas
+ const rawKey = attr.attributeName || attr.AttributeName;
+ if (rawKey) {
+ const key = rawKey.toLowerCase();
+ mappedAttributes[key] = (attr.value || attr.Value || '').toString();
+ }
+ });
+ }
+
+ const imagesArray = data.images || data.Images || [];
+
+ set({
+ step: 3,
+ selectedCategory: {
+ id: catId,
+ name: listing.categoryName || listing.CategoryName || 'Rubro'
+ } as any,
+ selectedOperation: {
+ id: listing.operationId || listing.OperationId,
+ name: 'Venta'
+ } as any,
+ attributes: mappedAttributes,
+ existingImages: imagesArray,
+ photos: []
+ });
+ },
+
+ removeExistingImage: (index: number) => set((state) => ({
+ existingImages: state.existingImages.filter((_, i) => i !== index)
+ })),
+
+ reset: () => {
+ localStorage.removeItem('wizard-storage');
+ set({ step: 1, selectedCategory: null, selectedOperation: null, attributes: {}, photos: [], existingImages: [] });
+ },
+ }),
+ {
+ name: 'wizard-storage',
+ storage: createJSONStorage(() => localStorage),
+ partialize: (state) => ({
+ step: state.step,
+ selectedCategory: state.selectedCategory,
+ selectedOperation: state.selectedOperation,
+ attributes: state.attributes,
+ }),
+ },
+ )
+);
\ No newline at end of file
diff --git a/frontend/public-web/src/types/index.ts b/frontend/public-web/src/types/index.ts
index cef30c6..c311d29 100644
--- a/frontend/public-web/src/types/index.ts
+++ b/frontend/public-web/src/types/index.ts
@@ -1,3 +1,4 @@
+// --- Tipos del Portal ---
export interface Listing {
id: number;
title: string;
@@ -8,7 +9,11 @@ export interface Listing {
operationId: number;
createdAt: string;
mainImageUrl?: string;
+ categoryName?: string;
+ status: string;
images?: ListingImage[];
+ viewCount: number;
+ overlayStatus?: 'Vendido' | 'Alquilado' | 'Reservado' | null;
}
export interface ListingAttribute {
@@ -33,9 +38,43 @@ export interface ListingDetail {
images: ListingImage[];
}
+// --- Tipos Agregados del Wizard ---
export interface Category {
id: number;
+ parentId?: number | null;
name: string;
- parentId?: number;
+ slug: string;
subcategories?: Category[];
+}
+
+export interface Operation {
+ id: number;
+ name: string;
+}
+
+export interface AttributeDefinition {
+ id: number;
+ name: string;
+ dataType: 'text' | 'number' | 'boolean' | 'date';
+ required: boolean;
+}
+
+// DTO para creación
+export interface CreateListingDto {
+ categoryId: number;
+ operationId: number;
+ title: string;
+ description: string;
+ price: number;
+ adFee: number;
+ currency: string;
+ status: string;
+ attributes: Record;
+ printText: string;
+ printDaysCount: number;
+ isBold?: boolean;
+ isFrame?: boolean;
+ printFontSize?: string;
+ printAlignment?: string;
+ imagesToClone?: string[];
}
\ No newline at end of file
diff --git a/frontend/public-web/src/utils/categoryTreeUtils.ts b/frontend/public-web/src/utils/categoryTreeUtils.ts
index 3703bf8..a268404 100644
--- a/frontend/public-web/src/utils/categoryTreeUtils.ts
+++ b/frontend/public-web/src/utils/categoryTreeUtils.ts
@@ -23,9 +23,8 @@ export const processCategoriesForSelect = (rawCategories: Category[]): FlatCateg
const map = new Map();
const roots: CategoryNode[] = [];
- // 1. Map
+ // 1. Mapear categorías
rawCategories.forEach(cat => {
- // @ts-ignore
map.set(cat.id, { ...cat, children: [] });
});
diff --git a/frontend/publish-wizard/.gitignore b/frontend/publish-wizard/.gitignore
deleted file mode 100644
index a547bf3..0000000
--- a/frontend/publish-wizard/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
-node_modules
-dist
-dist-ssr
-*.local
-
-# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
-.idea
-.DS_Store
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
diff --git a/frontend/publish-wizard/README.md b/frontend/publish-wizard/README.md
deleted file mode 100644
index d2e7761..0000000
--- a/frontend/publish-wizard/README.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# React + TypeScript + Vite
-
-This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
-
-Currently, two official plugins are available:
-
-- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
-- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
-
-## React Compiler
-
-The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
-
-## Expanding the ESLint configuration
-
-If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
-
-```js
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- // Other configs...
-
- // Remove tseslint.configs.recommended and replace with this
- tseslint.configs.recommendedTypeChecked,
- // Alternatively, use this for stricter rules
- tseslint.configs.strictTypeChecked,
- // Optionally, add this for stylistic rules
- tseslint.configs.stylisticTypeChecked,
-
- // Other configs...
- ],
- languageOptions: {
- parserOptions: {
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
- tsconfigRootDir: import.meta.dirname,
- },
- // other options...
- },
- },
-])
-```
-
-You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
-
-```js
-// eslint.config.js
-import reactX from 'eslint-plugin-react-x'
-import reactDom from 'eslint-plugin-react-dom'
-
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- // Other configs...
- // Enable lint rules for React
- reactX.configs['recommended-typescript'],
- // Enable lint rules for React DOM
- reactDom.configs.recommended,
- ],
- languageOptions: {
- parserOptions: {
- project: ['./tsconfig.node.json', './tsconfig.app.json'],
- tsconfigRootDir: import.meta.dirname,
- },
- // other options...
- },
- },
-])
-```
diff --git a/frontend/publish-wizard/eslint.config.js b/frontend/publish-wizard/eslint.config.js
deleted file mode 100644
index 5e6b472..0000000
--- a/frontend/publish-wizard/eslint.config.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import js from '@eslint/js'
-import globals from 'globals'
-import reactHooks from 'eslint-plugin-react-hooks'
-import reactRefresh from 'eslint-plugin-react-refresh'
-import tseslint from 'typescript-eslint'
-import { defineConfig, globalIgnores } from 'eslint/config'
-
-export default defineConfig([
- globalIgnores(['dist']),
- {
- files: ['**/*.{ts,tsx}'],
- extends: [
- js.configs.recommended,
- tseslint.configs.recommended,
- reactHooks.configs.flat.recommended,
- reactRefresh.configs.vite,
- ],
- languageOptions: {
- ecmaVersion: 2020,
- globals: globals.browser,
- },
- },
-])
diff --git a/frontend/publish-wizard/index.html b/frontend/publish-wizard/index.html
deleted file mode 100644
index 9ae067a..0000000
--- a/frontend/publish-wizard/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
- publish-wizard
-
-
-
-
-
-
diff --git a/frontend/publish-wizard/package-lock.json b/frontend/publish-wizard/package-lock.json
deleted file mode 100644
index d08df9c..0000000
--- a/frontend/publish-wizard/package-lock.json
+++ /dev/null
@@ -1,4313 +0,0 @@
-{
- "name": "publish-wizard",
- "version": "0.0.0",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "publish-wizard",
- "version": "0.0.0",
- "dependencies": {
- "@tailwindcss/postcss": "^4.1.18",
- "axios": "^1.13.2",
- "clsx": "^2.1.1",
- "framer-motion": "^12.23.26",
- "lucide-react": "^0.561.0",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "react-dropzone": "^14.3.8",
- "tailwind-merge": "^3.4.0",
- "zustand": "^5.0.9"
- },
- "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/@alloc/quick-lru": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
- "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "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"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@eslint-community/eslint-utils": {
- "version": "4.9.0",
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
- "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
- "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/@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.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz",
- "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz",
- "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz",
- "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz",
- "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz",
- "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz",
- "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz",
- "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz",
- "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz",
- "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz",
- "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz",
- "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz",
- "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz",
- "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz",
- "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz",
- "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz",
- "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz",
- "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz",
- "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz",
- "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz",
- "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz",
- "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.53.5",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz",
- "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "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/postcss": {
- "version": "4.1.18",
- "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz",
- "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==",
- "license": "MIT",
- "dependencies": {
- "@alloc/quick-lru": "^5.2.0",
- "@tailwindcss/node": "4.1.18",
- "@tailwindcss/oxide": "4.1.18",
- "postcss": "^8.4.41",
- "tailwindcss": "4.1.18"
- }
- },
- "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==",
- "dev": true,
- "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.4",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.4.tgz",
- "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "undici-types": "~7.16.0"
- }
- },
- "node_modules/@types/react": {
- "version": "19.2.7",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
- "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
- "devOptional": 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.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz",
- "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.50.0",
- "@typescript-eslint/type-utils": "8.50.0",
- "@typescript-eslint/utils": "8.50.0",
- "@typescript-eslint/visitor-keys": "8.50.0",
- "ignore": "^7.0.0",
- "natural-compare": "^1.4.0",
- "ts-api-utils": "^2.1.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.50.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.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz",
- "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/scope-manager": "8.50.0",
- "@typescript-eslint/types": "8.50.0",
- "@typescript-eslint/typescript-estree": "8.50.0",
- "@typescript-eslint/visitor-keys": "8.50.0",
- "debug": "^4.3.4"
- },
- "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.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz",
- "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.50.0",
- "@typescript-eslint/types": "^8.50.0",
- "debug": "^4.3.4"
- },
- "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.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz",
- "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.50.0",
- "@typescript-eslint/visitor-keys": "8.50.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.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz",
- "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==",
- "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.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz",
- "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.50.0",
- "@typescript-eslint/typescript-estree": "8.50.0",
- "@typescript-eslint/utils": "8.50.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.1.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.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz",
- "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==",
- "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.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz",
- "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/project-service": "8.50.0",
- "@typescript-eslint/tsconfig-utils": "8.50.0",
- "@typescript-eslint/types": "8.50.0",
- "@typescript-eslint/visitor-keys": "8.50.0",
- "debug": "^4.3.4",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
- "tinyglobby": "^0.2.15",
- "ts-api-utils": "^2.1.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.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz",
- "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.50.0",
- "@typescript-eslint/types": "8.50.0",
- "@typescript-eslint/typescript-estree": "8.50.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.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz",
- "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/types": "8.50.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/attr-accept": {
- "version": "2.2.5",
- "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
- "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "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.9",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.9.tgz",
- "integrity": "sha512-V8fbOCSeOFvlDj7LLChUcqbZrdKD9RU/VR260piF1790vT0mfLSwGc/Qzxv3IqiTukOpNtItePa0HBpMAj7MDg==",
- "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.30001760",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz",
- "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==",
- "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/clsx": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
- "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "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/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==",
- "devOptional": 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==",
- "dev": true,
- "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.6.0",
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
- "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==",
- "dev": true,
- "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/file-selector": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
- "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
- "license": "MIT",
- "dependencies": {
- "tslib": "^2.7.0"
- },
- "engines": {
- "node": ">= 12"
- }
- },
- "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/framer-motion": {
- "version": "12.23.26",
- "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz",
- "integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==",
- "license": "MIT",
- "dependencies": {
- "motion-dom": "^12.23.23",
- "motion-utils": "^12.23.6",
- "tslib": "^2.4.0"
- },
- "peerDependencies": {
- "@emotion/is-prop-valid": "*",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@emotion/is-prop-valid": {
- "optional": true
- },
- "react": {
- "optional": true
- },
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "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==",
- "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/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "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/lucide-react": {
- "version": "0.561.0",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.561.0.tgz",
- "integrity": "sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A==",
- "license": "ISC",
- "peerDependencies": {
- "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "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/motion-dom": {
- "version": "12.23.23",
- "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
- "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
- "license": "MIT",
- "dependencies": {
- "motion-utils": "^12.23.6"
- }
- },
- "node_modules/motion-utils": {
- "version": "12.23.6",
- "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
- "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
- "license": "MIT"
- },
- "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/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "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==",
- "dev": true,
- "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/prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
- "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/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-dropzone": {
- "version": "14.3.8",
- "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
- "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==",
- "license": "MIT",
- "dependencies": {
- "attr-accept": "^2.2.4",
- "file-selector": "^2.1.0",
- "prop-types": "^15.8.1"
- },
- "engines": {
- "node": ">= 10.13"
- },
- "peerDependencies": {
- "react": ">= 16.8 || 18.0.0"
- }
- },
- "node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT"
- },
- "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/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.53.5",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz",
- "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==",
- "dev": true,
- "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.53.5",
- "@rollup/rollup-android-arm64": "4.53.5",
- "@rollup/rollup-darwin-arm64": "4.53.5",
- "@rollup/rollup-darwin-x64": "4.53.5",
- "@rollup/rollup-freebsd-arm64": "4.53.5",
- "@rollup/rollup-freebsd-x64": "4.53.5",
- "@rollup/rollup-linux-arm-gnueabihf": "4.53.5",
- "@rollup/rollup-linux-arm-musleabihf": "4.53.5",
- "@rollup/rollup-linux-arm64-gnu": "4.53.5",
- "@rollup/rollup-linux-arm64-musl": "4.53.5",
- "@rollup/rollup-linux-loong64-gnu": "4.53.5",
- "@rollup/rollup-linux-ppc64-gnu": "4.53.5",
- "@rollup/rollup-linux-riscv64-gnu": "4.53.5",
- "@rollup/rollup-linux-riscv64-musl": "4.53.5",
- "@rollup/rollup-linux-s390x-gnu": "4.53.5",
- "@rollup/rollup-linux-x64-gnu": "4.53.5",
- "@rollup/rollup-linux-x64-musl": "4.53.5",
- "@rollup/rollup-openharmony-arm64": "4.53.5",
- "@rollup/rollup-win32-arm64-msvc": "4.53.5",
- "@rollup/rollup-win32-ia32-msvc": "4.53.5",
- "@rollup/rollup-win32-x64-gnu": "4.53.5",
- "@rollup/rollup-win32-x64-msvc": "4.53.5",
- "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/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/tailwind-merge": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
- "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
- "license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/dcastil"
- }
- },
- "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==",
- "dev": true,
- "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.1.0",
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
- "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18.12"
- },
- "peerDependencies": {
- "typescript": ">=4.8.4"
- }
- },
- "node_modules/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD"
- },
- "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.50.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz",
- "integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@typescript-eslint/eslint-plugin": "8.50.0",
- "@typescript-eslint/parser": "8.50.0",
- "@typescript-eslint/typescript-estree": "8.50.0",
- "@typescript-eslint/utils": "8.50.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==",
- "dev": 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.0",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz",
- "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==",
- "dev": true,
- "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.2.1",
- "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz",
- "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
- "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"
- }
- },
- "node_modules/zustand": {
- "version": "5.0.9",
- "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.9.tgz",
- "integrity": "sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==",
- "license": "MIT",
- "engines": {
- "node": ">=12.20.0"
- },
- "peerDependencies": {
- "@types/react": ">=18.0.0",
- "immer": ">=9.0.6",
- "react": ">=18.0.0",
- "use-sync-external-store": ">=1.2.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "immer": {
- "optional": true
- },
- "react": {
- "optional": true
- },
- "use-sync-external-store": {
- "optional": true
- }
- }
- }
- }
-}
diff --git a/frontend/publish-wizard/package.json b/frontend/publish-wizard/package.json
deleted file mode 100644
index 9ad710b..0000000
--- a/frontend/publish-wizard/package.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "name": "publish-wizard",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "tsc -b && vite build",
- "lint": "eslint .",
- "preview": "vite preview"
- },
- "dependencies": {
- "@tailwindcss/postcss": "^4.1.18",
- "axios": "^1.13.2",
- "clsx": "^2.1.1",
- "framer-motion": "^12.23.26",
- "lucide-react": "^0.561.0",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "react-dropzone": "^14.3.8",
- "tailwind-merge": "^3.4.0",
- "zustand": "^5.0.9"
- },
- "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/publish-wizard/postcss.config.js b/frontend/publish-wizard/postcss.config.js
deleted file mode 100644
index 1c87846..0000000
--- a/frontend/publish-wizard/postcss.config.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export default {
- plugins: {
- '@tailwindcss/postcss': {},
- autoprefixer: {},
- },
-}
diff --git a/frontend/publish-wizard/src/App.css b/frontend/publish-wizard/src/App.css
deleted file mode 100644
index b9d355d..0000000
--- a/frontend/publish-wizard/src/App.css
+++ /dev/null
@@ -1,42 +0,0 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/frontend/publish-wizard/src/App.tsx b/frontend/publish-wizard/src/App.tsx
deleted file mode 100644
index 817e5d1..0000000
--- a/frontend/publish-wizard/src/App.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import { useEffect, useState } from 'react';
-import { useWizardStore } from './store/wizardStore';
-import CategorySelection from './pages/Steps/CategorySelection';
-import OperationSelection from './pages/Steps/OperationSelection';
-import AttributeForm from './pages/Steps/AttributeForm';
-import PhotoUploadStep from './pages/Steps/PhotoUploadStep';
-import SummaryStep from './pages/Steps/SummaryStep';
-import { wizardService } from './services/wizardService';
-import type { AttributeDefinition } from './types';
-
-function App() {
- const { step, selectedCategory } = useWizardStore();
- const [definitions, setDefinitions] = useState([]);
-
- useEffect(() => {
- if (selectedCategory) {
- wizardService.getAttributes(selectedCategory.id).then(setDefinitions);
- }
- }, [selectedCategory]);
-
- return (
-
-
-
-
- {step === 1 && }
- {step === 2 && }
- {step === 3 && }
- {step === 4 && }
- {step === 5 && }
-
-
- );
-}
-
-export default App;
diff --git a/frontend/publish-wizard/src/index.css b/frontend/publish-wizard/src/index.css
deleted file mode 100644
index 0ea3710..0000000
--- a/frontend/publish-wizard/src/index.css
+++ /dev/null
@@ -1,16 +0,0 @@
-@import "tailwindcss";
-
-@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
-
-@theme {
- --font-sans: 'Inter', sans-serif;
- --color-brand-50: #f0f9ff;
- --color-brand-100: #e0f2fe;
- --color-brand-500: #0ea5e9;
- --color-brand-600: #0284c7;
- --color-brand-900: #0c4a6e;
-}
-
-body {
- @apply bg-slate-50 text-slate-900 font-sans;
-}
\ No newline at end of file
diff --git a/frontend/publish-wizard/src/main.tsx b/frontend/publish-wizard/src/main.tsx
deleted file mode 100644
index bef5202..0000000
--- a/frontend/publish-wizard/src/main.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { StrictMode } from 'react'
-import { createRoot } from 'react-dom/client'
-import './index.css'
-import App from './App.tsx'
-
-createRoot(document.getElementById('root')!).render(
-
-
- ,
-)
diff --git a/frontend/publish-wizard/src/pages/Steps/AttributeForm.tsx b/frontend/publish-wizard/src/pages/Steps/AttributeForm.tsx
deleted file mode 100644
index 6f07acb..0000000
--- a/frontend/publish-wizard/src/pages/Steps/AttributeForm.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import { useEffect, useState } from 'react';
-import { wizardService } from '../../services/wizardService';
-import { useWizardStore } from '../../store/wizardStore';
-import type { AttributeDefinition } from '../../types';
-import { StepWrapper } from '../../components/StepWrapper';
-
-export default function AttributeForm() {
- const { selectedCategory, selectedOperation, attributes, setAttribute, setStep } = useWizardStore();
- const [definitions, setDefinitions] = useState([]);
-
- useEffect(() => {
- if (selectedCategory) {
- wizardService.getAttributes(selectedCategory.id).then(setDefinitions);
- }
- }, [selectedCategory]);
-
- const handleSubmit = (e: React.FormEvent) => {
- e.preventDefault();
- setStep(4);
- };
-
- return (
-
-
-
- /
-
-
-
- Características
-
-
-
- );
-}
diff --git a/frontend/publish-wizard/src/pages/Steps/PhotoUploadStep.tsx b/frontend/publish-wizard/src/pages/Steps/PhotoUploadStep.tsx
deleted file mode 100644
index 68ab0dd..0000000
--- a/frontend/publish-wizard/src/pages/Steps/PhotoUploadStep.tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-import { useCallback } from 'react';
-import { useDropzone } from 'react-dropzone';
-import { StepWrapper } from '../../components/StepWrapper';
-import { useWizardStore } from '../../store/wizardStore';
-import { Upload, X } from 'lucide-react';
-
-export default function PhotoUploadStep() {
- const { setStep } = useWizardStore();
-
- return (
-
- Fotos del Aviso
- Muestra lo mejor de tu producto. Primera foto es la portada.
-
-
-
-
-
-
-
- );
-}
-
-function DropzoneArea() {
- const { addPhoto, removePhoto, photos } = useWizardStore();
-
- const onDrop = useCallback((acceptedFiles: File[]) => {
- acceptedFiles.forEach(file => {
- addPhoto(file);
- });
- }, [addPhoto]);
-
- const { getRootProps, getInputProps, isDragActive } = useDropzone({
- onDrop,
- accept: { 'image/*': [] },
- maxFiles: 10
- });
-
- return (
-
-
-
-
-
-
Arrastra fotos aquí, o click para seleccionar
-
Soporta JPG, PNG, WEBP
-
-
-
- {/* Previews */}
-
- {photos.map((file: File, index: number) => (
-
-
})
-
- {index === 0 && (
-
- Portada
-
- )}
-
- ))}
-
-
- );
-}
diff --git a/frontend/publish-wizard/src/pages/Steps/SummaryStep.tsx b/frontend/publish-wizard/src/pages/Steps/SummaryStep.tsx
deleted file mode 100644
index bc87f0d..0000000
--- a/frontend/publish-wizard/src/pages/Steps/SummaryStep.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import { useState } from 'react';
-import { useWizardStore } from '../../store/wizardStore';
-import { wizardService } from '../../services/wizardService';
-import { StepWrapper } from '../../components/StepWrapper';
-import type { AttributeDefinition } from '../../types';
-import { CreditCard, Wallet } from 'lucide-react';
-
-export default function SummaryStep({ definitions }: { definitions: AttributeDefinition[] }) {
- const { selectedCategory, selectedOperation, attributes, photos, setStep } = useWizardStore();
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [createdId, setCreatedId] = useState(null);
- const [paymentMethod, setPaymentMethod] = useState<'mercadopago' | 'stripe'>('mercadopago');
-
- const handlePublish = async () => {
- if (!selectedCategory || !selectedOperation) return;
-
- setIsSubmitting(true);
- try {
- const attributePayload: Record = {};
- definitions.forEach(def => {
- if (attributes[def.name]) {
- attributePayload[def.id] = attributes[def.name].toString();
- }
- });
-
- // Crear aviso con estado PENDING (Esperando pago)
- const payload = {
- categoryId: selectedCategory.id,
- operationId: selectedOperation.id,
- title: attributes['title'],
- description: 'Generated via Wizard',
- price: parseFloat(attributes['price']),
- currency: 'ARS',
- status: 'Pending', // <-- Importante para moderación
- attributes: attributePayload
- };
-
- const result = await wizardService.createListing(payload);
-
- // Upload Images
- if (photos.length > 0) {
- for (const photo of photos) {
- await wizardService.uploadImage(result.id, photo);
- }
- }
-
- // Simulación de Pago
- await new Promise(resolve => setTimeout(resolve, 2000)); // Fake network delay
-
- setCreatedId(result.id);
- } catch (error) {
- console.error(error);
- alert('Error al publicar');
- } finally {
- setIsSubmitting(false);
- }
- };
-
- if (createdId) {
- return (
-
-
-
✓
-
¡Pago Exitoso!
-
Tu aviso #{createdId} ha sido enviado a moderación.
-
- Comprobante de pago: {paymentMethod === 'mercadopago' ? 'MP-123456789' : 'ST-987654321'}
-
-
-
-
- );
- }
-
- return (
-
- Resumen y Pago
-
-
-
-
- Categoría
- {selectedCategory?.name}
-
-
- Operación
- {selectedOperation?.name}
-
-
-
-
{attributes['title']}
-
$ {attributes['price']}
-
-
- {definitions.map(def => attributes[def.name] && (
-
- {def.name}: {attributes[def.name]}
-
- ))}
-
-
-
- {/* Selector de Pago */}
-
-
Selecciona Método de Pago
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/frontend/publish-wizard/src/services/api.ts b/frontend/publish-wizard/src/services/api.ts
deleted file mode 100644
index f62cb5a..0000000
--- a/frontend/publish-wizard/src/services/api.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import axios from 'axios';
-
-const api = axios.create({
- baseURL: import.meta.env.VITE_API_URL, // Usa la variable de entorno
-});
-
-export default api;
\ No newline at end of file
diff --git a/frontend/publish-wizard/src/store/wizardStore.ts b/frontend/publish-wizard/src/store/wizardStore.ts
deleted file mode 100644
index c50718d..0000000
--- a/frontend/publish-wizard/src/store/wizardStore.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { create } from 'zustand';
-import type { Category, Operation } from '../types';
-
-interface WizardState {
- step: number;
- selectedCategory: Category | null;
- selectedOperation: Operation | null;
- attributes: Record;
- photos: File[]; // Added
- setStep: (step: number) => void;
- setCategory: (category: Category) => void;
- setOperation: (operation: Operation) => void;
- setAttribute: (key: string, value: any) => void;
- addPhoto: (file: File) => void; // Added
- removePhoto: (index: number) => void; // Added
- reset: () => void;
-}
-
-export const useWizardStore = create((set) => ({
- step: 1,
- selectedCategory: null,
- selectedOperation: null,
- attributes: {},
- photos: [],
- setStep: (step) => set({ step }),
- setCategory: (category) => set({ selectedCategory: category, step: 2 }), // Auto advance
- setOperation: (operation) => set({ selectedOperation: operation, step: 3 }), // Auto advance
- setAttribute: (key, value) => set((state) => ({
- attributes: { ...state.attributes, [key]: value }
- })),
- addPhoto: (file) => set((state) => ({ photos: [...state.photos, file] })),
- removePhoto: (index) => set((state) => ({ photos: state.photos.filter((_, i) => i !== index) })),
- reset: () => set({ step: 1, selectedCategory: null, selectedOperation: null, attributes: {}, photos: [] }),
-}));
diff --git a/frontend/publish-wizard/src/types/index.ts b/frontend/publish-wizard/src/types/index.ts
deleted file mode 100644
index 767bf26..0000000
--- a/frontend/publish-wizard/src/types/index.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-export interface Category {
- id: number;
- parentId?: number | null;
- name: string;
- slug: string;
- subcategories?: Category[];
-}
-
-export interface Operation {
- id: number;
- name: string;
-}
-
-export interface AttributeDefinition {
- id: number;
- name: string;
- dataType: 'text' | 'number' | 'boolean' | 'date';
- required: boolean;
-}
diff --git a/frontend/publish-wizard/src/types/listing.ts b/frontend/publish-wizard/src/types/listing.ts
deleted file mode 100644
index 02b6051..0000000
--- a/frontend/publish-wizard/src/types/listing.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-export interface CreateListingDto {
- categoryId: number;
- operationId: number;
- title: string;
- description: string;
- price: number;
- currency: string;
- userId?: number;
- attributes: Record; // definitionId -> Value
-}
-
-export interface Listing {
- id: number;
- // ...
-}
diff --git a/frontend/publish-wizard/tailwind.config.js b/frontend/publish-wizard/tailwind.config.js
deleted file mode 100644
index e536cf3..0000000
--- a/frontend/publish-wizard/tailwind.config.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/** @type {import('tailwindcss').Config} */
-export default {
- content: [
- "./index.html",
- "./src/**/*.{js,ts,jsx,tsx}",
- ],
- theme: {
- extend: {
- colors: {
- brand: {
- 50: '#f0f9ff',
- 100: '#e0f2fe',
- 500: '#0ea5e9',
- 600: '#0284c7',
- 900: '#0c4a6e',
- }
- },
- fontFamily: {
- sans: ['Inter', 'sans-serif'],
- }
- },
- },
- plugins: [],
-}
diff --git a/frontend/publish-wizard/tsconfig.app.json b/frontend/publish-wizard/tsconfig.app.json
deleted file mode 100644
index a9b5a59..0000000
--- a/frontend/publish-wizard/tsconfig.app.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
- "target": "ES2022",
- "useDefineForClassFields": true,
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
- "module": "ESNext",
- "types": ["vite/client"],
- "skipLibCheck": true,
-
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
- "moduleDetection": "force",
- "noEmit": true,
- "jsx": "react-jsx",
-
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "erasableSyntaxOnly": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["src"]
-}
diff --git a/frontend/publish-wizard/tsconfig.json b/frontend/publish-wizard/tsconfig.json
deleted file mode 100644
index 1ffef60..0000000
--- a/frontend/publish-wizard/tsconfig.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "files": [],
- "references": [
- { "path": "./tsconfig.app.json" },
- { "path": "./tsconfig.node.json" }
- ]
-}
diff --git a/frontend/publish-wizard/tsconfig.node.json b/frontend/publish-wizard/tsconfig.node.json
deleted file mode 100644
index 8a67f62..0000000
--- a/frontend/publish-wizard/tsconfig.node.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "compilerOptions": {
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
- "target": "ES2023",
- "lib": ["ES2023"],
- "module": "ESNext",
- "types": ["node"],
- "skipLibCheck": true,
-
- /* Bundler mode */
- "moduleResolution": "bundler",
- "allowImportingTsExtensions": true,
- "verbatimModuleSyntax": true,
- "moduleDetection": "force",
- "noEmit": true,
-
- /* Linting */
- "strict": true,
- "noUnusedLocals": true,
- "noUnusedParameters": true,
- "erasableSyntaxOnly": true,
- "noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
- },
- "include": ["vite.config.ts"]
-}
diff --git a/frontend/publish-wizard/vite.config.ts b/frontend/publish-wizard/vite.config.ts
deleted file mode 100644
index 8b0f57b..0000000
--- a/frontend/publish-wizard/vite.config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
-
-// https://vite.dev/config/
-export default defineConfig({
- plugins: [react()],
-})
diff --git a/src/SIGCM.API/Controllers/AttributeDefinitionsController.cs b/src/SIGCM.API/Controllers/AttributeDefinitionsController.cs
index dcfdeb6..c5c391d 100644
--- a/src/SIGCM.API/Controllers/AttributeDefinitionsController.cs
+++ b/src/SIGCM.API/Controllers/AttributeDefinitionsController.cs
@@ -1,3 +1,4 @@
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SIGCM.Domain.Entities;
using SIGCM.Domain.Interfaces;
@@ -16,6 +17,7 @@ public class AttributeDefinitionsController : ControllerBase
}
[HttpGet("category/{categoryId}")]
+ [AllowAnonymous]
public async Task GetByCategoryId(int categoryId)
{
var attributes = await _repository.GetByCategoryIdAsync(categoryId);
@@ -23,6 +25,7 @@ public class AttributeDefinitionsController : ControllerBase
}
[HttpPost]
+ [Authorize(Roles = "Admin")]
public async Task Create(AttributeDefinition attribute)
{
var id = await _repository.AddAsync(attribute);
@@ -31,6 +34,7 @@ public class AttributeDefinitionsController : ControllerBase
}
[HttpDelete("{id}")]
+ [Authorize(Roles = "Admin")]
public async Task Delete(int id)
{
await _repository.DeleteAsync(id);
diff --git a/src/SIGCM.API/Controllers/AuthController.cs b/src/SIGCM.API/Controllers/AuthController.cs
index a9a4bc1..017dcbb 100644
--- a/src/SIGCM.API/Controllers/AuthController.cs
+++ b/src/SIGCM.API/Controllers/AuthController.cs
@@ -1,3 +1,4 @@
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SIGCM.Application.DTOs;
using SIGCM.Application.Interfaces;
@@ -15,12 +16,60 @@ public class AuthController : ControllerBase
_authService = authService;
}
+ // Inicio de sesión tradicional
[HttpPost("login")]
public async Task Login(LoginDto dto)
{
- var token = await _authService.LoginAsync(dto.Username, dto.Password);
- if (token == null) return Unauthorized("Invalid credentials");
+ var result = await _authService.LoginAsync(dto.Username, dto.Password);
+ if (!result.Success) return Unauthorized(new { message = result.ErrorMessage });
+ return Ok(result);
+ }
+
+ // Registro de nuevos usuarios
+ [HttpPost("register")]
+ public async Task Register(RegisterDto dto)
+ {
+ var result = await _authService.RegisterAsync(dto.Username, dto.Email, dto.Password);
+ if (!result.Success) return BadRequest(new { message = result.ErrorMessage });
+ return Ok(result);
+ }
+
+ // Inicio de sesión con Google
+ [HttpPost("google-login")]
+ public async Task GoogleLogin([FromBody] string idToken)
+ {
+ var result = await _authService.GoogleLoginAsync(idToken);
+ if (!result.Success) return Unauthorized(new { message = result.ErrorMessage });
+ return Ok(result);
+ }
+
+ // Flujo MFA: Obtener secreto (QR)
+ [Authorize]
+ [HttpGet("mfa/setup")]
+ public async Task SetupMfa()
+ {
+ var userId = int.Parse(User.FindFirst("Id")?.Value!);
+ var secret = await _authService.GenerateMfaSecretAsync(userId);
+ return Ok(new { secret, qrCodeUri = $"otpauth://totp/SIGCM:{User.Identity?.Name}?secret={secret}&issuer=SIGCM" });
+ }
+
+ // Flujo MFA: Verificar y activar
+ [Authorize]
+ [HttpPost("mfa/verify")]
+ public async Task VerifyMfa([FromBody] string code)
+ {
+ var userId = int.Parse(User.FindFirst("Id")?.Value!);
+ var valid = await _authService.VerifyMfaCodeAsync(userId, code);
+ if (!valid) return BadRequest(new { message = "Código inválido" });
- return Ok(new { token });
+ await _authService.EnableMfaAsync(userId, true);
+ return Ok(new { success = true });
}
}
+
+public class RegisterDto
+{
+ public string Username { get; set; } = "";
+ public string Email { get; set; } = "";
+ public string Password { get; set; } = "";
+}
diff --git a/src/SIGCM.API/Controllers/CashClosingController.cs b/src/SIGCM.API/Controllers/CashClosingController.cs
new file mode 100644
index 0000000..788d32a
--- /dev/null
+++ b/src/SIGCM.API/Controllers/CashClosingController.cs
@@ -0,0 +1,182 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SIGCM.Application.DTOs;
+using SIGCM.Domain.Entities;
+using SIGCM.Infrastructure.Repositories;
+
+namespace SIGCM.API.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+[Authorize]
+public class CashClosingController : ControllerBase
+{
+ private readonly CashClosingRepository _closingRepo;
+ private readonly AuditRepository _auditRepo;
+
+ public CashClosingController(CashClosingRepository closingRepo, AuditRepository auditRepo)
+ {
+ _closingRepo = closingRepo;
+ _auditRepo = auditRepo;
+ }
+
+ ///
+ /// Procesar cierre de caja con arqueo ciego
+ /// El cajero declara sus totales sin ver primero lo que dice el sistema
+ ///
+ [HttpPost("close")]
+ [Authorize(Roles = "Cajero,Admin")]
+ public async Task CloseCash(CashClosingDto dto)
+ {
+ // Obtener usuario actual del JWT
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (!int.TryParse(userIdClaim, out int userId))
+ {
+ return Unauthorized("Usuario no identificado");
+ }
+
+ // Obtener los totales reales del sistema para el día actual
+ var today = DateTime.UtcNow.Date;
+ var systemTotals = await _closingRepo.GetPaymentTotalsByMethodAsync(userId, today);
+
+ // Calcular diferencias
+ var closing = new CashClosing
+ {
+ UserId = userId,
+ ClosingDate = DateTime.UtcNow,
+
+ // Valores declarados por el cajero
+ DeclaredCash = dto.DeclaredCash,
+ DeclaredDebit = dto.DeclaredDebit,
+ DeclaredCredit = dto.DeclaredCredit,
+ DeclaredTransfer = dto.DeclaredTransfer,
+
+ // Valores del sistema
+ SystemCash = systemTotals["Cash"],
+ SystemDebit = systemTotals["Debit"],
+ SystemCredit = systemTotals["Credit"],
+ SystemTransfer = systemTotals["Transfer"],
+
+ // Cálculo de diferencias
+ CashDifference = dto.DeclaredCash - systemTotals["Cash"],
+ DebitDifference = dto.DeclaredDebit - systemTotals["Debit"],
+ CreditDifference = dto.DeclaredCredit - systemTotals["Credit"],
+ TransferDifference = dto.DeclaredTransfer - systemTotals["Transfer"],
+
+ // Totales
+ TotalDeclared = dto.DeclaredCash + dto.DeclaredDebit + dto.DeclaredCredit + dto.DeclaredTransfer,
+ TotalSystem = systemTotals["Cash"] + systemTotals["Debit"] + systemTotals["Credit"] + systemTotals["Transfer"],
+
+ Notes = dto.Notes
+ };
+
+ // Calcular diferencia total
+ closing.TotalDifference = closing.TotalDeclared - closing.TotalSystem;
+
+ // Detectar discrepancias (tolerancia de $10 pesos)
+ closing.HasDiscrepancies = Math.Abs(closing.TotalDifference) > 10;
+
+ // Guardar el cierre
+ var closingId = await _closingRepo.CreateAsync(closing);
+
+ // Registrar en auditoría
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = userId,
+ Action = "CASH_CLOSING",
+ EntityId = closingId,
+ EntityType = "CashClosing",
+ Details = $"Cierre de caja. Diferencia: ${closing.TotalDifference}. Discrepancias: {closing.HasDiscrepancies}",
+ CreatedAt = DateTime.UtcNow
+ });
+
+ // Preparar respuesta
+ var result = new CashClosingResultDto
+ {
+ ClosingId = closingId,
+
+ DeclaredCash = closing.DeclaredCash,
+ DeclaredDebit = closing.DeclaredDebit,
+ DeclaredCredit = closing.DeclaredCredit,
+ DeclaredTransfer = closing.DeclaredTransfer,
+ TotalDeclared = closing.TotalDeclared,
+
+ SystemCash = closing.SystemCash,
+ SystemDebit = closing.SystemDebit,
+ SystemCredit = closing.SystemCredit,
+ SystemTransfer = closing.SystemTransfer,
+ TotalSystem = closing.TotalSystem,
+
+ CashDifference = closing.CashDifference,
+ DebitDifference = closing.DebitDifference,
+ CreditDifference = closing.CreditDifference,
+ TransferDifference = closing.TransferDifference,
+ TotalDifference = closing.TotalDifference,
+
+ HasDiscrepancies = closing.HasDiscrepancies,
+ Message = closing.HasDiscrepancies
+ ? "⚠️ ATENCIÓN: Se detectaron diferencias en el cierre. Un supervisor revisará tu caja."
+ : "✅ Cierre correcto. No se detectaron diferencias."
+ };
+
+ return Ok(result);
+ }
+
+ ///
+ /// Obtener cierres con discrepancias pendientes de aprobación (Solo Admin)
+ ///
+ [HttpGet("discrepancies")]
+ [Authorize(Roles = "Admin")]
+ public async Task GetDiscrepancies()
+ {
+ var discrepancies = await _closingRepo.GetDiscrepanciesAsync();
+ return Ok(discrepancies);
+ }
+
+ ///
+ /// Aprobar un cierre de caja con discrepancias (Solo Admin)
+ ///
+ [HttpPost("{id}/approve")]
+ [Authorize(Roles = "Admin")]
+ public async Task ApproveClosure(int id)
+ {
+ await _closingRepo.ApproveAsync(id);
+
+ // Registrar en auditoría
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (int.TryParse(userIdClaim, out int userId))
+ {
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = userId,
+ Action = "APPROVE_CASH_CLOSING",
+ EntityId = id,
+ EntityType = "CashClosing",
+ Details = "Cierre de caja aprobado por administrador",
+ CreatedAt = DateTime.UtcNow
+ });
+ }
+
+ return Ok(new { message = "Cierre aprobado exitosamente" });
+ }
+
+ ///
+ /// Obtener historial de cierres del usuario actual
+ ///
+ [HttpGet("history")]
+ [Authorize(Roles = "Cajero,Admin")]
+ public async Task GetHistory([FromQuery] DateTime? from, [FromQuery] DateTime? to)
+ {
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (!int.TryParse(userIdClaim, out int userId))
+ {
+ return Unauthorized();
+ }
+
+ var startDate = from ?? DateTime.UtcNow.AddMonths(-1);
+ var endDate = to ?? DateTime.UtcNow;
+
+ var closings = await _closingRepo.GetByUserAsync(userId, startDate, endDate);
+ return Ok(closings);
+ }
+}
diff --git a/src/SIGCM.API/Controllers/CashSessionsController.cs b/src/SIGCM.API/Controllers/CashSessionsController.cs
new file mode 100644
index 0000000..6f2d4df
--- /dev/null
+++ b/src/SIGCM.API/Controllers/CashSessionsController.cs
@@ -0,0 +1,171 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SIGCM.Application.DTOs;
+using SIGCM.Infrastructure.Repositories;
+using SIGCM.Domain.Entities; // <-- Faltaba para AuditLog
+using System.Security.Claims;
+using SIGCM.Infrastructure.Services; // <-- Recomendado para claridad en User.FindFirst
+
+namespace SIGCM.API.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+[Authorize]
+public class CashSessionsController : ControllerBase
+{
+ private readonly CashSessionRepository _repo;
+ private readonly AuditRepository _auditRepo; // <-- AGREGADO: Declaración del campo
+
+ // ACTUALIZADO: Inyección de ambos repositorios en el constructor
+ public CashSessionsController(CashSessionRepository repo, AuditRepository auditRepo)
+ {
+ _repo = repo;
+ _auditRepo = auditRepo;
+ }
+
+ [HttpGet("status")]
+ public async Task GetStatus()
+ {
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (string.IsNullOrEmpty(userIdClaim) || !int.TryParse(userIdClaim, out int userId))
+ return Unauthorized();
+
+ var session = await _repo.GetActiveSessionAsync(userId);
+ return Ok(new { isOpen = session != null, session });
+ }
+
+ [HttpPost("open")]
+ public async Task Open([FromBody] decimal openingBalance)
+ {
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (!int.TryParse(userIdClaim, out int userId)) return Unauthorized();
+
+ var existing = await _repo.GetActiveSessionAsync(userId);
+ if (existing != null) return BadRequest("Ya tienes una caja abierta");
+
+ var id = await _repo.OpenSessionAsync(userId, openingBalance);
+
+ // Opcional: Auditar la apertura
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = userId,
+ Action = "CASH_SESSION_OPENED",
+ EntityId = id,
+ EntityType = "CashSession",
+ Details = $"Caja abierta con fondo: ${openingBalance}",
+ CreatedAt = DateTime.UtcNow
+ });
+
+ return Ok(new { id });
+ }
+
+ [HttpPost("close")]
+ public async Task Close(CashClosingDto dto)
+ {
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (!int.TryParse(userIdClaim, out int userId)) return Unauthorized();
+
+ var session = await _repo.GetActiveSessionAsync(userId);
+ if (session == null) return BadRequest("No hay una sesión activa para cerrar");
+
+ var system = await _repo.GetSystemTotalsAsync(userId, session.OpeningDate, DateTime.UtcNow);
+
+ session.DeclaredCash = dto.DeclaredCash;
+ session.DeclaredCards = dto.DeclaredDebit + dto.DeclaredCredit;
+ session.DeclaredTransfers = dto.DeclaredTransfer;
+
+ session.SystemExpectedCash = (decimal)(system.Cash ?? 0m);
+ session.SystemExpectedCards = (decimal)(system.Cards ?? 0m);
+ session.SystemExpectedTransfers = (decimal)(system.Transfers ?? 0m);
+
+ decimal totalExpected = session.SystemExpectedCash.Value + session.OpeningBalance +
+ session.SystemExpectedCards.Value + session.SystemExpectedTransfers.Value;
+
+ decimal totalDeclared = dto.DeclaredCash + dto.DeclaredDebit + dto.DeclaredCredit + dto.DeclaredTransfer;
+
+ session.TotalDifference = totalDeclared - totalExpected;
+
+ await _repo.CloseSessionAsync(session);
+
+ // Auditar el cierre
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = userId,
+ Action = "CASH_SESSION_CLOSED",
+ EntityId = session.Id,
+ EntityType = "CashSession",
+ Details = $"Caja cerrada por el usuario. Diferencia detectada: ${session.TotalDifference}",
+ CreatedAt = DateTime.UtcNow
+ });
+
+ return Ok(new
+ {
+ message = "Caja cerrada. Pendiente de validación por supervisor.",
+ difference = session.TotalDifference,
+ sessionId = session.Id
+ });
+ }
+
+ [HttpGet("summary")]
+ public async Task GetSummary()
+ {
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (!int.TryParse(userIdClaim, out int userId)) return Unauthorized();
+
+ var session = await _repo.GetActiveSessionAsync(userId);
+ if (session == null) return BadRequest("No hay sesión activa");
+
+ var system = await _repo.GetSystemTotalsAsync(userId, session.OpeningDate, DateTime.UtcNow);
+
+ return Ok(new
+ {
+ openingBalance = session.OpeningBalance,
+ cashSales = (decimal)(system.Cash ?? 0m),
+ cardSales = (decimal)(system.Cards ?? 0m),
+ transferSales = (decimal)(system.Transfers ?? 0m),
+ totalExpected = session.OpeningBalance + (decimal)(system.Cash ?? 0m) + (decimal)(system.Cards ?? 0m) + (decimal)(system.Transfers ?? 0m)
+ });
+ }
+
+ [HttpGet("pending")]
+ [Authorize(Roles = "Admin")]
+ public async Task GetPending()
+ {
+ var pending = await _repo.GetPendingValidationAsync();
+ return Ok(pending);
+ }
+
+ [HttpPost("{id}/validate")]
+ [Authorize(Roles = "Admin")]
+ public async Task Validate(int id, [FromBody] string notes)
+ {
+ var adminIdClaim = User.FindFirst("Id")?.Value;
+ if (!int.TryParse(adminIdClaim, out int adminId)) return Unauthorized();
+
+ await _repo.ValidateSessionAsync(id, adminId, notes);
+
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = adminId,
+ Action = "CASH_SESSION_VALIDATED",
+ EntityId = id,
+ EntityType = "CashSession",
+ Details = $"Caja #{id} validada y liquidada. Notas: {notes}",
+ CreatedAt = DateTime.UtcNow
+ });
+
+ return Ok(new { message = "Sesión liquidada correctamente" });
+ }
+
+ [HttpGet("{id}/pdf")]
+ public async Task DownloadPdf(int id)
+ {
+ var session = await _repo.GetSessionDetailAsync(id);
+ if (session == null) return NotFound();
+
+ var pdfBytes = ReportGenerator.GenerateCashSessionPdf(session);
+ string fileName = $"Acta_Cierre_{id}_{DateTime.Now:yyyyMMdd}.pdf";
+
+ return File(pdfBytes, "application/pdf", fileName);
+ }
+}
\ No newline at end of file
diff --git a/src/SIGCM.API/Controllers/CategoriesController.cs b/src/SIGCM.API/Controllers/CategoriesController.cs
index a6e0a28..97f5e77 100644
--- a/src/SIGCM.API/Controllers/CategoriesController.cs
+++ b/src/SIGCM.API/Controllers/CategoriesController.cs
@@ -1,7 +1,10 @@
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SIGCM.Domain.Entities;
using SIGCM.Domain.Interfaces;
+using SIGCM.Infrastructure.Repositories;
+
namespace SIGCM.API.Controllers;
[ApiController]
@@ -10,14 +13,17 @@ public class CategoriesController : ControllerBase
{
private readonly ICategoryRepository _repository;
private readonly IListingRepository _listingRepo;
+ private readonly AuditRepository _auditRepo;
- public CategoriesController(ICategoryRepository repository, IListingRepository listingRepo)
+ public CategoriesController(ICategoryRepository repository, IListingRepository listingRepo, AuditRepository auditRepo)
{
_repository = repository;
_listingRepo = listingRepo;
+ _auditRepo = auditRepo;
}
[HttpGet]
+ [AllowAnonymous]
public async Task GetAll()
{
var categories = await _repository.GetAllAsync();
@@ -25,6 +31,7 @@ public class CategoriesController : ControllerBase
}
[HttpGet("{id}")]
+ [AllowAnonymous]
public async Task GetById(int id)
{
var category = await _repository.GetByIdAsync(id);
@@ -33,6 +40,7 @@ public class CategoriesController : ControllerBase
}
[HttpPost]
+ [Authorize(Roles = "Admin")]
public async Task Create(Category category)
{
// Regla: No crear hijos en padres con avisos
@@ -49,6 +57,7 @@ public class CategoriesController : ControllerBase
}
[HttpPut("{id}")]
+ [Authorize(Roles = "Admin")]
public async Task Update(int id, Category category)
{
if (id != category.Id) return BadRequest();
@@ -66,9 +75,26 @@ public class CategoriesController : ControllerBase
}
[HttpDelete("{id}")]
+ [Authorize(Roles = "Admin")]
public async Task Delete(int id)
{
await _repository.DeleteAsync(id);
+
+ // Audit Log
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (int.TryParse(userIdClaim, out int userId))
+ {
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = userId,
+ Action = "DELETE_CATEGORY",
+ EntityId = id,
+ EntityType = "Category",
+ Details = "Category deleted via API",
+ CreatedAt = DateTime.UtcNow
+ });
+ }
+
return NoContent();
}
diff --git a/src/SIGCM.API/Controllers/ClaimsController.cs b/src/SIGCM.API/Controllers/ClaimsController.cs
new file mode 100644
index 0000000..5f4884f
--- /dev/null
+++ b/src/SIGCM.API/Controllers/ClaimsController.cs
@@ -0,0 +1,74 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SIGCM.Domain.Entities;
+using SIGCM.Domain.Interfaces;
+using SIGCM.Infrastructure.Repositories;
+using SIGCM.Domain.Models;
+
+namespace SIGCM.API.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+[Authorize]
+public class ClaimsController : ControllerBase
+{
+ private readonly IClaimRepository _repository;
+ private readonly AuditRepository _auditRepo;
+
+ public ClaimsController(IClaimRepository repository, AuditRepository auditRepo)
+ {
+ _repository = repository;
+ _auditRepo = auditRepo;
+ }
+
+ [HttpPost]
+ public async Task Create(Claim claim)
+ {
+ System.Security.Claims.Claim? userIdClaim = User.FindFirst("Id");
+ var userId = int.Parse(userIdClaim?.Value!);
+ claim.CreatedByUserId = userId;
+ claim.CreatedAt = DateTime.UtcNow;
+ claim.Status = "Open";
+
+ var id = await _repository.CreateAsync(claim);
+
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = userId,
+ Action = "CLAIM_CREATED",
+ EntityId = id,
+ EntityType = "Claim",
+ Details = $"Reclamo creado para aviso {claim.ListingId}: {claim.ClaimType}"
+ });
+
+ return Ok(new { id });
+ }
+
+ [HttpGet("listing/{listingId}")]
+ public async Task GetByListing(int listingId)
+ {
+ var claims = await _repository.GetByListingIdAsync(listingId);
+ return Ok(claims);
+ }
+
+ [HttpPut("{id}/resolve")]
+ public async Task Resolve(int id, [FromBody] ResolveRequest request)
+ {
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (!int.TryParse(userIdClaim, out int userId)) return Unauthorized();
+
+ // Llamamos al repositorio pasando el objeto completo de solicitud
+ await _repository.UpdateStatusAsync(id, "Resolved", request, userId);
+
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = userId,
+ Action = "CLAIM_RESOLVED_TECHNICAL",
+ EntityId = id,
+ EntityType = "Claim",
+ Details = $"Reclamo {id} resuelto con ajustes técnicos aplicados."
+ });
+
+ return Ok(new { message = "Reclamo resuelto y aviso actualizado." });
+ }
+}
\ No newline at end of file
diff --git a/src/SIGCM.API/Controllers/ClientsController.cs b/src/SIGCM.API/Controllers/ClientsController.cs
index 6649fd0..94c0a67 100644
--- a/src/SIGCM.API/Controllers/ClientsController.cs
+++ b/src/SIGCM.API/Controllers/ClientsController.cs
@@ -1,10 +1,12 @@
using Microsoft.AspNetCore.Mvc;
+using SIGCM.Domain.Entities;
using SIGCM.Infrastructure.Repositories;
namespace SIGCM.API.Controllers;
[ApiController]
[Route("api/[controller]")]
+[Microsoft.AspNetCore.Authorization.Authorize]
public class ClientsController : ControllerBase
{
private readonly ClientRepository _repo;
@@ -29,10 +31,19 @@ public class ClientsController : ControllerBase
return Ok(clients);
}
- [HttpGet("{id}/history")]
- public async Task GetHistory(int id)
+ [HttpPut("{id}")]
+ public async Task Update(int id, Client client)
{
- var history = await _repo.GetClientHistoryAsync(id);
- return Ok(history);
+ if (id != client.Id) return BadRequest("ID de URL no coincide con el cuerpo");
+ await _repo.UpdateAsync(client);
+ return NoContent();
+ }
+
+ [HttpGet("{id}/summary")]
+ public async Task GetSummary(int id)
+ {
+ var summary = await _repo.GetClientSummaryAsync(id);
+ if (summary == null) return NotFound();
+ return Ok(summary);
}
}
\ No newline at end of file
diff --git a/src/SIGCM.API/Controllers/DashboardController.cs b/src/SIGCM.API/Controllers/DashboardController.cs
new file mode 100644
index 0000000..cfeed1d
--- /dev/null
+++ b/src/SIGCM.API/Controllers/DashboardController.cs
@@ -0,0 +1,41 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SIGCM.Domain.Interfaces;
+
+namespace SIGCM.API.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+[Authorize]
+public class DashboardController : ControllerBase
+{
+ private readonly IListingRepository _repository;
+
+ public DashboardController(IListingRepository repository)
+ {
+ _repository = repository;
+ }
+
+ // Obtiene estadísticas básicas para el dashboard principal
+ [HttpGet("stats")]
+ public async Task GetStats([FromQuery] DateTime? start, [FromQuery] DateTime? end)
+ {
+ var startDate = start ?? DateTime.UtcNow.AddDays(-7);
+ var endDate = end ?? DateTime.UtcNow;
+
+ var stats = await _repository.GetDashboardStatsAsync(startDate, endDate);
+ return Ok(stats);
+ }
+
+ // Obtiene analítica avanzada para reportes gerenciales detallados
+ [HttpGet("analytics")]
+ [Authorize(Roles = "Admin,Gerente")]
+ public async Task GetAdvancedAnalytics([FromQuery] DateTime? start, [FromQuery] DateTime? end)
+ {
+ var startDate = start ?? DateTime.UtcNow.AddMonths(-1);
+ var endDate = end ?? DateTime.UtcNow;
+
+ var analytics = await _repository.GetAdvancedAnalyticsAsync(startDate, endDate);
+ return Ok(analytics);
+ }
+}
diff --git a/src/SIGCM.API/Controllers/ExportsController.cs b/src/SIGCM.API/Controllers/ExportsController.cs
index f5f12a4..d0b6d12 100644
--- a/src/SIGCM.API/Controllers/ExportsController.cs
+++ b/src/SIGCM.API/Controllers/ExportsController.cs
@@ -2,7 +2,9 @@ using System.Text;
using System.Xml.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using SIGCM.Domain.Entities;
using SIGCM.Domain.Interfaces;
+using SIGCM.Infrastructure.Repositories;
namespace SIGCM.API.Controllers;
@@ -11,42 +13,294 @@ namespace SIGCM.API.Controllers;
[Authorize(Roles = "Admin,Diagramador")]
public class ExportsController : ControllerBase
{
- private readonly IListingRepository _repository;
+ private readonly IListingRepository _listingRepo;
+ private readonly ICategoryRepository _categoryRepo;
+ private readonly EditionClosureRepository _closureRepo;
+ private readonly AuditRepository _auditRepo;
- public ExportsController(IListingRepository repository)
+ public ExportsController(
+ IListingRepository listingRepo,
+ ICategoryRepository categoryRepo,
+ EditionClosureRepository closureRepo,
+ AuditRepository auditRepo)
{
- _repository = repository;
+ _listingRepo = listingRepo;
+ _categoryRepo = categoryRepo;
+ _closureRepo = closureRepo;
+ _auditRepo = auditRepo;
}
+ ///
+ /// Exportar XML para diagramación con estructura jerárquica y estilos
+ /// Compatible con Adobe InDesign y QuarkXPress
+ ///
[HttpGet("diagram")]
public async Task DownloadDiagram([FromQuery] DateTime date)
{
- var listings = await _repository.GetListingsForPrintAsync(date);
+ // Verificar si la edición está cerrada
+ var isClosed = await _closureRepo.IsEditionClosedAsync(date);
+
+ // Obtener avisos para la fecha
+ var listings = await _listingRepo.GetListingsForPrintAsync(date);
+
+ if (!listings.Any())
+ {
+ return NotFound(new { message = "No hay avisos para exportar en esa fecha." });
+ }
- // Agrupar por Rubro
- var grouped = listings.GroupBy(l => l.CategoryName ?? "Varios");
+ // Obtener todas las categorías para armar la jerarquía
+ var allCategories = await _categoryRepo.GetAllAsync();
+ var categoryDict = allCategories.ToDictionary(c => c.Id, c => c);
- // Construir XML usando XDocument (más seguro y limpio que StringBuilder)
+ // Crear estructura jerárquica con categorías raíz
+ var rootCategories = allCategories.Where(c => c.ParentId == null).OrderBy(c => c.Name);
+
+ // Construir XML con jerarquía completa
var doc = new XDocument(
- new XElement("Edition",
- new XAttribute("date", date.ToString("yyyy-MM-dd")),
- from g in grouped
- select new XElement("Category",
- new XAttribute("name", g.Key),
- from l in g
- select new XElement("Ad",
- new XAttribute("id", l.Id),
- // Usamos CDATA para proteger caracteres especiales en el texto
- new XCData(l.PrintText ?? l.Description ?? "")
- )
- )
- )
+ new XDeclaration("1.0", "utf-8", "yes"),
+ new XElement("Edition",
+ new XAttribute("date", date.ToString("yyyy-MM-dd")),
+ new XAttribute("closed", isClosed.ToString().ToLower()),
+ new XAttribute("totalAds", listings.Count()),
+
+ // Recorrer categorías raíz
+ from rootCat in rootCategories
+ select BuildCategoryElement(rootCat, categoryDict, listings)
+ )
);
- // Convertir a bytes
- var xmlBytes = Encoding.UTF8.GetBytes(doc.ToString());
- var fileName = $"diagrama_{date:yyyy-MM-dd}.xml";
+ // Registrar exportación en auditoría
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (int.TryParse(userIdClaim, out int userId))
+ {
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = userId,
+ Action = "EXPORT_XML",
+ EntityType = "Edition",
+ Details = $"Exportación XML para {date:yyyy-MM-dd}. Avisos: {listings.Count()}",
+ CreatedAt = DateTime.UtcNow
+ });
+ }
+
+ // Convertir a bytes con formato indentado
+ var xmlBytes = Encoding.UTF8.GetBytes(doc.ToString(SaveOptions.None));
+ var fileName = $"Edicion_{date:yyyy-MM-dd}.xml";
return File(xmlBytes, "application/xml", fileName);
}
+
+ // Método recursivo para construir la jerarquía de categorías
+ private XElement BuildCategoryElement(
+ Category category,
+ Dictionary categoryDict,
+ IEnumerable allListings)
+ {
+ // Filtrar avisos de esta categoría
+ var adsInCategory = allListings
+ .Where(l => l.CategoryId == category.Id)
+ .OrderBy(l => l.Title) // Orden alfabético
+ .ToList();
+
+ // Crear elemento de categoría
+ var categoryElement = new XElement("Category",
+ new XAttribute("id", category.Id),
+ new XAttribute("name", category.Name),
+ new XAttribute("adCount", adsInCategory.Count)
+ );
+
+ // Agregar avisos si es una categoría hoja (que tiene avisos)
+ if (adsInCategory.Any())
+ {
+ foreach (var ad in adsInCategory)
+ {
+ categoryElement.Add(
+ new XElement("Ad",
+ new XAttribute("id", ad.Id),
+
+ // Atributos de estilo para InDesign
+ new XAttribute("style", GetAdStyle(ad)),
+ new XAttribute("bold", ad.IsBold.ToString().ToLower()),
+ new XAttribute("frame", ad.IsFrame.ToString().ToLower()),
+ new XAttribute("fontSize", ad.PrintFontSize),
+ new XAttribute("alignment", ad.PrintAlignment),
+ new XAttribute("days", ad.PrintDaysCount),
+
+ // Texto del aviso en CDATA para proteger caracteres especiales
+ new XCData(ad.PrintText ?? ad.Description ?? "")
+ )
+ );
+ }
+ }
+
+ // Buscar sub-categorías recursivamente
+ var subCategories = categoryDict.Values
+ .Where(c => c.ParentId == category.Id)
+ .OrderBy(c => c.Name);
+
+ foreach (var subCat in subCategories)
+ {
+ categoryElement.Add(BuildCategoryElement(subCat, categoryDict, allListings));
+ }
+
+ return categoryElement;
+ }
+
+ // Generar string de estilo para InDesign
+ private string GetAdStyle(Listing ad)
+ {
+ var styles = new List();
+
+ if (ad.IsBold) styles.Add("bold");
+ if (ad.IsFrame) styles.Add("frame");
+ if (ad.PrintFontSize != "normal") styles.Add($"size-{ad.PrintFontSize}");
+ if (ad.PrintAlignment != "left") styles.Add($"align-{ad.PrintAlignment}");
+
+ return styles.Any() ? string.Join(" ", styles) : "normal";
+ }
+
+ ///
+ /// Obtener avisos huérfanos (pagados pero no incluidos en export)
+ ///
+ [HttpGet("orphan-ads")]
+ public async Task GetOrphanAds([FromQuery] DateTime date)
+ {
+ // Obtener avisos marcados para esa fecha pero con status incorrecto
+ var allAds = await _listingRepo.GetListingsForPrintAsync(date);
+
+ // Simulación: avisos con estado "Pending" o "Draft" que deberían haberse publicado
+ var orphans = allAds.Where(a =>
+ a.Status != "Published" &&
+ a.AdFee > 0 && // Está pagado
+ a.PrintStartDate.HasValue &&
+ a.PrintStartDate.Value.Date <= date.Date
+ );
+
+ return Ok(new {
+ date = date.ToString("yyyy-MM-dd"),
+ count = orphans.Count(),
+ orphans = orphans.Select(a => new {
+ a.Id,
+ a.Title,
+ a.Status,
+ a.AdFee,
+ a.PrintStartDate,
+ CategoryName = a.CategoryName
+ })
+ });
+ }
+
+ ///
+ /// Cerrar edición de una fecha específica
+ /// Una vez cerrada, no se pueden modificar avisos de esa fecha
+ ///
+ [HttpPost("close-edition")]
+ [Authorize(Roles = "Admin,Diagramador")]
+ public async Task CloseEdition([FromBody] CloseEditionRequest request)
+ {
+ // Verificar si ya está cerrada
+ var alreadyClosed = await _closureRepo.IsEditionClosedAsync(request.EditionDate);
+ if (alreadyClosed)
+ {
+ return BadRequest(new { message = "La edición ya está cerrada." });
+ }
+
+ // Obtener usuario actual
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (!int.TryParse(userIdClaim, out int userId))
+ {
+ return Unauthorized();
+ }
+
+ // Crear cierre
+ var closure = new EditionClosure
+ {
+ EditionDate = request.EditionDate.Date,
+ ClosureDateTime = DateTime.UtcNow,
+ ClosedByUserId = userId,
+ IsClosed = true,
+ Notes = request.Notes
+ };
+
+ var closureId = await _closureRepo.CloseEditionAsync(closure);
+
+ // Auditoría
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = userId,
+ Action = "CLOSE_EDITION",
+ EntityId = closureId,
+ EntityType = "EditionClosure",
+ Details = $"Edición {request.EditionDate:yyyy-MM-dd} cerrada. {request.Notes}",
+ CreatedAt = DateTime.UtcNow
+ });
+
+ return Ok(new {
+ message = "Edición cerrada exitosamente.",
+ closureId,
+ editionDate = request.EditionDate.ToString("yyyy-MM-dd")
+ });
+ }
+
+ ///
+ /// Reabrir una edición cerrada (solo Admin)
+ ///
+ [HttpPost("reopen-edition")]
+ [Authorize(Roles = "Admin")]
+ public async Task ReopenEdition([FromQuery] DateTime date)
+ {
+ await _closureRepo.ReopenEditionAsync(date);
+
+ // Auditoría
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (int.TryParse(userIdClaim, out int userId))
+ {
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = userId,
+ Action = "REOPEN_EDITION",
+ EntityType = "EditionClosure",
+ Details = $"Edición {date:yyyy-MM-dd} reabierta",
+ CreatedAt = DateTime.UtcNow
+ });
+ }
+
+ return Ok(new { message = "Edición reabierta exitosamente." });
+ }
+
+ ///
+ /// Obtener estado de una edición
+ ///
+ [HttpGet("edition-status")]
+ public async Task GetEditionStatus([FromQuery] DateTime date)
+ {
+ var closure = await _closureRepo.GetClosureByDateAsync(date);
+ var listings = await _listingRepo.GetListingsForPrintAsync(date);
+
+ return Ok(new {
+ date = date.ToString("yyyy-MM-dd"),
+ isClosed = closure?.IsClosed ?? false,
+ closedAt = closure?.ClosureDateTime,
+ closedBy = closure?.ClosedByUsername,
+ adCount = listings.Count(),
+ notes = closure?.Notes
+ });
+ }
+
+ ///
+ /// Historial de cierres
+ ///
+ [HttpGet("closure-history")]
+ public async Task GetClosureHistory()
+ {
+ var history = await _closureRepo.GetClosureHistoryAsync(30);
+ return Ok(history);
+ }
+}
+
+// DTO para cerrar edición
+public class CloseEditionRequest
+{
+ public DateTime EditionDate { get; set; }
+ public string? Notes { get; set; }
}
\ No newline at end of file
diff --git a/src/SIGCM.API/Controllers/ImagesController.cs b/src/SIGCM.API/Controllers/ImagesController.cs
index 1e280cc..f166217 100644
--- a/src/SIGCM.API/Controllers/ImagesController.cs
+++ b/src/SIGCM.API/Controllers/ImagesController.cs
@@ -1,66 +1,173 @@
using Microsoft.AspNetCore.Mvc;
using SIGCM.Domain.Entities;
using SIGCM.Domain.Interfaces;
+using SIGCM.Infrastructure.Services;
namespace SIGCM.API.Controllers;
[ApiController]
[Route("api/[controller]")]
+[Microsoft.AspNetCore.Authorization.Authorize]
public class ImagesController : ControllerBase
{
private readonly IImageRepository _repository;
private readonly IWebHostEnvironment _env;
+ private readonly ImageOptimizationService _imageService;
- public ImagesController(IImageRepository repository, IWebHostEnvironment env)
+ public ImagesController(
+ IImageRepository repository,
+ IWebHostEnvironment env,
+ ImageOptimizationService imageService)
{
_repository = repository;
_env = env;
+ _imageService = imageService;
}
+ ///
+ /// Upload con optimización automática
+ /// Genera: WebP principal, Thumbnail, Fallback JPEG
+ ///
[HttpPost("upload/{listingId}")]
public async Task Upload(int listingId, IFormFile file)
{
- if (file == null || file.Length == 0) return BadRequest("File is empty");
+ if (file == null || file.Length == 0)
+ return BadRequest("File is empty");
- // Validaciones básicas
- var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".webp" };
+ // Validar extensiones permitidas
+ var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".webp", ".gif" };
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
- if (!allowedExtensions.Contains(ext)) return BadRequest("Invalid file type");
+ if (!allowedExtensions.Contains(ext))
+ return BadRequest("Invalid file type. Allowed: JPG, PNG, WebP, GIF");
- // Si WebRootPath es nulo, construimos la ruta manualmente apuntando a la raíz del contenido + wwwroot
+ // Validar que sea realmente una imagen
+ using var validationStream = file.OpenReadStream();
+ var isValidImage = await _imageService.IsValidImageAsync(validationStream);
+ if (!isValidImage)
+ return BadRequest("File is not a valid image");
+
+ // Configurar rutas
string webRootPath = _env.WebRootPath ?? Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
-
- // Ruta física donde se guarda
var uploadDir = Path.Combine(webRootPath, "uploads", "listings", listingId.ToString());
-
- // Asegurar que la carpeta exista (esto crea toda la estructura si falta)
+
if (!Directory.Exists(uploadDir))
{
Directory.CreateDirectory(uploadDir);
}
- // Guardar archivo
- var fileName = $"{Guid.NewGuid()}{ext}";
- var filePath = Path.Combine(uploadDir, fileName);
+ var baseFilename = Guid.NewGuid().ToString();
- using (var stream = new FileStream(filePath, FileMode.Create))
+ // OPTIMIZAR LA IMAGEN
+ using var stream = file.OpenReadStream();
+ var optimization = await _imageService.OptimizeImageAsync(stream, file.FileName);
+
+ if (!optimization.Success)
{
- await file.CopyToAsync(stream);
+ return StatusCode(500, new { Error = "Image optimization failed", Details = optimization.Error });
}
- // Guardar metadata en BD
- // Ojo: La URL que guardamos en BD debe ser relativa para que el navegador la entienda
- var relativeUrl = $"/uploads/listings/{listingId}/{fileName}";
+ // 1. Guardar WebP (principal)
+ var webpFilename = $"{baseFilename}.webp";
+ var webpPath = Path.Combine(uploadDir, webpFilename);
+ await System.IO.File.WriteAllBytesAsync(webpPath, optimization.WebpData!);
+ // 2. Guardar Thumbnail
+ var thumbFilename = $"{baseFilename}_thumb.webp";
+ var thumbPath = Path.Combine(uploadDir, thumbFilename);
+ await System.IO.File.WriteAllBytesAsync(thumbPath, optimization.ThumbnailData!);
+
+ // 3. Guardar JPEG fallback
+ var jpegFilename = $"{baseFilename}.jpg";
+ var jpegPath = Path.Combine(uploadDir, jpegFilename);
+ await System.IO.File.WriteAllBytesAsync(jpegPath, optimization.JpegData!);
+
+ // URLs relativas
+ var webpUrl = $"/uploads/listings/{listingId}/{webpFilename}";
+ var thumbUrl = $"/uploads/listings/{listingId}/{thumbFilename}";
+ var jpegUrl = $"/uploads/listings/{listingId}/{jpegFilename}";
+
+ // Guardar metadata en BD (solo la imagen principal WebP)
var image = new ListingImage
{
ListingId = listingId,
- Url = relativeUrl,
+ Url = webpUrl,
IsMainInfo = false,
DisplayOrder = 0
};
await _repository.AddAsync(image);
- return Ok(new { Url = relativeUrl });
+ // Retornar todas las versiones y estadísticas
+ return Ok(new {
+ webpUrl,
+ thumbUrl,
+ jpegUrl,
+ optimization = new {
+ originalSize = optimization.OriginalSize,
+ webpSize = optimization.WebpSize,
+ compressionRatio = $"{optimization.CompressionRatio:F1}%",
+ dimensions = $"{optimization.OptimizedWidth}x{optimization.OptimizedHeight}",
+ thumbnailSize = optimization.ThumbnailSize
+ }
+ });
+ }
+
+ ///
+ /// Obtener información de una imagen sin descargarla
+ ///
+ [HttpHead("{listingId}/{filename}")]
+ public async Task GetImageInfo(int listingId, string filename)
+ {
+ // Seguridad: Solo permitimos el nombre del archivo, no rutas
+ string sanitizedFilename = Path.GetFileName(filename);
+ string webRootPath = _env.WebRootPath ?? Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
+ var imagePath = Path.Combine(webRootPath, "uploads", "listings", listingId.ToString(), sanitizedFilename);
+
+ if (!System.IO.File.Exists(imagePath))
+ return NotFound();
+
+ var fileInfo = new FileInfo(imagePath);
+ Response.Headers["Content-Length"] = fileInfo.Length.ToString();
+ Response.Headers["Content-Type"] = "image/webp";
+
+ return Ok();
+ }
+
+ ///
+ /// Eliminar imagen y todas sus versiones
+ ///
+ [HttpDelete("{imageId}")]
+ public async Task Delete(int imageId)
+ {
+ var image = await _repository.GetByIdAsync(imageId);
+ if (image == null) return NotFound();
+
+ // Extraer información de la URL
+ var parts = image.Url.Split('/');
+ var listingId = parts[3];
+ var filename = Path.GetFileNameWithoutExtension(parts[4]);
+
+ string webRootPath = _env.WebRootPath ?? Path.Combine(Directory.GetCurrentDirectory(), "wwwroot");
+ var uploadDir = Path.Combine(webRootPath, "uploads", "listings", listingId);
+
+ // Eliminar todas las versiones
+ var filesToDelete = new[]
+ {
+ Path.Combine(uploadDir, $"{filename}.webp"),
+ Path.Combine(uploadDir, $"{filename}_thumb.webp"),
+ Path.Combine(uploadDir, $"{filename}.jpg")
+ };
+
+ foreach (var file in filesToDelete)
+ {
+ if (System.IO.File.Exists(file))
+ {
+ System.IO.File.Delete(file);
+ }
+ }
+
+ // Eliminar de BD
+ await _repository.DeleteAsync(imageId);
+
+ return Ok(new { message = "Image and all variants deleted successfully" });
}
}
diff --git a/src/SIGCM.API/Controllers/ListingsController.cs b/src/SIGCM.API/Controllers/ListingsController.cs
index b847217..16b678b 100644
--- a/src/SIGCM.API/Controllers/ListingsController.cs
+++ b/src/SIGCM.API/Controllers/ListingsController.cs
@@ -1,3 +1,4 @@
+// src/SIGCM.API/Controllers/ListingsController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SIGCM.Application.DTOs;
@@ -14,6 +15,7 @@ public class ListingsController : ControllerBase
private readonly IListingRepository _repository;
private readonly ClientRepository _clientRepo;
private readonly AuditRepository _auditRepo;
+
public ListingsController(IListingRepository repository, ClientRepository clientRepo, AuditRepository auditRepo)
{
_repository = repository;
@@ -22,14 +24,23 @@ public class ListingsController : ControllerBase
}
[HttpPost]
+ [Authorize]
public async Task Create(CreateListingDto dto)
{
+ // SEGURIDAD: Forzamos la obtención del ID desde el Token JWT
+ var userIdClaim = User.FindFirst("Id")?.Value;
+
+ // Si por alguna razón el claim no está (configuración de JWT), no permitimos la creación
+ if (string.IsNullOrEmpty(userIdClaim) || !int.TryParse(userIdClaim, out int currentUserId))
+ {
+ return Unauthorized(new { message = "No se pudo identificar al usuario desde el token de seguridad." });
+ }
+
int? clientId = null;
- // Si viene información de cliente, aseguramos que exista en BD
+ // Lógica de Cliente (asegurar existencia en BD)
if (!string.IsNullOrWhiteSpace(dto.ClientDni))
{
- // Usamos "Consumidor Final" si no hay nombre pero hay DNI, o el nombre provisto
string clientName = string.IsNullOrWhiteSpace(dto.ClientName) ? "Consumidor Final" : dto.ClientName;
clientId = await _clientRepo.EnsureClientExistsAsync(clientName, dto.ClientDni);
}
@@ -41,13 +52,50 @@ public class ListingsController : ControllerBase
Title = dto.Title,
Description = dto.Description,
Price = dto.Price,
- Currency = dto.Currency,
- UserId = dto.UserId,
- Status = "Published", // Auto publish for now
- CreatedAt = DateTime.UtcNow
+ AdFee = dto.AdFee,
+ Currency = "ARS",
+ UserId = currentUserId,
+ ClientId = clientId,
+ Origin = dto.Origin,
+ Status = dto.Status,
+ ImagesToClone = dto.ImagesToClone,
+ CreatedAt = DateTime.UtcNow,
+ PrintText = dto.PrintText,
+ PrintDaysCount = dto.PrintDaysCount,
+ PrintStartDate = dto.PrintStartDate,
+ IsBold = dto.IsBold,
+ IsFrame = dto.IsFrame,
+ PrintFontSize = dto.PrintFontSize,
+ PrintAlignment = dto.PrintAlignment
};
- var id = await _repository.CreateAsync(listing, dto.Attributes);
+ // Conversión de Pagos
+ List? payments = null;
+ if (dto.Payments != null && dto.Payments.Any())
+ {
+ payments = dto.Payments.Select(p => new Payment
+ {
+ Amount = p.Amount,
+ PaymentMethod = p.PaymentMethod,
+ CardPlan = p.CardPlan,
+ Surcharge = p.Surcharge,
+ PaymentDate = DateTime.UtcNow
+ }).ToList();
+ }
+
+ var id = await _repository.CreateAsync(listing, dto.Attributes, payments);
+
+ // Registro de Auditoría
+ await _auditRepo.AddLogAsync(new AuditLog
+ {
+ UserId = currentUserId,
+ Action = "CREATE_LISTING",
+ EntityId = id,
+ EntityType = "Listing",
+ Details = $"Aviso creado por usuario autenticado. Total: ${dto.AdFee}",
+ CreatedAt = DateTime.UtcNow
+ });
+
return Ok(new { id });
}
@@ -57,10 +105,6 @@ public class ListingsController : ControllerBase
if (string.IsNullOrEmpty(q) && !categoryId.HasValue)
{
var listings = await _repository.GetAllAsync();
-
- // Populate images for list view (simplified N+1 for now, fix later with JOIN)
- // Ideally Repo returns a DTO with MainImage
- // We will fetch images separately in frontend or update repo later for performance.
return Ok(listings);
}
@@ -68,23 +112,43 @@ public class ListingsController : ControllerBase
return Ok(results);
}
+ [HttpGet("my")]
+ [Authorize]
+ public async Task GetMyListings()
+ {
+ // SEGURIDAD: Forzamos el ID del usuario desde el Claim
+ var userIdStr = User.FindFirst("Id")?.Value;
+ if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int userId))
+ return Unauthorized();
+
+ var listings = await _repository.GetByUserIdAsync(userId);
+ return Ok(listings);
+ }
+
[HttpGet("{id}")]
public async Task Get(int id)
{
+ await _repository.IncrementViewCountAsync(id);
var listingDetail = await _repository.GetDetailByIdAsync(id);
if (listingDetail == null) return NotFound();
return Ok(listingDetail);
}
- // Búsqueda Facetada (POST para enviar diccionario complejo)
[HttpPost("search")]
public async Task Search([FromBody] SearchRequest request)
{
- var results = await _repository.SearchFacetedAsync(request.Query, request.CategoryId, request.Filters);
+ var results = await _repository.SearchFacetedAsync(
+ request.Query,
+ request.CategoryId,
+ request.Filters,
+ request.From,
+ request.To,
+ request.Origin,
+ request.Status
+ );
return Ok(results);
}
- // Moderación: Obtener pendientes
[HttpGet("pending")]
[Authorize(Roles = "Admin,Moderador")]
public async Task GetPending()
@@ -93,30 +157,26 @@ public class ListingsController : ControllerBase
return Ok(pending);
}
- // Moderación: Cambiar estado
[HttpPut("{id}/status")]
[Authorize(Roles = "Admin,Moderador")]
public async Task UpdateStatus(int id, [FromBody] string status)
{
- // 1. Obtener ID del usuario desde el Token JWT
+ // Obtener el ID de quien realiza la acción (Administrador/Moderador)
var userIdStr = User.FindFirst("Id")?.Value;
- int? currentUserId = !string.IsNullOrEmpty(userIdStr) ? int.Parse(userIdStr) : null;
+ if (string.IsNullOrEmpty(userIdStr) || !int.TryParse(userIdStr, out int currentUserId))
+ return Unauthorized();
- // 2. Actualizar el estado del aviso
await _repository.UpdateStatusAsync(id, status);
- // 3. Registrar en Auditoría (Si tenemos el repositorio inyectado)
- if (currentUserId.HasValue)
+ await _auditRepo.AddLogAsync(new AuditLog
{
- await _auditRepo.AddLogAsync(new AuditLog
- {
- UserId = currentUserId.Value,
- Action = status == "Published" ? "Aprobar" : "Rechazar",
- EntityId = id,
- EntityType = "Listing",
- Details = $"El usuario cambió el estado del aviso #{id} a {status}"
- });
- }
+ UserId = currentUserId,
+ Action = status == "Published" ? "APPROVE_LISTING" : "REJECT_LISTING",
+ EntityId = id,
+ EntityType = "Listing",
+ Details = $"El usuario {currentUserId} cambió el estado del aviso #{id} a {status}",
+ CreatedAt = DateTime.UtcNow
+ });
return Ok();
}
@@ -126,6 +186,10 @@ public class ListingsController : ControllerBase
public string? Query { get; set; }
public int? CategoryId { get; set; }
public Dictionary? Filters { get; set; }
+ public DateTime? From { get; set; }
+ public DateTime? To { get; set; }
+ public string? Origin { get; set; }
+ public string? Status { get; set; }
}
[HttpGet("pending/count")]
@@ -134,4 +198,19 @@ public class ListingsController : ControllerBase
var count = await _repository.GetPendingCountAsync();
return Ok(count);
}
+
+ [HttpPatch("{id}/overlay")]
+ [Authorize]
+ public async Task UpdateOverlay(int id, [FromBody] string? status)
+ {
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (!int.TryParse(userIdClaim, out int userId)) return Unauthorized();
+
+ // Validar estados permitidos
+ var allowed = new[] { "Vendido", "Alquilado", "Reservado", null };
+ if (!allowed.Contains(status)) return BadRequest("Estado no permitido");
+
+ await _repository.UpdateOverlayStatusAsync(id, userId, status);
+ return NoContent();
+ }
}
\ No newline at end of file
diff --git a/src/SIGCM.API/Controllers/NotificationsController.cs b/src/SIGCM.API/Controllers/NotificationsController.cs
new file mode 100644
index 0000000..1815428
--- /dev/null
+++ b/src/SIGCM.API/Controllers/NotificationsController.cs
@@ -0,0 +1,28 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using SIGCM.Infrastructure.Repositories;
+
+[ApiController]
+[Route("api/[controller]")]
+[Authorize]
+public class NotificationsController : ControllerBase
+{
+ private readonly NotificationRepository _repo;
+ public NotificationsController(NotificationRepository repo) => _repo = repo;
+
+ [HttpGet]
+ public async Task GetMyNotifications()
+ {
+ var userId = int.Parse(User.FindFirst("Id")?.Value!);
+ var notifications = await _repo.GetByUserAsync(userId);
+ var unreadCount = await _repo.GetUnreadCountAsync(userId);
+ return Ok(new { items = notifications, unreadCount });
+ }
+
+ [HttpPut("{id}/read")]
+ public async Task MarkAsRead(int id)
+ {
+ await _repo.MarkAsReadAsync(id);
+ return Ok();
+ }
+}
\ No newline at end of file
diff --git a/src/SIGCM.API/Controllers/OperationsController.cs b/src/SIGCM.API/Controllers/OperationsController.cs
index bb7c63b..504ae64 100644
--- a/src/SIGCM.API/Controllers/OperationsController.cs
+++ b/src/SIGCM.API/Controllers/OperationsController.cs
@@ -1,3 +1,4 @@
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SIGCM.Domain.Entities;
using SIGCM.Domain.Interfaces;
@@ -16,6 +17,7 @@ public class OperationsController : ControllerBase
}
[HttpGet]
+ [AllowAnonymous]
public async Task GetAll()
{
var operations = await _repository.GetAllAsync();
@@ -23,6 +25,7 @@ public class OperationsController : ControllerBase
}
[HttpGet("{id}")]
+ [AllowAnonymous]
public async Task GetById(int id)
{
var operation = await _repository.GetByIdAsync(id);
@@ -31,6 +34,7 @@ public class OperationsController : ControllerBase
}
[HttpPost]
+ [Authorize(Roles = "Admin")]
public async Task Create(Operation operation)
{
var id = await _repository.AddAsync(operation);
@@ -39,6 +43,7 @@ public class OperationsController : ControllerBase
}
[HttpDelete("{id}")]
+ [Authorize(Roles = "Admin")]
public async Task Delete(int id)
{
await _repository.DeleteAsync(id);
diff --git a/src/SIGCM.API/Controllers/PaymentsController.cs b/src/SIGCM.API/Controllers/PaymentsController.cs
new file mode 100644
index 0000000..ed179e6
--- /dev/null
+++ b/src/SIGCM.API/Controllers/PaymentsController.cs
@@ -0,0 +1,116 @@
+// src/SIGCM.API/Controllers/PaymentsController.cs
+using MercadoPago.Client.Payment;
+using Microsoft.AspNetCore.Mvc;
+using SIGCM.Domain.Entities;
+using SIGCM.Domain.Interfaces;
+using SIGCM.Infrastructure.Services;
+
+namespace SIGCM.API.Controllers;
+
+[ApiController]
+[Route("api/[controller]")]
+public class PaymentsController : ControllerBase
+{
+ private readonly MercadoPagoService _mpService;
+ private readonly IListingRepository _listingRepo;
+
+ public PaymentsController(MercadoPagoService mpService, IListingRepository listingRepo)
+ {
+ _mpService = mpService;
+ _listingRepo = listingRepo;
+ }
+
+ /*
+ IMPLEMENTACIÓN REAL (COMENTADA PARA FASE FINAL)
+ [HttpPost("create-preference/{listingId}")]
+ public async Task CreatePreference(int listingId, [FromBody] decimal amount)
+ {
+ var listing = await _listingRepo.GetByIdAsync(listingId);
+ if (listing == null) return NotFound("Aviso no encontrado");
+
+ var preference = await _mpService.CreatePreferenceAsync(listing, amount);
+
+ return Ok(new {
+ id = preference.Id,
+ initPoint = preference.InitPoint, // Para redirección
+ sandboxInitPoint = preference.SandboxInitPoint // Para testing
+ });
+ }
+ */
+ [HttpPost("create-preference/{listingId}")]
+ public async Task CreatePreference(int listingId, [FromBody] decimal amount)
+ {
+ var listing = await _listingRepo.GetByIdAsync(listingId);
+ if (listing == null) return NotFound("Aviso no encontrado");
+
+ // Llamamos al servicio (que ahora devuelve el objeto simulado)
+ var preference = await _mpService.CreatePreferenceAsync(listing, amount);
+
+ return Ok(preference);
+ }
+
+ public class SimulationRequest { public decimal Amount { get; set; } }
+
+ [HttpPost("confirm-simulation/{listingId}")]
+ public async Task ConfirmSimulation(int listingId, [FromBody] SimulationRequest request)
+ {
+ var listing = await _listingRepo.GetByIdAsync(listingId);
+ if (listing == null) return NotFound();
+
+ // 1. El aviso queda en 'Pending' (Moderación), no 'Published'
+ await _listingRepo.UpdateStatusAsync(listingId, "Pending");
+
+ // 2. Registrar el pago
+ var paymentRecord = new Payment
+ {
+ ListingId = listingId,
+ Amount = request.Amount,
+ PaymentMethod = "MercadoPago (Simulado)",
+ PaymentDate = DateTime.UtcNow,
+ ExternalReference = listingId.ToString(),
+ ExternalId = "SIM-" + Guid.NewGuid().ToString().Substring(0, 8),
+ Status = "Approved"
+ };
+
+ await _listingRepo.AddPaymentAsync(paymentRecord);
+ return Ok(new { message = "Pago registrado. Aviso enviado a moderación." });
+ }
+
+ [HttpPost("webhook")]
+ public async Task Webhook([FromQuery] string id, [FromQuery] string topic)
+ {
+ if (topic == "payment")
+ {
+ var client = new PaymentClient();
+ var mpPayment = await client.GetAsync(long.Parse(id));
+
+ if (mpPayment.Status == "approved")
+ {
+ var listingId = int.Parse(mpPayment.ExternalReference);
+ var listing = await _listingRepo.GetByIdAsync(listingId);
+
+ if (listing != null && listing.Status != "Published")
+ {
+ // 1. Cambiar estado a PUBLISHED
+ await _listingRepo.UpdateStatusAsync(listingId, "Pending");
+
+ // 2. Registrar el pago en la tabla Payments
+ var paymentRecord = new Payment
+ {
+ ListingId = listingId,
+ Amount = mpPayment.TransactionAmount ?? 0,
+ PaymentMethod = "MercadoPago",
+ PaymentDate = DateTime.UtcNow,
+ ExternalReference = mpPayment.ExternalReference,
+ ExternalId = mpPayment.Id.ToString(),
+ Status = "Approved"
+ };
+
+ await _listingRepo.AddPaymentAsync(paymentRecord);
+ }
+ }
+ }
+
+ return Ok();
+ }
+}
diff --git a/src/SIGCM.API/Controllers/PricingController.cs b/src/SIGCM.API/Controllers/PricingController.cs
index 2a6d0b7..75986f2 100644
--- a/src/SIGCM.API/Controllers/PricingController.cs
+++ b/src/SIGCM.API/Controllers/PricingController.cs
@@ -14,11 +14,13 @@ public class PricingController : ControllerBase
{
private readonly PricingService _service;
private readonly PricingRepository _repository;
+ private readonly AuditRepository _auditRepo;
- public PricingController(PricingService service, PricingRepository repository)
+ public PricingController(PricingService service, PricingRepository repository, AuditRepository auditRepo)
{
_service = service;
_repository = repository;
+ _auditRepo = auditRepo;
}
// Usado por: Panel Mostrador (Cajero) y Admin
@@ -48,6 +50,22 @@ public class PricingController : ControllerBase
if (pricing.CategoryId == 0) return BadRequest("CategoryId es requerido");
await _repository.UpsertPricingAsync(pricing);
+
+ // Audit Log
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (int.TryParse(userIdClaim, out int userId))
+ {
+ await _auditRepo.AddLogAsync(new Domain.Entities.AuditLog
+ {
+ UserId = userId,
+ Action = "SAVE_PRICING",
+ EntityId = pricing.CategoryId,
+ EntityType = "CategoryPricing",
+ Details = $"Pricing updated for Category {pricing.CategoryId}",
+ CreatedAt = DateTime.UtcNow
+ });
+ }
+
return Ok(new { message = "Configuración guardada exitosamente" });
}
@@ -66,6 +84,22 @@ public class PricingController : ControllerBase
public async Task CreatePromotion(Promotion promo)
{
var id = await _repository.CreatePromotionAsync(promo);
+
+ // Audit Log
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (int.TryParse(userIdClaim, out int userId))
+ {
+ await _auditRepo.AddLogAsync(new Domain.Entities.AuditLog
+ {
+ UserId = userId,
+ Action = "CREATE_PROMOTION",
+ EntityId = id,
+ EntityType = "Promotion",
+ Details = $"Promotion {promo.Name} created",
+ CreatedAt = DateTime.UtcNow
+ });
+ }
+
return Ok(new { id });
}
@@ -75,6 +109,22 @@ public class PricingController : ControllerBase
{
if (id != promo.Id) return BadRequest();
await _repository.UpdatePromotionAsync(promo);
+
+ // Audit Log
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (int.TryParse(userIdClaim, out int userId))
+ {
+ await _auditRepo.AddLogAsync(new Domain.Entities.AuditLog
+ {
+ UserId = userId,
+ Action = "UPDATE_PROMOTION",
+ EntityId = id,
+ EntityType = "Promotion",
+ Details = $"Promotion {id} updated",
+ CreatedAt = DateTime.UtcNow
+ });
+ }
+
return NoContent();
}
@@ -83,6 +133,22 @@ public class PricingController : ControllerBase
public async Task DeletePromotion(int id)
{
await _repository.DeletePromotionAsync(id);
+
+ // Audit Log
+ var userIdClaim = User.FindFirst("Id")?.Value;
+ if (int.TryParse(userIdClaim, out int userId))
+ {
+ await _auditRepo.AddLogAsync(new Domain.Entities.AuditLog
+ {
+ UserId = userId,
+ Action = "DELETE_PROMOTION",
+ EntityId = id,
+ EntityType = "Promotion",
+ Details = $"Promotion {id} deleted",
+ CreatedAt = DateTime.UtcNow
+ });
+ }
+
return NoContent();
}
}
\ No newline at end of file
diff --git a/src/SIGCM.API/Controllers/ReportsController.cs b/src/SIGCM.API/Controllers/ReportsController.cs
index 3eb6001..1e1a1ce 100644
--- a/src/SIGCM.API/Controllers/ReportsController.cs
+++ b/src/SIGCM.API/Controllers/ReportsController.cs
@@ -22,6 +22,7 @@ public class ReportsController : ControllerBase
}
[HttpGet("dashboard")]
+ [Authorize(Roles = "Admin,GerenteVentas")]
public async Task GetDashboard([FromQuery] DateTime? from, [FromQuery] DateTime? to)
{
var start = from ?? DateTime.UtcNow.Date;
@@ -31,6 +32,7 @@ public class ReportsController : ControllerBase
}
[HttpGet("sales-by-category")]
+ [Authorize(Roles = "Admin,GerenteVentas")]
public async Task GetSalesByCategory([FromQuery] DateTime? from, [FromQuery] DateTime? to)
{
var start = from ?? DateTime.UtcNow.AddMonths(-1);
@@ -48,6 +50,7 @@ public class ReportsController : ControllerBase
}
[HttpGet("audit")]
+ [Authorize(Roles = "Admin")]
public async Task GetAuditLogs()
{
// Obtenemos los últimos 100 eventos
@@ -108,15 +111,39 @@ public class ReportsController : ControllerBase
[HttpGet("cashier-transactions")]
[Authorize(Roles = "Cajero,Admin")]
- public async Task GetCashierTransactions()
+ public async Task GetCashierTransactions(
+ [FromQuery] DateTime? from,
+ [FromQuery] DateTime? to,
+ [FromQuery] int? userId)
{
+ // 1. Obtener datos del usuario logueado
var userIdClaim = User.FindFirst("Id")?.Value;
+ var userRole = User.FindFirst(System.Security.Claims.ClaimTypes.Role)?.Value;
+
if (string.IsNullOrEmpty(userIdClaim)) return Unauthorized();
- int userId = int.Parse(userIdClaim);
- // Usamos el repositorio para traer los avisos de hoy de este usuario
- var transactions = await _listingRepo.GetDetailedReportAsync(DateTime.UtcNow, DateTime.UtcNow, userId);
+ // 2. Lógica de seguridad para el filtro de usuario:
+ // Si es Admin, puede ver a cualquier cajero (usa el userId que viene por query).
+ // Si es Cajero, SOLO puede verse a sí mismo (forzamos su propio ID).
+ int? targetUserId = (userRole == "Admin") ? userId : int.Parse(userIdClaim);
+
+ // 3. Manejo de fechas:
+ // Si no vienen, usamos el rango de hoy.
+ // Pero el frontend enviará los valores de los selectores.
+ var startDate = from ?? DateTime.UtcNow.Date;
+ var endDate = to ?? DateTime.UtcNow.Date;
+
+ // Llamamos al repositorio con las fechas REALES enviadas desde el frontend
+ var transactions = await _listingRepo.GetDetailedReportAsync(startDate, endDate, targetUserId);
return Ok(transactions);
}
+
+ [HttpGet("cajeros")]
+ [Authorize(Roles = "Cajero,Admin")]
+ public async Task GetCajeros()
+ {
+ var cajeros = await _listingRepo.GetActiveCashiersAsync();
+ return Ok(cajeros);
+ }
}
\ No newline at end of file
diff --git a/src/SIGCM.API/Middleware/ExceptionMiddleware.cs b/src/SIGCM.API/Middleware/ExceptionMiddleware.cs
new file mode 100644
index 0000000..b9ba589
--- /dev/null
+++ b/src/SIGCM.API/Middleware/ExceptionMiddleware.cs
@@ -0,0 +1,61 @@
+// src/SIGCM.API/Middleware/ExceptionMiddleware.cs
+using System.Net;
+using System.Text.Json;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+
+namespace SIGCM.API.Middleware;
+
+public class ExceptionMiddleware
+{
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+ private readonly IHostEnvironment _env;
+
+ public ExceptionMiddleware(RequestDelegate next, ILogger logger, IHostEnvironment env)
+ {
+ _next = next;
+ _logger = logger;
+ _env = env;
+ }
+
+ public async Task InvokeAsync(HttpContext context)
+ {
+ try
+ {
+ await _next(context);
+ }
+ catch (FluentValidation.ValidationException valEx)
+ {
+ context.Response.ContentType = "application/json";
+ context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
+
+ var errors = valEx.Errors.Select(e => new { e.PropertyName, e.ErrorMessage });
+ var response = new {
+ StatusCode = context.Response.StatusCode,
+ Message = "Error de validación",
+ Errors = errors
+ };
+
+ var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
+ await context.Response.WriteAsync(JsonSerializer.Serialize(response, options));
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, ex.Message);
+ context.Response.ContentType = "application/json";
+ context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+
+ var response = _env.IsDevelopment()
+ ? new ErrorDetails(context.Response.StatusCode, ex.Message, ex.StackTrace?.ToString())
+ : new ErrorDetails(context.Response.StatusCode, "Ocurrió un error interno en el servidor", "Consulte con soporte.");
+
+ var options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
+ var json = JsonSerializer.Serialize(response, options);
+
+ await context.Response.WriteAsync(json);
+ }
+ }
+}
+
+public record ErrorDetails(int StatusCode, string Message, string? Details);
diff --git a/src/SIGCM.API/Middleware/SecurityHeadersMiddleware.cs b/src/SIGCM.API/Middleware/SecurityHeadersMiddleware.cs
new file mode 100644
index 0000000..c776517
--- /dev/null
+++ b/src/SIGCM.API/Middleware/SecurityHeadersMiddleware.cs
@@ -0,0 +1,57 @@
+// src/SIGCM.API/Middleware/SecurityHeadersMiddleware.cs
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace SIGCM.API.Middleware
+{
+ public class SecurityHeadersMiddleware
+ {
+ private readonly RequestDelegate _next;
+
+ public SecurityHeadersMiddleware(RequestDelegate next)
+ {
+ _next = next;
+ }
+
+ public async Task InvokeAsync(HttpContext context)
+ {
+ // 1. Content-Security-Policy (CSP)
+ // Permitimos scripts de dominio propio, estilos, imágenes y fuentes.
+ // Ajustamos para permitir Mercado Pago y fuentes de Google si es necesario.
+ context.Response.Headers.Append("Content-Security-Policy",
+ "default-src 'self'; " +
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://sdk.mercadopago.com; " +
+ "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " +
+ "font-src 'self' https://fonts.gstatic.com data:; " +
+ "img-src 'self' data: https://*.mercadopago.com; " +
+ "frame-src 'self' https://*.mercadopago.com; " +
+ "connect-src 'self' https://api.mercadopago.com;");
+
+ // 2. Strict-Transport-Security (HSTS)
+ // Indica que el sitio solo debe ser accedido vía HTTPS (1 año)
+ context.Response.Headers.Append("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
+
+ // 3. X-Content-Type-Options
+ // Previene que el navegador intente "adivinar" el tipo MIME (MIME sniffing)
+ context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
+
+ // 4. X-Frame-Options
+ // Protege contra Clickjacking al prevenir que el sitio sea embebido en iframes externos
+ context.Response.Headers.Append("X-Frame-Options", "SAMEORIGIN");
+
+ // 5. Referrer-Policy
+ // Controla cuánta información de referencia se envía en las peticiones
+ context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
+
+ // 6. X-XSS-Protection
+ // Filtro contra XSS (aunque CSP es el estándar moderno, esto es para navegadores antiguos)
+ context.Response.Headers.Append("X-XSS-Protection", "1; mode=block");
+
+ // 7. Permissions-Policy
+ // Restringe el acceso a APIs del navegador (Cámara, Micrófono, etc.)
+ context.Response.Headers.Append("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
+
+ await _next(context);
+ }
+ }
+}
diff --git a/src/SIGCM.API/Program.cs b/src/SIGCM.API/Program.cs
index 53fded3..4c3b068 100644
--- a/src/SIGCM.API/Program.cs
+++ b/src/SIGCM.API/Program.cs
@@ -4,6 +4,9 @@ using Microsoft.IdentityModel.Tokens;
using SIGCM.Infrastructure;
using SIGCM.Infrastructure.Data;
using QuestPDF.Infrastructure;
+using FluentValidation;
+using FluentValidation.AspNetCore;
+using SIGCM.API.Middleware;
var builder = WebApplication.CreateBuilder(args);
@@ -11,9 +14,19 @@ QuestPDF.Settings.License = LicenseType.Community;
// 1. Agregar servicios al contenedor.
builder.Services.AddControllers();
+builder.Services.AddFluentValidationAutoValidation()
+ .AddFluentValidationClientsideAdapters();
+// Registrar validadores manualmente si es necesario o por asamblea
+builder.Services.AddValidatorsFromAssemblyContaining();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
+builder.Services.AddControllers()
+ .AddJsonOptions(options =>
+ {
+ options.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase;
+ });
+
// 2. Configurar Autenticación JWT
var key = Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Key"]!);
@@ -40,7 +53,7 @@ builder.Services.AddAuthentication(options =>
});
// 3. Agregar Capa de Infraestructura
-builder.Services.AddInfrastructure();
+builder.Services.AddInfrastructure(builder.Configuration);
// 4. Configurar CORS
builder.Services.AddCors(options =>
@@ -49,7 +62,7 @@ builder.Services.AddCors(options =>
policy =>
{
policy.WithOrigins(
- "http://localhost:5173",
+ "http://localhost:5173",
"http://localhost:5174",
"http://localhost:5175",
"http://localhost:5177")
@@ -58,10 +71,14 @@ builder.Services.AddCors(options =>
});
});
+
var app = builder.Build();
// --- Configuración del Pipeline HTTP ---
+app.UseMiddleware();
+app.UseMiddleware();
+
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
diff --git a/src/SIGCM.API/SIGCM.API.csproj b/src/SIGCM.API/SIGCM.API.csproj
index 7391f08..2bdf4ca 100644
--- a/src/SIGCM.API/SIGCM.API.csproj
+++ b/src/SIGCM.API/SIGCM.API.csproj
@@ -7,8 +7,12 @@
+
+
+
+
diff --git a/src/SIGCM.API/appsettings.json b/src/SIGCM.API/appsettings.json
index ef918f3..928d54d 100644
--- a/src/SIGCM.API/appsettings.json
+++ b/src/SIGCM.API/appsettings.json
@@ -13,5 +13,11 @@
"Key": "badb1a38d221c9e23bcf70958840ca7f5a5dc54f2047dadf7ce45b578b5bc3e2",
"Issuer": "SIGCMApi",
"Audience": "SIGCMAdmin"
+ },
+ "MercadoPago": {
+ "AccessToken": "TEST-71539281-2291-443b-873b-eb8647021589-122610-86ec037f07067d55d7b5b31cb9c1069b-1375354",
+ "SuccessUrl": "http://localhost:5173/publicar/exito",
+ "FailureUrl": "http://localhost:5173/publicar/error",
+ "NotificationUrl": "https://your-webhook-proxy.com/api/payments/webhook"
}
}
\ No newline at end of file
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/12/4fad7aa3-2c97-4350-981e-25c873aede16.jpg b/src/SIGCM.API/wwwroot/uploads/listings/12/4fad7aa3-2c97-4350-981e-25c873aede16.jpg
new file mode 100644
index 0000000..bcd9aa7
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/12/4fad7aa3-2c97-4350-981e-25c873aede16.jpg differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/12/4fad7aa3-2c97-4350-981e-25c873aede16.webp b/src/SIGCM.API/wwwroot/uploads/listings/12/4fad7aa3-2c97-4350-981e-25c873aede16.webp
new file mode 100644
index 0000000..f44b923
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/12/4fad7aa3-2c97-4350-981e-25c873aede16.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/12/4fad7aa3-2c97-4350-981e-25c873aede16_thumb.webp b/src/SIGCM.API/wwwroot/uploads/listings/12/4fad7aa3-2c97-4350-981e-25c873aede16_thumb.webp
new file mode 100644
index 0000000..1b693ad
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/12/4fad7aa3-2c97-4350-981e-25c873aede16_thumb.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/12/7e3cdaa8-5254-4c56-b5d8-7edb1abe5d9c.jpg b/src/SIGCM.API/wwwroot/uploads/listings/12/7e3cdaa8-5254-4c56-b5d8-7edb1abe5d9c.jpg
new file mode 100644
index 0000000..33e4f0b
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/12/7e3cdaa8-5254-4c56-b5d8-7edb1abe5d9c.jpg differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/12/7e3cdaa8-5254-4c56-b5d8-7edb1abe5d9c.webp b/src/SIGCM.API/wwwroot/uploads/listings/12/7e3cdaa8-5254-4c56-b5d8-7edb1abe5d9c.webp
new file mode 100644
index 0000000..8213f97
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/12/7e3cdaa8-5254-4c56-b5d8-7edb1abe5d9c.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/12/7e3cdaa8-5254-4c56-b5d8-7edb1abe5d9c_thumb.webp b/src/SIGCM.API/wwwroot/uploads/listings/12/7e3cdaa8-5254-4c56-b5d8-7edb1abe5d9c_thumb.webp
new file mode 100644
index 0000000..bbdc4c1
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/12/7e3cdaa8-5254-4c56-b5d8-7edb1abe5d9c_thumb.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/4/f5a279e6-fc1d-4c3e-a5b7-40370a4f1f23.jpg b/src/SIGCM.API/wwwroot/uploads/listings/4/f5a279e6-fc1d-4c3e-a5b7-40370a4f1f23.jpg
new file mode 100644
index 0000000..bcd9aa7
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/4/f5a279e6-fc1d-4c3e-a5b7-40370a4f1f23.jpg differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/4/f5a279e6-fc1d-4c3e-a5b7-40370a4f1f23.webp b/src/SIGCM.API/wwwroot/uploads/listings/4/f5a279e6-fc1d-4c3e-a5b7-40370a4f1f23.webp
new file mode 100644
index 0000000..f44b923
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/4/f5a279e6-fc1d-4c3e-a5b7-40370a4f1f23.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/4/f5a279e6-fc1d-4c3e-a5b7-40370a4f1f23_thumb.webp b/src/SIGCM.API/wwwroot/uploads/listings/4/f5a279e6-fc1d-4c3e-a5b7-40370a4f1f23_thumb.webp
new file mode 100644
index 0000000..1b693ad
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/4/f5a279e6-fc1d-4c3e-a5b7-40370a4f1f23_thumb.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/6/97e8e91b-7463-41a6-b125-1734090ee290.jpg b/src/SIGCM.API/wwwroot/uploads/listings/6/97e8e91b-7463-41a6-b125-1734090ee290.jpg
new file mode 100644
index 0000000..bcd9aa7
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/6/97e8e91b-7463-41a6-b125-1734090ee290.jpg differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/6/97e8e91b-7463-41a6-b125-1734090ee290.webp b/src/SIGCM.API/wwwroot/uploads/listings/6/97e8e91b-7463-41a6-b125-1734090ee290.webp
new file mode 100644
index 0000000..f44b923
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/6/97e8e91b-7463-41a6-b125-1734090ee290.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/6/97e8e91b-7463-41a6-b125-1734090ee290_thumb.webp b/src/SIGCM.API/wwwroot/uploads/listings/6/97e8e91b-7463-41a6-b125-1734090ee290_thumb.webp
new file mode 100644
index 0000000..1b693ad
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/6/97e8e91b-7463-41a6-b125-1734090ee290_thumb.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/7/312b3cbf-6d2f-4ec1-a8dc-b92f08a64191.jpg b/src/SIGCM.API/wwwroot/uploads/listings/7/312b3cbf-6d2f-4ec1-a8dc-b92f08a64191.jpg
new file mode 100644
index 0000000..bcd9aa7
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/7/312b3cbf-6d2f-4ec1-a8dc-b92f08a64191.jpg differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/7/312b3cbf-6d2f-4ec1-a8dc-b92f08a64191.webp b/src/SIGCM.API/wwwroot/uploads/listings/7/312b3cbf-6d2f-4ec1-a8dc-b92f08a64191.webp
new file mode 100644
index 0000000..f44b923
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/7/312b3cbf-6d2f-4ec1-a8dc-b92f08a64191.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/7/312b3cbf-6d2f-4ec1-a8dc-b92f08a64191_thumb.webp b/src/SIGCM.API/wwwroot/uploads/listings/7/312b3cbf-6d2f-4ec1-a8dc-b92f08a64191_thumb.webp
new file mode 100644
index 0000000..1b693ad
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/7/312b3cbf-6d2f-4ec1-a8dc-b92f08a64191_thumb.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/8/7d59b1f7-a192-4aa7-a415-1737eceb8f87.jpg b/src/SIGCM.API/wwwroot/uploads/listings/8/7d59b1f7-a192-4aa7-a415-1737eceb8f87.jpg
new file mode 100644
index 0000000..bcd9aa7
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/8/7d59b1f7-a192-4aa7-a415-1737eceb8f87.jpg differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/8/7d59b1f7-a192-4aa7-a415-1737eceb8f87.webp b/src/SIGCM.API/wwwroot/uploads/listings/8/7d59b1f7-a192-4aa7-a415-1737eceb8f87.webp
new file mode 100644
index 0000000..f44b923
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/8/7d59b1f7-a192-4aa7-a415-1737eceb8f87.webp differ
diff --git a/src/SIGCM.API/wwwroot/uploads/listings/8/7d59b1f7-a192-4aa7-a415-1737eceb8f87_thumb.webp b/src/SIGCM.API/wwwroot/uploads/listings/8/7d59b1f7-a192-4aa7-a415-1737eceb8f87_thumb.webp
new file mode 100644
index 0000000..1b693ad
Binary files /dev/null and b/src/SIGCM.API/wwwroot/uploads/listings/8/7d59b1f7-a192-4aa7-a415-1737eceb8f87_thumb.webp differ
diff --git a/src/SIGCM.Application/DTOs/AuthResult.cs b/src/SIGCM.Application/DTOs/AuthResult.cs
new file mode 100644
index 0000000..cce37b3
--- /dev/null
+++ b/src/SIGCM.Application/DTOs/AuthResult.cs
@@ -0,0 +1,11 @@
+namespace SIGCM.Application.DTOs;
+
+public class AuthResult
+{
+ public bool Success { get; set; }
+ public string? Token { get; set; }
+ public string? ErrorMessage { get; set; }
+ public bool IsLockedOut { get; set; }
+ public bool RequiresPasswordChange { get; set; }
+ public bool RequiresMfa { get; set; }
+}
diff --git a/src/SIGCM.Application/DTOs/CashClosingDto.cs b/src/SIGCM.Application/DTOs/CashClosingDto.cs
new file mode 100644
index 0000000..ce80b98
--- /dev/null
+++ b/src/SIGCM.Application/DTOs/CashClosingDto.cs
@@ -0,0 +1,42 @@
+namespace SIGCM.Application.DTOs;
+
+// DTO para iniciar el cierre de caja (arqueo ciego)
+public class CashClosingDto
+{
+ public decimal DeclaredCash { get; set; }
+ public decimal DeclaredDebit { get; set; }
+ public decimal DeclaredCredit { get; set; }
+ public decimal DeclaredTransfer { get; set; }
+ public string? Notes { get; set; }
+}
+
+// DTO de respuesta con el resultado del cierre
+public class CashClosingResultDto
+{
+ public int ClosingId { get; set; }
+
+ // Valores declarados por el cajero
+ public decimal DeclaredCash { get; set; }
+ public decimal DeclaredDebit { get; set; }
+ public decimal DeclaredCredit { get; set; }
+ public decimal DeclaredTransfer { get; set; }
+ public decimal TotalDeclared { get; set; }
+
+ // Valores reales del sistema
+ public decimal SystemCash { get; set; }
+ public decimal SystemDebit { get; set; }
+ public decimal SystemCredit { get; set; }
+ public decimal SystemTransfer { get; set; }
+ public decimal TotalSystem { get; set; }
+
+ // Diferencias detectadas
+ public decimal CashDifference { get; set; }
+ public decimal DebitDifference { get; set; }
+ public decimal CreditDifference { get; set; }
+ public decimal TransferDifference { get; set; }
+ public decimal TotalDifference { get; set; }
+
+ // Banderas de estado
+ public bool HasDiscrepancies { get; set; }
+ public string? Message { get; set; }
+}
diff --git a/src/SIGCM.Application/DTOs/ListingDtos.cs b/src/SIGCM.Application/DTOs/ListingDtos.cs
index 2bca837..fc70771 100644
--- a/src/SIGCM.Application/DTOs/ListingDtos.cs
+++ b/src/SIGCM.Application/DTOs/ListingDtos.cs
@@ -1,28 +1,43 @@
+// src/SIGCM.Application/DTOs/ListingDtos.cs
namespace SIGCM.Application.DTOs;
public class CreateListingDto
{
public int CategoryId { get; set; }
public int OperationId { get; set; }
- public required string Title { get; set; }
+ public string Title { get; set; } = string.Empty;
public string? Description { get; set; }
public decimal Price { get; set; }
+ public decimal AdFee { get; set; }
+ public string Status { get; set; } = "Draft";
public string Currency { get; set; } = "ARS";
public int? UserId { get; set; }
- public Dictionary Attributes { get; set; } = new();
- public string? PrintText { get; set; }
- public string? PrintFontSize { get; set; }
- public string? PrintAlignment { get; set; }
- public int PrintDaysCount { get; set; }
+ public int? ClientId { get; set; }
public string? ClientName { get; set; }
public string? ClientDni { get; set; }
+ public string Origin { get; set; } = "Mostrador";
+ public List? ImagesToClone { get; set; }
+
+ // Impresión
+ public string? PrintText { get; set; }
+ public DateTime? PrintStartDate { get; set; }
+ public int PrintDaysCount { get; set; }
+ public bool IsBold { get; set; }
+ public bool IsFrame { get; set; }
+ public string PrintFontSize { get; set; } = "normal";
+ public string PrintAlignment { get; set; } = "left";
+
+ // Atributos Dinámicos
+ public Dictionary Attributes { get; set; } = new();
+
+ // Pagos
+ public List? Payments { get; set; }
}
public class ListingDto : CreateListingDto
{
public int Id { get; set; }
public DateTime CreatedAt { get; set; }
- public required string Status { get; set; }
}
public class ListingDetailDto : ListingDto
@@ -35,14 +50,22 @@ public class ListingAttributeDto
{
public int ListingId { get; set; }
public int AttributeDefinitionId { get; set; }
- public required string AttributeName { get; set; }
- public required string Value { get; set; }
+ public string AttributeName { get; set; } = string.Empty;
+ public string Value { get; set; } = string.Empty;
}
public class ListingImageDto
{
public int Id { get; set; }
- public required string Url { get; set; }
+ public string Url { get; set; } = string.Empty;
public bool IsMainInfo { get; set; }
public int DisplayOrder { get; set; }
-}
\ No newline at end of file
+}
+
+public class PaymentDto
+{
+ public decimal Amount { get; set; }
+ public string PaymentMethod { get; set; } = string.Empty;
+ public string? CardPlan { get; set; }
+ public decimal Surcharge { get; set; }
+}
diff --git a/src/SIGCM.Application/Interfaces/IAuthService.cs b/src/SIGCM.Application/Interfaces/IAuthService.cs
index 06941b8..5159963 100644
--- a/src/SIGCM.Application/Interfaces/IAuthService.cs
+++ b/src/SIGCM.Application/Interfaces/IAuthService.cs
@@ -2,5 +2,15 @@ namespace SIGCM.Application.Interfaces;
public interface IAuthService
{
- Task LoginAsync(string username, string password);
+ // Autenticación estándar
+ Task LoginAsync(string username, string password);
+ Task RegisterAsync(string username, string email, string password);
+
+ // Autenticación social (Google)
+ Task GoogleLoginAsync(string idToken);
+
+ // Seguridad avanzada (MFA)
+ Task GenerateMfaSecretAsync(int userId);
+ Task VerifyMfaCodeAsync(int userId, string code);
+ Task EnableMfaAsync(int userId, bool enabled);
}
diff --git a/src/SIGCM.Application/SIGCM.Application.csproj b/src/SIGCM.Application/SIGCM.Application.csproj
index b08ec68..a5336ba 100644
--- a/src/SIGCM.Application/SIGCM.Application.csproj
+++ b/src/SIGCM.Application/SIGCM.Application.csproj
@@ -4,6 +4,10 @@
+
+
+
+
net10.0
enable
diff --git a/src/SIGCM.Application/Validators/CreateListingDtoValidator.cs b/src/SIGCM.Application/Validators/CreateListingDtoValidator.cs
new file mode 100644
index 0000000..b19c815
--- /dev/null
+++ b/src/SIGCM.Application/Validators/CreateListingDtoValidator.cs
@@ -0,0 +1,53 @@
+using FluentValidation;
+using SIGCM.Application.DTOs;
+
+namespace SIGCM.Application.Validators;
+
+public class CreateListingDtoValidator : AbstractValidator
+{
+ public CreateListingDtoValidator()
+ {
+ RuleFor(x => x.CategoryId)
+ .GreaterThan(0)
+ .WithMessage("Debe seleccionar un rubro válido.");
+
+ RuleFor(x => x.OperationId)
+ .GreaterThan(0)
+ .WithMessage("Debe seleccionar una operación válida.");
+
+ RuleFor(x => x.PrintText)
+ .NotEmpty()
+ .WithMessage("El texto del aviso no puede estar vacío.")
+ .MinimumLength(10)
+ .WithMessage("El texto del aviso es demasiado corto (mínimo 10 caracteres).");
+
+ RuleFor(x => x.PrintDaysCount)
+ .InclusiveBetween(1, 365)
+ .WithMessage("La duración debe ser entre 1 y 365 días.");
+
+ RuleFor(x => x.AdFee)
+ .GreaterThanOrEqualTo(0)
+ .WithMessage("El costo del aviso no puede ser negativo.");
+
+ RuleFor(x => x.ClientDni)
+ .NotEmpty()
+ .When(x => !string.IsNullOrEmpty(x.ClientName))
+ .WithMessage("Si especifica un nombre de cliente, el DNI/CUIT es obligatorio.");
+
+ RuleForEach(x => x.Payments).SetValidator(new PaymentDtoValidator());
+ }
+}
+
+public class PaymentDtoValidator : AbstractValidator
+{
+ public PaymentDtoValidator()
+ {
+ RuleFor(x => x.Amount)
+ .GreaterThan(0)
+ .WithMessage("El monto del pago debe ser mayor a 0.");
+
+ RuleFor(x => x.PaymentMethod)
+ .NotEmpty()
+ .WithMessage("Debe especificar un medio de pago.");
+ }
+}
diff --git a/src/SIGCM.Domain/Entities/CashClosing.cs b/src/SIGCM.Domain/Entities/CashClosing.cs
new file mode 100644
index 0000000..0958060
--- /dev/null
+++ b/src/SIGCM.Domain/Entities/CashClosing.cs
@@ -0,0 +1,38 @@
+namespace SIGCM.Domain.Entities;
+
+public class CashClosing
+{
+ public int Id { get; set; }
+ public int UserId { get; set; }
+ public DateTime ClosingDate { get; set; } = DateTime.UtcNow;
+
+ // Declaración del cajero (arqueo ciego)
+ public decimal DeclaredCash { get; set; }
+ public decimal DeclaredDebit { get; set; }
+ public decimal DeclaredCredit { get; set; }
+ public decimal DeclaredTransfer { get; set; }
+
+ // Valores del sistema (calculados)
+ public decimal SystemCash { get; set; }
+ public decimal SystemDebit { get; set; }
+ public decimal SystemCredit { get; set; }
+ public decimal SystemTransfer { get; set; }
+
+ // Diferencias
+ public decimal CashDifference { get; set; }
+ public decimal DebitDifference { get; set; }
+ public decimal CreditDifference { get; set; }
+ public decimal TransferDifference { get; set; }
+
+ // Totales
+ public decimal TotalDeclared { get; set; }
+ public decimal TotalSystem { get; set; }
+ public decimal TotalDifference { get; set; }
+
+ // Notas del cajero
+ public string? Notes { get; set; }
+
+ // Estado
+ public bool HasDiscrepancies { get; set; }
+ public bool IsApproved { get; set; }
+}
diff --git a/src/SIGCM.Domain/Entities/CashSession.cs b/src/SIGCM.Domain/Entities/CashSession.cs
new file mode 100644
index 0000000..6501346
--- /dev/null
+++ b/src/SIGCM.Domain/Entities/CashSession.cs
@@ -0,0 +1,34 @@
+namespace SIGCM.Domain.Entities;
+
+public class CashSession
+{
+ public int Id { get; set; }
+ public int UserId { get; set; }
+ public DateTime OpeningDate { get; set; }
+ public DateTime? ClosingDate { get; set; }
+ public string Status { get; set; } = "Open"; // Open, PendingValidation, Closed
+
+ // Apertura
+ public decimal OpeningBalance { get; set; }
+
+ // Declaración del Cajero
+ public decimal? DeclaredCash { get; set; }
+ public decimal? DeclaredCards { get; set; }
+ public decimal? DeclaredTransfers { get; set; }
+
+ // Valores calculados por Sistema
+ public decimal? SystemExpectedCash { get; set; }
+ public decimal? SystemExpectedCards { get; set; }
+ public decimal? SystemExpectedTransfers { get; set; }
+
+ public decimal? TotalDifference { get; set; }
+
+ // Auditoría de Validación
+ public int? ValidatedByUserId { get; set; }
+ public DateTime? ValidationDate { get; set; }
+ public string? ValidationNotes { get; set; }
+
+ // Auxiliares para UI
+ public string? Username { get; set; }
+ public string? ValidatorName { get; set; }
+}
\ No newline at end of file
diff --git a/src/SIGCM.Domain/Entities/Claim.cs b/src/SIGCM.Domain/Entities/Claim.cs
new file mode 100644
index 0000000..b84737a
--- /dev/null
+++ b/src/SIGCM.Domain/Entities/Claim.cs
@@ -0,0 +1,20 @@
+namespace SIGCM.Domain.Entities;
+
+public class Claim
+{
+ public int Id { get; set; }
+ public int ListingId { get; set; }
+ public int CreatedByUserId { get; set; }
+ public required string ClaimType { get; set; }
+ public required string Description { get; set; }
+ public string Status { get; set; } = "Open";
+ public string? SolutionDescription { get; set; }
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ public DateTime? ResolvedAt { get; set; }
+ public int? ResolvedByUserId { get; set; }
+ public string? OriginalValues { get; set; }
+
+ // Propiedades auxiliares para mostrar en UI
+ public string? CreatedByUsername { get; set; }
+ public string? ResolvedByUsername { get; set; }
+}
\ No newline at end of file
diff --git a/src/SIGCM.Domain/Entities/EditionClosure.cs b/src/SIGCM.Domain/Entities/EditionClosure.cs
new file mode 100644
index 0000000..04b3f98
--- /dev/null
+++ b/src/SIGCM.Domain/Entities/EditionClosure.cs
@@ -0,0 +1,14 @@
+namespace SIGCM.Domain.Entities;
+
+public class EditionClosure
+{
+ public int Id { get; set; }
+ public DateTime EditionDate { get; set; } // Fecha de la edición que se cierra
+ public DateTime ClosureDateTime { get; set; } = DateTime.UtcNow; // Cuándo se cerró
+ public int ClosedByUserId { get; set; } // Quién cerró la edición
+ public bool IsClosed { get; set; } = true;
+ public string? Notes { get; set; } // Notas del cierre
+
+ // Nombre del usuario (auxiliar para JOINs)
+ public string? ClosedByUsername { get; set; }
+}
diff --git a/src/SIGCM.Domain/Entities/Listing.cs b/src/SIGCM.Domain/Entities/Listing.cs
index f181277..01448eb 100644
--- a/src/SIGCM.Domain/Entities/Listing.cs
+++ b/src/SIGCM.Domain/Entities/Listing.cs
@@ -14,6 +14,10 @@ public class Listing
public string Status { get; set; } = "Draft";
public int? UserId { get; set; }
public int? ClientId { get; set; }
+ public int ViewCount { get; set; }
+ public string Origin { get; set; } = "Mostrador";
+ public string? OverlayStatus { get; set; }
+ public List? ImagesToClone { get; set; }
// Propiedades para impresión
public string? PrintText { get; set; }
diff --git a/src/SIGCM.Domain/Entities/Notification.cs b/src/SIGCM.Domain/Entities/Notification.cs
new file mode 100644
index 0000000..3379089
--- /dev/null
+++ b/src/SIGCM.Domain/Entities/Notification.cs
@@ -0,0 +1,12 @@
+namespace SIGCM.Domain.Entities;
+
+public class Notification
+{
+ public int Id { get; set; }
+ public int UserId { get; set; }
+ public required string Title { get; set; }
+ public required string Message { get; set; }
+ public required string Type { get; set; }
+ public bool IsRead { get; set; }
+ public DateTime CreatedAt { get; set; }
+}
\ No newline at end of file
diff --git a/src/SIGCM.Domain/Entities/Payment.cs b/src/SIGCM.Domain/Entities/Payment.cs
new file mode 100644
index 0000000..f307e60
--- /dev/null
+++ b/src/SIGCM.Domain/Entities/Payment.cs
@@ -0,0 +1,17 @@
+namespace SIGCM.Domain.Entities;
+
+public class Payment
+{
+ public int Id { get; set; }
+ public int ListingId { get; set; }
+ public decimal Amount { get; set; }
+ public required string PaymentMethod { get; set; } // Cash, Debit, Credit, Transfer, MercadoPago, Stripe
+ public string? CardPlan { get; set; }
+ public decimal Surcharge { get; set; }
+ public DateTime PaymentDate { get; set; } = DateTime.UtcNow;
+
+ // Campos para pagos externos (MP/Stripe)
+ public string? ExternalReference { get; set; } // Nuestro ID de preferencia
+ public string? ExternalId { get; set; } // ID transaccional de la pasarela
+ public string? Status { get; set; } // Pending, Approved, Rejected
+}
diff --git a/src/SIGCM.Domain/Entities/User.cs b/src/SIGCM.Domain/Entities/User.cs
index c73a535..1c67fde 100644
--- a/src/SIGCM.Domain/Entities/User.cs
+++ b/src/SIGCM.Domain/Entities/User.cs
@@ -8,4 +8,16 @@ public class User
public required string Role { get; set; }
public string? Email { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ public int FailedLoginAttempts { get; set; } = 0;
+ public DateTime? LockoutEnd { get; set; }
+ public bool MustChangePassword { get; set; } = true;
+ public bool IsActive { get; set; } = true;
+
+ // Campos de seguridad adicional
+ public DateTime? LastLogin { get; set; }
+
+ // Soporte para OAuth y MFA
+ public string? GoogleId { get; set; }
+ public bool IsMfaEnabled { get; set; }
+ public string? MfaSecret { get; set; }
}
diff --git a/src/SIGCM.Domain/Interfaces/IClaimRepository.cs b/src/SIGCM.Domain/Interfaces/IClaimRepository.cs
new file mode 100644
index 0000000..fa76979
--- /dev/null
+++ b/src/SIGCM.Domain/Interfaces/IClaimRepository.cs
@@ -0,0 +1,13 @@
+using SIGCM.Domain.Entities;
+using SIGCM.Domain.Models;
+
+namespace SIGCM.Domain.Interfaces;
+
+public interface IClaimRepository
+{
+ Task CreateAsync(Claim claim);
+ Task