Commit inicial - WordPress Análisis de Precios Unitarios

- WordPress core y plugins
- Tema Twenty Twenty-Four configurado
- Plugin allow-unfiltered-html.php simplificado
- .gitignore configurado para excluir wp-config.php y uploads

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-11-03 21:04:30 -06:00
commit a22573bf0b
24068 changed files with 4993111 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.984375 0H9.01562C9.57812 0 9.85938 0.6875 9.45312 1.09375L5.45312 5.09375C5.20312 5.34375 4.79688 5.34375 4.54688 5.09375L0.546875 1.09375C0.140625 0.6875 0.421875 0 0.984375 0Z" fill="#444648"/>
</svg>

After

Width:  |  Height:  |  Size: 309 B

View File

@@ -0,0 +1,3 @@
<svg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 6H0.96875C0.40625 6 0.125 5.34375 0.53125 4.9375L4.53125 0.9375C4.78125 0.6875 5.1875 0.6875 5.4375 0.9375L9.4375 4.9375C9.84375 5.34375 9.5625 6 9 6Z" fill="#444648"/>
</svg>

After

Width:  |  Height:  |  Size: 282 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 154 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,26 @@
const ToolsCollection = require('./tools');
const CategoryModel = require('../models/category');
/**
* Backbone collection for representing a collection of categories.
*/
module.exports = Backbone.Collection.extend( {
model: CategoryModel, // Reference to the CategoryModel
/**
* Parse the response data to create CategoryModel instances.
*
* @param {Object} response The response data.
* @returns {Array} An array of CategoryModel instances.
*/
parse: function( response ) {
// Iterate over each category in the response
return _.map( response, function( tools, categoryName ) {
// Create a CategoryModel for each category
return new CategoryModel( {
name: categoryName,
tools: new ToolsCollection(tools)
} );
} );
}
} );

View File

@@ -0,0 +1,10 @@
/**
* Backbone collection for representing a collection of tools.
*/
module.exports = Backbone.Collection.extend({
// Define the model for the collection
model: require('../models/tool'), // Reference to the Tool model
});

View File

