Este sistema permite añadir una funcionalidad de reservas personalizadas a productos en WooCommerce. Con esta integración, se pueden configurar horarios específicos de disponibilidad, días festivos no reservables y bloques de tiempo para gestionar las reservas de forma eficiente. Además, los clientes pueden seleccionar fecha y hora directamente desde la página del producto, y los administradores pueden gestionar todas las reservas desde el panel de control de WordPress.
El objetivo principal de este código es proporcionar una solución práctica y flexible para negocios que ofrecen servicios basados en reservas, como consultorías, alquileres o actividades programadas, optimizando la interacción entre clientes y administradores.
Explicación de las Funciones
1. Agregar Opciones de Configuración para Productos Reservables
- Se introduce una sección adicional en la página de configuración del producto en WooCommerce.
- Permite marcar un producto como reservable y configurar opciones como horarios disponibles, días festivos y duración de los bloques de reserva.
2. Visualización de Horarios Disponibles
- Se genera dinámicamente una tabla interactiva para que los administradores ingresen los horarios disponibles por día.
- Los horarios se almacenan en un formato JSON para facilitar su manipulación y uso.
3. Gestión de Días Festivos
- Se habilita un campo para ingresar fechas específicas (en formato
YYYY-MM-DD) como días no disponibles para reservas. - Estas fechas se excluyen automáticamente del selector de fechas para los clientes.

4. Configuración de Duración de Bloques de Reserva
- Se permite seleccionar la duración de los bloques de reserva (15, 30, 60 o 120 minutos).
- Esto determina los intervalos de tiempo que estarán disponibles para los clientes.
5. Selector de Fecha y Hora en la Página del Producto
- Los clientes pueden elegir una fecha y hora para su reserva mediante un formulario en la página del producto.
- La selección se limita a las fechas y horarios disponibles según la configuración del administrador.

6. Validación Antes de Añadir al Carrito
- Se verifica que la fecha y hora seleccionadas no estén reservadas antes de permitir que el cliente añada el producto al carrito.
- En caso de conflicto, se muestra un mensaje de error.
7. Almacenamiento de Reservas en el Carrito y el Pedido
- Las fechas y horas seleccionadas se guardan como metadatos del producto en el carrito y en el pedido.
- Esto asegura que la información de la reserva se mantenga durante todo el proceso de compra.
8. Gestión de Reservas en el Panel de Administración
- Se añade una nueva página en el panel de administración de WordPress para visualizar, cancelar y eliminar reservas.
- La página utiliza DataTables para una experiencia más interactiva, con opciones de búsqueda y ordenamiento.

