Soluciones de Pagos Digitales
para tu e-commerce.

Procesa pagos nacionales de forma segura y eficiente.
Integración rápida, tarifas competitivas y soporte 24/7.
Impulsa tu negocio con una solución de pagos todo en uno.

Potencia tu negocio con soluciones de pago inteligentes

En Octano Payments transformamos la manera en que cobras. Somos un equipo de especialistas en medios de pago, con experiencia multidisciplinaria y una sola misión: llevar tu comercio al siguiente nivel.

Nos enfocamos en que escales tus ventas, simplifiques tu operación y le brindes a tus clientes una experiencia de pago ágil, segura y sin fricciones. Te acompañamos en todo el proceso, con una plataforma sólida, soporte cercano y resultados medibles desde el primer día.

Habilitamos todos los canales de cobro electrónico que necesitas para empezar a vender más.

Integra pagos con tarjeta en tu sitio web fácilmente

Cobros rápidos, seguros y sin complicaciones

Experiencia de usuario optimizada para aumentar la conversión

Soporte humano, real y siempre disponible

Escalabilidad para que crezcas sin límites

Conversemos hoy

Servicios de
Pagos Completos.

Todo lo que necesitas para procesar pagos en línea de forma segura y eficiente.

Pagos con tarjeta

Acepta tarjetas de crédito y débito de las principales marcas: Visa, Mastercard.

Autorización instantánea

3D Secure

Tokenización

Seguridad avanzada

Protección de datos con encriptación de extremo a extremo y prevención de fraude.

Risk scoring

PCI compliance

Herramientas de onboarding

Herramientas de control de contracargos

APIs flexibles

Integración sencilla con APIs RESTful, SDKs y webhooks para todos los lenguajes.

RESTful APIs

SDKs nativos

Webhooks

Analytics & reporting

Dashboard en tiempo real con métricas de negocio y reportes detallados de transacciones.

Dashboard real-time

Reportes custom

Compliance local

Tu negocio en línea, sin complicaciones.

Con nuestra Solución Llave en Mano, llevamos tu comercio al entorno digital de forma rápida, segura y completamente gestionada. Sabemos que no todos los negocios tienen el tiempo o los recursos para desarrollar su propio sitio web o integrar medios de pago, por eso nosotros lo hacemos por ti.

Nos encargamos de principio a fin: desde el diseño de la estrategia digital hasta la implementación tecnológica, asegurando que puedas empezar a vender en línea sin preocuparte por la infraestructura, la seguridad o los procesos regulatorios.

Conversemos hoy

Consultoría personalizada
Analizamos tu negocio, tu mercado y tus necesidades para definir la mejor estrategia digital y de pagos.

Diseño y desarrollo del sitio web
Creamos una página moderna, responsiva y optimizada para convertir visitantes en clientes, adaptada a tu identidad de marca.

Integración de pagos segura y flexible
Incorporamos nuestra pasarela de pagos con soporte para tarjetas, cumpliendo con los más altos estándares de seguridad PCI DSS.

Configuración de órdenes y liquidaciones
Tus cobros se procesan y liquidan automáticamente, con reportes claros y en tiempo real.

Soporte técnico y operativo
Te acompañamos en cada etapa para garantizar una operación continua y sin fricciones.

Nuestro compromiso

Entregarte una solución completa, escalable y personalizada que acelere el lanzamiento de tu negocio digital, aumente tus ventas y te permita concentrarte en lo que realmente importa: hacer crecer tu empresa. Desde el primer día, tendrás una plataforma lista para operar, con tecnología probada y el respaldo de expertos en medios de pago.

Impulsa tu negocio al siguiente nivel
con una plataforma de pago
que crece contigo.

