feat: sitio CV completo v1.0.0

- 4 temas WordPress: persona-cv (activo), lumina, apex-cv, cvolio
- Plugin pflores-cv-fields con CMB2 (100% gratuito) — admin editable
- Generador PDF dinámico con Chromium headless (?cv_pdf=1)
- Datos del CV de Patricia Flores cargados en CMB2
- URL dinámica con soporte proxy SSL (NPM/X-Forwarded-Proto)
- WP Mail SMTP instalado (pendiente config SMTP)
- Documentación completa: PROMPT_IA.md, DEPLOY_WEB.md, PRODUCCION.md, VERSIONADO.md
This commit is contained in:
2026-06-05 11:21:30 -05:00
parent cc6545604c
commit 5846c515f3
77 changed files with 9567 additions and 220 deletions
+116
View File
@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
function lumina_should_track_visit(): bool
{
if (is_admin() || wp_doing_ajax() || wp_doing_cron()) {
return false;
}
if (! isset($_SERVER['REQUEST_METHOD']) || $_SERVER['REQUEST_METHOD'] !== 'GET') {
return false;
}
if (is_user_logged_in() && current_user_can('manage_options')) {
return false;
}
return is_front_page() || is_home() || is_singular();
}
function lumina_track_visit(): void
{
if (! lumina_should_track_visit()) {
return;
}
$requestUri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
$visitorHash = md5($requestUri . '|' . lumina_get_client_ip() . '|' . gmdate('Y-m-d-H'));
$transientKey = 'lumina_visit_' . $visitorHash;
if (get_transient($transientKey)) {
return;
}
set_transient($transientKey, '1', HOUR_IN_SECONDS);
$total = (int) get_option('lumina_total_visits', 0);
update_option('lumina_total_visits', $total + 1, false);
if (is_front_page()) {
$frontPage = (int) get_option('page_on_front');
if ($frontPage > 0) {
$views = (int) get_post_meta($frontPage, '_lumina_views', true);
update_post_meta($frontPage, '_lumina_views', $views + 1);
}
return;
}
$postId = get_queried_object_id();
if ($postId > 0) {
$views = (int) get_post_meta($postId, '_lumina_views', true);
update_post_meta($postId, '_lumina_views', $views + 1);
}
}
add_action('template_redirect', 'lumina_track_visit');
function lumina_get_client_ip(): string
{
$keys = [
'HTTP_X_FORWARDED_FOR',
'HTTP_CLIENT_IP',
'REMOTE_ADDR',
];
foreach ($keys as $key) {
if (! empty($_SERVER[$key])) {
$value = explode(',', (string) $_SERVER[$key]);
return trim($value[0]);
}
}
return '0.0.0.0';
}
function lumina_register_visits_widget(): void
{
wp_add_dashboard_widget(
'lumina_visits_widget',
__('Lumina CV: Visitas', 'lumina'),
'lumina_render_visits_widget'
);
}
add_action('wp_dashboard_setup', 'lumina_register_visits_widget');
function lumina_render_visits_widget(): void
{
$total = (int) get_option('lumina_total_visits', 0);
$frontPage = (int) get_option('page_on_front');
$frontViews = $frontPage > 0 ? (int) get_post_meta($frontPage, '_lumina_views', true) : 0;
?>
<p><strong><?php esc_html_e('Visitas totales registradas:', 'lumina'); ?></strong> <?php echo esc_html((string) $total); ?></p>
<p><strong><?php esc_html_e('Visitas a la portada:', 'lumina'); ?></strong> <?php echo esc_html((string) $frontViews); ?></p>
<p><?php esc_html_e('El contador evita duplicados del mismo visitante en la misma URL durante una hora.', 'lumina'); ?></p>
<?php
}
function lumina_add_views_column(array $columns): array
{
$columns['lumina_views'] = __('Visitas', 'lumina');
return $columns;
}
add_filter('manage_pages_columns', 'lumina_add_views_column');
add_filter('manage_posts_columns', 'lumina_add_views_column');
function lumina_render_views_column(string $column, int $postId): void
{
if ($column !== 'lumina_views') {
return;
}
echo esc_html((string) ((int) get_post_meta($postId, '_lumina_views', true)));
}
add_action('manage_pages_custom_column', 'lumina_render_views_column', 10, 2);
add_action('manage_posts_custom_column', 'lumina_render_views_column', 10, 2);