Contenido exclusivo para WordPress-3

Mostrar contenido exclusivo a cambio de email

¿Quieres ofrecer contenido exclusivo en WordPress y aumentar tu base de correos electrónicos? Con este sistema, puedes captar emails de manera profesional y efectiva. Los usuarios deberán ingresar su correo para desbloquear contenido premium, asegurando así que los datos recopilados sean válidos y de personas realmente interesadas en tus productos o servicios.

El proceso es simple: los visitantes ingresan su email, aceptan términos y reciben un código de verificación en su bandeja de entrada. Una vez validado, acceden de inmediato al contenido exclusivo sin restricciones. Además, este método permite registrar la actividad de cada usuario, vinculando su email con el contenido que consulta, lo que facilita la personalización de campañas de email marketing.

Ideal para negocios que buscan mejorar la conversión y fidelizar clientes, este sistema es fácil de configurar y adaptable a cualquier sitio web. Implementa esta estrategia en WordPress y convierte tus visitantes en leads de calidad.

Contenido exclusivo para WordPress

Debes agregar el siguiente código en el archivo functions.php de tu tema hijo o, si prefieres una opción más organizada y segura, utilizar un plugin como Code Snippets. Esto asegurará que los cambios se mantengan incluso después de actualizar tu tema.

Contenido exclusivo para WordPress-2
// Agrega una acción para insertar contenido en el <head> del sitio

add_action('wp_head', 'custom_blur_content_styles_and_scripts');