Ofrecemos una solución integral de cobros con tarjeta, ideal para negocios que venden en línea. Nuestra plataforma permite integrar pagos en tu sitio web de forma rápida, segura y eficiente, sin complicaciones técnicas.
Con nosotros, tu negocio puede:
Aceptar pagos con tarjeta desde cualquier parte del mundo.Maximizar tus ventas con una experiencia de pago optimizada.Contar con soporte dedicado en cada paso del proceso.
(function() {
     const config = {
       offset: 40,
       padding: 50,
       anchor: 'top',
       disabled: false,
       threshold: 768,
       scale: 0.92,
       scaleIncrement: 0.04,
       shadow: true,
       darken: true,
       rotate: true,
       perspective: 1000,
       animationDuration: 2,
       staggerOffset: 18,
       mobileScale: 0.6,
       disableMobile: false
     };
   
     const state = {
       lastScrollY: 0,
       scrollDirection: 'down'
     };
   
     function initScrollStack() {
       const containers = document.querySelectorAll('[data-scrollstack-container]');
       
       if (!containers.length) return;
       
       containers.forEach(container => {
         const cards = Array.from(container.querySelectorAll('[data-scrollstack-card]'));
         if (!cards.length) return;
         
         const containerConfig = {
           offset: parseFloat(container.getAttribute('data-scrollstack-offset')) || config.offset,
           padding: parseFloat(container.getAttribute('data-scrollstack-padding')) || config.padding,
           anchor: container.getAttribute('data-scrollstack-anchor') || config.anchor,
           disabled: container.getAttribute('data-scrollstack-disabled') === 'true' || config.disabled,
           threshold: parseInt(container.getAttribute('data-scrollstack-threshold')) || config.threshold,
           scale: parseFloat(container.getAttribute('data-scrollstack-scale')) || config.scale,
           scaleIncrement: parseFloat(container.getAttribute('data-scrollstack-scale-increment')) || config.scaleIncrement,
           shadow: container.getAttribute('data-scrollstack-shadow') !== 'false' && config.shadow,
           darken: container.getAttribute('data-scrollstack-darken') !== 'false' && config.darken,
           rotate: container.getAttribute('data-scrollstack-rotate') !== 'false' && config.rotate,
           perspective: parseInt(container.getAttribute('data-scrollstack-perspective')) || config.perspective,
           animationDuration: parseFloat(container.getAttribute('data-scrollstack-animation-duration')) || config.animationDuration,
           staggerOffset: parseFloat(container.getAttribute('data-scrollstack-stagger-offset')) || config.staggerOffset,
           mobileScale: parseFloat(container.getAttribute('data-scrollstack-mobile-scale')) || config.mobileScale,
           disableMobile: container.getAttribute('data-scrollstack-disable-mobile') === 'true' || config.disableMobile
         };
         
         if (containerConfig.rotate) {
           container.style.perspective = `${containerConfig.perspective}px`;
         }
         
         setupCards(container, cards, containerConfig);
         
         handleScrollEvent(container, cards, containerConfig);
         
         window.addEventListener('scroll', () => {
           const currentScrollY = window.scrollY;
           state.scrollDirection = currentScrollY > state.lastScrollY ? 'down' : 'up';
           state.lastScrollY = currentScrollY;
           
           handleScrollEvent(container, cards, containerConfig);
         });
         
         let resizeTimeout;
         window.addEventListener('resize', () => {
           clearTimeout(resizeTimeout);
           resizeTimeout = setTimeout(() => {
             setupCards(container, cards, containerConfig);
             handleScrollEvent(container, cards, containerConfig);
           }, 200);
         });
       });
     }
     
     function setupCards(container, cards, config) {
       const isMobile = window.innerWidth < config.threshold;
       
       if ((config.disabled) || (config.disableMobile && isMobile)) {
         cards.forEach(card => {
           card.style.position = '';
           card.style.top = '';
           card.style.zIndex = '';
           card.style.transform = '';
           card.style.boxShadow = '';
           card.style.transition = '';
           if (card._darkenOverlay) {
             card.removeChild(card._darkenOverlay);
             delete card._darkenOverlay;
           }
         });
         return;
       }
       
       container.dataset.containerHeight = container.offsetHeight;
       
       cards.forEach((card, index) => {
         const targetScale = Math.max(0.7, 1 - (index * config.scaleIncrement));
         card.dataset.targetScale = targetScale;
         
         if (!card.dataset.originalHeight) {
           card.dataset.originalHeight = card.offsetHeight;
           card.dataset.originalWidth = card.offsetWidth;
         }
         
         card.style.position = 'relative';
         card.style.zIndex = cards.length - index;
         card.style.transition = `transform ${config.animationDuration}s ease, box-shadow ${config.animationDuration}s ease`;
         card.style.willChange = 'transform, box-shadow';
         
         card.style.transformOrigin = 'center top';
         
         if (window.getComputedStyle(card).position === 'static') {
           card.style.position = 'relative';
         }
         
         if (config.darken && !card._darkenOverlay) {
           const overlay = document.createElement('div');
           overlay.style.position = 'absolute';
           overlay.style.top = '0';
           overlay.style.left = '0';
           overlay.style.width = '100%';
           overlay.style.height = '100%';
           overlay.style.backgroundColor = 'rgba(0, 0, 0, 0)';
           overlay.style.transition = `background-color ${config.animationDuration}s ease`;
           overlay.style.pointerEvents = 'none';
           overlay.style.zIndex = '1';
           overlay.style.borderRadius = 'inherit';
           overlay.className = 'scrollstack-card-overlay';
           
           if (card.firstChild) {
             card.insertBefore(overlay, card.firstChild);
           } else {
             card.appendChild(overlay);
           }
           
           card._darkenOverlay = overlay;
           
           Array.from(card.children).forEach(child => {
             if (child !== overlay) {
               if (window.getComputedStyle(child).position === 'static') {
                 child.style.position = 'relative';
               }
               child.style.zIndex = '2';
             }
           });
         }
         
         card.dataset.initialTop = card.offsetTop;
       });
     }
     
     function calculateStackingProgress(scrollY, triggerPoint, cardHeight) {
       const longAnimationRange = cardHeight * 1.5;
       const scrollPastTrigger = Math.max(0, scrollY - triggerPoint);
       const progress = Math.min(scrollPastTrigger / longAnimationRange, 1);
       return progress;
     }
     
     function applyVisualEffectsToParent(parentCard, childCard, progress, config) {
       const isMobile = window.innerWidth < config.threshold;
       
       if (config.disableMobile && isMobile) {
         resetCardEffects(parentCard);
         return;
       }
       
       const targetScale = parseFloat(parentCard.dataset.targetScale) || config.scale;
       const scaleFactor = 1 - ((1 - targetScale) * progress);
       
       let shadowOpacity = 0;
       let shadowBlur = 0;
       let shadowY = 0;
       
       if (config.shadow) {
         shadowOpacity = Math.min(0.25, 0.05 + (0.2 * progress));
         shadowBlur = Math.min(20, 5 + (15 * progress)) * (isMobile ? 0.7 : 1);
         shadowY = Math.min(15, 2 + (13 * progress)) * (isMobile ? 0.7 : 1);
       }
       
       const rotateX = config.rotate ? Math.min(5, 5 * progress) * (isMobile ? 0.7 : 1) : 0;
       
       parentCard.style.transform = `scale(${scaleFactor}) ${config.rotate ? `rotateX(${rotateX}deg)` : ''}`;
       
       if (config.shadow) {
         parentCard.style.boxShadow = `0 ${shadowY}px ${shadowBlur}px rgba(0, 0, 0, ${shadowOpacity})`;
       } else {
         parentCard.style.boxShadow = '';
       }
       
       if (config.darken && parentCard._darkenOverlay) {
         if (!isMobile) {
           const darkness = Math.min(0.2, 0.05 + (0.15 * progress));
           parentCard._darkenOverlay.style.backgroundColor = `rgba(0, 0, 0, ${darkness})`;
         } else {
           parentCard._darkenOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0)';
         }
       }
     }
     
     function resetCardEffects(card) {
       card.style.transform = 'scale(1) rotateX(0deg)';
       card.style.boxShadow = '';
       
       if (card._darkenOverlay) {
         card._darkenOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0)';
       }
     }
     
     function getCardStackStart(card, config) {
       return parseInt(card.dataset.initialTop) - config.padding;
     }
     
     function handleScrollEvent(container, cards, config) {
       const isMobile = window.innerWidth < config.threshold;
       
       if ((config.disabled) || (config.disableMobile && isMobile)) {
         cards.forEach(card => {
           card.style.position = 'relative';
           card.style.top = '0';
           resetCardEffects(card);
         });
         return;
       }
       
       const scrollY = window.scrollY;
       const lastCardIndex = cards.length - 1;
       
       cards.forEach((card, index) => {
         const cardHeight = parseInt(card.dataset.originalHeight);
         const stackStart = getCardStackStart(card, config);
         
         if (scrollY >= stackStart) {
           card.style.position = 'sticky';
           
           const offsetAdjustment = isMobile ? config.mobileScale : 1;
           const offset = (config.offset + (index * config.staggerOffset)) * offsetAdjustment;
           card.style.top = `${offset}px`;
         } else {
           card.style.position = 'relative';
           card.style.top = '0';
           resetCardEffects(card);
         }
       });
       
       for (let i = 0; i < cards.length; i++) {
         const card = cards[i];
         
         if (i < lastCardIndex) {
           const nextCard = cards[i + 1];
           
           if (nextCard.style.position === 'sticky') {
             const cardHeight = parseInt(card.dataset.originalHeight);
             const nextCardStackStart = getCardStackStart(nextCard, config);
             
             const progress = calculateStackingProgress(scrollY, nextCardStackStart, cardHeight);
             
             applyVisualEffectsToParent(card, nextCard, progress, config);
           }
         }
       }
     }
     
     function shouldReduceEffects() {
       const navigatorInfo = window.navigator;
       const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigatorInfo.userAgent);
       const isSlowConnection = navigatorInfo.connection && 
         (navigatorInfo.connection.saveData === true || 
         ['slow-2g', '2g', '3g'].includes(navigatorInfo.connection.effectiveType));
       const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
       
       return isMobile || isSlowConnection || prefersReducedMotion;
   }
   
   function initWithAccessibility() {
     if (shouldReduceEffects()) {
       config.shadow = false;
       config.rotate = false;
       config.animationDuration = 0.4;
       config.staggerOffset = 10;
     }
     
     initScrollStack();
   }
   
   if (document.readyState === 'loading') {
     document.addEventListener('DOMContentLoaded', initWithAccessibility);
   } else {
     initWithAccessibility();
   }
   
   window.scrollStack = {
     init: initWithAccessibility,
     toggleAnimations: function(enable) {
       const containers = document.querySelectorAll('[data-scrollstack-container]');
       containers.forEach(container => {
         container.setAttribute('data-scrollstack-disabled', enable ? 'false' : 'true');
         this.init();
       });
     },
     toggleMobileAnimations: function(enable) {
       const containers = document.querySelectorAll('[data-scrollstack-container]');
       containers.forEach(container => {
         container.setAttribute('data-scrollstack-disable-mobile', enable ? 'false' : 'true');
         this.init();
       });
     }
   };
})();