@@ -0,0 +1,314 @@
( function( $ ) {
// Wait for the DOM to be ready
$(document).ready(() => {
// Import necessary modules
const CategoriesCollection = require('./collections/categories');
const SearchView = require('./views/search');
const FilterView = require('./views/filter');
const {
getButtonHtml,
redirectToUri,
updateButtonClassWhileInstalling,
updateButtonClassIfInstallationFailed,
updateButtonClassAfterActivated,
updateButtonClassAfterInstallation,
handleError,
getGetStartedButtonText,
getActivateButtonText,
getInstallNowButtonText
} = require('./helperFunctions');
// Create a new instance of CategoriesCollection
const categoriesCollection = new CategoriesCollection();
// Define the GrowthTools view
const GrowthTools = Backbone.View.extend({
el: '.growth-tools-list',
events: {
'click .tve-action-btn-learn-more': 'redirectToUri',
'click .tve-action-btn-get-started': 'redirectToDashboard',
'click .tve-action-btn-install-now': 'initInstallation',
'click .tve-action-btn-activate': 'activatePlugin',
'click .tool-name': 'redirectToUri'
},
initialize: function() {
// Show loader while fetching data
TVE_Dash.showLoader(true);
const self = this;
categoriesCollection.url = TVD_AM_CONST.baseUrl;
categoriesCollection.fetch({
success: function( collection ) {
if ( collection.length === 0 ) {
// Handle case where no data is returned
self.$el.empty();
self.$el.append( '<div class="growth-tools-not-found"><span>No tools found</span><svg class="td-icon"><use xlink:href="#icon-no-tools-found"></use></svg></div>' );
} else {
// Set fetched data to view properties
self.categories = collection;
self.filteredTools = collection;
// Render the view after setting the collection
self.render();
}
},
error: function( collection, response ) {
// Error handling
TVE_Dash.err( 'Error fetching data:', response, 3000, null, 'top' );
},
complete: function() {
// Hide loader after rendering
TVE_Dash.hideLoader();
}
});
},
filterTools: function() {
// Get selected category and search text
const selectedOption = $( '#tools-category-select option:selected' ).text();
const category = encodeURIComponent( selectedOption );
const search = encodeURIComponent( $( '#tvd-search-growth-tools' ).val().trim().toLowerCase() );
// Show loader while fetching filtered data
TVE_Dash.showLoader( true );
const self = this;
categoriesCollection.url = TVD_AM_CONST.baseUrl + '?category=' + category + '&query=' + search;
categoriesCollection.fetch({
success: function( collection ) {
if ( collection.length === 0 ) {
// Handle case where no data is returned
self.$el.empty();
self.$el.append( '<div class="growth-tools-not-found"><span>No tools found</span><svg class="td-icon"><use xlink:href="#icon-no-tools-found"></use></svg></div>' );
} else {
// Set fetched data to view properties
self.categories = collection;
self.filteredTools = collection;
// Render the view after setting the collection
self.render();
}
},
error: function( collection, response ) {
// Error handling
TVE_Dash.err( 'Error fetching data:', response, 3000, null, 'top' );
},
complete: function() {
// Hide loader after rendering
TVE_Dash.hideLoader();
}
});
},
// Handle redirection to external URL
redirectToUri: function( event ) {
redirectToUri( event, '_blank' );
},
// Handle redirection to dashboard
redirectToDashboard: function( event ) {
redirectToUri( event );
},
// Initialize plugin installation process
initInstallation: function( event ) {
event.preventDefault();
const button = $( event.currentTarget );
const pluginSlug = button.data( 'plugin-slug' );
updateButtonClassWhileInstalling( button );
// Disable the button
button.prop( 'disabled', true ).text( 'Installing...' );
// Ensure plugin slug is available
if ( !pluginSlug ) {
TVE_Dash.err( 'Plugin slug is missing.', 3000, null, 'top' );
button.prop( 'disabled', false ).html( getInstallNowButtonText() );
return;
}
const apiUrl = TVD_AM_CONST.baseUrl;
const data = {
plugin_slug: pluginSlug,
action: 'install'
};
// Define request options
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( data ),
};
let self = this;
// Send the POST request using fetch
fetch( apiUrl, requestOptions )
.then( response => {
// Check if request was successful
if ( !response.ok ) {
return handleError( response );
}
return response.json(); // Parse response JSON
})
.then( response_data => {
// If response contains download link, install plugin
if ( response_data && response_data?.dl ) {
self.installPlugin( event );
} else {
// Otherwise, activate the plugin
TVE_Dash.success( response_data?.success, 3000, null, 'top' );
self.activatePlugin( event );
}
})
.catch( error => {
updateButtonClassIfInstallationFailed( button );
// Handle errors
TVE_Dash.err( 'Error: ' + error.message, 3000, null, 'top' );
button.prop( 'disabled', false ).html( getInstallNowButtonText() );
});
},
// Install plugin from remote URL
installPlugin: function( event ) {
const button = $( event.currentTarget );
const pluginSlug = button.data( 'plugin-slug' );
updateButtonClassWhileInstalling( button );
// Disable the button
button.prop( 'disabled', true ).text( 'Installing...' );
const apiUrl = TVD_AM_CONST.baseUrl;
const data = {
plugin_slug: pluginSlug,
action: 'install'
};
// Define request options
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( data ),
};
let self = this;
// Send the POST request using fetch
fetch( apiUrl, requestOptions )
.then( response => {
// Check if request was successful
if ( !response.ok ) {
return handleError( response );
}
return response.json();
})
.then( response_data => {
TVE_Dash.success( response_data?.success, 3000, null, 'top' );
updateButtonClassAfterInstallation( button );
button.html( getActivateButtonText() );
self.activatePlugin( event );
})
.catch( error => {
updateButtonClassIfInstallationFailed( button );
// Handle errors
TVE_Dash.err( 'Error: ' + error.message, 3000, null, 'top' );
button.prop( 'disabled', false ).html( getInstallNowButtonText() );
});
},
// Activate plugin
activatePlugin: function( event ) {
event.preventDefault();
const button = $( event.currentTarget );
const pluginSlug = button.data( 'plugin-slug' );
button.prop( 'disabled', true ).text( 'Activating' );
// Ensure plugin slug is available
if ( !pluginSlug ) {
TVE_Dash.err( 'Plugin slug is missing.', 3000, null, 'top' );
button.prop( 'disabled', false ).html( getActivateButtonText() );
return;
}
const apiUrl = TVD_AM_CONST.baseUrl;
const data = {
plugin_slug: pluginSlug,
action: 'activate'
};
// Define request options
const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify( data ),
};
// Send the POST request using fetch
fetch( apiUrl, requestOptions )
.then( response => {
if ( !response.ok ) {
return handleError( response );
}
return response.json(); // Parse response JSON
})
.then( data => {
// Handle successful response
TVE_Dash.success( data?.success, 3000, null, 'top' );
updateButtonClassAfterActivated( button );
button.prop( 'disabled', false ).html( getGetStartedButtonText() ); // Enable the button
})
.catch(error => {
// Handle errors
TVE_Dash.err( 'Error: ' + error.message, 3000, null, 'top' );
button.prop( 'disabled', false ).html( getActivateButtonText() );
});
},
render: function() {
// Empty the element
this.$el.empty();
// Check if categories collection exists
if ( !this.filteredTools || this.filteredTools.length === 0 ) {
return this;
}
const self = this;
// Loop through each category
this.filteredTools.each( function( category ) {
const $categoryItem = $( '<div class="growth-tools-category-item"></div>' );
const $categoryTitle = $( '<span class="category-title">' + category.get( 'name' ) + '</span>' );
$categoryItem.append( $categoryTitle );
// Check if category has tools
const tools = category.get( 'tools' );
if ( tools && tools.length > 0 ) {
const $toolsContainer = $( '<div class="growth-tools-card"></div>' );
// Loop through each tool in the category
tools.forEach( function( tool ) {
const $toolItem = $( '<div class="growth-tools-card-item"></div>' );
const $toolLogo = $( '<div class="growth-tool-logo"><svg class="td-icon"><use xlink:href="#' + tool.get( 'icon' ) + '"></use></svg></div>' );
const $toolContent = $( '<div class="growth-tool-content"><span class="tool-name" data-url="' + encodeURIComponent( tool.get( 'landing_url' ) ) + '">' + tool.get( 'name' ) + '</span><br/><span class="tool-summary">' + tool.get( 'summary' ) + '</span></div>' );
const $toolAction = $( getButtonHtml( tool ) );
$toolItem.append( $toolLogo, $toolContent, $toolAction );
$toolsContainer.append( $toolItem );
});
$categoryItem.append( $toolsContainer );
}
self.$el.append( $categoryItem );
});
return this;
}
});
// Instantiate GrowthTools view and related views
const growthToolsView = new GrowthTools();
const filterView = new FilterView({
growthToolsView: growthToolsView
});
const searchView = new SearchView({
growthToolsView: growthToolsView
});
// Render filter and search views
filterView.render();
searchView.render();
// Render GrowthTools view
growthToolsView.render();
});
} )( jQuery );

