diff --git a/ARCHIVOS DE ELEMENTOR/elementor-2472-2025-06-09.json b/ARCHIVOS DE ELEMENTOR/elementor-2472-2025-06-09.json new file mode 100644 index 0000000..0081f9c --- /dev/null +++ b/ARCHIVOS DE ELEMENTOR/elementor-2472-2025-06-09.json @@ -0,0 +1 @@ +{"content":[{"id":"14291da0","settings":{"flex_direction":"row","flex_gap":{"unit":"px","size":0,"column":"0","row":"0"},"content_width":"full","width":{"unit":"%","size":100},"min_height":{"unit":"px","size":0,"sizes":[]},"flex_align_items":"center","padding":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true},"background_background":"classic","background_color":"#FFFFFF","background_motion_fx_mouseTrack_effect":"yes","background_motion_fx_mouseTrack_speed":{"unit":"px","size":1.100000000000000088817841970012523233890533447265625,"sizes":[]},"presetTitle":"Contenedor","presetIcon":"eicon-container","width_mobile":{"unit":"%","size":"","sizes":[]},"flex_justify_content_mobile":"center","flex_gap_mobile":{"column":"0","row":"0","isLinked":true,"unit":"px","size":0},"margin_mobile":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true},"_flex_size":"none","_element_width":"initial"},"elements":[{"id":"1abee252","settings":{"flex_direction":"column","content_width":"full","width":{"unit":"%","size":25},"_flex_size":"none","_element_width":"initial","width_mobile":{"unit":"%","size":20,"sizes":[]},"flex_justify_content_mobile":"center","margin_mobile":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true},"padding_mobile":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true}},"elements":[{"id":"46ee95e","settings":{"menu_name":"Menu","menu":"menu-principal","menu_typography_typography":"custom","menu_typography_font_weight":"600","color_menu_item":"#525252","color_menu_item_hover":"#E76F51","pointer_color_menu_item_hover":"#2A9D8F","full_width":"stretch","padding_vertical_menu_item_mobile":{"unit":"px","size":0,"sizes":[]},"background_color_dropdown_item_hover":"#E76F51","toggle_background_color":"#E76F51","toggle_size_mobile":{"unit":"px","size":25,"sizes":[]},"toggle_border_width_mobile":{"unit":"px","size":0,"sizes":[]},"toggle_border_radius_mobile":{"unit":"px","size":0,"sizes":[]},"pointer_width":{"unit":"px","size":0,"sizes":[]},"padding_vertical_menu_item":{"unit":"px","size":18,"sizes":[]},"menu_id":45},"elements":[],"isInner":false,"widgetType":"nav-menu","elType":"widget"}],"isInner":true,"elType":"container"},{"id":"1ba19a2f","settings":{"flex_direction":"column","content_width":"full","width":{"unit":"%","size":61.14399999999999835154085303656756877899169921875},"_flex_size":"none","_element_width":"initial","width_mobile":{"unit":"%","size":51,"sizes":[]},"flex_direction_mobile":"column","flex_justify_content_mobile":"center","flex_align_items_mobile":"center","margin_mobile":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true},"padding_mobile":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true}},"elements":[{"id":"5f5e31c","settings":{"__dynamic__":{"image":"[elementor-tag id=\"\" name=\"site-logo\" settings=\"%7B%7D\"]"},"height":{"unit":"px","size":50,"sizes":[]},"align":"center","width":{"unit":"%","size":100,"sizes":[]},"_element_width":"initial","_element_custom_width":{"unit":"%","size":80.22100000000000363797880709171295166015625},"_flex_size":"none","width_mobile":{"unit":"%","size":100,"sizes":[]},"link_to":"custom","link":{"url":"https:\/\/webformwp.reflexoperu.com.pe\/home\/","is_external":"","nofollow":"","custom_attributes":""}},"elements":[],"isInner":false,"widgetType":"theme-site-logo","elType":"widget"}],"isInner":true,"elType":"container"},{"id":"17131b75","settings":{"remove_item_button_position":"bottom","price_quantity_position":"bottom","buttons_position":"top","automatically_open_cart":"yes","product_buttons_typography_typography":"custom","product_buttons_typography_font_size":{"unit":"px","size":14,"sizes":[]},"product_buttons_typography_font_weight":"600","button_border_radius":{"unit":"px","size":5,"sizes":[]},"view_cart_button_background_color":"#E76F51","cart_checkout_button_typography_typography":"custom","cart_checkout_button_typography_font_size":{"unit":"px","size":14,"sizes":[]},"cart_checkout_button_typography_font_weight":"600","alignment_mobile":"center","toggle_button_padding_mobile":{"unit":"px","top":"4","right":"4","bottom":"4","left":"4","isLinked":true},"_margin_mobile":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true},"_padding_mobile":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true},"_flex_align_self":"center","_flex_order":"end","toggle_icon_spacing":{"unit":"px","size":5,"sizes":[]},"_margin":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true},"_element_width":"initial","toggle_button_text_color":"#FFFFFF","toggle_button_icon_color":"#000000","toggle_button_background_color":"#E76F51","toggle_button_hover_text_color":"#FFFFFF","toggle_button_hover_icon_color":"#FFFFFF","toggle_button_hover_background_color":"#E77E51","toggle_button_border_radius":{"unit":"px","size":4,"sizes":[]},"toggle_icon_size":{"unit":"px","size":20,"sizes":[]}},"elements":[],"isInner":false,"widgetType":"woocommerce-menu-cart","elType":"widget"},{"id":"d9b02ee","settings":{"text":"","selected_icon":{"value":{"url":"https:\/\/webformwp.reflexoperu.com.pe\/wp-content\/uploads\/2025\/05\/user-solid.svg","id":2421},"library":"svg"},"align":"right","background_color":"#2A9D8F","text_padding":{"unit":"px","top":"10","right":"10","bottom":"10","left":"10","isLinked":true},"_padding":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true},"_flex_align_self":"center","_flex_order":"end","hide_tablet":"hidden-tablet","hide_mobile":"hidden-mobile","_element_width":"initial","_element_custom_width":{"unit":"%","size":5,"sizes":[]},"link":{"url":"https:\/\/webformwp.reflexoperu.com.pe\/contact-us\/","is_external":"","nofollow":"","custom_attributes":""}},"elements":[],"isInner":false,"widgetType":"button","elType":"widget"}],"isInner":false,"elType":"container"}],"page_settings":{"margin":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true},"padding":{"unit":"px","top":"0","right":"0","bottom":"0","left":"0","isLinked":true}},"version":"0.4","title":"Cart Header","type":"container"} \ No newline at end of file diff --git a/ARCHIVOS DE ELEMENTOR/mini-cart.php b/ARCHIVOS DE ELEMENTOR/mini-cart.php new file mode 100644 index 0000000..cab9872 --- /dev/null +++ b/ARCHIVOS DE ELEMENTOR/mini-cart.php @@ -0,0 +1,169 @@ +exists() && $cart_item['quantity'] > 0 && apply_filters('woocommerce_widget_cart_item_visible', true, $cart_item, $cart_item_key)); + + if (!$is_product_visible) { + return; + } + + $product_id = apply_filters('woocommerce_cart_item_product_id', $cart_item['product_id'], $cart_item, $cart_item_key); + $product_price = apply_filters('woocommerce_cart_item_price', WC()->cart->get_product_price($_product), $cart_item, $cart_item_key); + $product_permalink = apply_filters('woocommerce_cart_item_permalink', $_product->is_visible() ? $_product->get_permalink($cart_item) : '', $cart_item, $cart_item_key); + ?> +
+ +
+ get_image(), $cart_item, $cart_item_key); + + if (!$product_permalink): + echo wp_kses_post($thumbnail); + else: + printf('%s', esc_url($product_permalink), wp_kses_post($thumbnail)); + endif; + ?> +
+ +
+ get_name(), $cart_item, $cart_item_key) . ' '); + else: + echo wp_kses_post(apply_filters('woocommerce_cart_item_name', sprintf('%s', esc_url($product_permalink), $_product->get_name()), $cart_item, $cart_item_key)); + endif; + + do_action('woocommerce_after_cart_item_name', $cart_item, $cart_item_key); + + // Meta data. + echo wc_get_formatted_cart_item_data($cart_item); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ?> +
+ +
+ ' . sprintf('%s × %s', $cart_item['quantity'], $product_price) . '', $cart_item, $cart_item_key); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> +
+ +
+ ', + esc_url(wc_get_cart_remove_url($cart_item_key)), + $class, + __('Remove this item', 'elementor-pro'), + esc_attr($product_id), + esc_attr($cart_item_key), + esc_attr($_product->get_sku()) + ), $cart_item_key); + } ?> +
+
+ cart->get_cart(); + +if (empty($cart_items)) { ?> +
+
+ +
+ $cart_item) { + elementor_pro_render_mini_cart_item($cart_item_key, $cart_item); + } + + do_action('woocommerce_mini_cart_contents'); + ?> +
+ +
+ : + cart->get_cart_subtotal(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> +
+ + cart; + $frame_totals = array(); + $frame_quantities = array(); + + // Agrupar marcos por tipo + foreach ($cart->get_cart() as $cart_item) { + if (!empty($cart_item['frame_price']) && $cart_item['frame'] !== 'sin-marco') { + $frame_type = $cart_item['frame']; + $frame_price = floatval($cart_item['frame_price']); + $quantity = isset($cart_item['quantity']) ? $cart_item['quantity'] : 1; + + if ($frame_price > 0) { + // Acumular por tipo de marco + if (!isset($frame_totals[$frame_type])) { + $frame_totals[$frame_type] = 0; + $frame_quantities[$frame_type] = 0; + } + + $frame_totals[$frame_type] += $frame_price * $quantity; + $frame_quantities[$frame_type] += $quantity; + } + } + } + + // Mostrar cada tipo de marco agrupado + $total_frame_fees = 0; + foreach ($frame_totals as $frame_type => $total_price) { + if ($total_price > 0) { + $quantity_text = $frame_quantities[$frame_type] > 1 + ? ' (x' . $frame_quantities[$frame_type] . ')' + : ''; + + $fee_name = 'Marco: ' . esc_html($frame_type) . $quantity_text; + $total_frame_fees += $total_price; + ?> +
+ : + +
+ 0) { + $cart_total = WC()->cart->get_cart_contents_total(); + $final_total = $cart_total + $total_frame_fees; + ?> +
+ : + +
+ + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0eeeb2c --- /dev/null +++ b/README.md @@ -0,0 +1,622 @@ +# Web Form Plugin para WordPress + +## Índice +1. [Descripción](#descripción) +2. [Características](#características) +3. [Requisitos](#requisitos) +4. [Instalación](#instalación) +5. [Estructura del Plugin](#estructura-del-plugin) +6. [Uso](#uso) +7. [Documentación Técnica](#documentación-técnica) +8. [Solución de Problemas](#solución-de-problemas) +9. [Información Adicional](#información-adicional) + +## Descripción +El Web Form Plugin es una solución completa para integrar un formulario personalizado de impresión de imágenes en WordPress con WooCommerce. Permite a los usuarios subir imágenes, seleccionar tamaños, marcos y personalizar sus impresiones antes de añadirlas al carrito de compras. + +## Características +- Integración completa con WooCommerce +- Carga múltiple de imágenes +- Selección de tamaños predefinidos y personalizados +- Opciones de marcos con precios adicionales +- Vista previa de imágenes +- Carrito de compras personalizado +- Sistema de notificaciones personalizado +- Redirecciones inteligentes para usuarios no autenticados + +## Requisitos +- WordPress 5.0 o superior +- WooCommerce 3.0 o superior +- PHP 7.2 o superior +- Memoria PHP recomendada: 128M +- Tiempo máximo de ejecución: 300 segundos +- Límite de subida de archivos: 32M + +## Instalación +1. Sube el plugin a la carpeta `/wp-content/plugins/` de tu instalación de WordPress +2. Activa el plugin desde el panel de administración de WordPress +3. Configura el producto base para impresiones en WooCommerce > Impresión de Imágenes +4. Asegúrate de que WooCommerce esté correctamente configurado + +## Estructura del Plugin + +### Archivos Principales +- `web-form-plugin.php`: Archivo principal del plugin que contiene toda la lógica de integración con WordPress y WooCommerce + - Manejo de shortcodes + - Integración con WooCommerce + - Procesamiento de formularios + - Gestión de carrito + - Configuración del panel de administración + +### Directorio de Assets + +#### JavaScript (`assets/js/`) +- `main.js`: + - Lógica principal del formulario + - Manejo de eventos de usuario + - Validación de formularios + - Procesamiento de imágenes + - Integración con el carrito + + **Descripción detallada:** + Este es el archivo principal que coordina toda la funcionalidad del formulario. Maneja la inicialización de componentes, la carga de imágenes, la validación de datos y la comunicación con el backend. Implementa un sistema de estados para controlar el flujo del formulario y gestiona las interacciones del usuario con los diferentes elementos de la interfaz. + +- `cart-handler.js`: + - Actualización del mini carrito + - Manejo de eventos del carrito + - Cálculo de precios en tiempo real + - Actualización de fragmentos de WooCommerce + + **Descripción detallada:** + Gestiona todas las interacciones relacionadas con el carrito de compras. Implementa un sistema de actualización en tiempo real que mantiene sincronizado el mini carrito con el carrito principal de WooCommerce. Maneja los cálculos de precios, incluyendo descuentos y costos adicionales, y actualiza la interfaz de usuario de manera dinámica. + +- `custom-notifications.js`: + - Sistema de notificaciones personalizado + - Mensajes de éxito/error + - Notificaciones de carga + - Animaciones de notificación + + **Descripción detallada:** + Implementa un sistema de notificaciones personalizado que muestra mensajes al usuario de forma elegante y no intrusiva. Utiliza un sistema de colas para manejar múltiples notificaciones y asegura que se muestren en el orden correcto. Incluye animaciones suaves y diferentes estilos para distintos tipos de mensajes (éxito, error, advertencia, información). + +- `events.js`: + - Gestión de eventos del formulario + - Validación de campos + - Manejo de interacciones del usuario + - Eventos personalizados + + **Descripción detallada:** + Centraliza la gestión de eventos del formulario. Implementa un sistema de validación en tiempo real para los campos del formulario y maneja las interacciones del usuario con los diferentes elementos. Utiliza un patrón de eventos personalizados para mantener un código limpio y desacoplado. + +- `ios-picker.js`: + - Selector estilo iOS para opciones + - Animaciones suaves + - Soporte táctil + - Personalización de estilos + + **Descripción detallada:** + Implementa un selector de opciones con estilo iOS que proporciona una experiencia de usuario móvil mejorada. Incluye animaciones suaves para las transiciones, soporte completo para gestos táctiles y es altamente personalizable. Se integra perfectamente con el diseño general del formulario. + +- `main-menu-redirection.js`: + - Redirecciones del menú principal + - Manejo de URLs personalizadas + - Integración con el sistema de navegación + + **Descripción detallada:** + Gestiona las redirecciones del menú principal y la navegación del sitio. Implementa un sistema inteligente de redirección que mantiene el estado del usuario y asegura una navegación fluida entre las diferentes secciones del sitio. + +- `mini-cart-style-script.js`: + - Estilos del mini carrito + - Animaciones de actualización + - Diseño responsive + + **Descripción detallada:** + Maneja los estilos y animaciones del mini carrito. Implementa un diseño responsive que se adapta a diferentes tamaños de pantalla y proporciona animaciones suaves para las actualizaciones del carrito. Se integra con el sistema de temas de WordPress. + +- `picker-placeholder.js`: + - Manejo de placeholders en selectores + - Validación de selecciones + - Estilos personalizados + + **Descripción detallada:** + Gestiona los placeholders y la validación de los selectores del formulario. Implementa un sistema de validación visual que proporciona feedback inmediato al usuario y asegura que las selecciones sean válidas antes de enviar el formulario. + +- `requestHttp.js`: + - Gestión de peticiones AJAX + - Manejo de errores + - Procesamiento de respuestas + - Integración con WordPress + + **Descripción detallada:** + Centraliza todas las peticiones HTTP y AJAX del formulario. Implementa un sistema robusto de manejo de errores y reintentos, y procesa las respuestas del servidor de manera eficiente. Se integra con el sistema de nonces de WordPress para garantizar la seguridad. + +#### CSS (`assets/css/`) +- `styles.css`: Estilos principales del formulario +- `cart-page-style.css`: Estilos para la página del carrito +- `checkout-page-style.css`: Estilos para la página de checkout +- `notifications-loading-styles.css`: Estilos para notificaciones y estados de carga +- `order-details-style.css`: Estilos para detalles de pedidos +- `placeholder-loader.css`: Estilos para placeholders y estados de carga + +#### Frames (`assets/frames/`) +- Imágenes de marcos disponibles +- Recursos visuales para previsualizaciones +- Assets para la interfaz de usuario + +## Uso + +### Shortcode +Para mostrar el formulario en cualquier página o post, utiliza el shortcode: +``` +[web_form] +``` + +### Configuración +1. Ve a WooCommerce > Impresión de Imágenes +2. Selecciona el producto base que se utilizará para las impresiones +3. Guarda la configuración + +### Personalización del Formulario +El formulario se puede personalizar mediante: +- Filtros de WordPress +- Acciones de WooCommerce +- CSS personalizado +- JavaScript personalizado + +## Documentación Técnica + +### Hooks y Filtros + +#### Filtros Disponibles +```php +// Modificar el precio del producto +add_filter('woocommerce_product_get_price', 'custom_price_calculation', 10, 2); + +// Personalizar datos del carrito +add_filter('woocommerce_add_cart_item_data', 'custom_cart_item_data', 10, 3); + +// Modificar la visualización del carrito +add_filter('woocommerce_cart_item_name', 'custom_cart_item_name', 10, 3); +``` + +#### Acciones Disponibles +```php +// Antes de procesar el pedido +add_action('woocommerce_before_checkout_process', 'custom_before_checkout'); + +// Después de añadir al carrito +add_action('woocommerce_add_to_cart', 'custom_after_add_to_cart', 10, 6); +``` + +## Solución de Problemas + +### Problemas Comunes +1. Error de carga de imágenes: + - Verificar límites de PHP + - Comprobar permisos de directorio + - Validar formato de archivo + +2. Problemas con el carrito: + - Limpiar caché del navegador + - Verificar sesión de WooCommerce + - Comprobar configuración de cookies + +3. Errores de JavaScript: + - Verificar consola del navegador + - Comprobar conflictos con otros plugins + - Validar versiones de jQuery + +## Información Adicional +- Versión actual: 3.5.5 +- Última actualización: [05/06/2025] +- Autor: Enmanuel + +## Documentación de Archivos JavaScript + +### main.js +**Descripción General:** +El archivo `main.js` es el núcleo del plugin, encargado de coordinar todas las funcionalidades del formulario de impresión de imágenes. Este archivo maneja la lógica principal de la aplicación y actúa como el punto de entrada para todas las interacciones del usuario. + +**Funcionalidades Principales:** +- Inicialización y configuración de componentes +- Gestión de imágenes y previsualizaciones +- Interacción con el usuario +- Integración con WooCommerce +- Comunicación con el backend + +### cart-handler.js +**Descripción General:** +El archivo `cart-handler.js` es responsable de toda la lógica relacionada con el carrito de compras. Implementa un sistema de actualización en tiempo real que mantiene sincronizado el mini carrito con el carrito principal de WooCommerce. + +**Funcionalidades Principales:** +- Actualización dinámica del mini carrito +- Cálculo de precios en tiempo real +- Gestión de fragmentos de WooCommerce +- Manejo de eventos del carrito +- Actualización de la interfaz de usuario + +### custom-notifications.js +**Descripción General:** +El archivo `custom-notifications.js` implementa un sistema de notificaciones personalizado y elegante que proporciona feedback al usuario de manera no intrusiva. + +**Funcionalidades Principales:** +- Sistema de colas para notificaciones +- Diferentes tipos de mensajes (éxito, error, advertencia) +- Animaciones suaves +- Gestión de tiempo de visualización +- Personalización de estilos + +### events.js +**Descripción General:** +El archivo `events.js` centraliza la gestión de eventos del formulario, implementando un sistema robusto de validación y manejo de interacciones del usuario. + +**Funcionalidades Principales:** +- Validación en tiempo real de campos +- Manejo de eventos personalizados +- Gestión de interacciones del usuario +- Sistema de validación de formularios +- Patrón de eventos desacoplado + +### ios-picker.js +**Descripción General:** +El archivo `ios-picker.js` implementa un selector de opciones con estilo iOS que mejora significativamente la experiencia de usuario en dispositivos móviles. + +**Funcionalidades Principales:** +- Selector estilo iOS nativo +- Soporte completo para gestos táctiles +- Animaciones suaves +- Personalización de estilos +- Integración con el diseño general + +### main-menu-redirection.js +**Descripción General:** +El archivo `main-menu-redirection.js` gestiona las redirecciones del menú principal y la navegación del sitio, asegurando una experiencia de usuario fluida. + +**Funcionalidades Principales:** +- Sistema inteligente de redirección +- Manejo de URLs personalizadas +- Integración con la navegación +- Mantenimiento del estado del usuario +- Gestión de rutas + +### mini-cart-style-script.js +**Descripción General:** +El archivo `mini-cart-style-script.js` maneja los estilos y animaciones del mini carrito, proporcionando una experiencia visual agradable y responsive. + +**Funcionalidades Principales:** +- Estilos del mini carrito +- Animaciones de actualización +- Diseño responsive +- Integración con temas +- Personalización visual + +### picker-placeholder.js +**Descripción General:** +El archivo `picker-placeholder.js` gestiona los placeholders y la validación de los selectores del formulario, mejorando la experiencia de usuario. + +**Funcionalidades Principales:** +- Manejo de placeholders +- Validación visual de selecciones +- Estilos personalizados +- Feedback inmediato al usuario +- Integración con formularios + +### requestHttp.js +**Descripción General:** +El archivo `requestHttp.js` centraliza todas las peticiones HTTP y AJAX del formulario, implementando un sistema robusto de manejo de errores y respuestas. + +**Funcionalidades Principales:** +- Gestión de peticiones AJAX +- Manejo de errores robusto +- Sistema de reintentos +- Procesamiento de respuestas +- Integración con WordPress + +## Archivo Principal: main.js + +### Descripción General +El archivo `main.js` es el núcleo del plugin, encargado de coordinar todas las funcionalidades del formulario de impresión de imágenes. Este archivo maneja la lógica principal de la aplicación y actúa como el punto de entrada para todas las interacciones del usuario. + +**Funcionalidades Principales:** + +#### 1. Inicialización y Configuración +- Inicialización de componentes del formulario +- Configuración de eventos y listeners +- Carga de dependencias necesarias +- Verificación de compatibilidad del navegador + +#### 2. Gestión de Imágenes +- Carga y validación de imágenes +- Previsualización de imágenes +- Optimización de imágenes antes de subir +- Manejo de múltiples archivos +- Validación de formatos y tamaños + +#### 3. Interacción con el Usuario +- Manejo de eventos de arrastrar y soltar +- Selección de tamaños y marcos +- Validación en tiempo real de formularios +- Feedback visual para acciones del usuario +- Manejo de errores y mensajes + +#### 4. Integración con WooCommerce +- Comunicación con el carrito de compras +- Actualización de precios en tiempo real +- Manejo de variaciones de productos +- Procesamiento de pedidos + +#### 5. Comunicación con el Backend +- Envío de datos mediante AJAX +- Manejo de respuestas del servidor +- Gestión de errores de red +- Actualización de la interfaz según respuestas + +### Estructura del Código +```javascript +// Ejemplo de estructura básica +class WebForm { + constructor() { + this.initializeComponents(); + this.setupEventListeners(); + this.setupWooCommerceIntegration(); + } + + initializeComponents() { + // Inicialización de componentes + } + + setupEventListeners() { + // Configuración de eventos + } + + setupWooCommerceIntegration() { + // Integración con WooCommerce + } +} +``` + +### Dependencias +- jQuery +- WooCommerce +- Swiper.js (para galerías) +- Viewer.js (para previsualización de imágenes) + +### Consideraciones Técnicas +- Compatible con navegadores modernos +- Optimizado para dispositivos móviles +- Manejo de memoria eficiente +- Sistema de caché para mejor rendimiento + +### Ejemplos de Uso +```javascript +// Inicialización básica +const webForm = new WebForm(); + +// Manejo de eventos +webForm.on('imageUploaded', (image) => { + // Procesar imagen subida +}); + +// Actualización de precios +webForm.updatePrice({ + size: '10x15', + frame: 'classic', + quantity: 2 +}); +``` + +### Mejores Prácticas +1. Siempre validar datos antes de enviar +2. Implementar manejo de errores robusto +3. Optimizar imágenes antes de subir +4. Mantener el estado del formulario +5. Proporcionar feedback al usuario + +### Solución de Problemas Comunes +1. Errores de carga de imágenes + - Verificar formato y tamaño + - Comprobar permisos de archivo + - Validar conexión de red + +2. Problemas de integración con WooCommerce + - Verificar versión de WooCommerce + - Comprobar configuración del carrito + - Validar sesión de usuario + +3. Errores de JavaScript + - Revisar consola del navegador + - Verificar dependencias + - Comprobar compatibilidad + +## Documentación de Archivos CSS + +### styles.css +**Descripción General:** +El archivo `styles.css` contiene los estilos principales del formulario, definiendo la apariencia y el comportamiento visual de todos los componentes. + +**Características Principales:** +- Estilos base del formulario +- Diseño responsive +- Temas y variantes +- Animaciones y transiciones +- Soporte para temas oscuros/claros + +### cart-page-style.css +**Descripción General:** +El archivo `cart-page-style.css` maneja los estilos específicos de la página del carrito, asegurando una experiencia de usuario consistente. + +**Características Principales:** +- Diseño del carrito +- Estilos de productos +- Animaciones de actualización +- Diseño responsive +- Integración con temas + +### checkout-page-style.css +**Descripción General:** +El archivo `checkout-page-style.css` define los estilos de la página de checkout, optimizando el proceso de pago. + +**Características Principales:** +- Diseño del checkout +- Estilos de formularios +- Indicadores de progreso +- Validación visual +- Diseño responsive + +### notifications-loading-styles.css +**Descripción General:** +El archivo `notifications-loading-styles.css` maneja los estilos de las notificaciones y estados de carga. + +**Características Principales:** +- Estilos de notificaciones +- Indicadores de carga +- Animaciones de transición +- Estados de error/éxito +- Personalización de mensajes + +### order-details-style.css +**Descripción General:** +El archivo `order-details-style.css` define los estilos para la visualización de detalles de pedidos. + +**Características Principales:** +- Diseño de detalles de pedido +- Estilos de miniaturas +- Información de producto +- Estados de pedido +- Diseño responsive + +### placeholder-loader.css +**Descripción General:** +El archivo `placeholder-loader.css` maneja los estilos de los placeholders y estados de carga. + +**Características Principales:** +- Estilos de placeholders +- Animaciones de carga +- Estados de transición +- Diseño responsive +- Personalización de estilos + +## Seguridad + +### Medidas de Seguridad Implementadas +1. **Validación de Datos** + - Sanitización de entradas + - Validación de tipos de archivo + - Comprobación de tamaños + - Verificación de formatos + +2. **Protección contra Ataques** + - Nonces de WordPress + - Validación de permisos + - Protección CSRF + - Sanitización de URLs + +3. **Manejo de Archivos** + - Validación de tipos MIME + - Límites de tamaño + - Escaneo de malware + - Almacenamiento seguro + +4. **Autenticación y Autorización** + - Verificación de roles + - Control de acceso + - Sesiones seguras + - Tokens de autenticación + +### Buenas Prácticas +1. Mantener actualizado el plugin +2. Usar HTTPS +3. Implementar rate limiting +4. Realizar backups regulares +5. Monitorear logs de seguridad + +## Personalización Avanzada + +### Hooks y Filtros Personalizados +```php +// Personalizar el procesamiento de imágenes +add_filter('web_form_process_image', 'custom_image_processing', 10, 2); + +// Modificar el cálculo de precios +add_filter('web_form_calculate_price', 'custom_price_calculation', 10, 3); + +// Personalizar la validación de formularios +add_filter('web_form_validate_field', 'custom_field_validation', 10, 2); +``` + +### Personalización de Estilos +```css +/* Personalizar el contenedor principal */ +.web-form-container { + --primary-color: #your-color; + --secondary-color: #your-color; + --font-family: your-font; +} + +/* Personalizar componentes específicos */ +.web-form-input { + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} +``` + +### Personalización de JavaScript +```javascript +// Extender funcionalidad del formulario +WebForm.prototype.customMethod = function() { + // Tu código personalizado +}; + +// Agregar eventos personalizados +document.addEventListener('webForm:customEvent', function(e) { + // Manejar evento personalizado +}); +``` + +## Uso del Shortcode + +### Shortcode Básico +``` +[web_form] +``` + +### Shortcode con Opciones +``` +[web_form + product_id="123" + show_preview="true" + max_files="5" + allowed_types="jpg,png" +] +``` + +### Parámetros Disponibles +| Parámetro | Descripción | Valores | Default | +|---------------|-----------------------------|------------------|-------------| +| product_id | ID del producto base | número | configurado | +| show_preview | Mostrar previsualización | true/false | true | +| max_files | Máximo de archivos | número | 10 | +| allowed_types | Tipos de archivo permitidos | jpg,png,gif | todos | +| theme | Tema visual | light/dark | light | +| layout | Diseño del formulario | standard/compact | standard | + +### Ejemplos de Uso +```php +// Shortcode con tema oscuro +[web_form theme="dark"] + +// Shortcode con límite de archivos +[web_form max_files="3" allowed_types="jpg,png"] + +// Shortcode con diseño compacto +[web_form layout="compact" show_preview="false"] +``` + +### Integración con Temas +```php +// En tu archivo de tema +echo do_shortcode('[web_form]'); + +// Con parámetros personalizados +echo do_shortcode('[web_form theme="dark" layout="compact"]'); +``` diff --git a/assets/css/cart-page-style.css b/assets/css/cart-page-style.css new file mode 100644 index 0000000..644250d --- /dev/null +++ b/assets/css/cart-page-style.css @@ -0,0 +1,132 @@ +:root { + --font-size-cart-item: 0.875rem; +} + +table { + margin: 0; +} + +table.woocommerce-cart-form__contents tr.cart_item { + padding: 0 !important; +} + +/* Ocultar el botón del carrito en el carrito */ +body.woocommerce-cart .elementor-menu-cart__toggle { + display: none !important; +} + +/* Cart totals */ + +.woocommerce-cart .cart_totals { + margin-top: 0; +} + +/* Ocultar el botón de actualizar carrito en la página del carrito */ +.woocommerce-cart button[name="update_cart"] { + display: none !important; +} + +.woocommerce-Price-amount .amount { + display: none; +} + +@media (max-width: 768px) { + .woocommerce-cart .cart-collaterals, + .woocommerce-cart .woocommerce-cart-form { + width: 100%; + } + + .cart-collaterals { + display: none; + } + /* Ocultar nombre del producto*/ + .product-name a { + display :none; + } +} + +/* Desktop y tablet */ +.woocommerce-cart .cart_item .product-price { + position: relative; + min-height: 60px; + font-size: var(--font-size-cart-item) !important; +} + +/* Precio del marco siempre abajo */ +.woocommerce-cart .cart_item .product-price .precio-marco { + position: absolute; + left: 0; + bottom: 40px; + width: 100%; + padding-top: 2px; + z-index: 2; + font-size: var(--font-size-cart-item) !important; + +} + +/* Oculta específicamente la cantidad en el precio del carrito */ +.yaydp-cart-item-price .yaydp-cart-item-quantity { + display: none !important; + } + + /* Ajusta el espaciado resultante */ + .yaydp-cart-item-price .price { + margin-left: 0 !important; + padding-left: 0 !important; + } + +@media (max-width: 768px) { + .woocommerce-cart .cart_item .product-price .precio-marco { + display: none; + } +} + +@media (max-width: 480px) { + .woocommerce-cart .cart_item .product-price .precio-marco { + display: none; + } + + .woocommerce-cart .cart_item .product-name { + font-size: var(--font-size-cart-item) !important; + } + + .woocommerce-cart .cart_item .subtotal-base, .woocommerce-cart .cart_item .subtotal-marco { + font-size: var(--font-size-cart-item) !important; + } +} + +.image-thumbnails-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 5px; +} + +.image-thumb { + display: flex; + flex-direction: column; + align-items: center; + font-size: 12px; +} + +.image-thumb img { + max-width: 100%; + height: auto; + object-fit: cover; + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + aspect-ratio: 3/2; +} + +@media (max-width: 780px) { + .image-thumbnails-container { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (max-width: 480px) { + .image-thumbnails-container { + grid-template-columns: repeat(2, 1fr); + } +} + diff --git a/assets/css/checkout-page-style.css b/assets/css/checkout-page-style.css new file mode 100644 index 0000000..dcd370f --- /dev/null +++ b/assets/css/checkout-page-style.css @@ -0,0 +1,38 @@ +body.woocommerce-checkout .elementor-menu-cart__toggle { + display: none !important; +} + +.image-thumbnails-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 5px; +} + +.image-thumb { + display: flex; + flex-direction: column; + align-items: center; + font-size: 12px; +} + +.image-thumb img { + max-width: 100%; + height: auto; + object-fit: cover; + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + aspect-ratio: 3/2; +} + +@media (max-width: 780px) { + .image-thumbnails-container { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (max-width: 480px) { + .image-thumbnails-container { + grid-template-columns: repeat(2, 1fr); + } +} \ No newline at end of file diff --git a/assets/css/lost-password-page-style.css b/assets/css/lost-password-page-style.css new file mode 100644 index 0000000..6d7006a --- /dev/null +++ b/assets/css/lost-password-page-style.css @@ -0,0 +1,3 @@ +.woocommerce-lost-password .woocommerce-ResetPassword { + margin: 0 auto; +} \ No newline at end of file diff --git a/assets/css/notifications-loading-styles.css b/assets/css/notifications-loading-styles.css new file mode 100644 index 0000000..9f541b4 --- /dev/null +++ b/assets/css/notifications-loading-styles.css @@ -0,0 +1,186 @@ +/* Estilos para notificaciones */ +.notification { + position: fixed; + top: 20px; + right: 20px; + padding: 15px 20px; + border-radius: 5px; + box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2); + color: white; + font-weight: 500; + z-index: 1000; + opacity: 0; + transform: translateY(-20px); + transition: opacity 0.3s, transform 0.3s; +} + +/* Estilos para notificación de descuento*/ +#custom-alert { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.4); + display: flex; + justify-content: center; + align-items: center; + z-index: 9999; +} + +#custom-alert.hidden { + display: none; +} + +.custom-alert-box { + background-color: white; + padding: 25px 35px; + border-radius: 10px; + box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3); + text-align: center; + max-width: 90%; + font-family: sans-serif; +} + +.custom-alert-box p { + margin-bottom: 20px; + font-size: 18px; +} + +#custom-alert-close { + padding: 10px 20px; + background-color: #f44336; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 16px; + transition: background 0.2s ease; +} + +#custom-alert-close:hover { + background-color: #ef5b50; +} + +/*Fin del estilo*/ + +.notification.show { + opacity: 1; + transform: translateY(0); +} + +.notification.success { + background-color: #4caf50; +} + +.notification.error { + background-color: #f44336; +} + +.notification.info { + background-color: #2196f3; +} + +/* Estilo para errores de validación */ +.field-error { + color: #f44336; + font-size: 12px; + margin-top: 5px; +} + +/* Estilo para campos con error */ +input.error, +textarea.error { + border-color: #f44336; +} + +/* Estilos para indicador de carga */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + z-index: 1001; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s; +} + +.loading-overlay.show { + opacity: 1; + visibility: visible; +} + +.loading-spinner { + width: 50px; + height: 50px; + border: 5px solid #f3f3f3; + border-radius: 50%; + border-top: 5px solid #e76f51; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.loading-overlay p { + margin-top: 15px; + color: white; + font-size: var(--font-size-title); +} + +/* Estilo para mensajes de confirmación */ +.confirmation-message { + text-align: center; + padding: 20px; + background-color: #e8f5e9; + border-radius: 5px; + margin: 20px 0; +} + +.confirmation-message h3 { + color: #4caf50; + margin-top: 0; +} + +@media (max-width: 480px) { + .notification { + position: fixed; + top: 20px; + right: 20px; + padding: 15px 20px; + transform: translateY(-20px); + transition: opacity 0.3s, transform 0.3s; + } + + .notification.show { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 760px) { + .notification { + position: fixed; + top: 30px; + right: 20px; + padding: 15px 20px; + } + + .notification.show { + opacity: 1; + transform: translateY(0); + } +} diff --git a/assets/css/order-details-style.css b/assets/css/order-details-style.css new file mode 100644 index 0000000..67f9711 --- /dev/null +++ b/assets/css/order-details-style.css @@ -0,0 +1,50 @@ +li { + padding-left: 10px; +} + +.woocommerce-thankyou-order-received { + padding: 5px 10px !important; +} + +.woocommerce-thankyou-order-details { + padding: 0 !important; +} + +.shop_table.order_details, .shop_table.woocommerce-MyAccount-orders { + padding: 5px 10px !important; +} + +.image-thumbnails-container { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 5px; +} + +.image-thumb { + display: flex; + flex-direction: column; + align-items: center; + font-size: 12px; +} + +.image-thumb img { + max-width: 100%; + height: auto; + object-fit: cover; + border: 1px solid #ccc; + border-radius: 4px; + width: 100%; + aspect-ratio: 3/2; +} + +@media (max-width: 780px) { + .image-thumbnails-container { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (max-width: 480px) { + .image-thumbnails-container { + grid-template-columns: repeat(2, 1fr); + } +} diff --git a/assets/css/placeholder-loader.css b/assets/css/placeholder-loader.css new file mode 100644 index 0000000..831766b --- /dev/null +++ b/assets/css/placeholder-loader.css @@ -0,0 +1,81 @@ + /* Placeholder con efecto glow */ + .placeholder { + background-color: #e9ecef; + border-radius: 4px; + display: inline-block; + height: 1.25rem; + width: 100%; +} + +.placeholder-glow .placeholder { + animation: placeholder-glow 2s ease-in-out infinite alternate; +} + +@keyframes placeholder-glow { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } + + 100% { + opacity: 1; + } +} + +/* Variantes de tamaño */ +.placeholder-xs { + height: 0.75rem; +} + +.placeholder-sm { + height: 1rem; +} + +.placeholder-lg { + height: 1.5rem; +} + +.placeholder-xl { + height: 2rem; +} + +/* Variantes de ancho */ +.placeholder-25 { + width: 25%; +} + +.placeholder-50 { + width: 50%; +} + +.placeholder-75 { + width: 75%; +} + +.placeholder-100 { + width: 100%; +} + +/* Formas especiales */ +.placeholder-circle { + border-radius: 50%; + width: 3rem; + height: 3rem; +} + +.placeholder-rounded { + border-radius: 50rem; +} + +/* Estilos específicos para el picker placeholder */ +.picker-placeholder { + padding: 0; + text-align: center; +} + +.picker-placeholder .placeholder { + margin: 5px auto; +} \ No newline at end of file diff --git a/styles.css b/assets/css/styles.css similarity index 76% rename from styles.css rename to assets/css/styles.css index 7646713..86c3def 100644 --- a/styles.css +++ b/assets/css/styles.css @@ -5,8 +5,8 @@ --font-title: "Krona One", serif; --font-content: "Montserrat", serif; - --font-size-title: 1.25rem; - --font-size-content: 1.125rem; + --font-size-title: 1.125rem; + --font-size-content: 1rem; } body { @@ -15,7 +15,7 @@ body { color: #333; margin: 0 auto; box-sizing: border-box; - /* max-width: 600px; */ + /*max-width: 650px;*/ } .upload-area { @@ -108,8 +108,8 @@ body { align-items: center; font-size: var(--font-size-title); color: var(--primary-color); - gap: 10px; - padding-bottom: 10px; + gap: 5px; + padding-bottom: 5px; } .radio-group { @@ -124,10 +124,6 @@ body { gap: 5px; } - .radio-input { - margin-right: 6px; - } - .quantity-field { display: flex; } @@ -158,7 +154,7 @@ body { } .comment-area { - width: calc(100% - 10px); + width: 100%; min-height: 80px; padding: 20px; border: 1px solid #dee2e6; @@ -174,19 +170,29 @@ body { margin: 0 auto; } +.frames { + padding: 0 30px; +} + .swiper { width: 100%; } .swiper-slide { + display: flex; + align-items: center; + justify-content: center; background-position: center; background-size: contain; - width: 250px; + /*width: 250px;*/ + height: 200px !important; } .swiper-slide img { display: block; width: 100%; + object-fit: contain; + height: 100%; } .swiper-slide.selected { @@ -195,6 +201,20 @@ body { transition: box-shadow 0.3s ease, border 0.3s ease; /* Transición suave */ } +.swiper-slide .price { + font-size: 1.2rem; + font-weight: 900; + color: #fff; + text-shadow: 1px 1px 3px black; + padding: 0.5rem; + background-color: rgba(0, 0, 0, 0.2); + text-align: center; + position: absolute; + top: 150px; + left: 50%; + transform: translateX(-50%); +} + @keyframes show{ from{ opacity: 0; @@ -265,39 +285,50 @@ body { } .custom-size input { - max-width: 40%; + max-width: 50%; } .comment-area { min-height: 120px; - width: calc(100% - 20px); + width: 100%; padding-right: 15px; /* Ajustar espacio al lado derecho para pantallas más pequeñas */ } .frames { padding: 0 30px; } + + .swiper-slide .price { + top: 150px; + padding: 0.5rem; + font-size: var(--font-size-content); + text-shadow: 1px 1px 10px black; + background-color: rgba(0, 0, 0, 0.3); + text-wrap: nowrap; + } } @media (max-width: 480px) { - .desktop-upload-text { - display: none; - } - - .mobile-upload-text { - display: block; - } - - .images-grid { - grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); - } + .desktop-upload-text { + display: none; + } - .frames { - padding: 0 30px; + .mobile-upload-text { + display: block; } - + + .images-grid { + grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); + } + + .custom-size { + width: 100%; + padding: 0; + margin: 0 auto; + } + .add-to-cart-btn { padding: 8px; width: 100%; @@ -307,82 +338,31 @@ body { width: 100%; padding: 10px 0 10px 0;/* Ajustar espacio al lado derecho para pantallas más pequeñas */ } - } - - /* Estilos para notificaciones */ -.notification { - position: fixed; - top: 20px; - right: 20px; - padding: 15px 20px; - border-radius: 5px; - box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2); - color: white; - font-weight: 500; - z-index: 1000; - opacity: 0; - transform: translateY(-20px); - transition: opacity 0.3s, transform 0.3s; -} - -.notification.show { - opacity: 1; - transform: translateY(0); -} - -.notification.success { - background-color: #4CAF50; -} - -.notification.error { - background-color: #f44336; -} - -.notification.info { - background-color: #2196F3; -} - -/* Estilos para indicador de carga */ -.loading-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - z-index: 1001; - opacity: 0; - visibility: hidden; - transition: opacity 0.3s; -} -.loading-overlay.show { - opacity: 1; - visibility: visible; -} - -.loading-spinner { - width: 50px; - height: 50px; - border: 5px solid #f3f3f3; - border-radius: 50%; - border-top: 5px solid #e76f51; - animation: spin 1s linear infinite; -} + .swiper-slide .price { + top: 150px; + padding: 0.5rem; + font-size: var(--font-size-content); + text-shadow: 1px 1px 10px black; + background-color: rgba(0, 0, 0, 0.3); + text-wrap: nowrap; + } + } -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } +.image-item.low-quality { + border: 2px dashed #ff9800; + position: relative; } -.loading-overlay p { - margin-top: 15px; - color: white; - font-size: var(--font-size-title); +.quality-warning { + position: absolute; + bottom: 4px; + left: 4px; + background-color: #ffeb3b; + color: #000; + padding: 2px 6px; + font-size: 12px; + border-radius: 4px; } /* Estilo para el botón de eliminar imagen */ @@ -437,18 +417,6 @@ body { border-color: var(--secondary-color); } -/* Estilo para errores de validación */ -.field-error { - color: #f44336; - font-size: 12px; - margin-top: 5px; -} - -/* Estilo para campos con error */ -input.error, textarea.error { - border-color: #f44336; -} - /* Estilos para el panel de administración */ .admin-settings-container { max-width: 800px; @@ -522,31 +490,38 @@ input.error, textarea.error { object-fit: contain; } -/* Estilo para mensajes de confirmación */ -.confirmation-message { - text-align: center; - padding: 20px; - background-color: #e8f5e9; - border-radius: 5px; - margin: 20px 0; +.viewer-zoom-in { + background-color: #f7703a !important; } -.confirmation-message h3 { - color: #4CAF50; - margin-top: 0; +.viewer-zoom-out { + background-color: #f7703a !important; +} + +.viewer-close { + background-color: #af1b1b !important; } -/* Mostrar la notificación */ -.notification.show { +.zoom-icon-btn { + position: absolute; + bottom: 10px; + right: 10px; + background: transparent; + border: none; + cursor: pointer; + width: 30px; + height: 30px; + padding: 0; + top: 10px; +} +/* Barra latertal del carrito */ + +.elementor-menu-cart__container[aria-hidden="false"] { opacity: 1; + transform: translateX(0); } -/* Estilo específico para dispositivos móviles */ -@media (max-width: 768px) { - .notification { - align-items: center; - text-align: center; - font-size: 14px; - max-width: 80%; - } -} \ No newline at end of file +/* Ocultar el botón de actualizar carrito en la página del carrito */ +.woocommerce-cart button[name="update_cart"] { + display: none !important; +} diff --git a/assets/frames/2JAFuz7tzcaH-cuenta.png b/assets/frames/2JAFuz7tzcaH-cuenta.png new file mode 100644 index 0000000..20f9418 Binary files /dev/null and b/assets/frames/2JAFuz7tzcaH-cuenta.png differ diff --git a/assets/frames/Glamifiedpeach-1.jpg b/assets/frames/Glamifiedpeach-1.jpg new file mode 100644 index 0000000..c92704d Binary files /dev/null and b/assets/frames/Glamifiedpeach-1.jpg differ diff --git a/assets/frames/arte-1.jpg b/assets/frames/arte-1.jpg new file mode 100644 index 0000000..7bb8639 Binary files /dev/null and b/assets/frames/arte-1.jpg differ diff --git a/assets/js/cart-handler.js b/assets/js/cart-handler.js new file mode 100644 index 0000000..8a151b5 --- /dev/null +++ b/assets/js/cart-handler.js @@ -0,0 +1,127 @@ +jQuery(document).ready(function ($) { + let previousValue = 0; + + // Función para evitar validaciones excesivas mientras escribe + function debounce(func, wait) { + let timeout; + return function (...args) { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), wait); + }; + } + + // Función principal de validación + function handleQuantityChange(input) { + const rawValue = input.val(); + const max = parseInt(input.attr("max")) || 100; + const min = parseInt(input.attr("min")) || 1; + + if (rawValue === "") return; + + let currentValue = parseInt(rawValue); + + if (isNaN(currentValue)) return; + + if (currentValue > max) { + input.val(max); + showNotification( + "¡La cantidad máxima permitida es " + max + "!", + "error" + ); + currentValue = max; + } + + if (currentValue < min) { + input.val(min); + showNotification("La cantidad no puede ser menor a " + min, "error"); + currentValue = min; + } + + if (currentValue !== previousValue) { + previousValue = currentValue; + input.closest("form").find('button[name="update_cart"]').trigger("click"); + } + } + + const debouncedQuantityChange = debounce(function (input) { + handleQuantityChange(input); + }, 500); + + // Detecta escritura manual + $(document).on("input", "input.qty", function () { + const input = $(this); + const value = input.val(); + if (value === "" || isNaN(parseInt(value))) return; + debouncedQuantityChange(input); + }); + + // Detecta al salir del campo + $(document).on("blur", "input.qty", function () { + handleQuantityChange($(this)); + }); + + // Detecta tecla Enter + $(document).on("keydown", "input.qty", function (e) { + if (e.key === "Enter") { + const val = $(this).val(); + const min = parseInt($(this).attr("min")) || 1; + + if (val === "" || isNaN(parseInt(val)) || parseInt(val) < min) { + e.preventDefault(); + $(this).val(min); + showNotification( + "Por favor ingresa una cantidad válida antes de continuar", + "error" + ); + } + } + }); + + // ✅ Detecta clics en los botones de cantidad (+/-) + $(document) + .on("mousedown", 'input.qty[type="number"]', function () { + previousValue = parseInt($(this).val()) || 1; + }) + .on("mouseup", 'input.qty[type="number"]', function () { + setTimeout(() => handleQuantityChange($(this)), 1000); + }); + + // Detecta clics en los botones personalizados de Botiga (+ y -) + $(document).on( + "click", + ".botiga-quantity-plus, .botiga-quantity-minus", + function (e) { + e.preventDefault(); // Evita que el enlace haga scroll a arriba + const input = $(this).siblings("input.qty"); + + // Espera a que se actualice el valor + setTimeout(() => { + handleQuantityChange(input); + }, 1000); // Puedes ajustar el tiempo si es necesario + } + ); + + // 🚫 Bloquea el checkout si hay cantidades vacías o inválidas + $(document).on("click", "a.checkout-button", function (e) { + let isValid = true; + + $("input.qty").each(function () { + const val = $(this).val(); + const min = parseInt($(this).attr("min")) || 1; + + if (val === "" || isNaN(parseInt(val)) || parseInt(val) < min) { + isValid = false; + $(this).val(min); + showNotification( + "No puedes proceder al checkout con cantidades vacías o inválidas.", + "error" + ); + $(this).focus(); + } + }); + + if (!isValid) { + e.preventDefault(); // Bloquea el enlace de checkout + } + }); +}); diff --git a/assets/js/custom-notifications.js b/assets/js/custom-notifications.js new file mode 100644 index 0000000..b4ee2e2 --- /dev/null +++ b/assets/js/custom-notifications.js @@ -0,0 +1,67 @@ +// Función para mostrar notificaciones +function showNotification(message, type = "info") { + const notification = document.createElement("div"); + notification.className = `notification ${type}`; + notification.textContent = message; + document.body.appendChild(notification); + + setTimeout(() => notification.classList.add("show"), 10); + setTimeout(() => { + notification.classList.remove("show"); + setTimeout(() => document.body.removeChild(notification), 600); + }, 2000); +} + +// Función para mostrar indicador de carga dinámico +function showLoadingIndicator(totalImages) { + const loadingOverlay = document.createElement("div"); + loadingOverlay.className = "loading-overlay"; + loadingOverlay.innerHTML = ` +
+

Procesando imagen 0/${totalImages}...

+ `; + document.body.appendChild(loadingOverlay); + setTimeout(() => loadingOverlay.classList.add("show"), 10); + + // Retornamos una función para actualizar el progreso + return { + updateProgress: (current) => { + const progressText = loadingOverlay.querySelector("p"); + if (progressText) { + progressText.textContent = `Procesando imagen ${current}/${totalImages}...`; + } + }, + remove: () => { + loadingOverlay.classList.remove("show"); + setTimeout(() => loadingOverlay.remove(), 500); // Animación de fade out + } + }; +} + +// Función para ocultar indicador de carga +function hideLoadingIndicator() { + const loadingOverlay = document.querySelector(".loading-overlay"); + if (loadingOverlay) { + loadingOverlay.classList.remove("show"); + + setTimeout(() => { + if (document.body.contains(loadingOverlay)) { + document.body.removeChild(loadingOverlay); + } + }, 300); + } +} + +// Alerta para descuentos aplicados con Yaycommerce +function showCustomAlert(message) { + const alertOverlay = document.getElementById("custom-alert"); + const alertMessage = document.getElementById("custom-alert-message"); + const alertClose = document.getElementById("custom-alert-close"); + + alertMessage.textContent = message; + alertOverlay.classList.remove("hidden"); + + alertClose.onclick = () => { + alertOverlay.classList.add("hidden"); + }; +} diff --git a/assets/js/events.js b/assets/js/events.js new file mode 100644 index 0000000..92e09ce --- /dev/null +++ b/assets/js/events.js @@ -0,0 +1,64 @@ +let priceParam = 0; +let priceBase = 'base'; +let marcoSeleccionado = 0; // 0 es el precio del marco sin seleccionar +priceDimensionsBaseMain = null; + +// EVENTO SELECCION DE MARCO PRECIO ---------------------------------------------------------------------------------------------------------- + +function calcularPrecioMarco(price) { + marcoSeleccionado = price; + window.marcoSeleccionado = marcoSeleccionado; // Enviar el precio del marco seleccionado a main.js + + // Actualizar el precio total + updateTotalPriceWithFrame(); +} + +// FUNCIÓN PARA ACTUALIZAR PRECIO TOTAL INCLUYENDO MARCO ---------------------------------------------------------------------------------------------------------- + +function updateTotalPriceWithFrame() { + const quantity = parseInt(document.querySelector(".quantity-field input").value) || 1; + + let precioDimensiones = parseFloat(priceDimensionsBaseMain) || 0; + let precioMarco = parseFloat(marcoSeleccionado) || 0; + + // El precio total es: (precio de dimensiones + precio de marco por unidad) * cantidad + let precioTotal = ((precioDimensiones / quantity) + precioMarco) * quantity; + + document.getElementById('total-price-button').innerText = `S/. ${precioTotal.toFixed(2)}`; +} + +// EVENTO SELECCION DE MARCO PRECIO ---------------------------------------------------------------------------------------------------------- + +document.getElementById("size-picker").addEventListener("click", function (e) { + if (e.target.classList.contains("carousel-item")) { + // Usar la nueva función que incluye el marco + updateTotalPriceWithFrame(); + } +}); + +// EVENTO REINICIAR PRECIOS ---------------------------------------------------------------------------------------------------------- + +function reiniciarPrecioMarco() { + priceBase = 'base'; + priceParam = 0; +} + +// EVENTO REINICIAR PRECIOS ---------------------------------------------------------------------------------------------------------- + +// EVENTO VOLVER AL PRECIO ORIGINAL AL SELECCIONAR SIN MARCOS ---------------------------------------------------------------------------- + +function calcularPrecioSinMarco() { + // Remover la selección de todos los marcos + document.querySelectorAll('.swiper-slide.selected').forEach(el => { + el.classList.remove('selected'); + }); + + // Reiniciar el precio del marco + marcoSeleccionado = 0; + window.marcoSeleccionado = 0; // Asegurarse de que también se actualice en el ámbito global + + // Actualizar el precio total sin marco + updateTotalPriceWithFrame(); +} + +// EVENTO VOLVER AL PRECIO ORIGINAL AL SELECCIONAR SIN MARCOS ---------------------------------------------------------------------------- \ No newline at end of file diff --git a/ios-picker.js b/assets/js/ios-picker.js similarity index 83% rename from ios-picker.js rename to assets/js/ios-picker.js index 9d31832..9f96ca0 100644 --- a/ios-picker.js +++ b/assets/js/ios-picker.js @@ -21,6 +21,12 @@ border-radius: 4px; touch-action: pan-y; margin: 0 auto; + outline: none; /* Remove default focus outline */ + } + + .carousel-container:focus { + border-color: var(--selected-text-color); + box-shadow: 0 0 0 2px rgba(228, 146, 125, 0.3); } .carousel-track { @@ -37,7 +43,6 @@ justify-content: center; font-size: 0.875rem; color: color: #070707;; - // padding: 0 20px; cursor: pointer; user-select: none; } @@ -71,13 +76,13 @@ } - .picker-title { - text-align: center; - margin-bottom: 20px; - font-size: 20px; - font-weight: 600; - color: var(--text-color); - } + // .picker-title { + // text-align: center; + // margin-bottom: 20px; + // font-size: 20px; + // font-weight: 600; + // color: var(--text-color); + // } .picker-wrapper { display: 20px; @@ -140,7 +145,7 @@ this.carouselContainer.appendChild(highlight); this.carouselContainer.appendChild(this.carouselTrack); - this.pickerWrapper.appendChild(this.titleElement); + // this.pickerWrapper.appendChild(this.titleElement); this.pickerWrapper.appendChild(this.carouselContainer); if (this.options.container) { @@ -179,9 +184,51 @@ } setupKeyboardNavigation() { + // Add tabindex to make the picker focusable + this.carouselContainer.setAttribute('tabindex', '0'); + + // Track if picker has focus or is being interacted with + let hasFocus = false; + + // Set focus state when picker is clicked or touched + this.carouselContainer.addEventListener('mousedown', () => { + hasFocus = true; + this.carouselContainer.focus(); + }); + + this.carouselContainer.addEventListener('touchstart', () => { + hasFocus = true; + this.carouselContainer.focus(); + }, { passive: true }); + + // Focus events + this.carouselContainer.addEventListener('focus', () => { + hasFocus = true; + }); + + this.carouselContainer.addEventListener('blur', () => { + hasFocus = false; + }); + + // Clear focus when touching outside + document.addEventListener('touchstart', (e) => { + if (!this.carouselContainer.contains(e.target)) { + hasFocus = false; + } + }, { passive: true }); + + // Only respond to arrow keys when picker has focus document.addEventListener('keydown', (e) => { - if (e.key === 'ArrowUp') this.carousel.prev(); - if (e.key === 'ArrowDown') this.carousel.next(); + if (!hasFocus) return; + + if (e.key === 'ArrowUp') { + e.preventDefault(); // Prevent page scrolling + this.carousel.prev(); + } + if (e.key === 'ArrowDown') { + e.preventDefault(); // Prevent page scrolling + this.carousel.next(); + } }); } @@ -227,7 +274,7 @@ } setupEvents() { - this.element.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: true }); + this.element.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: false }); this.element.addEventListener('touchmove', this.handleTouchMove.bind(this), { passive: false }); this.element.addEventListener('touchend', this.handleTouchEnd.bind(this)); this.element.addEventListener('mousedown', this.handleMouseDown.bind(this)); @@ -320,6 +367,16 @@ } handleWheel(e) { + // Only respond to wheel events when inside the carousel container + const bounds = this.element.getBoundingClientRect(); + const isInside = + e.clientX >= bounds.left && + e.clientX <= bounds.right && + e.clientY >= bounds.top && + e.clientY <= bounds.bottom; + + if (!isInside) return; + e.preventDefault(); if (this.isAnimating) return; diff --git a/assets/js/main-menu-redirection.js b/assets/js/main-menu-redirection.js new file mode 100644 index 0000000..bdb9bf6 --- /dev/null +++ b/assets/js/main-menu-redirection.js @@ -0,0 +1,15 @@ +document.addEventListener('DOMContentLoaded', function() { + // Selecciona TODOS los enlaces que contengan 'my-account' en el menú de Elementor + const accountLinks = document.querySelectorAll('.elementor-nav-menu a[href*="my-account"]'); + + accountLinks.forEach(link => { + // Cambia URL y texto según el estado de login + if (document.body.classList.contains('logged-in')) { + link.href = 'https://webformwp.reflexoperu.com.pe/my-account/'; + link.textContent = 'Mi cuenta'; + } else { + link.href = 'https://webformwp.reflexoperu.com.pe/login/'; + link.textContent = 'Iniciar sesión'; + } + }); +}); \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js new file mode 100644 index 0000000..8909b9f --- /dev/null +++ b/assets/js/main.js @@ -0,0 +1,1004 @@ +// Variable global para seleccion de tamaños y almacenar múltiples imágenes +var selectedOptionSize; +let currentSelectedFrame = "frame1"; // Valor por defecto para el marco +let uploadedImagesData = []; // Array para almacenar múltiples imágenes +let sizePicker; +let selectedSize; +let currentSizePrices; +let selectedSizeId; + +let priceDimensionsBaseMain; +let variation_id = 0; + +document.addEventListener("DOMContentLoaded", function () { + getDimensions(); + setupEventListeners(); + setupCommentLengthChecker(); + + updateTotalPrice(); +}); + +function initializeSizePicker() { + const sizePickerContainer = document.getElementById("size-picker"); + + // Obtener y ordenar las opciones de tamaño + const sizeOptions = Object.entries(window.dimensionPrecioBase) + .map(([size, data]) => ({ + value: size, + label: `${size.replace("X", "cm Ancho X ")}cm Alto => (S/.${data.price})`, + // Agregar propiedades para ordenamiento + width: size === "custom" ? Infinity : parseInt(size.split("X")[0]), + height: size === "custom" ? Infinity : parseInt(size.split("X")[1]), + isCustom: size === "custom", + })) + .sort((a, b) => { + // Poner dimensión personalizada al final + if (a.isCustom) return 1; + if (b.isCustom) return -1; + + // Ordenar por ancho, luego por alto + if (a.width === b.width) { + return a.height - b.height; + } + return a.width - b.width; + }); + + // Crear picker con opciones ordenadas + sizePicker = new iOSPicker({ + container: sizePickerContainer, + items: sizeOptions, + onChange: (value) => { + selectedSize = value; + revalidateImagesWithNewSize(value); + updateTotalPrice(); + reiniciarPrecioMarco(); + }, + }); +} +function initializeFramePicker() { + const framePicker = document.getElementById("frame-picker"); + if (!framePicker) return; + + const pickerItems = framePicker.querySelectorAll(".swiper-slide"); + pickerItems.forEach((item) => { + item.addEventListener("click", function () { + pickerItems.forEach((i) => i.classList.remove("selected")); + item.classList.add("selected"); + currentSelectedFrame = item.dataset.value; + }); + }); +} + +function setupCommentLengthChecker() { + const commentArea = document.querySelector(".comment-area"); + + commentArea.addEventListener("input", function () { + const maxLength = 255; + const currentLength = this.value.length; + + if (currentLength >= maxLength) { + showNotification( + "Has alcanzado el límite máximo de 255 caracteres en los comentarios.", + "error" + ); + } + }); +} + +function setupEventListeners() { + const quantityInput = document.querySelector(".quantity-field input"); + + quantityInput.addEventListener("focus", function () { + if (this.value === "1") { + this.value = ""; + } + }); + + quantityInput.addEventListener("input", function () { + let value = parseInt(this.value); + + // Si es mayor a 1000, vuelve al mismo valor + if (value > 1000) { + this.value = 1000; + showNotification( + "La cantidad no puede ser mayor a 1000 unidades", + "error" + ); + } + + updateTotalPrice(); + reiniciarPrecioMarco(); + }); + + quantityInput.addEventListener("blur", function () { + let value = parseInt(this.value); + + if (isNaN(value) || value < 1) { + this.value = 1; + } + }); + + document + .getElementById("custom-size__width") + .addEventListener("input", handleCustomSize); + document + .getElementById("custom-size__height") + .addEventListener("input", handleCustomSize); + document + .querySelector(".add-to-cart-btn") + .addEventListener("click", addToCart); +} + +function handleCustomSize() { + const widthInput = document.getElementById("custom-size__width"); + const heightInput = document.getElementById("custom-size__height"); + + // ANCHO + widthInput.addEventListener("focus", function () { + if (this.value === "10") this.value = ""; + }); + + widthInput.addEventListener("input", function () { + let value = parseInt(this.value); + if (value > 60) { + this.value = 60; + showNotification("El ancho no puede ser mayor a 60 cm.", "error"); + } + updateTotalPrice(); + reiniciarPrecioMarco(); + }); + + widthInput.addEventListener("blur", function () { + let value = parseInt(this.value); + if (isNaN(value) || value < 10) { + this.value = 10; + showNotification("El ancho no puede ser menor a 10 cm.", "error"); + } + updateTotalPrice(); + reiniciarPrecioMarco(); + }); + + // ALTO + heightInput.addEventListener("focus", function () { + if (this.value === "15") this.value = ""; + }); + + heightInput.addEventListener("input", function () { + let value = parseInt(this.value); + if (value > 100) { + this.value = 100; + showNotification("El alto no puede ser mayor a 100 cm.", "error"); + } + updateTotalPrice(); + reiniciarPrecioMarco(); + }); + + heightInput.addEventListener("blur", function () { + let value = parseInt(this.value); + if (isNaN(value) || value < 15) { + this.value = 15; + showNotification("El alto no puede ser menor a 15 cm.", "error"); + } + updateTotalPrice(); + reiniciarPrecioMarco(); + }); + + document + .querySelector(".add-to-cart-btn") + .addEventListener("click", addToCart); +} + +// Función para alternar entre opciones de tamaño estándar y personalizado +function toggleSizeOptions(type) { + if (type === "standard") { + document.getElementById("standard-sizes").style.display = "block"; + document.getElementById("custom-size").style.display = "none"; + this.selectedOptionSize = "standard"; + updateTotalPrice(); + reiniciarPrecioMarco(); + } else { + document.getElementById("standard-sizes").style.display = "none"; + document.getElementById("custom-size").style.display = "flex"; + this.selectedOptionSize = "custom"; + + // Entrada de tamaño personalizado + const widthInput = document.getElementById("custom-size__width"); + const heightInput = document.getElementById("custom-size__height"); + if (widthInput) widthInput.value = ""; + if (heightInput) heightInput.value = ""; + + // Inicializa el precio en 0.00 + document.getElementById("total-price-button").innerText = `S/. 0.00`; + } +} + +// Función para inicializar el visor de imágenes (Viewer.js) para las imágenes del Swiper +function initializeViewerForSwiper() { + const slides = document.querySelectorAll(".swiper-slide"); + + slides.forEach((slide) => { + const img = slide.querySelector("img"); // Busca la imagen dentro del slide + const btn = slide.querySelector(".zoom-icon-btn"); // Busca el botón con clase .zoom-icon-btn dentro del slide + + if (btn && img) { + let viewerInstance = null; + + // Elimina cualquier listener previo antes de agregar uno nuevo + btn.removeEventListener("click", btn._viewerClickHandler); + + // Define el handler como una propiedad del botón para evitar duplicados + btn._viewerClickHandler = function (event) { + event.stopPropagation(); + + // Evita crear múltiples viewers + if (viewerInstance) { + viewerInstance.destroy(); + } + + // Crea un contenedor temporal solo para zoom + const clone = img.cloneNode(true); + const tempContainer = document.createElement("div"); + tempContainer.style.display = "none"; + tempContainer.appendChild(clone); + document.body.appendChild(tempContainer); + + viewerInstance = new Viewer(clone, { + inline: false, + navbar: false, + title: false, + toolbar: { + zoomIn: { + show: true, + title: "Zoom In", + size: "large", + }, + zoomOut: { + show: true, + title: "Zoom Out", + size: "large", + }, + oneToOne: false, + reset: false, + prev: false, + play: false, + next: false, + rotateLeft: false, + rotateRight: false, + flipHorizontal: false, + flipVertical: false, + }, + viewed() { + viewerInstance.zoomTo(1); + }, + hidden() { + viewerInstance.destroy(); + tempContainer.remove(); + viewerInstance = null; + }, + }); + + // Muestra el visor + viewerInstance.show(); + }; + + // Agrega el listener actualizado + btn.addEventListener("click", btn._viewerClickHandler); + } + }); +} + +function toggleFrameOptions(show) { + // Verificar si hay imágenes subidas antes de mostrar las opciones de marco + if (show && uploadedImagesData.length === 0) { + showNotification( + "Por favor, sube al menos una imagen antes de seleccionar un marco.", + "error" + ); + return; + } + + document.getElementById("frame-ios-picker-wrapper").style.display = show + ? "block" + : "none"; + + if (show) { + if (!window.mySwiper) { + window.mySwiper = new Swiper(".mySwiper", { + effect: "coverflow", + grabCursor: true, + centeredSlides: true, + slidesPerView: "2", + coverflowEffect: { + rotate: 20, + strech: 0, + depth: 150, + modifier: 3, + slideShadows: true, + }, + loop: true, + }); + } + } + + // Inicializa Viewer.js para las imágenes del Swiper + initializeViewerForSwiper(); +} + +// Variables globales para almacenar las imágenes +// uploadedImagesData está declarado al inicio del archivo como un array vacío + +const uploadArea = document.getElementById("upload-area"); +const uploadElements = document.getElementById("upload-elements"); +const fileInput = document.getElementById("file-input"); +const imagesGrid = document.getElementById("images-grid"); + +// Función para activar el input de archivo +function triggerFileInput() { + fileInput.click(); +} + +// Click en el área de subida +uploadArea.addEventListener("click", function (e) { + // Verificar si el clic fue en un botón de eliminar o en una imagen + if ( + e.target.classList.contains("remove-image-btn") || + e.target.closest(".remove-image-btn") || + e.target.tagName === "IMG" + ) { + return; + } + + triggerFileInput(); +}); + +// Manejo de archivos seleccionados +fileInput.addEventListener("change", handleFiles); + +// Manejo de arrastrar y soltar +uploadArea.addEventListener("dragover", (e) => { + e.preventDefault(); + uploadArea.style.backgroundColor = "#f1f1f1"; +}); + +uploadArea.addEventListener("dragleave", (e) => { + e.preventDefault(); + uploadArea.style.backgroundColor = "#f8f9fa"; +}); + +uploadArea.addEventListener("drop", (e) => { + e.preventDefault(); + uploadArea.style.backgroundColor = "#f8f9fa"; + + if (e.dataTransfer.files.length > 0) { + handleFiles({ target: { files: e.dataTransfer.files } }); + } +}); + +// Función para manejar los archivos - Modificada para soportar múltiples imágenes +function handleFiles(e) { + const files = e.target.files; // Obtiene los archivos seleccionados + + hasInvalidFiles(files, e); // Comprobar si hay imágenes con formato incompatible + + const isValid = validateAndUploadImages(files, uploadedImagesData, fileInput); + if (!isValid) { + // El proceso se detiene, ya se notificó al usuario + return; + } + + // Continuar con el proceso de subida + + const isCustomSize = selectedOptionSize === "custom"; // Verifica si el tamaño es personalizado + + if (sizePicker) { + selectedSize = sizePicker.getValue(); // Obtiene el tamaño seleccionado + // Si es personalizado, obtiene los valores actuales de ancho y alto + let customWidth, customHeight; + if (isCustomSize) { + customWidth = parseInt( + document.getElementById("custom-size__width").value + ); + customHeight = parseInt( + document.getElementById("custom-size__height").value + ); + } + + const lowQualityIndexes = []; // Array para almacenar índices de imágenes de baja calidad + + if (files.length > 0) { + uploadElements.classList.add("has-images"); // Añadir clase para mostrar que hay imágenes + } + + let pending = files.length; // Contador de archivos pendientes de procesar + + for (let i = 0; i < files.length; i++) { + const file = files[i]; + + // Verificar si el archivo es una imagen + if (!file.type.match("image.*")) { + pending--; // Decrementar el contador de pendientes si no es una imagen + continue; + } + + const reader = new FileReader(); // Crear un nueva instancia FileReader para cada archivo + + reader.onload = function (event) { + const img = new Image(); + img.src = event.target.result; // Asignar la URL de la imagen al objeto Image + + img.onload = function () { + const { minWidth, minHeight } = + getMinimumPixelsForImage(selectedSize); // Calcular los píxeles mínimos por imagen + const isLowQuality = + img.naturalWidth < minWidth || img.naturalHeight < minHeight; // Verificar si la imagen es de baja calidad + + const imageId = "img_" + Date.now() + "_" + i; // Generar un ID único para la imagen + + // Almacenar los datos de la imagen en el array global + uploadedImagesData.push({ + id: imageId, + file: file, + lowQuality: isLowQuality, + }); + + // Crear el elemento de imagen en el DOM + const imageItem = document.createElement("div"); + imageItem.className = "image-item"; + imageItem.dataset.imageId = imageId; // Asignar el ID al elemento + + if (isLowQuality) { + imageItem.classList.add("low-quality"); // Añadir clase para baja calidad + lowQualityIndexes.push(i + 1); // Guarda el índice (basado en 1) de la imagen Posición humana + } + + // Crear el elemento de imagen y botón de eliminar + const imgElement = document.createElement("img"); + imgElement.src = event.target.result; + imgElement.alt = "Imagen subida"; + + // Crea el botón de eliminar imagen + const removeBtn = document.createElement("button"); + removeBtn.className = "remove-image-btn"; + removeBtn.textContent = "X"; + removeBtn.addEventListener("click", function (e) { + e.stopPropagation(); // Evitar que el evento de clic se propague al contenedor + uploadedImagesData = uploadedImagesData.filter( + (img) => img.id !== imageId + ); // Eliminar la imagen del array + imageItem.remove(); // Eliminar el elemento del DOM + if (imagesGrid.children.length === 0) { + uploadElements.classList.remove("has-images"); // Remover clase si no hay imágenes + } + updateTotalImageSize(); // ✅ Aquí se actualiza el tamaño total + updateTotalPrice(); // Actualizar el precio total + reiniciarPrecioMarco(); + }); + + // Crear la etiqueta de advertencia de baja calidad + const warningLabel = document.createElement("div"); + warningLabel.className = "quality-warning"; + warningLabel.textContent = "⚠️ Resolución baja"; + if (!isLowQuality) warningLabel.style.display = "none"; // Ocultar si no es baja calidad + + // Añadir los elementos al contenedor de imágenes + imageItem.appendChild(imgElement); + imageItem.appendChild(warningLabel); + imageItem.appendChild(removeBtn); + imagesGrid.appendChild(imageItem); + + updateTotalPrice(); // Actualizar el precio total + reiniciarPrecioMarco(); + + pending--; // Decrementar el contador de pendientes + }; + }; + + reader.readAsDataURL(file); // Leer el archivo como URL de datos + } + + fileInput.value = ""; // Resetea el input para permitir subir el mismo archivo otra vez + } else { + showNotification("Por favor espere la carga de dimensiones..."); + } +} + +function hasInvalidFiles(files, e) { + let hasInvalidFiles = false; + const allowedFileFormat = [ + "image/jpeg", + "image/webp", + "image/png", + "image/svg+xml", + ]; + + // Itera sobre cada archivo y muestra su tipo MIME en consola + for (let i = 0; i < files.length; i++) { + const file = files[i]; + + // Verifica si el tipo del archivo está en la lista de permitidos + if (!allowedFileFormat.includes(file.type)) { + console.error("❌ Formato no permitido:", file.type); + hasInvalidFiles = true; + } + + // Bloquea la subida si hay archivos no permitidos + if (hasInvalidFiles) { + showCustomAlert(`La imagen ${file.name} tiene un formato incompatible. Solo se permiten imágenes en formato JPEG, WebP, PNG o SVG`); + e.target.value = ""; // Limpia el input (elimina los archivos seleccionados) + } + } +} + +function updateTotalImageSize() { + const totalSize = uploadedImagesData.reduce( + (sum, img) => sum + img.file.size, + 0 + ); + return totalSize; +} + +function validateAndUploadImages(files, uploadedImagesData, fileInput) { + const MAX_IMAGES = 10; + const MAX_FILE_SIZE = 40 * 1024 * 1024; // 40MB + const MAX_TOTAL_SIZE = 40 * 1024 * 1024; // 40MB + + const currentImagesCount = uploadedImagesData.length; + const totalAfterUpload = currentImagesCount + files.length; + + // Verificar el total de imágenes (subidas + nuevas) + if (totalAfterUpload > MAX_IMAGES) { + const remainingSlots = MAX_IMAGES - currentImagesCount; + if (remainingSlots <= 0) { + showNotification( + `Ya tienes el máximo de ${MAX_IMAGES} imágenes. Elimina algunas para subir más.` + ); + } else { + showNotification( + `Solo puedes subir ${remainingSlots} imágenes más. Has seleccionado ${files.length}.` + ); + } + fileInput.value = ""; + return false; + } + + // Calcular tamaño total incluyendo imágenes ya cargadas + let totalSize = uploadedImagesData.reduce( + (sum, img) => sum + img.file.size, + 0 + ); + + // Verificar tamaños de archivos individuales y acumular total + for (let i = 0; i < files.length; i++) { + const file = files[i]; + + // Verificar tamaño individual + if (file.size > MAX_FILE_SIZE) { + const fileSizeMB = (file.size / 1024 / 1024).toFixed(1); + showNotification( + `La imagen "${file.name}" (${fileSizeMB}MB) excede el límite de 40MB por archivo.` + ); + fileInput.value = ""; + return false; + } + + totalSize += file.size; + } + + // Verificar tamaño total acumulado + if (totalSize > MAX_TOTAL_SIZE) { + const totalSizeMB = (totalSize / 1024 / 1024).toFixed(1); + showNotification( + `El tamaño total de todas las imágenes (${totalSizeMB}MB) excede el límite de 40MB.` + ); + fileInput.value = ""; + return false; + } + return true; +} + +// Factor de conversión de cm a píxeles (ajusta según la calidad requerida) +const PIXELS_PER_CM = 30; + +// Función para calcular los requisitos mínimos de píxeles según el tamaño de la imagen +function getMinimumPixelsForImage(size) { + const [widthCm, heightCm] = size.split("X").map(Number); // Convierte "10X15" a [10, 15] + const minWidth = widthCm * PIXELS_PER_CM; // Calcula el ancho mínimo en píxeles + const minHeight = heightCm * PIXELS_PER_CM; // Calcula la altura mínima en píxeles + return { minWidth, minHeight }; // Devuelve un objeto con los valores mínimos +} + +// Nueva función para validar imágenes con tamaños personalizados +function validateImagesWithCustomSize(customWidth, customHeight) { + const minWidth = customWidth * PIXELS_PER_CM; // Ancho mínimo en píxeles + const minHeight = customHeight * PIXELS_PER_CM; // Alto mínimo en píxeles + const lowQualityIndexes = []; // Índices de imágenes de baja calidad + + // Recorre las imágenes subidas + uploadedImagesData.forEach((imageData, index) => { + const imgElement = imagesGrid.querySelector( + `[data-image-id="${imageData.id}"] img` + ); + const warningLabel = imagesGrid.querySelector( + `[data-image-id="${imageData.id}"] .quality-warning` + ); + + if (imgElement) { + const tempImg = new Image(); + tempImg.src = imgElement.src; + + tempImg.onload = () => { + const isLowQuality = + tempImg.naturalWidth < minWidth || tempImg.naturalHeight < minHeight; + + // Actualiza el estado de calidad en el array global + imageData.lowQuality = isLowQuality; + + // Modifica clases visuales y etiqueta + if (isLowQuality) { + lowQualityIndexes.push(index + 1); // Índice basado en 1 + warningLabel.style.display = "block"; // Mostrar advertencia + } else { + warningLabel.style.display = "none"; // Ocultar advertencia + } + }; + } + }); +} + +// Modificar el evento de entrada para tamaños personalizados +document + .getElementById("custom-size__width") + .addEventListener("input", function () { + const customWidth = parseInt(this.value); + const customHeight = parseInt( + document.getElementById("custom-size__height").value + ); + + if (!isNaN(customWidth) && !isNaN(customHeight)) { + validateImagesWithCustomSize(customWidth, customHeight); + } + }); + +document + .getElementById("custom-size__height") + .addEventListener("input", function () { + const customWidth = parseInt( + document.getElementById("custom-size__width").value + ); + const customHeight = parseInt(this.value); + + if (!isNaN(customWidth) && !isNaN(customHeight)) { + validateImagesWithCustomSize(customWidth, customHeight); + } + }); + +// Modificado para incluir el nombre del archivo +function analyzeImagePixels(img, imageSize, fileName) { + const { minWidth, minHeight } = getMinimumPixelsForImage(imageSize); // Obtener los requisitos mínimos de píxeles + + // Verificar si la imagen se ha cargado correctamente + if (img.naturalWidth === 0 || img.naturalHeight === 0) { + return `La imagen "${fileName}" no se cargó correctamente.`; + } + + // Verificar si la imagen cumple con los requisitos mínimos + if (img.naturalWidth < minWidth || img.naturalHeight < minHeight) { + return `La imagen "${fileName}" no tiene suficiente calidad para imprimir en ${imageSize.replace( + "X", + "x" + )} cm. Debe tener al menos ${minWidth}x${minHeight} píxeles.`; + } + + return null; // No hay error +} + +// Functión para revalidar imágenes con el nuevo tamaño seleccionado +function revalidateImagesWithNewSize(newSize) { + const { minWidth, minHeight } = getMinimumPixelsForImage(newSize); // Obtener los nuevos requisitos mínimos + const lowQualityIndexes = []; // Array para almacenar índices de imágenes de baja calidad + + // Recorre las imágenes en el orden en que aparecen en el grid + const imageItems = Array.from(imagesGrid.querySelectorAll(".image-item")); + + imageItems.forEach((item, index) => { + const imageId = item.dataset.imageId; // Obtener el ID de la imagen + const data = uploadedImagesData.find((img) => img.id === imageId); // Buscar en el array de datos + const imgElement = item.querySelector("img"); // Obtener el elemento de imagen + const warningLabel = item.querySelector(".quality-warning"); // Obtener la etiqueta de advertencia + + const tempImg = new Image(); // Crear un nuevo objeto Image para cargar la imagen + tempImg.src = imgElement.src; // Asignar la URL de la imagen al objeto Image + + tempImg.onload = () => { + const isLowQuality = + tempImg.naturalWidth < minWidth || tempImg.naturalHeight < minHeight; + + // Actualiza el estado de calidad en el array global + data.lowQuality = isLowQuality; + + // Modifica clases visuales y etiqueta + if (isLowQuality) { + item.classList.add("low-quality"); + if (warningLabel) warningLabel.style.display = "block"; // Mostrar advertencia + lowQualityIndexes.push(index + 1); // Índice basado en el orden del grid + } else { + item.classList.remove("low-quality"); + if (warningLabel) warningLabel.style.display = "none"; // Ocultar advertencia + } + }; + }); +} + +function getSelectedSizePrice() { + selectedSize = sizePicker.getValue(); // Obtener el tamaño seleccionado + return currentSizePrices[selectedSize] || 0; // Retornar el precio correspondiente +} + +// Función para añadir al carrito - Modificada para procesar múltiples imágenes de forma secuencial +function addToCart() { + if (uploadedImagesData.length === 0) { + showNotification("Por favor, sube al menos una imagen primero.", "error"); + return; + } + + const quantity = document.querySelector(".quantity-field input").value; + const isCustomSize = selectedOptionSize == "custom"; + + let size, customWidth, customHeight, pricePerUnit; + let variation_id = 0; + if (isCustomSize) { + customWidth = document.getElementById("custom-size__width").value; + customHeight = document.getElementById("custom-size__height").value; + if (!customWidth || !customHeight) { + showNotification("Ingresa las medidas personalizadas", "error"); + return; + } + size = `${customWidth}X${customHeight}`; + pricePerUnit = parseInt(customHeight) * parseInt(customWidth) * 0.03; + pricePerUnit = Math.round(pricePerUnit); + // Usar el custom_variation_id de la dimensión personalizada + if ( + window.customDimensionObj && + window.customDimensionObj.custom_variation_id + ) { + variation_id = window.customDimensionObj.custom_variation_id; + } else if ( + typeof customDimensionObj !== "undefined" && + customDimensionObj && + customDimensionObj.custom_variation_id + ) { + variation_id = customDimensionObj.custom_variation_id; + } else { + variation_id = 0; + } + } else { + size = sizePicker.getValue(); + const sizeData = window.dimensionPrecioBase[size]; + if (sizeData) { + pricePerUnit = sizeData.price; + variation_id = sizeData.variation_id; + } + } + + // Validar que el campo de comentarios no esté vacío + const comments = document.querySelector(".comment-area").value.trim(); + if (!comments) { + showNotification("Por favor, escribe un comentario.", "error"); + document.querySelector(".comment-area").focus(); + return; + } + const withFrame = + document.querySelector('input[name="frame"]:checked').value !== "sin-marco"; + let frame = "sin-marco"; + let frame_price = 0; + + if (withFrame) { + const selectedFrameElement = document.querySelector( + "#frame-picker .swiper-slide.selected" + ); + frame = selectedFrameElement ? currentSelectedFrame : "Sin marco"; + // marcoSeleccionado viene de events.js y tiene el precio del marco seleccionado + frame_price = window.marcoSeleccionado; + } + + // Mostrar notificación de descuento aplicado con YayCommerce + if (quantity >= 4 && !isCustomSize) { + showCustomAlert( + "¡Buenísima noticia! Se ha aplicado un descuento especial a tu compra 🎉." + ); + } + + if (typeof ajax_object === "undefined") { + console.error( + "❌ ERROR: ajax_object no está definido. Verifica que el script de WordPress está cargando correctamente." + ); + showNotification( + "Error de configuración. Contacte con el administrador.", + "error" + ); + return; + } + + // Procesar de manera asíncrona las imágenes notificando el progreso + async function processImages(images) { + const loading = showLoadingIndicator(images.length); + + for (let i = 0; i < images.length; i++) { + try { + loading.updateProgress(i + 1); // Actualiza el contador + + // Simula procesamiento (reemplazar con tu lógica real) + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Procesar imagen aquí... + console.log(`Procesando imagen ${i + 1}`); + + } catch (error) { + console.error("Error al procesar imagen:", error); + } + } + + loading.remove(); // Oculta el indicador al finalizar + } + + // Llamada de la functión + processImages(uploadedImagesData); + + // --- NUEVO: Enviar todas las imágenes en un solo FormData --- + const formData = new FormData(); + formData.append("action", "process_print_image"); + formData.append("nonce", ajax_object.nonce); + formData.append("quantity", quantity); + formData.append("size", size); + formData.append("variation_id", variation_id); + formData.append("price", pricePerUnit); + formData.append("is_custom_size", isCustomSize); + formData.append("custom_width", customWidth || 0); + formData.append("custom_height", customHeight || 0); + formData.append("comments", comments); + formData.append("frame", frame); + formData.append("frame_price", frame_price); + + // Agregar todas las imágenes como image_data[] + uploadedImagesData.forEach((imageData, idx) => { + formData.append("image_data[]", imageData.file, imageData.file.name); + }); + + fetch(ajax_object.ajax_url, { + method: "POST", + body: formData, + credentials: "same-origin", + }) + .then((response) => response.json()) + .then((data) => { + hideLoadingIndicator(); + if (data.success) { + showNotification("¡Imágenes añadidas al carrito!", "success"); + + // Actualizar el fragmento del carrito + if (typeof wc_cart_fragments_params !== "undefined") { + jQuery(document.body).trigger("wc_fragment_refresh"); + } + + // Mostrar automáticamente la barra lateral del carrito de Elementor + setTimeout(function () { + // Buscar y hacer clic en el botón que abre el carrito lateral + const cartToggleBtn = document.querySelector( + ".elementor-menu-cart__toggle_button" + ); + if (cartToggleBtn) { + cartToggleBtn.click(); + } else { + // Alternativa: usar el evento personalizado de Elementor para abrir el carrito lateral + jQuery(document.body).trigger("wc_fragments_refreshed"); + jQuery(document.body).trigger("elementor/popup/show"); + } + }, 2500); // Pequeño retraso para asegurar que los fragmentos se actualizaron + + // Limpiar todos los campos del formulario + resetForm(); + } else { + showNotification( + data.data || "Error al procesar las imágenes.", + "error" + ); + } + }) + .catch((error) => { + hideLoadingIndicator(); + showNotification( + "Error al procesar las imágenes. Por favor intenta de nuevo.", + "error" + ); + console.error(error); + }); +} + +// Función para resetear el formulario +function resetForm() { + // Limpiar imágenes + uploadedImagesData = []; + imagesGrid.innerHTML = ""; + uploadElements.classList.remove("has-images"); + + // Resetear cantidad + const quantityInput = document.querySelector(".quantity-field input"); + if (quantityInput) { + quantityInput.value = "1"; + } + + // Resetear comentarios + const commentArea = document.querySelector(".comment-area"); + if (commentArea) { + commentArea.value = ""; + } + + // Resetear tamaño personalizado + const customWidthInput = document.getElementById("custom-size__width"); + const customHeightInput = document.getElementById("custom-size__height"); + if (customWidthInput) customWidthInput.value = "10"; + if (customHeightInput) customHeightInput.value = "15"; + + // Resetear el radio input a "sin marco" + const noFrameRadio = document.getElementById("noFrame"); // Selecciona el radio "Sin marco" + if (noFrameRadio) { + noFrameRadio.checked = true; // Marca el radio + toggleFrameOptions(false); // Oculta el selector de marcos + calcularPrecioSinMarco(); // Asegura que el precio se actualice + } + + // Resetear el picker de tamaño + if (sizePicker) { + sizePicker.destroy(); + initializeSizePicker(); + } + + // Resetear el precio total + document.getElementById("total-price-button").innerText = "S/. 0.00"; + + // Resetear la selección de tipo de tamaño + if (selectedOptionSize === "custom") { + toggleSizeOptions("standard"); + } +} + +function updateTotalPrice() { + if (uploadedImagesData.length === 0) { + document.getElementById("total-price-button").innerText = `S/. 0.00`; + return; + } + + const quantity = + parseInt(document.querySelector(".quantity-field input").value) || 1; + let pricePerUnit = 0; + + if (selectedOptionSize === "custom") { + // Lógica para tamaños personalizados (igual que antes) + const width = + parseInt(document.getElementById("custom-size__width").value) || 0; + const height = + parseInt(document.getElementById("custom-size__height").value) || 0; + + if (width > 0 && height > 0) { + pricePerUnit = Math.round(height * width * 0.03); + } + } else { + // Para tamaños estándar, usar precio del dimensionPrecioBase + const sizeData = window.dimensionPrecioBase[selectedSize] || { price: 0 }; + pricePerUnit = sizeData.price; + } + + const totalPrice = pricePerUnit * quantity; + priceDimensionsBaseMain = totalPrice.toFixed(2); + + // Agregar marco si corresponde + let precioMarco = parseFloat(window.marcoSeleccionado) || 0; + let precioFinal = totalPrice + precioMarco * quantity; + + document.getElementById( + "total-price-button" + ).innerText = `S/. ${precioFinal.toFixed(2)}`; +} + +// Exponer la función al ámbito global para evitar problemas con `onclick` +window.addToCart = addToCart; diff --git a/assets/js/mini-cart-style-script.js b/assets/js/mini-cart-style-script.js new file mode 100644 index 0000000..940a592 --- /dev/null +++ b/assets/js/mini-cart-style-script.js @@ -0,0 +1,23 @@ +// Esperar a que el DOM esté listo +document.addEventListener('DOMContentLoaded', function() { + // Crear un elemento @@ -18,7 +21,7 @@

Arrastra tus imágenes aquí o haz clic para subir

Sube tus imágenes aquí

- +
@@ -35,7 +38,7 @@
- +
@@ -52,7 +55,15 @@
-
+ + +
+ +
- -
- Comentarios adicionales + Indicaciones de impresión
- +
- - + + + +
-