¿Cómo lo hacemos?

Herramientas profesionales diseñadas para maximizar tus conversiones y simplificar la gestión de pagos

Solicitud de Documentación.

Revisión y validación de documentación.

Revisión de volumen mensual de operación.

Análisis de riesgo.

Oferta Comercial.

Firma de Contrato de Servicios.

Envío de Documentación de API de integración.

Ejecución de Pruebas transaccionales de Venta, Devolución y cancelación.

Validación de script de pruebas.

Configuración ambiente de Producción.

Liberación de credenciales de producción.

Ejecución de pruebas controladas.

Validación de compensación de transacciones de prueba.

Liquidación de operaciones.

Soporte especializado.

¿Necesitas un plan personalizado? Tenemos soluciones flexibles para tu negocio.

Contactar equipo de ventas

Los requisitos solicitados a los prospectos de nuevos comercios para el proceso de onboarding, son los siguientes:

Identificación Oficial Vigente

Credencial de elector, pasaporte o cédula profesional del representante o apoderado legal.

Comprobante de domicilio del establecimiento/local

Recibo de luz, agua, teléfono, gas y/o internet (con antigüedad no mayor a 2 meses).
Nota: el estado de cuenta bancario no aplica como soporte de domicilio.

Acta Constitutiva

Documento con datos de socios que forman la empresa
y están facultados para firmas en procesos legales.
(Aplica en personas morales)