View File

@@ -0,0 +1,260 @@
/**
* Get the button text based on the status of the tool.
*
* @param {string} status The status of the tool.
* @returns {string} The button text.
*/
function getButtonText( status ) {
let content;
switch ( status ) {
case "Activated":
content = getGetStartedButtonText();
break;
case "Installed":
content = getActivateButtonText();
break;
case "Not installed":
content = getInstallNowButtonText();
break;
default:
content = getLearnMoreButtonText();
}
return content;
}
function getLearnMoreButtonText() {
return '<span>Learn More</span>';
}
function getInstallNowButtonText() {
return '<span>Install Now</span>';
}
function getActivateButtonText() {
return '<span>Activate</span>';
}
function getGetStartedButtonText() {
return '<span>Get Started</span>';
}
/**
* Get the action class for the button based on the status of the tool.
*
* @param {string} status The status of the tool.
* @returns {string} The action class for the button.
*/
function getActionClass( status ) {
let className;
switch ( status ) {
case "Activated":
className = 'tve-action-btn-get-started';
break;
case "Installed":
className = 'tve-action-btn-activate';
break;
case "Not installed":
className = 'tve-action-btn-install-now';
break;
default:
className = 'tve-action-btn-learn-more';
}
return className;
}
/**
* Get the button class based on the status of the tool.
*
* @param {string} status The status of the tool.
* @returns {string} The button class.
*/
function getButtonClass( status ) {
let className;
switch ( status ) {
case "Activated":
//Get Started
className = 'tve-btn-get-started';
break;
case "Installed":
//Activate
className = 'tve-btn-action-activate';
break;
case "Not installed":
//Activate
className = 'tve-btn-install-now';
break;
default:
//install Now or Learn More
className = 'tve-btn-learn-more';
}
return className;
}
/**
* Get the button URI based on the status of the tool.
*
* @param {string} status The status of the tool.
* @param {object} tool The tool object.
* @returns {string} The encoded URI for the button.
*/
function getButtonUri( status, tool ) {
let url;
switch ( status ) {
case "Learn More":
url = tool.get( 'landing_url' );
break;
default:
url = tool.get( 'dashboard_uri' );
}
return encodeURIComponent( url );
}
/**
* Get the HTML for the button based on the tool object.
*
* @param {object} tool The tool object.
* @returns {string} The HTML for the button.
*/
function getButtonHtml( tool ) {
let status = tool.get( 'status' );
let pluginSlug = tool.get( 'plugin_slug' );
const uriData = getButtonUri( status, tool )
return '<div class="growth-tool-action"><button class="tve-btn ' + getButtonClass( status ) + ' ' + getActionClass( status ) + '" data-url="' + uriData + '" data-plugin-slug="' + pluginSlug + '">' + getButtonText( status ) + '</button></div>';
}
/**
* Redirect to the URI specified in the event target's dataset.
*
* @param {Event} event The click event.
* @param {string} target The target window or tab to open the URI.
*/
function redirectToUri( event, target = '_self' ) {
const { target: eventTarget } = event || {};
let element = eventTarget;
while ( element ) {
const { dataset } = element;
if ( dataset && dataset.url ) {
const path = decodeURIComponent( dataset.url );
window.open( path, target );
return;
}
// Move up to the parent element
element = element.parentElement;
}
return false;
}
function updateButtonClassWhileInstalling ( button ) {
// Remove previous classes
if ( button.hasClass('tve-action-btn-install-now') ) {
button.removeClass('tve-action-btn-install-now');
}
// Remove install now design
if ( button.hasClass('tve-btn-info') ) {
button.removeClass('tve-btn-info');
}
// Add new class
if ( !button.hasClass('tve-btn-action-installing') ) {
button.addClass('tve-btn-action-installing');
}
}
function updateButtonClassIfInstallationFailed( button ) {
// Remove previous classes
if ( button.hasClass('tve-btn-action-installing') ) {
button.removeClass('tve-btn-action-installing');
}
// Add new class
if ( !button.hasClass('tve-btn-info') ) {
button.addClass('tve-btn-info');
}
if ( !button.hasClass('tve-action-btn-install-now') ) {
button.addClass('tve-action-btn-install-now');
}
}
/**
* Update the button class after installation.
*
* @param {jQuery} button The button element.
*/
function updateButtonClassAfterInstallation( button ) {
// Remove previous classes
if ( button.hasClass( 'tve-action-btn-install-now' ) ) {
button.removeClass( 'tve-action-btn-install-now' );
}
if ( button.hasClass( 'tve-btn-action-installing' ) ) {
button.removeClass( 'tve-btn-action-installing' );
}
// Add new class
if ( !button.hasClass('tve-btn-action-activating') ) {
button.addClass( 'tve-btn-action-activating' );
}
if ( !button.hasClass( 'tve-btn-action-activating' ) ) {
button.addClass( 'tve-btn-action-activating' );
}
}
/**
* Update the button class after activation.
*
* @param {jQuery} button The button element.
*/
function updateButtonClassAfterActivated( button ) {
// Remove previous classes
if ( button.hasClass( 'tve-btn-info' ) ) {
button.removeClass( 'tve-btn-info' );
}
if ( button.hasClass( 'tve-action-btn-activate' ) ) {
button.removeClass( 'tve-action-btn-activate' );
}
// Add new classes
if ( !button.hasClass( 'tve-btn-get-started' ) ) {
button.addClass( 'tve-btn-get-started' );
}
if ( !button.hasClass( 'tve-action-btn-get-started' ) ) {
button.addClass( 'tve-action-btn-get-started' );
}
}
/**
* Handles errors returned from a fetch response.
* Parses JSON response and rejects promise with error message.
*
* @param {Response} response The fetch response object.
* @returns {Promise} A promise rejected with the error message.
*/
function handleError( response ) {
return response.json().then( errorData => {
return Promise.reject( new Error( errorData.message ) );
});
}
// Export the functions to make them accessible from other files
export {
getButtonText,
getActionClass,
getButtonClass,
getButtonHtml,
redirectToUri,
updateButtonClassWhileInstalling,
updateButtonClassIfInstallationFailed,
updateButtonClassAfterInstallation,
updateButtonClassAfterActivated,
handleError,
getGetStartedButtonText,
getActivateButtonText,
getLearnMoreButtonText,
getInstallNowButtonText
};