9. Estilos Personalizados para Formularios de Reserva
- Se aplican estilos modernos y responsivos a los formularios de selección de fecha y hora para mejorar la experiencia del usuario.
10. Reemplazo del Botón «Añadir al Carrito»
- Para productos reservables, el botón estándar se reemplaza con un enlace que redirige a la página del producto para seleccionar una fecha y hora.
11. Gestión de Reservas Existentes
- Una función verifica las reservas existentes para evitar conflictos de horarios.
- También se proporciona la opción de cancelar o eliminar reservas desde la página de administración.
12. Funciones Auxiliares
- Obtener Reservas Existentes: Recupera todas las reservas asociadas a un producto desde los pedidos.
- Validar Fechas y Horarios: Se asegura de que las selecciones respeten las configuraciones de horarios y días festivos.
- Conversión de Días al Español: Traduce los nombres de días al español para la presentación en el frontend.
El siguiente código tienes que agregarlo en el functions.php de tu tema hijo o utilizar un plugin como CODE SNIPPETS.
// Mostrar opciones de configuración en el producto
add_action('woocommerce_product_options_general_product_data', function() {
global $post;
// Obtener los valores guardados
$horarios_guardados = get_post_meta($post->ID, '_horario_disponible', true);
$horarios_guardados = $horarios_guardados ? json_decode($horarios_guardados, true) : [];
$festivos_guardados = get_post_meta($post->ID, '_dias_festivos', true);
echo '<div class="options_group">';
// Producto reservable
woocommerce_wp_checkbox([
'id' => '_es_reservable',
'label' => __('Es Reservable', 'woocommerce'),
'description' => __('Haz que este producto sea reservable en horarios específicos.', 'woocommerce')
]);
// Tabla de horarios disponibles
echo '<label for="_horario_disponible_table">' . __('Horarios Disponibles', 'woocommerce') . '</label>';
echo '<div id="_horario_disponible_table" class="styled-horarios-table"></div>';
echo '<textarea id="_horario_disponible" name="_horario_disponible" style="display:none;">' . esc_textarea(json_encode($horarios_guardados)) . '</textarea>';
echo '<p class="description">' . __('Edita los horarios en la tabla. Ejemplo: Lunes 08:00-14:00,16:00-20:00.', 'woocommerce') . '</p>';
// Campo para días festivos
woocommerce_wp_text_input([
'id' => '_dias_festivos',
'label' => __('Días Festivos', 'woocommerce'),
'description' => __('Ingresa las fechas de días festivos en formato YYYY-MM-DD separadas por comas.', 'woocommerce'),
'desc_tip' => true,
'value' => esc_attr($festivos_guardados)
]);
// Duración de bloques
woocommerce_wp_select([
'id' => '_duracion_bloque_reserva',
'label' => __('Duración de Bloques', 'woocommerce'),
'description' => __('Selecciona la duración de cada bloque de reserva.', 'woocommerce'),
'options' => [
'15' => __('15 Minutos', 'woocommerce'),
'30' => __('30 Minutos', 'woocommerce'),
'60' => __('1 Hora', 'woocommerce'),
'120' => __('2 Horas', 'woocommerce')
]
]);
echo '</div>';
// Estilos CSS para la tabla
echo '<style>
.styled-horarios-table table {
width: 100%;
border-collapse: collapse;
margin-top: 10px;
margin-bottom: 10px;
}
.styled-horarios-table th {
background-color: #f4f4f4;
font-weight: bold;
text-align: left;
padding: 8px;
border: 1px solid #ccc;
}
.styled-horarios-table td {
padding: 8px;
border: 1px solid #ccc;
vertical-align: middle;
}
.styled-horarios-table td input {
width: 100%;
padding: 6px;
font-size: 14px;
}
.styled-horarios-table tr:nth-child(even) {
background-color: #f9f9f9;
}
.styled-horarios-table tr:hover {
background-color: #f1f1f1;
}
</style>';
// Script para generar la tabla
echo '<script>
document.addEventListener("DOMContentLoaded", function() {
const days = ["lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo"];
const horarios = document.querySelector("#_horario_disponible").value ? JSON.parse(document.querySelector("#_horario_disponible").value) : {};
const tableContainer = document.querySelector("#_horario_disponible_table");
const createTable = () => {
const table = document.createElement("table");
const headerRow = document.createElement("tr");
const dayHeader = document.createElement("th");
dayHeader.textContent = "Día";
const horarioHeader = document.createElement("th");
horarioHeader.textContent = "Horarios";
headerRow.appendChild(dayHeader);
headerRow.appendChild(horarioHeader);
table.appendChild(headerRow);
days.forEach(day => {
const row = document.createElement("tr");
const dayCell = document.createElement("td");
dayCell.textContent = day.charAt(0).toUpperCase() + day.slice(1);
const horarioCell = document.createElement("td");
const input = document.createElement("input");
input.type = "text";
input.dataset.day = day;
input.value = horarios[day] || "";
horarioCell.appendChild(input);
row.appendChild(dayCell);
row.appendChild(horarioCell);
table.appendChild(row);
});
tableContainer.appendChild(table);
// Actualizar el campo oculto con el JSON
table.addEventListener("input", () => {
const updatedHorarios = {};
table.querySelectorAll("input").forEach(input => {
updatedHorarios[input.dataset.day] = input.value.trim();
});
document.querySelector("#_horario_disponible").value = JSON.stringify(updatedHorarios);
});
};
createTable();
});
</script>';
});
// Guardar datos del producto
add_action('woocommerce_process_product_meta', function($post_id) {
$es_reservable = isset($_POST['_es_reservable']) ? 'yes' : 'no';
update_post_meta($post_id, '_es_reservable', $es_reservable);
if (isset($_POST['_horario_disponible'])) {
update_post_meta($post_id, '_horario_disponible', sanitize_text_field($_POST['_horario_disponible']));
}
if (isset($_POST['_dias_festivos'])) {
update_post_meta($post_id, '_dias_festivos', sanitize_text_field($_POST['_dias_festivos']));
}
if (isset($_POST['_duracion_bloque_reserva'])) {
update_post_meta($post_id, '_duracion_bloque_reserva', sanitize_text_field($_POST['_duracion_bloque_reserva']));
}
});
add_filter('woocommerce_before_add_to_cart_button', function() {
global $product;
if ($product->get_meta('_es_reservable') === 'yes') {
$horarios = json_decode($product->get_meta('_horario_disponible'), true);
$festivos = $product->get_meta('_dias_festivos');
$reservas_existentes = obtener_reservas($product->get_id());
$festivos_array = $festivos ? explode(',', $festivos) : [];
$festivos_array = array_map(function($fecha) {
$date = DateTime::createFromFormat('Y-m-d', trim($fecha));
return $date ? $date->format('Y-m-d') : null;
}, $festivos_array);
$festivos_array = array_filter($festivos_array); // Eliminar nulos
$duracion_bloque = $product->get_meta('_duracion_bloque_reserva') ?: 30; // Por defecto 30 minutos
echo '<div class="modern-reservation-form">';
echo '<label for="dia_reserva">Selecciona tu día:</label>';
echo '<select name="dia_reserva" class="modern-select" required>';
$fecha_actual = new DateTime();
$fecha_actual->modify('+1 day'); // Solo a partir del día siguiente
for ($i = 0; $i < 90; $i++) {
$dia_semana = strtolower($fecha_actual->format('l'));
$dia_semana_es = convertir_dia_a_espanol($dia_semana);
$fecha_actual_str = $fecha_actual->format('Y-m-d');
// Verificar si el día tiene horarios, no está en festivos y no tiene todas las horas reservadas
if (
isset($horarios[$dia_semana_es]) &&
!empty($horarios[$dia_semana_es]) &&
!in_array($fecha_actual_str, $festivos_array) &&
!todas_horas_reservadas($fecha_actual_str, $horarios[$dia_semana_es], $reservas_existentes)
) {
echo '<option value="' . esc_attr($fecha_actual_str) . '">' . esc_html($fecha_actual->format('d/m/Y')) . ' (' . ucfirst($dia_semana_es) . ')</option>';
}
$fecha_actual->modify('+1 day');
}
echo '</select>';
echo '<label for="hora_reserva">Selecciona tu hora:</label>';
echo '<select name="hora_reserva" class="modern-select" required></select>';
echo '</div>';
echo '<style>
.modern-reservation-form {
display: flex;
flex-direction: column;
gap: 1rem;
margin: 1.5rem 0;
}
.modern-reservation-form label {
font-weight: bold;
font-size: 1rem;
color: #333;
}
.modern-select {
font-size: 1rem;
padding: 0.75rem;
border: 2px solid #ddd;
border-radius: 5px;
background-color: #fff;
transition: all 0.3s ease;
color: #333;
}
.modern-select:focus {
border-color: #007bff;
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
outline: none;
}
.modern-select:hover {
border-color: #0056b3;
}
</style>';
echo '<script>
document.addEventListener("DOMContentLoaded", function() {
const horarios = ' . json_encode($horarios) . ';
const reservas = ' . json_encode($reservas_existentes) . ';
const duracionBloque = ' . $duracion_bloque . ';
const selectDia = document.querySelector("select[name=dia_reserva]");
const selectHora = document.querySelector("select[name=hora_reserva]");
selectDia.addEventListener("change", function() {
const diaSeleccionado = this.value; // Valor en formato Y-m-d
const diaSemana = new Date(diaSeleccionado).toLocaleDateString("es-ES", { weekday: "long" }).toLowerCase();
selectHora.innerHTML = ""; // Limpiar las opciones previas
if (horarios[diaSemana]) {
horarios[diaSemana].split(",").forEach(rango => {
const [inicio, fin] = rango.split("-");
let hora = new Date("1970-01-01T" + inicio + ":00");
const finHora = new Date("1970-01-01T" + fin + ":00");
while (hora < finHora) {
const horaTexto = hora.toTimeString().slice(0, 5);
const valor = diaSeleccionado + " " + horaTexto;
// Comprobar si la hora está reservada
if (!reservas.includes(valor)) {
const option = document.createElement("option");
option.value = horaTexto;
option.textContent = horaTexto;
selectHora.appendChild(option);
}
hora.setMinutes(hora.getMinutes() + parseInt(duracionBloque));
}
});
}
});
// Disparar el evento de cambio para el primer día seleccionado
if (selectDia.value) {
selectDia.dispatchEvent(new Event("change"));
}
});
</script>';
}
});
// Función para obtener reservas existentes
function obtener_reservas($product_id) {
$reservas = [];
$orders = wc_get_orders(['limit' => -1, 'status' => ['wc-completed', 'wc-processing', 'wc-on-hold', 'wc-pending']]);
foreach ($orders as $order) {
foreach ($order->get_items() as $item) {
if ($item->get_product_id() == $product_id) {
$dia = $item->get_meta('Día Reservado');
$hora = $item->get_meta('Hora Reservada');
if ($dia && $hora) {
$reservas[] = "$dia $hora";
}
}
}
}
return $reservas;
}
// Verificar si todas las horas de un día están reservadas
function todas_horas_reservadas($fecha, $horarios_dia, $reservas) {
$horas_reservadas = array_filter($reservas, function($reserva) use ($fecha) {
return strpos($reserva, $fecha) === 0;
});
$total_horas = 0;
foreach (explode(',', $horarios_dia) as $rango) {
[$inicio, $fin] = explode('-', $rango);
$inicio_hora = new DateTime("1970-01-01T$inicio:00");
$fin_hora = new DateTime("1970-01-01T$fin:00");
while ($inicio_hora < $fin_hora) {
$total_horas++;
$inicio_hora->modify('+30 minutes');
}
}
return count($horas_reservadas) >= $total_horas;
}
// Convertir día al español
function convertir_dia_a_espanol($dia) {
$dias = [
'monday' => 'lunes',
'tuesday' => 'martes',
'wednesday' => 'miércoles',
'thursday' => 'jueves',
'friday' => 'viernes',
'saturday' => 'sábado',
'sunday' => 'domingo'
];
return $dias[$dia] ?? $dia;
}
// Validar antes de añadir al carrito
add_filter('woocommerce_add_to_cart_validation', function($passed, $product_id) {
if (isset($_POST['dia_reserva'], $_POST['hora_reserva'])) {
$reserva = sanitize_text_field($_POST['dia_reserva']) . ' ' . sanitize_text_field($_POST['hora_reserva']);
if (in_array($reserva, obtener_reservas($product_id))) {
wc_add_notice('La fecha y hora seleccionadas ya están reservadas.', 'error');
return false;
}
}
return $passed;
}, 10, 2);
// Guardar reserva en el carrito
add_filter('woocommerce_add_cart_item_data', function($cart_item_data, $product_id, $variation_id) {
if (isset($_POST['dia_reserva'], $_POST['hora_reserva'])) {
$cart_item_data['dia_reserva'] = sanitize_text_field($_POST['dia_reserva']);
$cart_item_data['hora_reserva'] = sanitize_text_field($_POST['hora_reserva']);
}
return $cart_item_data;
}, 10, 3);
// Mostrar reserva en el carrito y la página de pago
add_filter('woocommerce_get_item_data', function($item_data, $cart_item) {
if (isset($cart_item['dia_reserva'], $cart_item['hora_reserva'])) {
$item_data[] = [
'key' => __('Día Reservado', 'woocommerce'),
'value' => $cart_item['dia_reserva']
];
$item_data[] = [
'key' => __('Hora Reservada', 'woocommerce'),
'value' => $cart_item['hora_reserva']
];
}
return $item_data;
}, 10, 2);
// Guardar reserva en el pedido
add_action('woocommerce_checkout_create_order_line_item', function($item, $cart_item_key, $values) {
if (isset($values['dia_reserva'], $values['hora_reserva'])) {
$item->add_meta_data('Día Reservado', $values['dia_reserva']);
$item->add_meta_data('Hora Reservada', $values['hora_reserva']);
$item->add_meta_data('Estado de la Reserva', 'Activa');
}
}, 10, 3);
// Página de administración para gestionar reservas
add_action('admin_menu', function() {
add_menu_page(
__('Reservas', 'woocommerce'),
__('Reservas', 'woocommerce'),
'manage_woocommerce',
'reservas',
'mostrar_pagina_reservas',
'dashicons-calendar-alt',
56
);
});
function mostrar_pagina_reservas() {
echo '<div class="wrap">';
echo '<h1>' . __('Gestión de Reservas', 'woocommerce') . '</h1>';
if (isset($_POST['cancelar_reserva'])) {
procesar_cancelacion_reserva($_POST['reserva_dia'], $_POST['reserva_hora'], $_POST['producto']);
echo '<div class="updated"><p>' . __('Reserva cancelada correctamente.', 'woocommerce') . '</p></div>';
}
if (isset($_POST['eliminar_reserva'])) {
eliminar_reserva($_POST['reserva_dia'], $_POST['reserva_hora'], $_POST['producto']);
echo '<div class="updated"><p>' . __('Reserva eliminada correctamente.', 'woocommerce') . '</p></div>';
}
$reservas = obtener_reservas_admin();
if (!empty($reservas)) {
echo '<table id="reservas-table" class="wp-list-table widefat fixed striped">';
echo '<thead><tr>';
echo '<th>' . __('Cliente', 'woocommerce') . '</th>';
echo '<th>' . __('Email', 'woocommerce') . '</th>';
echo '<th>' . __('Teléfono', 'woocommerce') . '</th>';
echo '<th>' . __('Producto', 'woocommerce') . '</th>';
echo '<th>' . __('Fecha Reservada', 'woocommerce') . '</th>';
echo '<th>' . __('Hora Reservada', 'woocommerce') . '</th>';
echo '<th>' . __('Estado', 'woocommerce') . '</th>';
echo '<th>' . __('Acciones', 'woocommerce') . '</th>';
echo '</tr></thead><tbody>';
foreach ($reservas as $reserva) {
echo '<tr>';
echo '<td><a href="mailto:' . esc_html($reserva['email']) . '">' . esc_html($reserva['cliente']) . '</a></td>';
echo '<td>' . esc_html($reserva['email']) . '</td>';
echo '<td>' . esc_html($reserva['telefono']) . '</td>';
echo '<td>' . esc_html($reserva['producto']) . '</td>';
echo '<td>' . esc_html($reserva['fecha']) . '</td>';
echo '<td>' . esc_html($reserva['hora']) . '</td>';
$estado_color = $reserva['estado'] === 'Cancelada' ? 'red' : 'green';
echo '<td style="color:' . $estado_color . ';">' . esc_html($reserva['estado']) . '</td>';
echo '<td>';
if ($reserva['estado'] !== 'Cancelada') {
echo '<form method="post" style="display:inline-block;">';
echo '<input type="hidden" name="reserva_dia" value="' . esc_attr($reserva['fecha']) . '">';
echo '<input type="hidden" name="reserva_hora" value="' . esc_attr($reserva['hora']) . '">';
echo '<input type="hidden" name="producto" value="' . esc_attr($reserva['producto']) . '">';
echo '<button type="submit" name="cancelar_reserva" class="button-link-delete">' . __('Cancelar', 'woocommerce') . '</button>';
echo '</form>';
}
if ($reserva['estado'] === 'Cancelada') {
echo '<form method="post" style="display:inline-block;">';
echo '<input type="hidden" name="reserva_dia" value="' . esc_attr($reserva['fecha']) . '">';
echo '<input type="hidden" name="reserva_hora" value="' . esc_attr($reserva['hora']) . '">';
echo '<input type="hidden" name="producto" value="' . esc_attr($reserva['producto']) . '">';
echo '<button type="submit" name="eliminar_reserva" class="button-link-delete">' . __('Eliminar', 'woocommerce') . '</button>';
echo '</form>';
}
echo '</td>';
echo '</tr>';
}
echo '</tbody></table>';
} else {
echo '<p>' . __('No hay reservas disponibles.', 'woocommerce') . '</p>';
}
echo '</div>';
}
function obtener_reservas_admin() {
$reservas = [];
$orders = wc_get_orders(['limit' => -1, 'status' => ['wc-completed', 'wc-processing', 'wc-on-hold', 'wc-pending']]);
foreach ($orders as $order) {
foreach ($order->get_items() as $item) {
$fecha = $item->get_meta('Día Reservado');
$hora = $item->get_meta('Hora Reservada');
$estado = $item->get_meta('Estado de la Reserva') ?: 'Activa';
if ($fecha && $hora) {
$reservas[] = [
'cliente' => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(),
'email' => $order->get_billing_email(),
'telefono' => $order->get_billing_phone(),
'producto' => $item->get_name(),
'fecha' => $fecha,
'hora' => $hora,
'estado' => $estado
];
}
}
}
return $reservas;
}
// Funciones adicionales al código previo
function eliminar_reserva($dia, $hora, $producto) {
$orders = wc_get_orders(['limit' => -1, 'status' => ['wc-completed', 'wc-processing', 'wc-on-hold', 'wc-pending']]);
foreach ($orders as $order) {
foreach ($order->get_items() as $item_id => $item) {
if ($item->get_meta('Día Reservado') === $dia && $item->get_meta('Hora Reservada') === $hora && $item->get_name() === $producto) {
wc_delete_order_item_meta($item_id, 'Día Reservado');
wc_delete_order_item_meta($item_id, 'Hora Reservada');
wc_delete_order_item_meta($item_id, 'Estado de la Reserva');
break 2; // Salir de ambos bucles
}
}
}
}
function procesar_cancelacion_reserva($dia, $hora, $producto) {
$orders = wc_get_orders(['limit' => -1, 'status' => ['wc-completed', 'wc-processing', 'wc-on-hold', 'wc-pending']]);
foreach ($orders as $order) {
foreach ($order->get_items() as $item_id => $item) {
if ($item->get_meta('Día Reservado') === $dia && $item->get_meta('Hora Reservada') === $hora && $item->get_name() === $producto) {
wc_update_order_item_meta($item_id, 'Estado de la Reserva', 'Cancelada');
break 2; // Salir de ambos bucles
}
}
}
}
// Encolar DataTables solo en la página de reservas
add_action('admin_enqueue_scripts', function($hook) {
if ($hook === 'toplevel_page_reservas') { // Cambia 'toplevel_page_reservas' según el slug de tu página
// Incluir estilos y scripts de DataTables desde el CDN
wp_enqueue_script('datatables-js', 'https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js', ['jquery'], null, true);
wp_enqueue_style('datatables-css', 'https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css');
// Inicializar DataTables en la tabla de reservas
wp_add_inline_script('datatables-js', '
jQuery(document).ready(function($) {
$("#reservas-table").DataTable({
"language": {
"url": "//cdn.datatables.net/plug-ins/1.13.4/i18n/es-ES.json"
},
"order": [[4, "asc"]], // Ordenar por la columna "Fecha Reservada" (índice 4)
"pageLength": 10, // Mostrar 10 filas por página
"responsive": true // Hacer la tabla responsiva
});
});
');
}
});
// Reemplazar el botón de "Añadir al carrito" en la tienda para productos reservables
add_filter('woocommerce_loop_add_to_cart_link', function($link, $product) {
if ($product->get_meta('_es_reservable') === 'yes') {
$product_url = get_permalink($product->get_id());
$button_text = __('Elegir Fecha y Hora', 'woocommerce');
return '<a href="' . esc_url($product_url) . '" class="button choose-date-time">' . esc_html($button_text) . '</a>';
}
return $link;
}, 10, 2);
Lenguaje del código: PHP (php)
En conclusión, este sistema de reservas personalizadas para WooCommerce proporciona una solución robusta y flexible para gestionar productos o servicios que requieren selección de horarios específicos. Desde la configuración de horarios y días festivos hasta la gestión avanzada de reservas desde el panel administrativo, esta integración permite a los negocios ofrecer una experiencia fluida tanto para los clientes como para los administradores.
Gracias a funcionalidades como la validación de reservas existentes, la personalización de bloques de tiempo y un diseño intuitivo, este código mejora la interacción con los usuarios finales y optimiza los procesos de reserva. Es una herramienta ideal para negocios que buscan simplificar la administración de sus servicios y garantizar una experiencia de usuario profesional y personalizada.
Contáctame por WhatsApp para servicios de desarrollo personalizado, ajustes en tu tienda online o cualquier solución a medida que necesites.
➡️Haz clic aquí para contactarme por WhatsApp
Nos vemos en el siguiente tutorial.
Un saludo.




Agregar Comentario