Constancia de alta de Registro Federal de Contribuyentes

Documento Alta ante el SAT con datos de actividad o giro comercial del cliente.

URL del comercio

Dirección en internet o página web en donde el comercio promociona sus servicios.

(function() {
     const config = {
       offset: 40,
       padding: 50,
       anchor: 'top',
       disabled: false,
       threshold: 768,
       scale: 0.92,
       scaleIncrement: 0.04,
       shadow: true,
       darken: true,
       rotate: true,
       perspective: 1000,
       animationDuration: 2,
       staggerOffset: 18,
       mobileScale: 0.6,
       disableMobile: false
     };
   
     const state = {
       lastScrollY: 0,
       scrollDirection: 'down'
     };
   
     function initScrollStack() {
       const containers = document.querySelectorAll('[data-scrollstack-container]');
       
       if (!containers.length) return;
       
       containers.forEach(container => {
         const cards = Array.from(container.querySelectorAll('[data-scrollstack-card]'));
         if (!cards.length) return;
         
         const containerConfig = {
           offset: parseFloat(container.getAttribute('data-scrollstack-offset')) || config.offset,
           padding: parseFloat(container.getAttribute('data-scrollstack-padding')) || config.padding,
           anchor: container.getAttribute('data-scrollstack-anchor') || config.anchor,
           disabled: container.getAttribute('data-scrollstack-disabled') === 'true' || config.disabled,
           threshold: parseInt(container.getAttribute('data-scrollstack-threshold')) || config.threshold,
           scale: parseFloat(container.getAttribute('data-scrollstack-scale')) || config.scale,
           scaleIncrement: parseFloat(container.getAttribute('data-scrollstack-scale-increment')) || config.scaleIncrement,
           shadow: container.getAttribute('data-scrollstack-shadow') !== 'false' && config.shadow,
           darken: container.getAttribute('data-scrollstack-darken') !== 'false' && config.darken,
           rotate: container.getAttribute('data-scrollstack-rotate') !== 'false' && config.rotate,
           perspective: parseInt(container.getAttribute('data-scrollstack-perspective')) || config.perspective,
           animationDuration: parseFloat(container.getAttribute('data-scrollstack-animation-duration')) || config.animationDuration,
           staggerOffset: parseFloat(container.getAttribute('data-scrollstack-stagger-offset')) || config.staggerOffset,
           mobileScale: parseFloat(container.getAttribute('data-scrollstack-mobile-scale')) || config.mobileScale,
           disableMobile: container.getAttribute('data-scrollstack-disable-mobile') === 'true' || config.disableMobile
         };
         
         if (containerConfig.rotate) {
           container.style.perspective = `${containerConfig.perspective}px`;
         }
         
         setupCards(container, cards, containerConfig);
         
         handleScrollEvent(container, cards, containerConfig);
         
         window.addEventListener('scroll', () => {
           const currentScrollY = window.scrollY;
           state.scrollDirection = currentScrollY > state.lastScrollY ? 'down' : 'up';
           state.lastScrollY = currentScrollY;
           
           handleScrollEvent(container, cards, containerConfig);
         });
         
         let resizeTimeout;
         window.addEventListener('resize', () => {
           clearTimeout(resizeTimeout);
           resizeTimeout = setTimeout(() => {
             setupCards(container, cards, containerConfig);
             handleScrollEvent(container, cards, containerConfig);
           }, 200);
         });
       });
     }
     
     function setupCards(container, cards, config) {
       const isMobile = window.innerWidth < config.threshold;
       
       if ((config.disabled) || (config.disableMobile && isMobile)) {
         cards.forEach(card => {
           card.style.position = '';
           card.style.top = '';
           card.style.zIndex = '';
           card.style.transform = '';
           card.style.boxShadow = '';
           card.style.transition = '';
           if (card._darkenOverlay) {
             card.removeChild(card._darkenOverlay);
             delete card._darkenOverlay;
           }
         });
         return;
       }
       
       container.dataset.containerHeight = container.offsetHeight;
       
       cards.forEach((card, index) => {
         const targetScale = Math.max(0.7, 1 - (index * config.scaleIncrement));
         card.dataset.targetScale = targetScale;
         
         if (!card.dataset.originalHeight) {
           card.dataset.originalHeight = card.offsetHeight;
           card.dataset.originalWidth = card.offsetWidth;
         }
         
         card.style.position = 'relative';
         card.style.zIndex = cards.length - index;
         card.style.transition = `transform ${config.animationDuration}s ease, box-shadow ${config.animationDuration}s ease`;
         card.style.willChange = 'transform, box-shadow';
         
         card.style.transformOrigin = 'center top';
         
         if (window.getComputedStyle(card).position === 'static') {
           card.style.position = 'relative';
         }
         
         if (config.darken && !card._darkenOverlay) {
           const overlay = document.createElement('div');
           overlay.style.position = 'absolute';
           overlay.style.top = '0';
           overlay.style.left = '0';
           overlay.style.width = '100%';
           overlay.style.height = '100%';
           overlay.style.backgroundColor = 'rgba(0, 0, 0, 0)';
           overlay.style.transition = `background-color ${config.animationDuration}s ease`;
           overlay.style.pointerEvents = 'none';
           overlay.style.zIndex = '1';
           overlay.style.borderRadius = 'inherit';
           overlay.className = 'scrollstack-card-overlay';
           
           if (card.firstChild) {
             card.insertBefore(overlay, card.firstChild);
           } else {
             card.appendChild(overlay);
           }
           
           card._darkenOverlay = overlay;
           
           Array.from(card.children).forEach(child => {
             if (child !== overlay) {
               if (window.getComputedStyle(child).position === 'static') {
                 child.style.position = 'relative';
               }
               child.style.zIndex = '2';
             }
           });
         }
         
         card.dataset.initialTop = card.offsetTop;
       });
     }
     
     function calculateStackingProgress(scrollY, triggerPoint, cardHeight) {
       const longAnimationRange = cardHeight * 1.5;
       const scrollPastTrigger = Math.max(0, scrollY - triggerPoint);
       const progress = Math.min(scrollPastTrigger / longAnimationRange, 1);
       return progress;
     }
     
     function applyVisualEffectsToParent(parentCard, childCard, progress, config) {
       const isMobile = window.innerWidth < config.threshold;
       
       if (config.disableMobile && isMobile) {
         resetCardEffects(parentCard);
         return;
       }
       
       const targetScale = parseFloat(parentCard.dataset.targetScale) || config.scale;
       const scaleFactor = 1 - ((1 - targetScale) * progress);
       
       let shadowOpacity = 0;
       let shadowBlur = 0;
       let shadowY = 0;
       
       if (config.shadow) {
         shadowOpacity = Math.min(0.25, 0.05 + (0.2 * progress));
         shadowBlur = Math.min(20, 5 + (15 * progress)) * (isMobile ? 0.7 : 1);
         shadowY = Math.min(15, 2 + (13 * progress)) * (isMobile ? 0.7 : 1);
       }
       
       const rotateX = config.rotate ? Math.min(5, 5 * progress) * (isMobile ? 0.7 : 1) : 0;
       
       parentCard.style.transform = `scale(${scaleFactor}) ${config.rotate ? `rotateX(${rotateX}deg)` : ''}`;
       
       if (config.shadow) {
         parentCard.style.boxShadow = `0 ${shadowY}px ${shadowBlur}px rgba(0, 0, 0, ${shadowOpacity})`;
       } else {
         parentCard.style.boxShadow = '';
       }
       
       if (config.darken && parentCard._darkenOverlay) {
         if (!isMobile) {
           const darkness = Math.min(0.2, 0.05 + (0.15 * progress));
           parentCard._darkenOverlay.style.backgroundColor = `rgba(0, 0, 0, ${darkness})`;
         } else {
           parentCard._darkenOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0)';
         }
       }
     }
     
     function resetCardEffects(card) {
       card.style.transform = 'scale(1) rotateX(0deg)';
       card.style.boxShadow = '';
       
       if (card._darkenOverlay) {
         card._darkenOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0)';
       }
     }
     
     function getCardStackStart(card, config) {
       return parseInt(card.dataset.initialTop) - config.padding;
     }
     
     function handleScrollEvent(container, cards, config) {
       const isMobile = window.innerWidth < config.threshold;
       
       if ((config.disabled) || (config.disableMobile && isMobile)) {
         cards.forEach(card => {
           card.style.position = 'relative';
           card.style.top = '0';
           resetCardEffects(card);
         });
         return;
       }
       
       const scrollY = window.scrollY;
       const lastCardIndex = cards.length - 1;
       
       cards.forEach((card, index) => {
         const cardHeight = parseInt(card.dataset.originalHeight);
         const stackStart = getCardStackStart(card, config);
         
         if (scrollY >= stackStart) {
           card.style.position = 'sticky';
           
           const offsetAdjustment = isMobile ? config.mobileScale : 1;
           const offset = (config.offset + (index * config.staggerOffset)) * offsetAdjustment;
           card.style.top = `${offset}px`;
         } else {
           card.style.position = 'relative';
           card.style.top = '0';
           resetCardEffects(card);
         }
       });
       
       for (let i = 0; i < cards.length; i++) {
         const card = cards[i];
         
         if (i < lastCardIndex) {
           const nextCard = cards[i + 1];
           
           if (nextCard.style.position === 'sticky') {
             const cardHeight = parseInt(card.dataset.originalHeight);
             const nextCardStackStart = getCardStackStart(nextCard, config);
             
             const progress = calculateStackingProgress(scrollY, nextCardStackStart, cardHeight);
             
             applyVisualEffectsToParent(card, nextCard, progress, config);
           }
         }
       }
     }
     
     function shouldReduceEffects() {
       const navigatorInfo = window.navigator;
       const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigatorInfo.userAgent);
       const isSlowConnection = navigatorInfo.connection && 
         (navigatorInfo.connection.saveData === true || 
         ['slow-2g', '2g', '3g'].includes(navigatorInfo.connection.effectiveType));
       const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
       
       return isMobile || isSlowConnection || prefersReducedMotion;
   }
   
   function initWithAccessibility() {
     if (shouldReduceEffects()) {
       config.shadow = false;
       config.rotate = false;
       config.animationDuration = 0.4;
       config.staggerOffset = 10;
     }
     
     initScrollStack();
   }
   
   if (document.readyState === 'loading') {
     document.addEventListener('DOMContentLoaded', initWithAccessibility);
   } else {
     initWithAccessibility();
   }
   
   window.scrollStack = {
     init: initWithAccessibility,
     toggleAnimations: function(enable) {
       const containers = document.querySelectorAll('[data-scrollstack-container]');
       containers.forEach(container => {
         container.setAttribute('data-scrollstack-disabled', enable ? 'false' : 'true');
         this.init();
       });
     },
     toggleMobileAnimations: function(enable) {
       const containers = document.querySelectorAll('[data-scrollstack-container]');
       containers.forEach(container => {
         container.setAttribute('data-scrollstack-disable-mobile', enable ? 'false' : 'true');
         this.init();
       });
     }
   };
})();