function custom_blur_content_styles_and_scripts() {

    // Si el usuario actual es un administrador, no se aplica el difuminado ni se cargan estilos y scripts

    if (current_user_can('administrator')) {

        return;

    }

    ?>

    <!-- Estilos CSS -->

    <style>

        /* Contenedor principal para el contenido difuminado */

        #blurred-content-wrapper { position: relative; }

        

        /* El contenido que será difuminado inicialmente */

        #content-blur {

            filter: blur(8px); /* Aplica un efecto de difuminado */

            transition: filter 0.3s ease-in-out; /* Suaviza el cambio al desbloquear */

            opacity: 0.7; /* Reduce ligeramente la opacidad */

        }

        

        /* Overlay que aparece sobre el contenido */

        #blur-overlay {

            position: absolute;

            top: 0;

            left: 0;

            width: 100%;

            height: 100%;

            background: rgba(255, 255, 255, 0.7); /* Fondo semitransparente */

            display: flex;

            justify-content: center; /* Centra el formulario horizontalmente */

            align-items: center; /* Centra el formulario verticalmente */

            z-index: 10; /* Asegura que esté por encima del contenido */

        }



        /* Contenedor del formulario */

        #form-container {

            background: white;

            padding: 20px;

            border-radius: 10px;

            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); /* Sombra para darle profundidad */

            text-align: left;

            max-width: 500px;

            margin: 0 auto;

        }



        /* Estilos para los inputs y botones dentro del formulario */

        #form-container input, #form-container button {

            width: 100%; /* Ocupan todo el ancho disponible */

            box-sizing: border-box; /* Incluye padding y border en el ancho total */

        }

        

        /* Estilo específico para los inputs */

        #form-container input {

            padding: 10px;

            margin: 10px 0;

            border: 1px solid #ddd;

            border-radius: 5px;

        }



        /* Estilo para los botones */

        #form-container button {

            padding: 10px 20px;

            background: #0073aa; /* Color azul */

            color: white; /* Texto en blanco */

            border: none;

            border-radius: 5px;

            cursor: pointer;

            margin-top: 15px;

            font-size: 16px;

        }

        

        /* Efecto hover en botones */

        #form-container button:hover { background: #005177; }

        

        /* Estado deshabilitado para los botones */

        #form-container button:disabled { background: #ccc; cursor: not-allowed; }



        /* Caja de términos y condiciones, oculta inicialmente */

        #terms-box {

            display: none; /* No visible hasta que se activa */

            background: #f9f9f9;

            border: 1px solid #ddd;

            padding: 15px;

            margin-top: 10px;

            border-radius: 5px;

        }



        /* Título dentro de la caja de términos */

        #terms-box h4 { margin-top: 0; }

    </style>



    <!-- JavaScript -->

    <script>

        // Se ejecuta cuando el DOM está completamente cargado

        document.addEventListener("DOMContentLoaded", function () {

            // Obtiene referencias a elementos clave

            const showTermsLink = document.getElementById("show-terms");

            const termsBox = document.getElementById("terms-box");

            const acceptTermsButton = document.getElementById("accept-terms");

            const sendCodeButton = document.getElementById("send-code");

            const verifyCodeButton = document.getElementById("verify-code");

            const emailForm = document.getElementById("email-form");

            const codeField = document.getElementById("code-field");

            const errorMessage = document.getElementById("error-message");

            const blurOverlay = document.getElementById("blur-overlay");

            const contentBlur = document.getElementById("content-blur");



            // Muestra los términos y condiciones cuando se hace clic en el enlace

            showTermsLink.addEventListener("click", function (e) {

                e.preventDefault(); // Previene el comportamiento por defecto del enlace

                termsBox.style.display = "block"; // Muestra la caja de términos

            });



            // Marca los términos como aceptados y habilita el botón para enviar el código

            acceptTermsButton.addEventListener("click", function () {

                termsBox.style.display = "none"; // Oculta la caja de términos

                document.getElementById("terms-text").innerHTML = "Has aceptado los términos y condiciones.";

                sendCodeButton.disabled = false; // Habilita el botón de "Recibir código"

            });



            // Envía el código al email proporcionado

            sendCodeButton.addEventListener("click", function () {

                const email = document.getElementById("user-email").value;

                const referer = window.location.href;



                if (!email) {

                    alert("Por favor, ingresa un email válido."); // Valida el email

                    return;

                }



                sendCodeButton.disabled = true; // Desactiva el botón temporalmente

                sendCodeButton.textContent = "Verificando...";



                // Envío de solicitud AJAX al servidor

                fetch(ajaxurl, {

                    method: "POST",

                    headers: { "Content-Type": "application/x-www-form-urlencoded" },

                    body: new URLSearchParams({ action: "check_or_send_code", email, referer }),

                })

                    .then((response) => response.json())

                    .then((data) => {

                        sendCodeButton.disabled = false;

                        sendCodeButton.textContent = "Recibir código";



                        if (data.success) {

                            emailForm.style.display = "none"; // Oculta el formulario de email

                            codeField.style.display = "block"; // Muestra el campo para ingresar el código

                            alert("Código enviado a tu email.");

                        } else {

                            alert(data.data); // Muestra un mensaje de error si la solicitud falla

                        }

                    })

                    .catch((error) => {

                        console.error(error); // Muestra el error en la consola

                        sendCodeButton.disabled = false;

                        sendCodeButton.textContent = "Recibir código";

                        alert("Ocurrió un error al procesar la solicitud.");

                    });

            });



            // Verifica el código ingresado por el usuario

            verifyCodeButton.addEventListener("click", function () {

                const email = document.getElementById("user-email").value;

                const code = document.getElementById("user-code").value;



                if (!email || !code) {

                    alert("Por favor, ingresa tu email y el código."); // Valida ambos campos

                    return;

                }



                verifyCodeButton.disabled = true; // Desactiva el botón temporalmente

                verifyCodeButton.textContent = "Verificando...";



                // Solicitud AJAX para verificar el código

                fetch(ajaxurl, {

                    method: "POST",

                    headers: { "Content-Type": "application/x-www-form-urlencoded" },

                    body: new URLSearchParams({ action: "verify_code", email, code }),

                })

                    .then((response) => response.json())

                    .then((data) => {

                        verifyCodeButton.disabled = false;

                        verifyCodeButton.textContent = "Ver contenido";



                        if (data.success) {

                            blurOverlay.style.display = "none"; // Elimina el overlay

                            contentBlur.style.filter = "none"; // Elimina el efecto de difuminado

                        } else {

                            errorMessage.style.display = "block"; // Muestra un mensaje de error

                            errorMessage.textContent = "Código incorrecto o expirado.";

                        }

                    })

                    .catch((error) => {

                        console.error(error); // Muestra el error en la consola

                        verifyCodeButton.disabled = false;

                        verifyCodeButton.textContent = "Ver contenido";

                        alert("Ocurrió un error al verificar el código.");

                    });

            });

        });

    </script>

    <?php

}



