You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Descarga reportes PDF/XLSX (inventory & loans) con responseType: 'blob'.
Nuevas utilidades: getRecursionPreview.
Estado: reportLoading, currentLanguage expuesto.
Shop.jsx
Refactor de filtros y ordenamiento:
Extrae mapa SORT_LABEL_KEYS y getSortLabel() para evitar ternarios en JSX.
Añade búsqueda por autor (authorSearch) como campo nuevo, integrado con applyFiltersAndSort.
Simplifica dropdowns (categoría/orden), reset de filtros y cálculo hasActiveFilters.
Paginación por itemsPerPage = 10 y scroll-to-top en cambio de página.
Mejor manejo de estado local filteredBooks y efecto dependiente del applyFiltersAndSort.
Shelves.jsx
Nueva UI/UX para gestión de estanterías:
Create/Assign modals con validaciones.
Vista de estanterías con badge de status y barra de progreso por peso.
Modales avanzados:
Dangerous Combinations (brute-force) con opción analyzeAllDangerous.
Optimize (backtracking) con analyzeAllOptimize.
GroupedCategoriesPanel despliega conteos por categoría (resultado del endpoint).
Filtrado de availableBooks = books.filter(...) (no asignados y con pageCount).
Indicadores de loading y mensajes contextualizados.
auth.guard.ts
Nueva función extractToken(request) que busca token primero en cookie cookies.token y luego en Authorization header (Bearer).
Añadida interfaz RequestWithUser para request.userId.
Mejor manejo de errores JWT: mapJwtError(error) convierte TokenExpiredError y JsonWebTokenError en UnauthorizedException con mensajes claros.
Registros (console.log) para depurar origen del token y presencia.
email.service.ts
Implementa OnModuleInit y onModuleInit() para configurar transporter al iniciar módulo.
Lectura robusta de env: EMAIL_HOST, EMAIL_PORT, EMAIL_USER, EMAIL_PASS, EMAIL_FORCE_SMTP.
Si credenciales SMTP disponibles: crea transporter y opcionalmente verify() (saltado con EMAIL_FORCE_SMTP=true).
Fallback a Ethereal (test account) cuando no hay SMTP real.
Plantillas HTML completas para sendPasswordResetEmail y sendPasswordChangedConfirmation; logs con nodemailer.getTestMessageUrl(info).
loan.middleware.ts
Nuevo middleware setupLoanMiddleware() que añade hooks Mongoose:
pre('save') para calcular dueDate automáticamente (loanDate + 15 días) si falta.
pre('save') para validar que dueDate > loanDate (lanza Error si no).
pre('save') para calcular lateFee si status === 'overdue'` (0.5 por día) y log.
migration.service.ts
Servicio de migración completo con:
runFullMigration() que ejecuta sub-migraciones (users, products, images, translations, addresses, orders, shelves, loans, reservations), recalcula multas y devuelve stats con tiempos.
remigrateShelves() que limpia tablas relacionadas y re-migra solo shelves.
getMigrationStatus() compara conteos Mongo vs MySQL y retorna diferencias.
Implementaciones detalladas por colección (upserts, normalización, order_items, product_translations).
Lógica para resolver/crear branches (incluye introspección INFORMATION_SCHEMA y defaultValueForColumn).
migrateShelfRow() con normalización de campos y creación de shelf_books.
recalculateFines() que inserta filas en fines con SQL (DATEDIFF * 0.50).
Auto-calcula expiresAt = requestDate + 30 días si falta.
Valida que expiresAt > requestDate antes de guardar (lanza Error si no).
user.service.ts
Login/register/profile:
register() y login() generan JWT; login() ahora crea sessionToken (JWT pequeño) y lo guarda en usuario; expira token en 7d; retorna expiresIn e isAdmin (compara con ADMIN_EMAIL).
isAuthenticated() y getProfile() devuelven isAdmin basado en ADMIN_EMAIL.
updateProfile():
Verifica unicidad de email (`checkEmailUniqueness).
Validación de cambio de contraseña con validatePasswordChange.
Soporte de subida de profileImage a Cloudinary (uploadProfileImage borra anterior).
Password reset flow:
forgotPassword() genera token JWT tipo password-reset (1h) y llama a EmailService.sendPasswordResetEmail.
resetPassword() valida token de tipo password-reset, reemplaza contraseña, y manda confirmación por email.
Registra lastLogin, lastActivity, lastLogout en eventos relevantes.
Manejo de carrito: addToCart, updateCart (persisten en user.cartData).
ShopContext.jsx
axios defaults (withCredentials, baseURL)y añade interceptores:localStorage.token.fetchBooks(lang)usa endpoint condicional/api/product/listo/api/product/list/{lang}; escuchai18n.languageChanged.fetchUser()con protección para no borrar token en errores de red, set deloginTimeyisAdmin.searchByTitleOrAuthor->POST /api/product/search/linearsearchByISBN->POST /api/product/search/binarysortProductsByPrice->GET /api/product/sort-by-priceapplyFiltersAndSort(migró lógica de filtros/ordenamiento al contexto)FormData, manejo de imagen, country codes y validaciones de contraseña.addToCart, updateQuantity, getCartCount, getCartAmount(sincroniza con backend).fetchShelves, createShelf, assignBookToShelf, removeBookFromShelf.findDangerousCombinations->GET /api/shelf/dangerous-combinations/:idoptimizeShelf->GET /api/shelf/optimize/:idgetUserLoans, createLoan, returnBook, getAllLoansgetUserReservationStats, getUserReservationList, getWaitingList, createReservation, cancelReservationgetRecursionPreview.reportLoading, currentLanguageexpuesto.Shop.jsx
SORT_LABEL_KEYSygetSortLabel()para evitar ternarios en JSX.authorSearch) como campo nuevo, integrado conapplyFiltersAndSort.hasActiveFilters.itemsPerPage = 10yscroll-to-topen cambio de página.filteredBooksy efecto dependiente delapplyFiltersAndSort.Shelves.jsx
brute-force) con opciónanalyzeAllDangerous.backtracking) conanalyzeAllOptimize.GroupedCategoriesPaneldespliega conteos por categoría (resultado del endpoint).availableBooks = books.filter(...)(no asignados y conpageCount).loadingy mensajes contextualizados.auth.guard.ts
extractToken(request)que busca token primero en cookiecookies.tokeny luego enAuthorization header (Bearer).RequestWithUserpararequest.userId.mapJwtError(error)convierteTokenExpiredErroryJsonWebTokenErrorenUnauthorizedExceptioncon mensajes claros.console.log) para depurar origen del token y presencia.email.service.ts
OnModuleInityonModuleInit()para configurar transporter al iniciar módulo.EMAIL_HOST, EMAIL_PORT, EMAIL_USER, EMAIL_PASS, EMAIL_FORCE_SMTP.verify()(saltado conEMAIL_FORCE_SMTP=true).sendPasswordResetEmailysendPasswordChangedConfirmation; logs connodemailer.getTestMessageUrl(info).loan.middleware.ts
pre('save')para calculardueDateautomáticamente (loanDate+ 15 días) si falta.pre('save')para validar quedueDate > loanDate(lanza Error si no).pre('save')para calcularlateFee sistatus === 'overdue'` (0.5 por día) y log.migration.service.ts
runFullMigration()que ejecuta sub-migraciones (users, products, images, translations, addresses, orders, shelves, loans, reservations), recalcula multas y devuelve stats con tiempos.remigrateShelves()que limpia tablas relacionadas y re-migra soloshelves.getMigrationStatus()compara conteos Mongo vs MySQL y retorna diferencias.order_items,product_translations).INFORMATION_SCHEMAydefaultValueForColumn).migrateShelfRow()con normalización de campos y creación deshelf_books.recalculateFines()que inserta filas en fines con SQL (DATEDIFF * 0.50).upsertNamedEntity,emptyStats,recordError,closeMySQL.product.service.ts
sortedInventorymantenido en memoria (ordenado por ISBN) yonModuleInit()para inicializarlo.addProduct()sube imágenes a Cloudinary, genera ISBN si falta y usa insertion sort para mantenersortedInventory.searchByTitleOrAuthor(linear search) ysearchByISBN(binary search sobresortedInventory) — logs incluidos.generateValueReport()(merge sort por valor) y endpoints para reportes.migrateLegacyProducts()para rellenar ISBN/autor/páginas/editorial/publicationYear con funciones utilitarias (migration.utils).calculateTotalValueByCategory(recursión de pila) — devuelve log de ejecución.calculateAverageWeightByCategory(recursión de cola) — devuelve log y promedio.reservation.middleware.ts
setupReservationMiddleware()añadehooks pre('validate'):expiresAt = requestDate + 30días si falta.expiresAt > requestDateantes de guardar (lanza Error si no).user.service.ts
register()ylogin()generan JWT;login()ahora creasessionToken(JWT pequeño) y lo guarda en usuario; expira token en 7d; retornaexpiresIneisAdmin(compara conADMIN_EMAIL).isAuthenticated()ygetProfile()devuelvenisAdminbasado enADMIN_EMAIL.updateProfile():validatePasswordChange.profileImagea Cloudinary (uploadProfileImageborra anterior).forgotPassword()genera token JWT tipo password-reset (1h) y llama a EmailService.sendPasswordResetEmail.resetPassword()valida token de tipo password-reset, reemplaza contraseña, y manda confirmación por email.lastLogin,lastActivity,lastLogouten eventos relevantes.addToCart, updateCart(persisten en user.cartData).