¿Listos para comenzar?

Nuestro equipo de expertos está listo para ayudarte
a implementar la mejor solución de pagos para tu negocio

Información de contacto

Oficinas

Sierra Gorda 36, Lomas de Chapultepec,
Miguel Hidalgo, 11000,
Ciudad de México, CDMX

Teléfono

+52 55 6586 6405

Correo electrónico

comercial@octanopayments.com

Horarios

Lunes a Viernes: 9:00 – 18:00
Soporte 24/7 disponible

Contáctanos

Completa el formulario
y nos pondremos en contacto contigo
en menos de 24 horas
Al enviar este formulario, aceptas nuestra política de privacidad.

¿Necesitas una demo?

Agenda una llamada con nuestro equipo para ver Octano Payments en acción
Agendar demo
(function() {
     const config = {
       offset: 40,
       padding: 50,
       anchor: 'top',
       disabled: false,
       threshold: 768,
       scale: 0.92,
       scaleIncrement: 0.04,
       shadow: true,
       darken: true,
       rotate: true,
       perspective: 1000,
       animationDuration: 2,
       staggerOffset: 18,
       mobileScale: 0.6,
       disableMobile: false
     };
   
     const state = {
       lastScrollY: 0,
       scrollDirection: 'down'
     };
   
     function initScrollStack() {
       const containers = document.querySelectorAll('[data-scrollstack-container]');
       
       if (!containers.length) return;
       
       containers.forEach(container => {
         const cards = Array.from(container.querySelectorAll('[data-scrollstack-card]'));
         if (!cards.length) return;
         
         const containerConfig = {
           offset: parseFloat(container.getAttribute('data-scrollstack-offset')) || config.offset,
           padding: parseFloat(container.getAttribute('data-scrollstack-padding')) || config.padding,
           anchor: container.getAttribute('data-scrollstack-anchor') || config.anchor,
           disabled: container.getAttribute('data-scrollstack-disabled') === 'true' || config.disabled,
           threshold: parseInt(container.getAttribute('data-scrollstack-threshold')) || config.threshold,
           scale: parseFloat(container.getAttribute('data-scrollstack-scale')) || config.scale,
           scaleIncrement: parseFloat(container.getAttribute('data-scrollstack-scale-increment')) || config.scaleIncrement,
           shadow: container.getAttribute('data-scrollstack-shadow') !== 'false' && config.shadow,
           darken: container.getAttribute('data-scrollstack-darken') !== 'false' && config.darken,
           rotate: container.getAttribute('data-scrollstack-rotate') !== 'false' && config.rotate,
           perspective: parseInt(container.getAttribute('data-scrollstack-perspective')) || config.perspective,
           animationDuration: parseFloat(container.getAttribute('data-scrollstack-animation-duration')) || config.animationDuration,
           staggerOffset: parseFloat(container.getAttribute('data-scrollstack-stagger-offset')) || config.staggerOffset,
           mobileScale: parseFloat(container.getAttribute('data-scrollstack-mobile-scale')) || config.mobileScale,
           disableMobile: container.getAttribute('data-scrollstack-disable-mobile') === 'true' || config.disableMobile
         };
         
         if (containerConfig.rotate) {
           container.style.perspective = `${containerConfig.perspective}px`;
         }
         
         setupCards(container, cards, containerConfig);
         
         handleScrollEvent(container, cards, containerConfig);
         
         window.addEventListener('scroll', () => {
           const currentScrollY = window.scrollY;
           state.scrollDirection = currentScrollY > state.lastScrollY ? 'down' : 'up';
           state.lastScrollY = currentScrollY;
           
           handleScrollEvent(container, cards, containerConfig);
         });
         
         let resizeTimeout;
         window.addEventListener('resize', () => {
           clearTimeout(resizeTimeout);
           resizeTimeout = setTimeout(() => {
             setupCards(container, cards, containerConfig);
             handleScrollEvent(container, cards, containerConfig);
           }, 200);
         });
       });
     }
     
     function setupCards(container, cards, config) {
       const isMobile = window.innerWidth < config.threshold;
       
       if ((config.disabled) || (config.disableMobile && isMobile)) {
         cards.forEach(card => {
           card.style.position = '';
           card.style.top = '';
           card.style.zIndex = '';
           card.style.transform = '';
           card.style.boxShadow = '';
           card.style.transition = '';
           if (card._darkenOverlay) {
             card.removeChild(card._darkenOverlay);
             delete card._darkenOverlay;
           }
         });
         return;
       }
       
       container.dataset.containerHeight = container.offsetHeight;
       
       cards.forEach((card, index) => {
         const targetScale = Math.max(0.7, 1 - (index * config.scaleIncrement));
         card.dataset.targetScale = targetScale;
         
         if (!card.dataset.originalHeight) {
           card.dataset.originalHeight = card.offsetHeight;
           card.dataset.originalWidth = card.offsetWidth;
         }
         
         card.style.position = 'relative';
         card.style.zIndex = cards.length - index;
         card.style.transition = `transform ${config.animationDuration}s ease, box-shadow ${config.animationDuration}s ease`;
         card.style.willChange = 'transform, box-shadow';
         
         card.style.transformOrigin = 'center top';
         
         if (window.getComputedStyle(card).position === 'static') {
           card.style.position = 'relative';
         }
         
         if (config.darken && !card._darkenOverlay) {
           const overlay = document.createElement('div');
           overlay.style.position = 'absolute';
           overlay.style.top = '0';
           overlay.style.left = '0';
           overlay.style.width = '100%';
           overlay.style.height = '100%';
           overlay.style.backgroundColor = 'rgba(0, 0, 0, 0)';
           overlay.style.transition = `background-color ${config.animationDuration}s ease`;
           overlay.style.pointerEvents = 'none';
           overlay.style.zIndex = '1';
           overlay.style.borderRadius = 'inherit';
           overlay.className = 'scrollstack-card-overlay';
           
           if (card.firstChild) {
             card.insertBefore(overlay, card.firstChild);
           } else {
             card.appendChild(overlay);
           }
           
           card._darkenOverlay = overlay;
           
           Array.from(card.children).forEach(child => {
             if (child !== overlay) {
               if (window.getComputedStyle(child).position === 'static') {
                 child.style.position = 'relative';
               }
               child.style.zIndex = '2';
             }
           });
         }
         
         card.dataset.initialTop = card.offsetTop;
       });
     }
     
     function calculateStackingProgress(scrollY, triggerPoint, cardHeight) {
       const longAnimationRange = cardHeight * 1.5;
       const scrollPastTrigger = Math.max(0, scrollY - triggerPoint);
       const progress = Math.min(scrollPastTrigger / longAnimationRange, 1);
       return progress;
     }
     
     function applyVisualEffectsToParent(parentCard, childCard, progress, config) {
       const isMobile = window.innerWidth < config.threshold;
       
       if (config.disableMobile && isMobile) {
         resetCardEffects(parentCard);
         return;
       }
       
       const targetScale = parseFloat(parentCard.dataset.targetScale) || config.scale;
       const scaleFactor = 1 - ((1 - targetScale) * progress);
       
       let shadowOpacity = 0;
       let shadowBlur = 0;
       let shadowY = 0;
       
       if (config.shadow) {
         shadowOpacity = Math.min(0.25, 0.05 + (0.2 * progress));
         shadowBlur = Math.min(20, 5 + (15 * progress)) * (isMobile ? 0.7 : 1);
         shadowY = Math.min(15, 2 + (13 * progress)) * (isMobile ? 0.7 : 1);
       }
       
       const rotateX = config.rotate ? Math.min(5, 5 * progress) * (isMobile ? 0.7 : 1) : 0;
       
       parentCard.style.transform = `scale(${scaleFactor}) ${config.rotate ? `rotateX(${rotateX}deg)` : ''}`;
       
       if (config.shadow) {
         parentCard.style.boxShadow = `0 ${shadowY}px ${shadowBlur}px rgba(0, 0, 0, ${shadowOpacity})`;
       } else {
         parentCard.style.boxShadow = '';
       }
       
       if (config.darken && parentCard._darkenOverlay) {
         if (!isMobile) {
           const darkness = Math.min(0.2, 0.05 + (0.15 * progress));
           parentCard._darkenOverlay.style.backgroundColor = `rgba(0, 0, 0, ${darkness})`;
         } else {
           parentCard._darkenOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0)';
         }
       }
     }
     
     function resetCardEffects(card) {
       card.style.transform = 'scale(1) rotateX(0deg)';
       card.style.boxShadow = '';
       
       if (card._darkenOverlay) {
         card._darkenOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0)';
       }
     }
     
     function getCardStackStart(card, config) {
       return parseInt(card.dataset.initialTop) - config.padding;
     }
     
     function handleScrollEvent(container, cards, config) {
       const isMobile = window.innerWidth < config.threshold;
       
       if ((config.disabled) || (config.disableMobile && isMobile)) {
         cards.forEach(card => {
           card.style.position = 'relative';
           card.style.top = '0';
           resetCardEffects(card);
         });
         return;
       }
       
       const scrollY = window.scrollY;
       const lastCardIndex = cards.length - 1;
       
       cards.forEach((card, index) => {
         const cardHeight = parseInt(card.dataset.originalHeight);
         const stackStart = getCardStackStart(card, config);
         
         if (scrollY >= stackStart) {
           card.style.position = 'sticky';
           
           const offsetAdjustment = isMobile ? config.mobileScale : 1;
           const offset = (config.offset + (index * config.staggerOffset)) * offsetAdjustment;
           card.style.top = `${offset}px`;
         } else {
           card.style.position = 'relative';
           card.style.top = '0';
           resetCardEffects(card);
         }
       });
       
       for (let i = 0; i < cards.length; i++) {
         const card = cards[i];
         
         if (i < lastCardIndex) {
           const nextCard = cards[i + 1];
           
           if (nextCard.style.position === 'sticky') {
             const cardHeight = parseInt(card.dataset.originalHeight);
             const nextCardStackStart = getCardStackStart(nextCard, config);
             
             const progress = calculateStackingProgress(scrollY, nextCardStackStart, cardHeight);
             
             applyVisualEffectsToParent(card, nextCard, progress, config);
           }
         }
       }
     }
     
     function shouldReduceEffects() {
       const navigatorInfo = window.navigator;
       const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigatorInfo.userAgent);
       const isSlowConnection = navigatorInfo.connection && 
         (navigatorInfo.connection.saveData === true || 
         ['slow-2g', '2g', '3g'].includes(navigatorInfo.connection.effectiveType));
       const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
       
       return isMobile || isSlowConnection || prefersReducedMotion;
   }
   
   function initWithAccessibility() {
     if (shouldReduceEffects()) {
       config.shadow = false;
       config.rotate = false;
       config.animationDuration = 0.4;
       config.staggerOffset = 10;
     }
     
     initScrollStack();
   }
   
   if (document.readyState === 'loading') {
     document.addEventListener('DOMContentLoaded', initWithAccessibility);
   } else {
     initWithAccessibility();
   }
   
   window.scrollStack = {
     init: initWithAccessibility,
     toggleAnimations: function(enable) {
       const containers = document.querySelectorAll('[data-scrollstack-container]');
       containers.forEach(container => {
         container.setAttribute('data-scrollstack-disabled', enable ? 'false' : 'true');
         this.init();
       });
     },
     toggleMobileAnimations: function(enable) {
       const containers = document.querySelectorAll('[data-scrollstack-container]');
       containers.forEach(container => {
         container.setAttribute('data-scrollstack-disable-mobile', enable ? 'false' : 'true');
         this.init();
       });
     }
   };
})();