// Agrega una acción para insertar el valor de `ajaxurl` en el frontend

add_action('wp_enqueue_scripts', 'add_ajaxurl_to_frontend');

function add_ajaxurl_to_frontend() {

    ?>

    <script type="text/javascript">

        // Define una variable global `ajaxurl` con la URL del archivo admin-ajax.php

        // Esto es necesario para que las solicitudes AJAX puedan ser enviadas desde el frontend

        var ajaxurl = "<?php echo admin_url('admin-ajax.php'); ?>";

    </script>

    <?php

}





// Agrega un shortcode para implementar el contenido difuminado en las páginas o publicaciones

add_shortcode('blur_content', 'custom_blur_content_shortcode');

function custom_blur_content_shortcode($atts, $content = null) {

    // Inicia la captura de salida

    ob_start();

    ?>

    <div id="blurred-content-wrapper">

        <!-- Contenedor del contenido difuminado -->

        <div id="content-blur">

            <?php 

            // Procesa y muestra el contenido dentro del shortcode, permitiendo que contenga otros shortcodes

            echo do_shortcode($content); 

            ?>

        </div>

        

        <!-- Overlay que bloquea el contenido hasta que se desbloquee -->

        <div id="blur-overlay">

            <div id="form-container">

                <!-- Título del formulario -->

                <h3>Para ver este contenido, ingresa tu email:</h3>

                

                <!-- Descripción del proceso -->

                <p>Recibirás un código de 6 dígitos en tu correo electrónico que debes ingresar en el formulario. Una vez validado el código tendrás acceso directo al contenido. Si cambias de página o la actualizas tendrás que volver a solicitar el código.</p>

                

                <!-- Texto inicial sobre los términos y condiciones -->

                <p id="terms-text">Para continuar, acepta los <a href="#" id="show-terms" style="color: #0073aa; text-decoration: underline;">términos y condiciones</a>.</p>

                

                <!-- Caja de términos y condiciones (inicialmente oculta) -->

                <div id="terms-box">

                    <h4>Términos y Condiciones</h4>

                    <p>Tu email será usado para enviarte contenido relevante. No se compartirá con terceras personas. Puedes darte de baja en cualquier momento enviándonos un correo a [email protected].</p>

                    <button type="button" id="accept-terms">Acepto los términos</button>

                </div>

                

                <!-- Formulario para ingresar el email -->

                <form id="email-form">

                    <input type="email" id="user-email" placeholder="Tu email" required>

                    <button type="button" id="send-code" disabled>Recibir código</button>

                </form>

                

                <!-- Campo para ingresar el código recibido (inicialmente oculto) -->

                <div id="code-field" style="display: none;">

                    <input type="text" id="user-code" placeholder="Ingresa el código" required>

                    <button type="button" id="verify-code">Ver contenido</button>

                </div>

                

                <!-- Mensaje de error para códigos incorrectos o expirados -->

                <p id="error-message" style="color: red; display: none;">Código incorrecto o expirado.</p>

            </div>

        </div>

    </div>

    <?php

    // Devuelve el contenido capturado para que sea procesado por WordPress

    return ob_get_clean();

}





// Registra las acciones AJAX para enviar el código, tanto para usuarios autenticados como para visitantes

add_action('wp_ajax_nopriv_check_or_send_code', 'check_or_send_code');

add_action('wp_ajax_check_or_send_code', 'check_or_send_code');