View File

@@ -0,0 +1,11 @@
const ToolsCollection = require('../collections/tools');
/**
* Backbone model representing a tool.
*/
module.exports = Backbone.Model.extend({
defaults: {
name: '', // Default name of the tool
tools: new ToolsCollection() // Collection of tools
}
});

View File

@@ -0,0 +1,14 @@
/**
* Backbone model for representing a tool.
*/
module.exports = Backbone.Model.extend({
// Default attributes for the tool
defaults: {
name: '', // Name of the tool
path: '', // Path of the tool
icon: '', // Icon representing the tool
status: '', // Status of the tool
summary: '', // Summary or description of the tool
landing_url: '' //url of landing page of the tool
}
});

View File

@@ -0,0 +1,47 @@
/**
* Backbone view for rendering and managing the tools category select dropdown.
*/
( function ( $ ) {
module.exports = Backbone.View.extend( {
// Template for the select dropdown
template: _.template( '<select id="tools-category-select" class="tools-category-select"></select>' ),
// Element to which the view is bound
el: '.tvd-filter-tools',
// DOM events handled by the view
events: {
'change #tools-category-select': 'filterTools' // Change event for category selection
},
/**
* Initialize the view.
* @param {object} options - Options for the view.
* @param {object} options.growthToolsView - Reference to the growth tools view.
*/
initialize: function ( options ) {
this.toolsCategory = JSON.parse( TVD_AM_CONST.tools_category ); // Parse tools category JSON
this.growthToolsView = options.growthToolsView; // Reference to the growth tools view
this.render(); // Render the view
},
// Filter tools based on the selected category
filterTools: function () {
this.growthToolsView.filterTools(); // Call the filterTools method of the growth tools view
},
// Render the view
render: function () {
var $select = $( this.template() ); // Create a select element using the template
$select.append( '<option value="">All Tools</option>' ); // Add default "All Tools" option
_.each( this.toolsCategory, function ( category ) {
$select.append( '<option value="' + category + '">' + category + '</option>' ); // Add options for each category
} );
this.$el.html( $select ); // Add the select element to the view's element
this.$( '#tools-category-select' ).select2( {
minimumResultsForSearch: Infinity
} ); // Initialize select2 plugin for the dropdown
return this; // Return the view instance for chaining
}
} );
} )( jQuery );