function check_or_send_code() {

    // Sanitiza y valida el email enviado desde el frontend

    $email = sanitize_email($_POST['email']);

    $referer = sanitize_text_field($_POST['referer']);



    // Si el email no es válido, devuelve un error

    if (!is_email($email)) {

        error_log("Email inválido: $email"); // Registra el error en los logs

        wp_send_json_error('Email inválido.'); // Envía la respuesta con error al frontend

    }



    // Recupera la lista de visitantes almacenada en las opciones de WordPress

    $visitantes = get_option('visitantes_emails', array());



    // Asegura que la lista de visitantes sea un array

    if (!is_array($visitantes)) {

        error_log("visitantes_emails no es un array válido.");

        $visitantes = array();

    }



    // Obtiene información del artículo actual (si corresponde)

    $post_id = url_to_postid($referer) ?: 0; // ID del artículo basado en la URL referida

    $post_title = $post_id ? get_the_title($post_id) : 'Sin título';

    $post_url = $post_id ? get_permalink($post_id) : 'Sin URL';



    // Si el email no está registrado en la lista de visitantes, se añade

    if (!array_key_exists($email, $visitantes)) {

        $visitantes[$email] = array(

            'fecha' => current_time('mysql'), // Fecha y hora actual

            'articulo' => array( // Información del artículo

                'title' => $post_title,

                'url' => $post_url,

            ),

            'acepto_terminos' => true, // Marca como que aceptó términos

        );



        // Actualiza la lista de visitantes en las opciones de WordPress

        if (!update_option('visitantes_emails', $visitantes)) {

            error_log("No se pudo actualizar visitantes_emails.");

        }

    }



    // Genera un código aleatorio de 6 caracteres

    $code = substr(str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'), 0, 6);



    // Guarda el código en un transitorio (temporal) asociado al email, válido por 15 minutos

    set_transient('email_code_' . $email, $code, 15 * MINUTE_IN_SECONDS);



    // Construye el mensaje de correo electrónico con el código

    $message = "

    <html>

    <body>

        <h2>Tu Código de Verificación</h2>

        <p>Usa este código: <strong>$code</strong></p>

        <p>Válido por 15 minutos.</p>

    </body>

    </html>";



    // Envía el correo electrónico con el código

    wp_mail($email, 'Código de Verificación', $message, array('Content-Type: text/html; charset=UTF-8'));



    // Responde al frontend indicando que el código fue enviado

    wp_send_json_success(['unlocked' => false]);

}





// Registra las acciones AJAX para verificar el código, tanto para usuarios autenticados como para visitantes

add_action('wp_ajax_nopriv_verify_code', 'verify_user_code');

add_action('wp_ajax_verify_code', 'verify_user_code');



function verify_user_code() {

    // Sanitiza y obtiene el email y código enviados desde el frontend

    $email = sanitize_email($_POST['email']);

    $code = sanitize_text_field($_POST['code']);



    // Recupera el código almacenado en el transitorio para el email proporcionado

    $stored_code = get_transient('email_code_' . $email);



    // Verifica si el código existe o ha expirado

    if (!$stored_code) {

        // Responde con un error si el transitorio no existe

        wp_send_json_error('Código expirado o no encontrado.');

    }



    // Compara el código ingresado con el almacenado (sin considerar espacios en blanco)

    if (trim($stored_code) === trim($code)) {

        // Si el código es correcto, elimina el transitorio para evitar reutilización

        delete_transient('email_code_' . $email);



        // Crea una cookie que indica que el email ha sido verificado, válida por 1 día

        setcookie('email_verified', $email, time() + DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN);



        // Responde con éxito al frontend

        wp_send_json_success('Código correcto.');

    } else {

        // Responde con un error si el código no coincide

        wp_send_json_error('Código incorrecto o expirado.');

    }

}





// Agrega una página personalizada al menú de administración

add_action('admin_menu', 'create_visitantes_page');



function create_visitantes_page() {

    // Usa la función `add_menu_page` para agregar una nueva página al menú de administración

    add_menu_page(

        'Visitantes', // Título de la página

        'Visitantes', // Texto del menú

        'manage_options', // Capacidad requerida para acceder

        'visitantes', // Slug de la página

        'render_visitantes_page', // Función que renderiza el contenido de la página

        'dashicons-email-alt', // Icono del menú

        20 // Posición en el menú

    );

}



// Función que renderiza la página de administración de visitantes

function render_visitantes_page() {

    // Obtiene la lista de visitantes almacenada en las opciones de WordPress

    $visitantes = get_option('visitantes_emails', array());



    // Asegura que la lista sea un array

    if (!is_array($visitantes)) {

        $visitantes = array();

    }



    // Maneja las acciones del formulario (eliminar o exportar)

    if (isset($_POST['action']) && $_POST['action'] === 'delete') {

        // Si se seleccionaron emails para eliminar

        if (!empty($_POST['emails'])) {

            foreach ($_POST['emails'] as $email) {

                unset($visitantes[$email]); // Elimina el email de la lista

            }

            update_option('visitantes_emails', $visitantes); // Actualiza la lista en la base de datos

        }

    } elseif (isset($_POST['action']) && $_POST['action'] === 'export_csv') {

        // Si se solicita la exportación a CSV

        export_visitantes_to_csv($visitantes); // Llama a la función para exportar

    }



    // HTML para mostrar la tabla de visitantes

    ?>

    <div class="wrap">

        <h1>Visitantes</h1>

        <form method="post" action="">

            <table class="wp-list-table widefat fixed striped">

                <thead>

                    <tr>

                        <th><input type="checkbox" id="select-all"></th> <!-- Checkbox para seleccionar todos -->

                        <th>Email</th>

                        <th>Fecha</th>

                        <th>Artículo</th>

                        <th>Términos Aceptados</th>

                    </tr>

                </thead>

                <tbody>

                    <?php if (!empty($visitantes)): ?>

                        <?php foreach ($visitantes as $email => $info): ?>

                            <tr>

                                <td><input type="checkbox" name="emails[]" value="<?php echo esc_attr($email); ?>"></td>

                                <td><?php echo esc_html($email); ?></td>

                                <td><?php echo esc_html($info['fecha'] ?? 'N/A'); ?></td>

                                <td>

                                    <?php if (!empty($info['articulo']['title']) && !empty($info['articulo']['url'])): ?>

                                        <a href="<?php echo esc_url($info['articulo']['url']); ?>" target="_blank">

                                            <?php echo esc_html($info['articulo']['title']); ?>

                                        </a>

                                    <?php else: ?>

                                        N/A

                                    <?php endif; ?>

                                </td>

                                <td><?php echo !empty($info['acepto_terminos']) ? 'Sí' : 'No'; ?></td>

                            </tr>

                        <?php endforeach; ?>

                    <?php else: ?>

                        <tr>

                            <td colspan="5">No hay visitantes registrados.</td>

                        </tr>

                    <?php endif; ?>

                </tbody>

            </table>

            <p>

                <!-- Botón para eliminar visitantes seleccionados -->

                <button type="submit" name="action" value="delete" class="button button-secondary">Eliminar Seleccionados</button>

                <!-- Botón para exportar a CSV -->

                <button type="submit" name="action" value="export_csv" class="button button-primary">Exportar a CSV</button>

            </p>

        </form>

    </div>

    <?php

}



// Función para exportar la lista de visitantes a un archivo CSV

function export_visitantes_to_csv($visitantes) {

    // Si no hay visitantes registrados, detiene la ejecución con un mensaje

    if (empty($visitantes)) {

        header('Content-Type: text/plain; charset=utf-8');

        echo 'No hay datos para exportar.';

        exit;

    }



    // Asegúrate de que no se haya enviado salida previa (HTML, errores, etc.)

    if (ob_get_length()) {

        ob_clean(); // Limpia cualquier salida previa en el buffer

    }



    // Configuración de encabezados para la descarga del archivo CSV

    header('Content-Type: text/csv; charset=utf-8');

    header('Content-Disposition: attachment; filename="visitantes.csv"');



    // Abre la salida para escribir los datos

    $output = fopen('php://output', 'w');



    // Escribe los encabezados del archivo CSV

    fputcsv($output, array('Email', 'Fecha', 'Título del Artículo', 'URL del Artículo', 'Términos Aceptados'));



    // Escribe los datos de cada visitante en filas

    foreach ($visitantes as $email => $info) {

        fputcsv($output, array(

            $email, // Email del visitante

            $info['fecha'] ?? 'N/A', // Fecha de registro o 'N/A'

            $info['articulo']['title'] ?? 'N/A', // Título del artículo o 'N/A'

            $info['articulo']['url'] ?? 'N/A', // URL del artículo o 'N/A'

            !empty($info['acepto_terminos']) ? 'Sí' : 'No', // Indicador de aceptación de términos

        ));

    }



    // Cierra el puntero de salida

    fclose($output);

    exit; // Termina la ejecución del script

}



Explicación del Código para mostrar contenido exclusivo en WordPress

  1. Agregar estilos y scripts personalizados
    • Acción: add_action('wp_head', 'custom_blur_content_styles_and_scripts')
    • Función: Inserta los estilos CSS y el JavaScript necesarios en la cabecera del sitio.
    • Detalles:
      • Define los estilos para el contenido desenfocado, el formulario de email y la superposición.
      • Incluye un script para gestionar la interacción del usuario (mostrar términos y condiciones, enviar código de verificación y desbloquear contenido).
      • Se excluye a los administradores para evitar carga innecesaria.
  2. Habilitar AJAX en el frontend
    • Acción: add_action('wp_enqueue_scripts', 'add_ajaxurl_to_frontend')
    • Función: Define la variable global ajaxurl para facilitar solicitudes AJAX al backend de WordPress.
    • Detalles:
      • ajaxurl apunta a admin-ajax.php, permitiendo la comunicación entre el frontend y el servidor.
  3. Shortcode para desenfoque de contenido
    • Acción: add_shortcode('blur_content', 'custom_blur_content_shortcode')
    • Función: Permite proteger contenido con un shortcode [blur_content].
    • Detalles:
      • Genera el HTML con desenfoque, superposición y formulario.
      • Usa el contenido entre el shortcode como el texto a proteger.
      • Se implementa fácilmente en cualquier página o entrada.
  4. Procesamiento del código de verificación
    • Función: check_or_send_code()
    • Propósito: Gestiona solicitudes AJAX para validar o enviar códigos de verificación.
    • Detalles:
      • Verifica el email ingresado por el usuario.
      • Genera un código único de 6 caracteres y lo almacena temporalmente.
      • Envía el código por correo electrónico.
      • Registra el email, la fecha y la URL del contenido accedido.
  5. Verificación del código ingresado
    • Función: verify_user_code()
    • Propósito: Confirma si el usuario ingresó el código correcto.
    • Detalles:
      • Compara el código almacenado con el ingresado.
      • Si es correcto, elimina el código temporal y configura una cookie para desbloquear el contenido.
      • Si es incorrecto o ha expirado, muestra un mensaje de error.
  6. Creación de la página «Visitantes»
    • Función: create_visitantes_page()
    • Propósito: Agrega una sección en el panel de administración para gestionar emails recopilados.
    • Detalles:
      • Añade un menú llamado «Visitantes».
      • Muestra una tabla con emails, fecha, artículo visitado y aceptación de términos.
      • Permite eliminar registros o exportarlos en CSV.
  7. Renderización de la página de visitantes
    • Función: render_visitantes_page()
    • Propósito: Muestra la lista de usuarios registrados.
    • Detalles:
      • Tabla con datos recopilados.
      • Opciones para eliminar o exportar emails.
      • Botón para descargar el listado en CSV.
  8. Exportación de datos a CSV
    • Función: export_visitantes_to_csv()
    • Propósito: Genera un archivo CSV descargable con los visitantes registrados.
    • Detalles:
      • Incluye los campos: email, fecha, artículo visitado y términos aceptados.
      • Configura los encabezados HTTP para descarga inmediata.
  9. Estilos CSS personalizados
    • Propósito: Definir el diseño del contenido desenfocado y los formularios.
    • Detalles:
      • Usa filter: blur(8px) para desenfoque del contenido.
      • Estiliza el formulario para facilitar la interacción.
      • Aplica transiciones suaves para mejorar la experiencia del usuario.
  10. JavaScript en el frontend
  • Propósito: Gestionar la interacción del usuario en la web.
  • Detalles:
    • Mostrar términos: Expande la sección de términos y condiciones al hacer clic.
    • Enviar código: Solicita el código de verificación vía AJAX.
    • Verificar código: Confirma si el código ingresado es correcto para desbloquear contenido.
    • Manejo de errores: Informa al usuario en caso de fallos.

Resumen:

En resumen, integrar un sistema de captura de emails en WordPress, como el detallado en este artículo, es una estrategia eficaz para expandir tu base de clientes potenciales. Este código no solo protege el contenido exclusivo, sino que también permite recopilar datos valiosos de los visitantes, optimizando la personalización de ofertas y campañas. Además, su capacidad para exportar y gestionar registros lo convierte en una herramienta clave para cualquier estrategia de email marketing.

Si estás profundizando tus conocimientos en el mundo WordPress y necesitas un servicio de hosting que se adapte a tus necesidades, te invitamos a conocer los planes de WordPress Hosting que DonWeb tiene para ofrecerte.

Santiago Molina
Santiago Molina

Ingeniero Industrial / Especialista en marketing / Programador web