View File

@@ -0,0 +1,87 @@
( function( $ ) {
/**
* Backbone view for the growth tools search.
*/
module.exports = Backbone.View.extend( {
// Template for the search input
template: _.template( '<input type="search" id="tvd-search-growth-tools" name="tvd-search" placeholder="Search"><button type="button" class="tvd-tools-search-icon" aria-label="Search"><svg class="td-icon"><use xlink:href="#icon-tvd-search"></use></svg></button><button type="button" class="tvd-clear-search-icon" aria-label="Clear" style="display: none;"><svg class="td-icon"><use xlink:href="#icon-tvd-cross"></use></svg></button>' ),
// Element where the view will be rendered
el: '.tvd-search-elem',
// Events handled by the view
events: {
'click .tvd-clear-search-icon': 'clearSearch',
'keyup #tvd-search-growth-tools': 'handleSearchKeyPress',
'input #tvd-search-growth-tools': 'toggleClearIcon'
},
/**
* Initialize the view.
*
* @param {Object} options Options for the view.
*/
initialize(options) {
this.growthToolsView = options.growthToolsView;
},
/**
* Handle search button click.
*/
searchTools: function() {
this.growthToolsView.filterTools();
},
/**
* Handle clear search button click.
*/
clearSearch: function() {
// Clear the search input
$( '#tvd-search-growth-tools' ).val('');
// Call the API to fetch data
this.growthToolsView.filterTools();
// Hide the clear icon
$( '.tvd-clear-search-icon' ).hide();
// Show the search icon
$( '.tvd-tools-search-icon' ).show();
},
/**
* Handle key press event for search input.
*
* @param {Event} event The keyup event.
*/
handleSearchKeyPress: function(event) {
if ( event.keyCode === 13 ) { // Enter key code
this.growthToolsView.filterTools();
}
},
/**
* Toggle the visibility of the clear icon based on input value.
*/
toggleClearIcon: function() {
let inputVal = $( '#tvd-search-growth-tools' ).val();
if ( inputVal.trim().length > 0 ) {
// Hide the search icon
$( '.tvd-tools-search-icon' ).hide();
// Show the clear icon
$( '.tvd-clear-search-icon' ).show();
} else {
// Hide the clear icon
$( '.tvd-clear-search-icon' ).hide();
// Show the search icon
$( '.tvd-tools-search-icon' ).show();
}
},
/**
* Render the view.
*
* @returns {Object} The rendered view element.
*/
render() {
this.$el.html( this.template() );
return this.$el;
},
} );
} )( jQuery );