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
@@ -0,0 +1,499 @@
<?php
/**
* Generador de PDF del CV — replica el diseño del DOCX original.
* Endpoint: /?cv_pdf=1 → descarga CV_Patricia_Flores_Sanchez.pdf
*
* Diseño fiel al DOCX:
* - Columna única, A4
* - Colores: #1B3A5C (navy), #3A3A3A (cuerpo), #666666 (secundario)
* - Fuentes: nombre 19pt, cargos 10pt, cuerpo 9pt, secundario 8pt
* - Secciones: MAYÚSCULAS + borde inferior navy
* - Cargo + período en la misma línea (izq / der)
* - Certificaciones: Título — Emisor Año
* - Competencias: Grupo: habilidad · habilidad · ...
*/
declare(strict_types=1);
add_filter('query_vars', function (array $vars): array {
$vars[] = 'cv_pdf';
return $vars;
});
add_action('template_redirect', 'pflores_handle_pdf_download');
function pflores_handle_pdf_download(): void
{
if ((string) get_query_var('cv_pdf') !== '1') {
return;
}
$chromium = pflores_find_chromium();
if ($chromium === '') {
wp_die('Chromium no disponible en el servidor.', 'Error PDF', ['response' => 500]);
}
$cv = function_exists('pflores_get_cv_data') ? pflores_get_cv_data() : [];
$html = pflores_build_cv_html($cv);
$ts = time();
$tmpDir = sys_get_temp_dir();
$htmlFile = $tmpDir . '/cv_pf_' . $ts . '.html';
$pdfFile = $tmpDir . '/cv_pf_' . $ts . '.pdf';
file_put_contents($htmlFile, $html);
$cmd = escapeshellcmd($chromium)
. ' --headless --no-sandbox --disable-gpu'
. ' --run-all-compositor-stages-before-draw'
. ' --print-to-pdf=' . escapeshellarg($pdfFile)
. ' --print-to-pdf-no-header'
. ' "file://' . $htmlFile . '"'
. ' 2>/dev/null';
exec($cmd);
@unlink($htmlFile);
if (! file_exists($pdfFile) || filesize($pdfFile) < 1024) {
@unlink($pdfFile);
wp_die('No se pudo generar el PDF.', 'Error PDF', ['response' => 500]);
}
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="CV_Patricia_Flores_Sanchez.pdf"');
header('Content-Length: ' . filesize($pdfFile));
header('Cache-Control: no-cache, no-store, must-revalidate');
readfile($pdfFile);
@unlink($pdfFile);
exit;
}
function pflores_find_chromium(): string
{
foreach (['chromium', 'chromium-browser', 'google-chrome', 'google-chrome-stable'] as $bin) {
$path = trim((string) shell_exec('which ' . escapeshellarg($bin) . ' 2>/dev/null'));
if ($path !== '' && file_exists($path)) {
return $path;
}
}
return '';
}
/* ═══════════════════════════════════════════════════════════
CONSTRUCTOR HTML — réplica fiel del DOCX
═══════════════════════════════════════════════════════════ */
function pflores_build_cv_html(array $cv): string
{
/* ── datos ── */
$hero = $cv['hero'] ?? [];
$metrics = $cv['metrics'] ?? [];
$highlights = $cv['highlights'] ?? [];
$summary = $cv['summary'] ?? [];
$impact = $cv['impact'] ?? [];
$experience = $cv['experience'] ?? [];
$education = $cv['education'] ?? [];
$certifications = $cv['certifications'] ?? [];
$competencies = $cv['competencies'] ?? [];
$volunteer = $cv['volunteer'] ?? [];
$additional = $cv['additional'] ?? [];
$name = $hero['name'] ?? '';
$role = $hero['role'] ?? '';
$role2 = $hero['role2'] ?? '';
$location = $hero['location'] ?? '';
$email = $hero['email'] ?? '';
$phone = $hero['phone'] ?? '';
$linkedin = $hero['linkedin'] ?? '';
$avail = $hero['availability'] ?? '';
$focus = $hero['focus'] ?? '';
$langs = $hero['languages'] ?? '';
$web = $hero['web'] ?? '';
$intro = $hero['intro'] ?? '';
/* ── helpers ── */
$h = fn(string $s): string => htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$nl = "\n";
/* ── estilos (replica el DOCX) ── */
$css = '
* { box-sizing: border-box; margin: 0; padding: 0; }
@page {
size: A4;
margin: 16mm 18mm 16mm 18mm;
}
body {
font-family: Arial, Helvetica, sans-serif;
font-size: 9pt;
color: #3A3A3A;
background: #ffffff;
line-height: 1.45;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* ── CABECERA ── */
.cv-name {
font-size: 19pt;
font-weight: 700;
color: #1B3A5C;
line-height: 1.1;
margin-bottom: 3px;
}
.cv-role {
font-size: 10pt;
font-weight: 700;
color: #1B3A5C;
margin-bottom: 2px;
}
.cv-contact {
font-size: 8pt;
color: #666666;
margin-bottom: 10px;
}
.cv-contact span { margin: 0 4px; }
/* ── SECCIONES ── */
.sec {
margin-bottom: 10px;
}
.sec-title {
font-size: 10pt;
font-weight: 700;
color: #1B3A5C;
text-transform: uppercase;
letter-spacing: .04em;
border-bottom: 1.5px solid #1B3A5C;
padding-bottom: 2px;
margin-bottom: 6px;
}
/* ── PERFIL ── */
.profile-text {
font-size: 9pt;
color: #3A3A3A;
line-height: 1.55;
text-align: justify;
}
/* ── EXPERIENCIA ── */
.exp-item { margin-bottom: 8px; }
.exp-header {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.exp-title {
font-size: 10pt;
font-weight: 700;
color: #1B3A5C;
}
.exp-period {
font-size: 9pt;
color: #3A3A3A;
font-style: italic;
white-space: nowrap;
margin-left: 8px;
}
.exp-company {
font-size: 9pt;
color: #666666;
margin-bottom: 3px;
}
.exp-desc {
font-size: 9pt;
color: #3A3A3A;
margin-bottom: 3px;
font-style: italic;
}
.exp-bullets {
margin: 0;
padding-left: 0;
list-style: none;
}
.exp-bullets li {
font-size: 9pt;
color: #3A3A3A;
padding-left: 12px;
position: relative;
margin-bottom: 2px;
line-height: 1.45;
text-align: justify;
}
.exp-bullets li::before {
content: "•";
position: absolute;
left: 0;
color: #1B3A5C;
font-weight: 700;
}
/* ── FORMACIÓN ── */
.edu-item {
margin-bottom: 6px;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.edu-left { flex: 1; }
.edu-degree {
font-size: 9pt;
font-weight: 700;
color: #1B3A5C;
}
.edu-institution {
font-size: 8pt;
color: #666666;
margin-top: 1px;
}
.edu-detail {
font-size: 8pt;
color: #666666;
font-style: italic;
margin-top: 1px;
}
.edu-year {
font-size: 9pt;
font-weight: 700;
color: #1B3A5C;
white-space: nowrap;
margin-left: 8px;
}
/* ── CERTIFICACIONES ── */
.cert-item {
display: flex;
justify-content: space-between;
font-size: 8pt;
color: #3A3A3A;
margin-bottom: 2.5px;
line-height: 1.4;
}
.cert-title { flex: 1; padding-right: 8px; }
.cert-year { color: #1B3A5C; font-weight: 700; white-space: nowrap; }
/* ── COMPETENCIAS ── */
.comp-line {
font-size: 9pt;
color: #3A3A3A;
margin-bottom: 3px;
line-height: 1.45;
}
.comp-line strong { color: #1B3A5C; }
/* ── VOLUNTARIADO / ADICIONAL ── */
.vol-header {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.vol-title { font-size: 9pt; font-weight: 700; color: #1B3A5C; }
.vol-year { font-size: 9pt; font-weight: 700; color: #1B3A5C; }
.vol-body { font-size: 9pt; color: #3A3A3A; line-height: 1.45; margin-top: 1px; }
.add-line {
font-size: 9pt;
color: #3A3A3A;
margin-bottom: 2px;
}
.add-line strong { color: #1B3A5C; }
.cv-footer-line {
font-size: 8pt;
color: #999999;
text-align: right;
margin-top: 8px;
border-top: .5px solid #dddddd;
padding-top: 4px;
}
';
/* ═══════════ SECCIÓN: PERFIL PROFESIONAL ═══════════ */
// Combina intro + summary (como el DOCX)
$summaryBody = $summary['body'] ?? '';
$profileFull = trim($intro);
if ($summaryBody && $summaryBody !== $intro) {
// Evitar duplicar si intro ya contiene el resumen
if (strpos($profileFull, substr($summaryBody, 0, 30)) === false) {
$profileFull .= ' ' . $summaryBody;
}
}
/* ═══════════ SECCIÓN: EXPERIENCIA ═══════════ */
$expHtml = '';
foreach ($experience as $job) {
$expHtml .= '<div class="exp-item">';
$expHtml .= '<div class="exp-header">';
$expHtml .= '<span class="exp-title">' . $h($job['title'] ?? '') . '</span>';
$expHtml .= '<span class="exp-period">' . $h($job['period'] ?? '') . '</span>';
$expHtml .= '</div>';
$company = $h($job['company'] ?? '');
$loc = $h($job['location'] ?? '');
if ($company || $loc) {
$expHtml .= '<p class="exp-company">' . $company . ($loc ? ' &nbsp;|&nbsp; ' . $loc : '') . '</p>';
}
if (! empty($job['lead'])) {
$expHtml .= '<p class="exp-desc">' . $h($job['lead']) . '</p>';
}
if (! empty($job['bullets'])) {
$expHtml .= '<ul class="exp-bullets">';
foreach ($job['bullets'] as $bullet) {
$expHtml .= '<li>' . $h($bullet) . '</li>';
}
$expHtml .= '</ul>';
}
$expHtml .= '</div>';
}
/* ═══════════ SECCIÓN: FORMACIÓN ═══════════ */
$eduHtml = '';
foreach ($education as $edu) {
$eduHtml .= '<div class="edu-item">';
$eduHtml .= '<div class="edu-left">';
$eduHtml .= '<p class="edu-degree">' . $h($edu['title'] ?? '') . '</p>';
$eduHtml .= '<p class="edu-institution">' . $h($edu['institution'] ?? '') . '</p>';
if (! empty($edu['detail'])) {
$eduHtml .= '<p class="edu-detail">' . $h($edu['detail']) . '</p>';
}
$eduHtml .= '</div>';
$eduHtml .= '<span class="edu-year">' . $h($edu['year'] ?? '') . '</span>';
$eduHtml .= '</div>';
}
/* ═══════════ SECCIÓN: CERTIFICACIONES ═══════════ */
$certHtml = '';
foreach ($certifications as $cert) {
$title = $h($cert['title'] ?? '');
$issuer = $h($cert['issuer'] ?? '');
$year = $h($cert['year'] ?? '');
$certHtml .= '<div class="cert-item">';
$certHtml .= '<span class="cert-title">' . $title . ($issuer ? ' — ' . $issuer : '') . '</span>';
$certHtml .= '<span class="cert-year">' . $year . '</span>';
$certHtml .= '</div>';
}
/* ═══════════ SECCIÓN: COMPETENCIAS ═══════════ */
$compHtml = '';
foreach ($competencies as $groupName => $items) {
$skills = [];
foreach ($items as $item) {
$skills[] = is_array($item) ? ($item['name'] ?? '') : (string) $item;
}
$skillsStr = implode(' · ', array_filter($skills));
$compHtml .= '<p class="comp-line"><strong>' . $h($groupName) . ':</strong>&nbsp; ' . $h($skillsStr) . '</p>';
}
/* ═══════════ SECCIÓN: VOLUNTARIADO ═══════════ */
$volHtml = '';
if (! empty($volunteer['title'])) {
$volHtml .= '<div class="vol-header">';
$volHtml .= '<span class="vol-title">' . $h($volunteer['title']) . '</span>';
$volHtml .= '<span class="vol-year">' . $h($volunteer['year'] ?? '') . '</span>';
$volHtml .= '</div>';
if (! empty($volunteer['body'])) {
$volHtml .= '<p class="vol-body">' . $h($volunteer['body']) . '</p>';
}
}
/* ═══════════ SECCIÓN: INFORMACIÓN ADICIONAL ═══════════ */
$addHtml = '';
$addLines = [
'Idiomas' => $langs,
'Disponibilidad' => $avail,
'Enfoque' => $focus,
];
// Añadir los del array additional filtrando redundancias
foreach ($additional as $k => $v) {
if ($v !== '' && ! in_array($k, ['Disponibilidad', 'Idiomas'], true)) {
$addLines[$k] = $v;
}
}
foreach ($addLines as $k => $v) {
if ($v === '') continue;
$addHtml .= '<p class="add-line"><strong>' . $h($k) . ':</strong>&nbsp;' . $h($v) . '</p>';
}
if ($web) {
$addHtml .= '<p class="add-line"><strong>Web:</strong>&nbsp;' . $h($web) . '</p>';
}
/* ═══════════ LÍNEA DE CONTACTO (cabecera) ═══════════ */
$contactParts = [];
if ($email) $contactParts[] = 'Correo: ' . $email;
if ($phone) $contactParts[] = 'Tel.: ' . $phone;
if ($linkedin) {
// Mostrar URL limpia sin https://www.
$lnDisplay = preg_replace('#^https?://(www\.)?#', '', urldecode($linkedin));
$contactParts[] = 'LinkedIn: ' . $lnDisplay;
}
if ($location) $contactParts[] = $location;
$contactLine = implode(' | ', $contactParts);
/* ═══════════ TÍTULO del documento ═══════════ */
$fullRole = $role . ($role2 ? ' | ' . $role2 : '');
$year = gmdate('Y');
/* ═══════════ HTML COMPLETO ═══════════ */
return '<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>CV — ' . $h($name) . '</title>
<style>' . $css . '</style>
</head>
<body>
<!-- CABECERA -->
<p class="cv-name">' . $h($name) . '</p>
<p class="cv-role">' . $h($fullRole) . '</p>
<p class="cv-contact">' . $h($contactLine) . '</p>
<!-- PERFIL PROFESIONAL -->
<div class="sec">
<p class="sec-title">Perfil profesional</p>
<p class="profile-text">' . $h($profileFull) . '</p>
</div>
<!-- EXPERIENCIA PROFESIONAL -->
<div class="sec">
<p class="sec-title">Experiencia profesional</p>
' . $expHtml . '
</div>
<!-- FORMACIÓN ACADÉMICA -->
<div class="sec">
<p class="sec-title">Formación académica</p>
' . $eduHtml . '
</div>
<!-- CERTIFICACIONES RELEVANTES -->
<div class="sec">
<p class="sec-title">Certificaciones relevantes</p>
' . $certHtml . '
</div>
<!-- COMPETENCIAS CLAVE -->
' . ($compHtml ? '<div class="sec">
<p class="sec-title">Competencias clave</p>
' . $compHtml . '
</div>' : '') . '
<!-- VOLUNTARIADO -->
' . ($volHtml ? '<div class="sec">
<p class="sec-title">Voluntariado</p>
' . $volHtml . '
</div>' : '') . '
<!-- INFORMACIÓN ADICIONAL -->
' . ($addHtml ? '<div class="sec">
<p class="sec-title">Información adicional</p>
' . $addHtml . '
</div>' : '') . '
<p class="cv-footer-line">' . $h($location) . ' — ' . $year . '</p>
</body>
</html>';
}
@@ -0,0 +1,350 @@
<?php
/**
* Plugin Name: Pflores CV Fields
* Description: Página de opciones y campos para el CV de Patricia Flores. Usa CMB2 (100 % gratuito, WordPress.org).
* Version: 2.0.0
* Author: mgonzalez
* Text Domain: pflores-cv
*/
declare(strict_types=1);
if (! defined('ABSPATH')) {
exit;
}
require_once __DIR__ . '/pdf-generator.php';
/* ─────────────────────────────────────────────
1. PÁGINAS DE OPCIONES (menú WP-Admin)
────────────────────────────────────────────── */
add_action('cmb2_admin_init', 'pflores_register_pages');
function pflores_register_pages(): void
{
/* ── PERFIL Y CONTACTO ── */
$perfil = new_cmb2_box([
'id' => 'pflores_perfil',
'title' => 'Perfil y contacto',
'object_types' => ['options-page'],
'option_key' => 'pflores_perfil',
'menu_title' => 'CV Patricia Flores',
'icon_url' => 'dashicons-id-alt',
'position' => 3,
'save_button' => 'Guardar cambios',
'capability' => 'manage_options',
]);
$perfil->add_field(['id' => 'pf_name', 'name' => 'Nombre completo', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_role', 'name' => 'Cargo principal', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_role2', 'name' => 'Sector / subtítulo', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_intro', 'name' => 'Introducción', 'type' => 'textarea', 'attributes' => ['rows' => 4]]);
$perfil->add_field(['id' => 'pf_summary_title', 'name' => 'Título del perfil', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_summary_body', 'name' => 'Resumen profesional', 'type' => 'textarea', 'attributes' => ['rows' => 5]]);
$perfil->add_field(['id' => 'pf_location', 'name' => 'Ubicación', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_email', 'name' => 'Correo electrónico', 'type' => 'text_email']);
$perfil->add_field(['id' => 'pf_phone', 'name' => 'Teléfono', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_linkedin', 'name' => 'LinkedIn URL', 'type' => 'text_url']);
$perfil->add_field(['id' => 'pf_availability', 'name' => 'Disponibilidad', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_focus', 'name' => 'Enfoque profesional', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_languages', 'name' => 'Idiomas', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_photo', 'name' => 'Foto de perfil', 'type' => 'file', 'options' => ['url' => false], 'text' => ['add_upload_file_text' => 'Seleccionar foto']]);
$perfil->add_field(['id' => 'pf_born', 'name' => 'Fecha de nacimiento', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_ci', 'name' => 'Cédula de identidad', 'type' => 'text']);
$perfil->add_field(['id' => 'pf_web', 'name' => 'Web profesional', 'type' => 'text']);
/* ── EXPERIENCIA ── */
$exp = new_cmb2_box([
'id' => 'pflores_experiencia',
'title' => 'Experiencia profesional',
'object_types' => ['options-page'],
'option_key' => 'pflores_experiencia',
'parent_slug' => 'pflores_perfil',
'menu_title' => 'Experiencia',
'save_button' => 'Guardar cambios',
'capability' => 'manage_options',
]);
$exp_group = $exp->add_field([
'id' => 'pf_experience',
'type' => 'group',
'description' => 'Cargos del más reciente al más antiguo',
'options' => [
'group_title' => 'Cargo {#}',
'add_button' => 'Añadir cargo',
'remove_button' => 'Eliminar cargo',
'sortable' => true,
'closed' => true,
],
]);
$exp->add_group_field($exp_group, ['id' => 'period', 'name' => 'Período (ej: Abr. 2022 – Presente)', 'type' => 'text']);
$exp->add_group_field($exp_group, ['id' => 'title', 'name' => 'Cargo', 'type' => 'text']);
$exp->add_group_field($exp_group, ['id' => 'company', 'name' => 'Institución / Empresa', 'type' => 'text']);
$exp->add_group_field($exp_group, ['id' => 'location', 'name' => 'Lugar', 'type' => 'text']);
$exp->add_group_field($exp_group, ['id' => 'lead', 'name' => 'Descripción breve', 'type' => 'textarea_small']);
$exp->add_group_field($exp_group, [
'id' => 'bullets_text',
'name' => 'Logros y actividades',
'type' => 'textarea',
'description' => 'Un punto por línea. Sin guiones ni asteriscos.',
'attributes' => ['rows' => 8],
]);
/* ── FORMACIÓN Y CERTIFICACIONES ── */
$form = new_cmb2_box([
'id' => 'pflores_formacion',
'title' => 'Formación y certificaciones',
'object_types' => ['options-page'],
'option_key' => 'pflores_formacion',
'parent_slug' => 'pflores_perfil',
'menu_title' => 'Formación',
'save_button' => 'Guardar cambios',
'capability' => 'manage_options',
]);
$edu_group = $form->add_field([
'id' => 'pf_education',
'type' => 'group',
'options' => ['group_title' => 'Título {#}', 'add_button' => 'Añadir título', 'remove_button' => 'Eliminar', 'sortable' => true, 'closed' => true],
]);
$form->add_group_field($edu_group, ['id' => 'year', 'name' => 'Año', 'type' => 'text', 'attributes' => ['size' => 6]]);
$form->add_group_field($edu_group, ['id' => 'title', 'name' => 'Título', 'type' => 'text']);
$form->add_group_field($edu_group, ['id' => 'institution', 'name' => 'Institución','type' => 'text']);
$form->add_group_field($edu_group, ['id' => 'detail', 'name' => 'Detalle / Nota de reconocimiento', 'type' => 'textarea_small']);
$cert_group = $form->add_field([
'id' => 'pf_certifications',
'type' => 'group',
'description' => 'Certificaciones relevantes',
'options' => ['group_title' => 'Certificación {#}', 'add_button' => 'Añadir certificación', 'remove_button' => 'Eliminar', 'sortable' => true, 'closed' => true],
]);
$form->add_group_field($cert_group, ['id' => 'year', 'name' => 'Año', 'type' => 'text', 'attributes' => ['size' => 6]]);
$form->add_group_field($cert_group, ['id' => 'title', 'name' => 'Título', 'type' => 'text']);
$form->add_group_field($cert_group, ['id' => 'issuer', 'name' => 'Emisor', 'type' => 'text']);
/* ── COMPETENCIAS ── */
$comp = new_cmb2_box([
'id' => 'pflores_competencias',
'title' => 'Competencias y habilidades',
'object_types' => ['options-page'],
'option_key' => 'pflores_competencias',
'parent_slug' => 'pflores_perfil',
'menu_title' => 'Competencias',
'save_button' => 'Guardar cambios',
'capability' => 'manage_options',
]);
$comp_group = $comp->add_field([
'id' => 'pf_competencies',
'type' => 'group',
'options' => ['group_title' => 'Grupo {#}', 'add_button' => 'Añadir grupo', 'remove_button' => 'Eliminar', 'sortable' => true, 'closed' => true],
]);
$comp->add_group_field($comp_group, ['id' => 'group_name', 'name' => 'Nombre del grupo (ej: Finanzas y Operaciones)', 'type' => 'text']);
$comp->add_group_field($comp_group, [
'id' => 'skills_text',
'name' => 'Habilidades',
'type' => 'textarea',
'description' => 'Una por línea. Formato: Nombre de la habilidad|85 (el número es el porcentaje 0-100).',
'attributes' => ['rows' => 7],
]);
/* ── MÉTRICAS, IMPACTO Y EXTRAS ── */
$extra = new_cmb2_box([
'id' => 'pflores_extras',
'title' => 'Métricas, impacto y datos adicionales',
'object_types' => ['options-page'],
'option_key' => 'pflores_extras',
'parent_slug' => 'pflores_perfil',
'menu_title' => 'Métricas / Impacto',
'save_button' => 'Guardar cambios',
'capability' => 'manage_options',
]);
$metrics_group = $extra->add_field([
'id' => 'pf_metrics',
'type' => 'group',
'description' => 'Contadores principales del héroe',
'options' => ['group_title' => 'Métrica {#}', 'add_button' => 'Añadir métrica', 'remove_button' => 'Eliminar', 'sortable' => true, 'closed' => false],
]);
$extra->add_group_field($metrics_group, ['id' => 'value', 'name' => 'Valor (ej: 22+, USD 5 M)', 'type' => 'text']);
$extra->add_group_field($metrics_group, ['id' => 'suffix', 'name' => 'Sufijo (opcional)', 'type' => 'text', 'attributes' => ['size' => 8]]);
$extra->add_group_field($metrics_group, ['id' => 'label', 'name' => 'Etiqueta descriptiva', 'type' => 'text']);
$hl_group = $extra->add_field([
'id' => 'pf_highlights',
'type' => 'group',
'description' => 'Logros destacados con número grande',
'options' => ['group_title' => 'Logro {#}', 'add_button' => 'Añadir logro', 'remove_button' => 'Eliminar', 'sortable' => true, 'closed' => false],
]);
$extra->add_group_field($hl_group, ['id' => 'value', 'name' => 'Valor (ej: 74 %, 9/10)', 'type' => 'text']);
$extra->add_group_field($hl_group, ['id' => 'suffix', 'name' => 'Sufijo (opcional)', 'type' => 'text', 'attributes' => ['size' => 8]]);
$extra->add_group_field($hl_group, ['id' => 'label', 'name' => 'Descripción', 'type' => 'text']);
$impact_group = $extra->add_field([
'id' => 'pf_impact',
'type' => 'group',
'description' => 'Áreas de impacto ejecutivo',
'options' => ['group_title' => 'Impacto {#}', 'add_button' => 'Añadir área', 'remove_button' => 'Eliminar', 'sortable' => true, 'closed' => false],
]);
$extra->add_group_field($impact_group, ['id' => 'title', 'name' => 'Título', 'type' => 'text']);
$extra->add_group_field($impact_group, ['id' => 'body', 'name' => 'Descripción', 'type' => 'textarea_small']);
$extra->add_field(['id' => 'pf_volunteer_title', 'name' => 'Voluntariado — Título', 'type' => 'text']);
$extra->add_field(['id' => 'pf_volunteer_year', 'name' => 'Voluntariado — Año', 'type' => 'text', 'attributes' => ['size' => 8]]);
$extra->add_field(['id' => 'pf_volunteer_body', 'name' => 'Voluntariado — Descripción', 'type' => 'textarea_small']);
}
/* ─────────────────────────────────────────────
2. FUNCIÓN CENTRAL — lee de CMB2
────────────────────────────────────────────── */
function pflores_get_cv_data(): array
{
if (! function_exists('cmb2_get_option')) {
return pflores_cv_fallback_data();
}
$perfil = get_option('pflores_perfil', []);
$experiencia = get_option('pflores_experiencia', []);
$formacion = get_option('pflores_formacion', []);
$competencias = get_option('pflores_competencias', []);
$extras = get_option('pflores_extras', []);
// ── Experiencia ──
$experience = [];
foreach ((array) ($experiencia['pf_experience'] ?? []) as $job) {
$bullets = array_values(array_filter(
array_map('trim', explode("\n", (string) ($job['bullets_text'] ?? '')))
));
$experience[] = [
'period' => $job['period'] ?? '',
'title' => $job['title'] ?? '',
'company' => $job['company'] ?? '',
'location' => $job['location'] ?? '',
'lead' => $job['lead'] ?? '',
'bullets' => $bullets,
];
}
// ── Educación ──
$education = [];
foreach ((array) ($formacion['pf_education'] ?? []) as $edu) {
$education[] = [
'year' => $edu['year'] ?? '',
'title' => $edu['title'] ?? '',
'institution' => $edu['institution'] ?? '',
'detail' => $edu['detail'] ?? '',
];
}
// ── Certificaciones ──
$certifications = [];
foreach ((array) ($formacion['pf_certifications'] ?? []) as $cert) {
$certifications[] = [
'year' => $cert['year'] ?? '',
'title' => $cert['title'] ?? '',
'issuer' => $cert['issuer'] ?? '',
];
}
// ── Competencias (nombre|porcentaje) ──
$competencies = [];
foreach ((array) ($competencias['pf_competencies'] ?? []) as $group) {
$groupName = $group['group_name'] ?? '';
if ($groupName === '') {
continue;
}
$skills = [];
foreach (array_filter(array_map('trim', explode("\n", (string) ($group['skills_text'] ?? '')))) as $line) {
$parts = explode('|', $line, 2);
$skills[] = ['name' => trim($parts[0]), 'pct' => isset($parts[1]) ? (int) trim($parts[1]) : 80];
}
$competencies[$groupName] = $skills;
}
// ── Métricas ──
$metrics = [];
foreach ((array) ($extras['pf_metrics'] ?? []) as $m) {
$metrics[] = ['value' => $m['value'] ?? '', 'suffix' => $m['suffix'] ?? '', 'label' => $m['label'] ?? ''];
}
// ── Highlights ──
$highlights = [];
foreach ((array) ($extras['pf_highlights'] ?? []) as $h) {
$highlights[] = ['value' => $h['value'] ?? '', 'suffix' => $h['suffix'] ?? '', 'label' => $h['label'] ?? ''];
}
// ── Impacto ──
$impact = [];
foreach ((array) ($extras['pf_impact'] ?? []) as $item) {
$impact[] = ['title' => $item['title'] ?? '', 'body' => $item['body'] ?? ''];
}
// ── Foto (CMB2 file field devuelve ID; obtenemos la URL) ──
$photoId = $perfil['pf_photo'] ?? '';
$photoUrl = $photoId ? (string) wp_get_attachment_url((int) $photoId) : '';
return [
'hero' => [
'eyebrow' => 'Currículum ejecutivo',
'name' => $perfil['pf_name'] ?? '',
'role' => $perfil['pf_role'] ?? '',
'role2' => $perfil['pf_role2'] ?? '',
'intro' => $perfil['pf_intro'] ?? '',
'location' => $perfil['pf_location'] ?? '',
'email' => $perfil['pf_email'] ?? '',
'phone' => $perfil['pf_phone'] ?? '',
'linkedin' => $perfil['pf_linkedin'] ?? '',
'availability' => $perfil['pf_availability'] ?? '',
'focus' => $perfil['pf_focus'] ?? '',
'languages' => $perfil['pf_languages'] ?? '',
'born' => $perfil['pf_born'] ?? '',
'id' => $perfil['pf_ci'] ?? '',
'web' => $perfil['pf_web'] ?? '',
],
'photo' => $photoUrl,
'metrics' => $metrics,
'highlights' => $highlights,
'summary' => [
'title' => $perfil['pf_summary_title'] ?? '',
'body' => $perfil['pf_summary_body'] ?? '',
],
'impact' => $impact,
'experience' => $experience,
'education' => $education,
'certifications' => $certifications,
'competencies' => $competencies,
'volunteer' => [
'title' => $extras['pf_volunteer_title'] ?? '',
'year' => $extras['pf_volunteer_year'] ?? '',
'body' => $extras['pf_volunteer_body'] ?? '',
],
'additional' => [
'Idiomas' => $perfil['pf_languages'] ?? '',
'Disponibilidad' => $perfil['pf_availability'] ?? '',
'C.I.' => $perfil['pf_ci'] ?? '',
'Fecha de nacimiento' => $perfil['pf_born'] ?? '',
'Web profesional' => $perfil['pf_web'] ?? '',
],
];
}
/* ─────────────────────────────────────────────
3. FALLBACK si CMB2 no está activo
────────────────────────────────────────────── */
function pflores_cv_fallback_data(): array
{
return [
'hero' => ['eyebrow' => 'Currículum ejecutivo', 'name' => 'Patricia Soledad Flores Sánchez', 'role' => 'Gerente / Jefa de Operaciones Financieras', 'role2' => 'Sector financiero y microfinanzas', 'intro' => 'Ejecutiva financiera con más de 22 años de trayectoria en banca y microfinanzas.', 'location' => 'Loja, Ecuador', 'email' => 'patricflorsanchez@gmail.com', 'phone' => '+593 99 469 8405', 'linkedin' => 'https://www.linkedin.com/in/patricia-soledad-flores-s%C3%A1nchez-43b84418/', 'availability' => 'Inmediata | Presencial / Híbrida / Remota', 'focus' => 'Operaciones financieras, control interno y transformación digital', 'languages' => 'Español nativo | Inglés básico en formación activa', 'born' => '3 de agosto de 1980', 'id' => '1103591648', 'web' => 'pflores.linkedcorp.net'],
'photo' => '',
'metrics' => [],
'highlights' => [],
'summary' => ['title' => '', 'body' => ''],
'impact' => [],
'experience' => [],
'education' => [],
'certifications' => [],
'competencies' => [],
'volunteer' => ['title' => '', 'year' => '', 'body' => ''],
'additional' => [],
];
}
@@ -0,0 +1,187 @@
<?php
/**
* Carga los datos iniciales del CV de Patricia Flores en CMB2.
* URL: /wp-content/plugins/pflores-cv-fields/populate-data.php?key=pflores2024
*/
if (($_GET['key'] ?? '') !== 'pflores2024') {
http_response_code(403); exit('Acceso denegado.');
}
require_once dirname(__DIR__, 3) . '/wp-load.php';
if (! function_exists('cmb2_get_option')) {
exit('CMB2 no está activo.');
}
$ok = [];
/* ════════════════════════════════════════════
PERFIL Y CONTACTO → option: pflores_perfil
════════════════════════════════════════════ */
update_option('pflores_perfil', [
'pf_name' => 'Patricia Soledad Flores Sánchez',
'pf_role' => 'Gerente / Jefa de Operaciones Financieras',
'pf_role2' => 'Sector financiero y microfinanzas',
'pf_intro' => 'Ejecutiva financiera con más de 22 años de trayectoria en banca y microfinanzas. Máster en Dirección y Planificación Financiera, reconocido por la SENECYT en Ecuador, con especialización en gestión operativa, transformación digital de procesos, control interno y liderazgo de equipos multidisciplinarios.',
'pf_summary_title' => 'Perfil estratégico con enfoque en eficiencia, control y ejecución sostenible',
'pf_summary_body' => 'Actualmente lidera la operatividad de 26 agencias a nivel nacional y coordina aproximadamente 1.500 operaciones de microcrédito mensuales, por un monto cercano a USD 5 millones. También articula la recaudación de cerca de 28.000 clientes por canales internos y externos, integrando visión operativa, criterio financiero, disciplina de control y liderazgo sostenido del desempeño.',
'pf_location' => 'Loja, Ecuador',
'pf_email' => 'patricflorsanchez@gmail.com',
'pf_phone' => '+593 99 469 8405',
'pf_linkedin' => 'https://www.linkedin.com/in/patricia-soledad-flores-s%C3%A1nchez-43b84418/',
'pf_availability' => 'Inmediata | Presencial / Híbrida / Remota',
'pf_focus' => 'Operaciones financieras, control interno y transformación digital',
'pf_languages' => 'Español nativo | Inglés básico en formación activa',
'pf_born' => '3 de agosto de 1980',
'pf_ci' => '1103591648',
'pf_web' => 'pflores.linkedcorp.net',
'pf_photo' => '',
]);
$ok[] = '✓ Perfil y contacto (17 campos)';
/* ════════════════════════════════════════════
EXPERIENCIA → option: pflores_experiencia
════════════════════════════════════════════ */
update_option('pflores_experiencia', [
'pf_experience' => [
[
'period' => 'Abr. 2022 – Presente',
'title' => 'Jefa de Operaciones',
'company' => 'Fundación FACES Microfinanzas',
'location' => 'Loja, Ecuador',
'lead' => 'Responsable del desempeño operativo nacional, cumplimiento del POA, control interno, digitalización de procesos y gestión de equipos.',
'bullets_text' =>
"Cumplimiento del POA: 98 % (Q2 2025), 96 % (Q3 2025), 100 % (Q4 2025) y 105 % (Q1 2026).\n" .
"La transformación digital logró que los desembolsos por transferencia representen el 47,80 % del monto total en Q1 2026.\n" .
"Redujo los requerimientos de soporte desde Dirección en un 74 %, de 1.489 a 382, mediante la descentralización de procesos hacia las agencias.\n" .
"Automatizó el proceso del seguro de desgravamen con cálculo de prima, conciliación mensual, liquidación de siniestros y asientos contables automatizados.\n" .
"Elevó el índice de cumplimiento operativo nacional del 65 % al 88 % con la matriz de supervisión y certificación por agencia.\n" .
"Redujo el tiempo de atención del seguro de desgravamen de 17 a 10 días.\n" .
"Redujo los tiempos de desembolso a 1,68 días en Q3 2025, mejor resultado del año frente a la meta institucional máxima de 2 días.\n" .
"La efectividad de recuperación de mora alcanzó 90,60 % en Q1 2026, con USD 3,06 millones recuperados sobre USD 3,37 millones en mora.\n" .
"Redujo el índice de rotación del área del 41,46 % al 10,24 % entre diciembre de 2022 y marzo de 2026 mediante mejoras salariales, remuneración variable, evaluación trimestral y un plan de carrera con certificación de 60 horas para asistentes de caja.\n" .
"Participa en comités estratégicos, auditorías internas y externas, y en representación institucional ante organismos financieros internacionales de desarrollo.",
],
[
'period' => 'Feb. 2021 – Mar. 2022',
'title' => 'Supervisora de Operaciones',
'company' => 'Fundación FACES Microfinanzas',
'location' => 'Loja, Ecuador',
'lead' => 'Supervisó operaciones de 18 agencias a nivel nacional y regularizó procesos críticos inexistentes o incompletos.',
'bullets_text' =>
"Reconstruyó desde cero las bases de cálculo del seguro de desgravamen y regularizó pagos pendientes desde enero de 2021.\n" .
"Elaboró el instructivo de constitución de garantías hipotecarias, proceso que no existía previamente en la institución.\n" .
"Inició el proyecto de automatización de pagos por depósitos y transferencias.",
],
[
'period' => '2019 – 2021',
'title' => 'Actualización y formación profesional',
'company' => 'Formación independiente',
'location' => 'Ecuador',
'lead' => 'Etapa enfocada en fortalecimiento técnico y actualización profesional tras su salida de Mutualista Pichincha.',
'bullets_text' => "SAP FI (30 h).\nExcel Empresarial (24 h).\nPrevención de Lavado de Activos — UAFE (5 h).",
],
[
'period' => 'Ago. 2004 – May. 2019',
'title' => 'Oficial Operativa Regional / Jefa Operativa',
'company' => 'Mutualista Pichincha',
'location' => 'Loja, Ecuador',
'lead' => 'Trayectoria ascendente de 15 años desde Cajera hasta Jefa Operativa Regional.',
'bullets_text' =>
"Reconocida a nivel nacional por excelencia en control operativo.\n" .
"La agencia se mantuvo consistentemente por encima del 80 % en gestión de excepciones de crédito hipotecario.\n" .
"Administró proyecto inmobiliario institucional en Loja.\n" .
"Gestionó alertas ALA/FT con 100 % de cumplimiento en comités trimestrales y auditorías internas sin observaciones significativas de forma sostenida.",
],
[
'period' => 'Ene. 2002 – Nov. 2003',
'title' => 'Cajera',
'company' => 'Cooperativa de Ahorro y Crédito El Porvenir',
'location' => 'Loja, Ecuador',
'lead' => 'Inicio de carrera en el frente operativo financiero con disciplina de caja y apoyo contable.',
'bullets_text' =>
"Gestión de caja sin faltantes ni sobrantes.\n" .
"Contrato renovado de forma anticipada a los tres meses.\n" .
"Apoyo al área de Contabilidad y reemplazo temporal de la contadora durante vacaciones.",
],
],
]);
$ok[] = '✓ Experiencia (5 cargos, 23 bullets)';
/* ════════════════════════════════════════════
FORMACIÓN Y CERTIFICACIONES → pflores_formacion
════════════════════════════════════════════ */
update_option('pflores_formacion', [
'pf_education' => [
['year' => '2021', 'title' => 'Máster Universitario en Dirección y Planificación Financiera', 'institution' => 'Universidad Europea Miguel de Cervantes — Valladolid, España', 'detail' => 'Título reconocido por la SENECYT — Ecuador, febrero de 2023 | Reg. 2021/394270.'],
['year' => '2007', 'title' => 'Ingeniera en Administración en Banca y Finanzas', 'institution' => 'Universidad Técnica Particular de Loja (UTPL)', 'detail' => ''],
],
'pf_certifications' => [
['year' => '2026', 'title' => 'Mujeres que Lideran: Alta Gerencia', 'issuer' => 'Foro UNIR — Universidad Internacional de La Rioja'],
['year' => '2025', 'title' => 'Programa Internacional de Habilidades Gerenciales (48 h)', 'issuer' => 'IDEASGROUP — Ecuador / México'],
['year' => '2025', 'title' => 'Transforma tu Trabajo con Inteligencia Artificial (12 h)', 'issuer' => 'Red de Instituciones Financieras de Desarrollo'],
['year' => '2025', 'title' => 'Excel: Básico a Dashboard (40 h)', 'issuer' => 'DMARURI Training & Consulting'],
['year' => '2024', 'title' => 'Administración por Procesos — BPM (64 h)', 'issuer' => 'Escuela Politécnica Nacional — CEC'],
['year' => '2023', 'title' => 'Formación de Formadores (80 h)', 'issuer' => 'Ministerio del Trabajo del Ecuador | Certificación vigente hasta 2028'],
['year' => '2023', 'title' => 'Power BI (40 h) | Indicadores Clave de Desempeño — KPI (12 h)', 'issuer' => 'Liderazgo Capacitación & Eventos'],
['year' => '2020', 'title' => 'Gestión Financiera ERP SAP FI — Key User (30 h)', 'issuer' => 'Socio SAP certificado'],
['year' => '2020', 'title' => 'Prevención de Lavado de Activos y Financiamiento de Delitos', 'issuer' => 'UAFE — Ecuador'],
['year' => '2013', 'title' => 'Tributación Integral (36 h)', 'issuer' => 'Universidad Técnica Particular de Loja'],
],
]);
$ok[] = '✓ Formación (2 títulos) + Certificaciones (10)';
/* ════════════════════════════════════════════
COMPETENCIAS → pflores_competencias
════════════════════════════════════════════ */
update_option('pflores_competencias', [
'pf_competencies' => [
['group_name' => 'Finanzas y Operaciones', 'skills_text' => "Gestión de operaciones financieras|95\nControl interno y cumplimiento|95\nGestión de cartera y microfinanzas|90\nConciliación bancaria / Tesorería|90\nALA/FT y prevención de riesgos|88"],
['group_name' => 'Tecnología y Procesos', 'skills_text' => "Transformación digital / BPM|85\nSAP FI|78\nExcel avanzado y dashboards|85\nPower BI|75\nIA aplicada al trabajo|70"],
['group_name' => 'Liderazgo', 'skills_text' => "Equipos de hasta 48 personas|95\nGestión del cambio organizacional|90\nFormación de formadores|88\nRemuneración variable y plan de carrera|85\nDiversidad, equidad e inclusión|80"],
],
]);
$ok[] = '✓ Competencias (3 grupos, 15 habilidades)';
/* ════════════════════════════════════════════
MÉTRICAS, IMPACTO Y EXTRAS → pflores_extras
════════════════════════════════════════════ */
update_option('pflores_extras', [
'pf_metrics' => [
['value' => '22+', 'suffix' => '', 'label' => 'años de experiencia en banca y microfinanzas'],
['value' => '26', 'suffix' => '', 'label' => 'agencias bajo liderazgo operativo nacional'],
['value' => '48', 'suffix' => '', 'label' => 'personas a cargo en la estructura operativa'],
['value' => 'USD 5 M','suffix' => '', 'label' => 'en operaciones mensuales de microcrédito'],
['value' => '28.000', 'suffix' => '', 'label' => 'clientes coordinados en recaudación por canales internos y externos'],
],
'pf_highlights' => [
['value' => '9/10', 'suffix' => '', 'label' => 'calificación de gestión con resultado excelente en cuatro trimestres consecutivos'],
['value' => '74 %', 'suffix' => '', 'label' => 'reducción de requerimientos de soporte desde Dirección por descentralización de procesos'],
['value' => '1,68 días','suffix' => '', 'label' => 'tiempo de desembolso logrado en Q3 2025, mejor resultado del año'],
['value' => '75 %', 'suffix' => '', 'label' => 'reducción del índice de rotación del área entre 2022 y 2026'],
],
'pf_impact' => [
['title' => 'Transformación digital', 'body' => 'Lideró desembolsos por transferencia bancaria, integración de APIs bancarias y digitalización de expedientes y pagarés en 13 agencias.'],
['title' => 'Automatización crítica', 'body' => 'Automatizó el seguro de desgravamen, desde el cálculo de prima hasta la liquidación contable, reduciendo tiempos de 17 a 10 días.'],
['title' => 'Supervisión nacional', 'body' => 'Implementó una matriz de supervisión y certificación por agencia, elevando el índice de cumplimiento operativo del 65 % al 88 %.'],
],
'pf_volunteer_title' => 'Voluntaria — Hogar San Camilo de Lellis',
'pf_volunteer_year' => '2025',
'pf_volunteer_body' => 'Hogar de niñas con discapacidad | Loja, Ecuador. Alineado a la visión y misión de responsabilidad social de la Fundación FACES Microfinanzas.',
]);
$ok[] = '✓ Métricas (5) + Highlights (4) + Impacto (3) + Voluntariado';
?>
<!doctype html><html><head><meta charset="utf-8"><title>Datos CV cargados</title>
<style>body{font-family:monospace;background:#0f1117;color:#e0e3ef;padding:2rem;line-height:1.8}h1{color:#22d3ee}li{color:#22c55e}.links a{color:#22d3ee;display:block;margin:.3rem 0}.warn{color:#f59e0b;margin-top:1.5rem}</style>
</head><body>
<h1>✅ CV de Patricia Flores — datos cargados con CMB2</h1>
<ul><?php foreach ($ok as $r) echo "<li>$r</li>"; ?></ul>
<div class="links" style="margin-top:1.5rem">
<a href="/wp-admin">→ Ir al admin de WordPress</a>
<a href="/wp-admin/admin.php?page=pflores_perfil">→ CV Patricia Flores → Perfil</a>
<a href="/">→ Ver el CV en el sitio</a>
</div>
<p class="warn">⚠ Elimina este archivo una vez verificado.</p>
</body></html>
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

@@ -0,0 +1,10 @@
<svg width="640" height="760" viewBox="0 0 640 760" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="640" height="760" rx="48" fill="#F7EFE4"/>
<circle cx="320" cy="238" r="112" fill="#E6B596"/>
<path d="M182 612C182 515.35 260.35 437 357 437H283C379.65 437 458 515.35 458 612V640H182V612Z" fill="#D4582B"/>
<path d="M221 230C221 175.324 265.324 131 320 131C374.676 131 419 175.324 419 230V284C419 338.676 374.676 383 320 383C265.324 383 221 338.676 221 284V230Z" fill="#1F1D1A"/>
<path d="M244 245C244 202.474 278.474 168 321 168C363.526 168 398 202.474 398 245V283C398 325.526 363.526 360 321 360C278.474 360 244 325.526 244 283V245Z" fill="#F5CBB2"/>
<rect x="116" y="488" width="408" height="28" rx="14" fill="#F2DDCC"/>
<rect x="116" y="536" width="332" height="24" rx="12" fill="#F2DDCC"/>
<rect x="116" y="578" width="280" height="24" rx="12" fill="#F2DDCC"/>
</svg>

After

Width:  |  Height:  |  Size: 902 B

@@ -0,0 +1,183 @@
/**
* Apex CV — theme.js
* Mobile sidebar toggle · Print button · Scroll spy
*/
(function () {
'use strict';
/* -----------------------------------------------------------------------
Helpers
----------------------------------------------------------------------- */
function qs(selector, ctx) {
return (ctx || document).querySelector(selector);
}
function qsa(selector, ctx) {
return Array.from((ctx || document).querySelectorAll(selector));
}
/* -----------------------------------------------------------------------
Mobile sidebar
----------------------------------------------------------------------- */
var sidebar = qs('#ax-sidebar');
var toggle = qs('.ax-mobile-toggle');
var overlay = null;
function createOverlay() {
if (overlay) return overlay;
overlay = document.createElement('div');
overlay.className = 'ax-sidebar-overlay';
overlay.setAttribute('aria-hidden', 'true');
document.body.appendChild(overlay);
overlay.addEventListener('click', closeSidebar);
return overlay;
}
function openSidebar() {
if (!sidebar || !toggle) return;
sidebar.classList.add('is-open');
toggle.setAttribute('aria-expanded', 'true');
var ov = createOverlay();
// force reflow before adding class for transition
ov.offsetHeight; // eslint-disable-line no-unused-expressions
ov.classList.add('is-visible');
document.body.style.overflow = 'hidden';
}
function closeSidebar() {
if (!sidebar || !toggle) return;
sidebar.classList.remove('is-open');
toggle.setAttribute('aria-expanded', 'false');
if (overlay) overlay.classList.remove('is-visible');
document.body.style.overflow = '';
}
function isMobile() {
return window.innerWidth <= 768;
}
if (toggle) {
toggle.addEventListener('click', function () {
var isOpen = sidebar && sidebar.classList.contains('is-open');
if (isOpen) {
closeSidebar();
} else {
openSidebar();
}
});
}
/* Close sidebar when clicking outside on mobile */
document.addEventListener('click', function (e) {
if (!isMobile()) return;
if (!sidebar || !sidebar.classList.contains('is-open')) return;
if (sidebar.contains(e.target)) return;
if (toggle && toggle.contains(e.target)) return;
closeSidebar();
});
/* Close sidebar on nav link click (mobile) */
qsa('.ax-nav__link').forEach(function (link) {
link.addEventListener('click', function () {
if (isMobile()) {
closeSidebar();
}
});
});
/* Close sidebar on resize back to desktop */
window.addEventListener('resize', function () {
if (!isMobile() && sidebar && sidebar.classList.contains('is-open')) {
closeSidebar();
}
});
/* -----------------------------------------------------------------------
Print button
----------------------------------------------------------------------- */
qsa('[data-apex-print]').forEach(function (btn) {
btn.addEventListener('click', function () {
window.print();
});
});
/* -----------------------------------------------------------------------
Scroll spy via IntersectionObserver
----------------------------------------------------------------------- */
var navLinks = qsa('.ax-nav__link');
if ('IntersectionObserver' in window && navLinks.length > 0) {
var activeSection = null;
function setActive(sectionId) {
if (activeSection === sectionId) return;
activeSection = sectionId;
navLinks.forEach(function (link) {
var target = link.getAttribute('data-section') || link.getAttribute('href').replace('#', '');
if (target === sectionId) {
link.classList.add('is-active');
link.setAttribute('aria-current', 'true');
} else {
link.classList.remove('is-active');
link.removeAttribute('aria-current');
}
});
}
// Collect sections that correspond to nav links
var sectionIds = navLinks.map(function (link) {
return link.getAttribute('data-section') || link.getAttribute('href').replace('#', '');
});
var sectionEls = sectionIds
.map(function (id) { return document.getElementById(id); })
.filter(Boolean);
// Track which sections are currently intersecting
var visibleSections = new Set();
var observer = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
visibleSections.add(entry.target.id);
} else {
visibleSections.delete(entry.target.id);
}
});
// Activate the topmost visible section (matches reading order)
var toActivate = null;
sectionEls.forEach(function (el) {
if (visibleSections.has(el.id) && toActivate === null) {
toActivate = el.id;
}
});
if (toActivate) {
setActive(toActivate);
}
},
{
rootMargin: '-10% 0px -60% 0px',
threshold: 0
}
);
sectionEls.forEach(function (el) { observer.observe(el); });
}
/* -----------------------------------------------------------------------
Smooth scroll for anchor links (fallback for older browsers)
----------------------------------------------------------------------- */
qsa('a[href^="#"]').forEach(function (link) {
link.addEventListener('click', function (e) {
var href = link.getAttribute('href');
if (!href || href === '#') return;
var target = document.querySelector(href);
if (!target) return;
e.preventDefault();
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
});
})();
@@ -0,0 +1,5 @@
<?php declare(strict_types=1); ?>
</div><!-- /.ax-layout -->
<?php wp_footer(); ?>
</body>
</html>
@@ -0,0 +1,351 @@
<?php
declare(strict_types=1);
get_header();
// ---------------------------------------------------------------------------
// Data source: plugin first, theme fallback
// ---------------------------------------------------------------------------
$cv = function_exists('pflores_get_cv_data') ? pflores_get_cv_data() : apex_cv_data();
$hero = $cv['hero'];
$metrics = $cv['metrics'];
$highlights = $cv['highlights'];
$summary = $cv['summary'];
$impact = $cv['impact'];
$experience = $cv['experience'];
$education = $cv['education'];
$certifications = $cv['certifications'];
$competencies = $cv['competencies'];
$volunteer = $cv['volunteer'];
$additional = $cv['additional'];
$acfPhoto = $cv['photo'] ?? '';
$profileImage = $acfPhoto ?: get_template_directory_uri() . '/assets/images/patricia-flores-profile.jpeg';
$cvFile = get_template_directory_uri() . '/assets/docs/CV_Patricia_Flores_Sanchez_ES_FINAL.docx';
$qrImage = get_template_directory_uri() . '/assets/images/patricia-flores-qr.png';
$publicUrl = 'https://pflores.linkedcorp.net';
// Build flat skills list for sidebar pills
$skillsFlat = [];
foreach ($competencies as $groupItems) {
foreach ((array) $groupItems as $item) {
$skillsFlat[] = is_array($item) ? ($item['name'] ?? '') : (string) $item;
}
}
$skills = array_filter($skillsFlat);
?>
<?php /* ===================================================================
SIDEBAR
=================================================================== */ ?>
<aside class="ax-sidebar" id="ax-sidebar" role="complementary" aria-label="<?php esc_attr_e('Perfil y navegación', 'apex'); ?>">
<?php /* --- Profile ------------------------------------------------ */ ?>
<div class="ax-profile">
<div class="ax-profile__avatar-wrap">
<img
class="ax-profile__avatar"
src="<?php echo esc_url($profileImage); ?>"
alt="<?php echo esc_attr($hero['name']); ?>"
width="120"
height="120"
>
<span class="ax-profile__status" aria-label="<?php esc_attr_e('Disponible', 'apex'); ?>"></span>
</div>
<h1 class="ax-profile__name"><?php echo esc_html($hero['name']); ?></h1>
<p class="ax-profile__role"><?php echo esc_html($hero['role']); ?></p>
<p class="ax-profile__role2"><?php echo esc_html($hero['role2'] ?? ''); ?></p>
</div>
<?php /* --- Navigation --------------------------------------------- */ ?>
<nav class="ax-nav" aria-label="<?php esc_attr_e('Secciones del CV', 'apex'); ?>">
<?php echo wp_kses_post(apex_render_nav()); ?>
</nav>
<?php /* --- Contact ------------------------------------------------ */ ?>
<div class="ax-sidebar-section">
<span class="ax-sidebar-label"><?php esc_html_e('Contacto', 'apex'); ?></span>
<ul class="ax-contact-list">
<li class="ax-contact-list__item">
<svg class="ax-icon" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" fill="currentColor"/>
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" fill="currentColor"/>
</svg>
<a href="mailto:<?php echo esc_attr(antispambot($hero['email'])); ?>">
<?php echo esc_html($hero['email']); ?>
</a>
</li>
<li class="ax-contact-list__item">
<svg class="ax-icon" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M2 3a1 1 0 011-1h2.153a1 1 0 01.986.836l.74 4.435a1 1 0 01-.54 1.06l-1.548.773a11.037 11.037 0 006.105 6.105l.774-1.548a1 1 0 011.059-.54l4.435.74a1 1 0 01.836.986V17a1 1 0 01-1 1h-2C7.82 18 2 12.18 2 5V3z" fill="currentColor"/>
</svg>
<a href="tel:<?php echo esc_attr(preg_replace('/[^0-9+]/', '', $hero['phone'])); ?>">
<?php echo esc_html($hero['phone']); ?>
</a>
</li>
<li class="ax-contact-list__item">
<svg class="ax-icon" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd" fill="currentColor"/>
</svg>
<span><?php echo esc_html($hero['location']); ?></span>
</li>
<?php if (! empty($hero['linkedin'])) : ?>
<li class="ax-contact-list__item">
<svg class="ax-icon" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path d="M16.338 16.338H13.67V12.16c0-.995-.017-2.277-1.387-2.277-1.39 0-1.601 1.086-1.601 2.207v4.248H8.014V7.688h2.558v1.17h.036c.356-.675 1.227-1.387 2.526-1.387 2.703 0 3.203 1.778 3.203 4.092v4.774zM5.005 6.575a1.548 1.548 0 11-.003-3.096 1.548 1.548 0 01.003 3.096zm-1.337 9.763H6.34V7.688H3.667v8.65zM17.668 1H2.328C1.595 1 1 1.581 1 2.298v15.403C1 18.418 1.595 19 2.328 19h15.34c.734 0 1.332-.582 1.332-1.299V2.298C19 1.581 18.402 1 17.668 1z" fill="currentColor"/>
</svg>
<a href="<?php echo esc_url($hero['linkedin']); ?>" target="_blank" rel="noreferrer noopener">
LinkedIn
</a>
</li>
<?php endif; ?>
</ul>
</div>
<?php /* --- Skills / Competencies ---------------------------------- */ ?>
<div class="ax-sidebar-section">
<span class="ax-sidebar-label"><?php esc_html_e('Competencias', 'apex'); ?></span>
<div class="ax-skill-pills">
<?php foreach ($competencies as $groupName => $groupItems) : ?>
<p class="ax-skill-group-label"><?php echo esc_html($groupName); ?></p>
<?php foreach ((array) $groupItems as $item) : ?>
<?php $name = is_array($item) ? ($item['name'] ?? '') : (string) $item; ?>
<?php if ($name !== '') : ?>
<span class="ax-skill-pill"><?php echo esc_html($name); ?></span>
<?php endif; ?>
<?php endforeach; ?>
<?php endforeach; ?>
</div>
</div>
<?php /* --- Languages / Availability ------------------------------- */ ?>
<div class="ax-sidebar-section">
<span class="ax-sidebar-label"><?php esc_html_e('Idiomas', 'apex'); ?></span>
<p class="ax-sidebar-text"><?php echo esc_html($additional['Idiomas'] ?? 'Español nativo | Inglés básico'); ?></p>
<span class="ax-sidebar-label" style="margin-top:.75rem"><?php esc_html_e('Disponibilidad', 'apex'); ?></span>
<p class="ax-sidebar-text"><?php echo esc_html($hero['availability']); ?></p>
</div>
<?php /* --- Action buttons ----------------------------------------- */ ?>
<div class="ax-sidebar-section ax-sidebar-section--actions">
<a class="ax-btn ax-btn--primary" href="<?php echo esc_url(home_url('/?cv_pdf=1')); ?>">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
<?php esc_html_e('Descargar CV PDF', 'apex'); ?>
</a>
<a class="ax-btn ax-btn--ghost" href="<?php echo esc_url(home_url('/?cv_pdf=1')); ?>" target="_blank">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
<?php esc_html_e('Descargar PDF', 'apex'); ?>
</a>
</div>
</aside><!-- /.ax-sidebar -->
<?php /* ===================================================================
MAIN CONTENT
=================================================================== */ ?>
<main class="ax-main" id="main-content">
<div class="ax-content-wrap">
<?php /* ── SOBRE MÍ ─────────────────────────────────────────── */ ?>
<section id="sobre-mi" class="ax-section">
<header class="ax-section__head">
<span class="ax-section__eyebrow"><?php esc_html_e('Perfil profesional', 'apex'); ?></span>
<h2 class="ax-section__title"><?php echo esc_html($summary['title']); ?></h2>
</header>
<p class="ax-lead"><?php echo esc_html($summary['body']); ?></p>
<?php /* Metrics grid — 5 columns */ ?>
<div class="ax-metrics">
<?php foreach ($metrics as $metric) : ?>
<article class="ax-metric-card">
<strong class="ax-metric-card__value"><?php echo esc_html($metric['value']); ?></strong>
<span class="ax-metric-card__label"><?php echo esc_html($metric['label']); ?></span>
</article>
<?php endforeach; ?>
</div>
</section>
<?php /* ── IMPACTO ──────────────────────────────────────────── */ ?>
<section id="impacto" class="ax-section">
<header class="ax-section__head">
<span class="ax-section__eyebrow"><?php esc_html_e('Impacto ejecutivo', 'apex'); ?></span>
<h2 class="ax-section__title"><?php esc_html_e('Resultados concretos con alcance nacional', 'apex'); ?></h2>
</header>
<?php /* Highlights — 2×2 grid */ ?>
<div class="ax-highlights">
<?php foreach ($highlights as $hl) : ?>
<article class="ax-highlight-card">
<strong class="ax-highlight-card__value"><?php echo esc_html($hl['value']); ?></strong>
<span class="ax-highlight-card__label"><?php echo esc_html($hl['label']); ?></span>
</article>
<?php endforeach; ?>
</div>
<?php /* Impact cards — 3 columns */ ?>
<div class="ax-impact-cards">
<?php foreach ($impact as $item) : ?>
<article class="ax-impact-card">
<h3 class="ax-impact-card__title"><?php echo esc_html($item['title']); ?></h3>
<p class="ax-impact-card__body"><?php echo esc_html($item['body']); ?></p>
</article>
<?php endforeach; ?>
</div>
</section>
<?php /* ── EXPERIENCIA ─────────────────────────────────────── */ ?>
<section id="experiencia" class="ax-section">
<header class="ax-section__head">
<span class="ax-section__eyebrow"><?php esc_html_e('Experiencia profesional', 'apex'); ?></span>
<h2 class="ax-section__title"><?php esc_html_e('Trayectoria sólida en banca, operaciones y microfinanzas', 'apex'); ?></h2>
</header>
<div class="ax-timeline">
<?php foreach ($experience as $job) : ?>
<article class="ax-timeline__item">
<div class="ax-timeline__period">
<span class="ax-timeline__period-text"><?php echo esc_html($job['period']); ?></span>
<span class="ax-timeline__dot" aria-hidden="true"></span>
</div>
<div class="ax-timeline__content">
<h3 class="ax-timeline__job-title"><?php echo esc_html($job['title']); ?></h3>
<p class="ax-timeline__meta">
<?php echo esc_html($job['company']); ?>
<span class="ax-timeline__sep" aria-hidden="true">·</span>
<?php echo esc_html($job['location']); ?>
</p>
<p class="ax-timeline__lead"><?php echo esc_html($job['lead']); ?></p>
<?php if (! empty($job['bullets'])) : ?>
<ul class="ax-timeline__bullets">
<?php foreach ($job['bullets'] as $bullet) : ?>
<li><?php echo esc_html($bullet); ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<?php /* ── FORMACIÓN ───────────────────────────────────────── */ ?>
<section id="formacion" class="ax-section">
<header class="ax-section__head">
<span class="ax-section__eyebrow"><?php esc_html_e('Formación académica', 'apex'); ?></span>
<h2 class="ax-section__title"><?php esc_html_e('Base técnica y formación ejecutiva de alto nivel', 'apex'); ?></h2>
</header>
<div class="ax-edu-cards">
<?php foreach ($education as $edu) : ?>
<article class="ax-edu-card">
<span class="ax-edu-card__year"><?php echo esc_html($edu['year']); ?></span>
<div class="ax-edu-card__body">
<h3 class="ax-edu-card__title"><?php echo esc_html($edu['title']); ?></h3>
<p class="ax-edu-card__institution"><?php echo esc_html($edu['institution']); ?></p>
<?php if (! empty($edu['detail'])) : ?>
<p class="ax-edu-card__detail"><?php echo esc_html($edu['detail']); ?></p>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<?php /* ── CERTIFICACIONES ─────────────────────────────────── */ ?>
<section id="certificaciones" class="ax-section">
<header class="ax-section__head">
<span class="ax-section__eyebrow"><?php esc_html_e('Certificaciones relevantes', 'apex'); ?></span>
<h2 class="ax-section__title"><?php esc_html_e('Actualización continua en gerencia, procesos y analítica', 'apex'); ?></h2>
</header>
<div class="ax-cert-list">
<?php foreach ($certifications as $cert) : ?>
<article class="ax-cert-item">
<span class="ax-cert-item__year"><?php echo esc_html($cert['year']); ?></span>
<div class="ax-cert-item__body">
<h3 class="ax-cert-item__title"><?php echo esc_html($cert['title']); ?></h3>
<p class="ax-cert-item__issuer"><?php echo esc_html($cert['issuer']); ?></p>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<?php /* ── CONTACTO ────────────────────────────────────────── */ ?>
<section id="contacto" class="ax-section">
<header class="ax-section__head">
<span class="ax-section__eyebrow"><?php esc_html_e('Información adicional', 'apex'); ?></span>
<h2 class="ax-section__title"><?php esc_html_e('Datos clave, voluntariado y tarjeta de presentación', 'apex'); ?></h2>
</header>
<div class="ax-extras-grid">
<?php /* Volunteer */ ?>
<div class="ax-extras-grid__card">
<span class="ax-sidebar-label"><?php esc_html_e('Voluntariado', 'apex'); ?></span>
<h3 class="ax-extras__heading"><?php echo esc_html($volunteer['title']); ?></h3>
<p class="ax-extras__year"><?php echo esc_html($volunteer['year']); ?></p>
<p class="ax-extras__body"><?php echo esc_html($volunteer['body']); ?></p>
</div>
<?php /* Additional data */ ?>
<div class="ax-extras-grid__card">
<span class="ax-sidebar-label"><?php esc_html_e('Datos personales', 'apex'); ?></span>
<dl class="ax-data-list">
<?php foreach ($additional as $label => $value) : ?>
<div class="ax-data-list__row">
<dt><?php echo esc_html($label); ?></dt>
<dd><?php echo esc_html($value); ?></dd>
</div>
<?php endforeach; ?>
</dl>
</div>
<?php /* Business card / QR */ ?>
<div class="ax-extras-grid__card ax-extras-grid__card--bcard">
<span class="ax-sidebar-label"><?php esc_html_e('Tarjeta de presentación', 'apex'); ?></span>
<article class="ax-bcard">
<div class="ax-bcard__header">
<p class="ax-bcard__name"><?php echo esc_html($hero['name']); ?></p>
<p class="ax-bcard__role"><?php echo esc_html($hero['role']); ?></p>
</div>
<div class="ax-bcard__body">
<div class="ax-bcard__qr">
<img
src="<?php echo esc_url($qrImage); ?>"
alt="<?php esc_attr_e('Código QR — perfil web de Patricia Flores', 'apex'); ?>"
width="120"
height="120"
>
</div>
<div class="ax-bcard__info">
<p><?php echo esc_html($hero['location']); ?></p>
<p>
<a href="tel:<?php echo esc_attr(preg_replace('/[^0-9+]/', '', $hero['phone'])); ?>">
<?php echo esc_html($hero['phone']); ?>
</a>
</p>
<p>
<a href="mailto:<?php echo esc_attr(antispambot($hero['email'])); ?>">
<?php echo esc_html($hero['email']); ?>
</a>
</p>
<p>
<a href="<?php echo esc_url($publicUrl); ?>" target="_blank" rel="noreferrer noopener">
<?php echo esc_html($publicUrl); ?>
</a>
</p>
</div>
</div>
</article>
</div>
</div><!-- /.ax-extras-grid -->
</section>
</div><!-- /.ax-content-wrap -->
</main><!-- /.ax-main -->
<?php get_footer(); ?>
@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
require_once get_template_directory() . '/inc/customizer.php';
require_once get_template_directory() . '/inc/cv-data.php';
require_once get_template_directory() . '/inc/visits.php';
// ---------------------------------------------------------------------------
// Theme setup
// ---------------------------------------------------------------------------
function apex_setup(): void
{
add_theme_support('title-tag');
add_theme_support('post-thumbnails');
add_theme_support('editor-styles');
add_theme_support('responsive-embeds');
add_theme_support('wp-block-styles');
add_theme_support('align-wide');
register_nav_menus([
'primary' => __('Menú principal', 'apex'),
]);
}
add_action('after_setup_theme', 'apex_setup');
// ---------------------------------------------------------------------------
// Enqueue assets
// ---------------------------------------------------------------------------
function apex_enqueue_assets(): void
{
wp_enqueue_style(
'apex-fonts',
'https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap',
[],
null
);
wp_enqueue_style(
'apex-theme',
get_template_directory_uri() . '/assets/css/theme.css',
['apex-fonts'],
wp_get_theme()->get('Version')
);
wp_enqueue_script(
'apex-theme',
get_template_directory_uri() . '/assets/js/theme.js',
[],
wp_get_theme()->get('Version'),
true
);
}
add_action('wp_enqueue_scripts', 'apex_enqueue_assets');
// ---------------------------------------------------------------------------
// Body classes
// ---------------------------------------------------------------------------
function apex_body_classes(array $classes): array
{
$classes[] = 'apex-shell';
if (is_front_page()) {
$classes[] = 'apex-front-page';
}
return $classes;
}
add_filter('body_class', 'apex_body_classes');
// ---------------------------------------------------------------------------
// Navigation: render sidebar nav list
// ---------------------------------------------------------------------------
function apex_render_nav(): string
{
$items = [
'#sobre-mi' => __('Sobre mí', 'apex'),
'#impacto' => __('Impacto', 'apex'),
'#experiencia' => __('Experiencia', 'apex'),
'#formacion' => __('Formación', 'apex'),
'#certificaciones' => __('Certificaciones', 'apex'),
'#contacto' => __('Contacto', 'apex'),
];
$html = '<ul class="ax-nav__list">';
foreach ($items as $href => $label) {
$html .= sprintf(
'<li class="ax-nav__item"><a class="ax-nav__link" href="%s" data-section="%s">%s</a></li>',
esc_url($href),
esc_attr(ltrim($href, '#')),
esc_html($label)
);
}
$html .= '</ul>';
return $html;
}
// ---------------------------------------------------------------------------
// Redirect sample page
// ---------------------------------------------------------------------------
function apex_redirect_default_sample_page(): void
{
if (! is_page()) {
return;
}
$page = get_queried_object();
if (! $page instanceof WP_Post) {
return;
}
if ($page->post_name === 'pagina-ejemplo' || $page->post_name === 'sample-page') {
wp_safe_redirect(home_url('/'), 301);
exit;
}
}
add_action('template_redirect', 'apex_redirect_default_sample_page', 1);
@@ -0,0 +1,14 @@
<?php declare(strict_types=1); ?>
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); ?>
<button class="ax-mobile-toggle" type="button" aria-expanded="false" aria-controls="ax-sidebar" aria-label="<?php esc_attr_e('Menú', 'apex'); ?>">
<span></span><span></span><span></span>
</button>
<div class="ax-layout">
@@ -0,0 +1,5 @@
<?php
declare(strict_types=1);
// No customizer settings - data comes from pflores-cv-fields plugin.
@@ -0,0 +1,204 @@
<?php
declare(strict_types=1);
/**
* Fallback CV data for the Apex CV theme.
* Primary data source: pflores_get_cv_data() from the pflores-cv-fields plugin.
* This function is only called when that plugin is not active.
*/
function apex_cv_data(): array
{
return [
'hero' => [
'name' => 'Patricia Soledad Flores Sánchez',
'role' => 'Gerente / Jefa de Operaciones Financieras',
'role2' => 'Sector financiero y microfinanzas',
'intro' => 'Ejecutiva financiera con más de 22 años de trayectoria en banca y microfinanzas. Máster en Dirección y Planificación Financiera, reconocido por la SENECYT en Ecuador, con especialización en gestión operativa, transformación digital de procesos, control interno y liderazgo de equipos multidisciplinarios.',
'location' => 'Loja, Ecuador',
'email' => 'patricflorsanchez@gmail.com',
'phone' => '+593 99 469 8405',
'linkedin' => 'https://www.linkedin.com/in/patricia-soledad-flores-s%C3%A1nchez-43b84418/',
'availability' => 'Inmediata | Presencial / Híbrida / Remota',
'focus' => 'Operaciones financieras, control interno y transformación digital',
],
'metrics' => [
['value' => '22+', 'label' => 'años de experiencia en banca y microfinanzas'],
['value' => '26', 'label' => 'agencias bajo liderazgo operativo nacional'],
['value' => '48', 'label' => 'personas a cargo en la estructura operativa'],
['value' => 'USD 5 M', 'label' => 'en operaciones mensuales de microcrédito'],
['value' => '28.000', 'label' => 'clientes coordinados en recaudación por canales internos y externos'],
],
'highlights' => [
['value' => '9/10', 'label' => 'calificación de gestión con resultado excelente en cuatro trimestres consecutivos'],
['value' => '74 %', 'label' => 'reducción de requerimientos de soporte desde Dirección por descentralización de procesos'],
['value' => '1,68 días', 'label' => 'tiempo de desembolso logrado en Q3 2025, mejor resultado del año'],
['value' => '75 %', 'label' => 'reducción del índice de rotación del área entre 2022 y 2026'],
],
'summary' => [
'title' => 'Perfil estratégico con enfoque en eficiencia, control y ejecución sostenible',
'body' => 'Actualmente lidera la operatividad de 26 agencias a nivel nacional y coordina aproximadamente 1.500 operaciones de microcrédito mensuales, por un monto cercano a USD 5 millones. También articula la recaudación de cerca de 28.000 clientes por canales internos y externos, integrando visión operativa, criterio financiero, disciplina de control y liderazgo sostenido del desempeño.',
],
'impact' => [
[
'title' => 'Transformación digital',
'body' => 'Lideró desembolsos por transferencia bancaria, integración de APIs bancarias y digitalización de expedientes y pagarés en 13 agencias.',
],
[
'title' => 'Automatización crítica',
'body' => 'Automatizó el seguro de desgravamen, desde el cálculo de prima hasta la liquidación contable, reduciendo tiempos de 17 a 10 días.',
],
[
'title' => 'Supervisión nacional',
'body' => 'Implementó una matriz de supervisión y certificación por agencia, elevando el índice de cumplimiento operativo del 65 % al 88 %.',
],
],
'experience' => [
[
'period' => 'Abr. 2022 – Presente',
'title' => 'Jefa de Operaciones',
'company' => 'Fundación FACES Microfinanzas',
'location' => 'Loja, Ecuador',
'lead' => 'Responsable del desempeño operativo nacional, cumplimiento del POA, control interno, digitalización de procesos y gestión de equipos.',
'bullets' => [
'Cumplimiento del POA: 98 % (Q2 2025), 96 % (Q3 2025), 100 % (Q4 2025) y 105 % (Q1 2026).',
'La transformación digital logró que los desembolsos por transferencia representen el 47,80 % del monto total en Q1 2026.',
'Redujo los requerimientos de soporte desde Dirección en un 74 %, de 1.489 a 382, mediante la descentralización de procesos hacia las agencias.',
'Automatizó el proceso del seguro de desgravamen con cálculo de prima, conciliación mensual, liquidación de siniestros y asientos contables automatizados.',
'Elevó el índice de cumplimiento operativo nacional del 65 % al 88 % con la matriz de supervisión y certificación por agencia.',
'Redujo el tiempo de atención del seguro de desgravamen de 17 a 10 días.',
'Redujo los tiempos de desembolso a 1,68 días en Q3 2025, mejor resultado del año frente a la meta institucional máxima de 2 días.',
'La efectividad de recuperación de mora alcanzó 90,60 % en Q1 2026, con USD 3,06 millones recuperados sobre USD 3,37 millones en mora.',
'Redujo el índice de rotación del área del 41,46 % al 10,24 % entre diciembre de 2022 y marzo de 2026 mediante mejoras salariales, remuneración variable, evaluación trimestral y un plan de carrera con certificación de 60 horas para asistentes de caja.',
'Participa en comités estratégicos, auditorías internas y externas, y en representación institucional ante organismos financieros internacionales de desarrollo.',
],
],
[
'period' => 'Feb. 2021 – Mar. 2022',
'title' => 'Supervisora de Operaciones',
'company' => 'Fundación FACES Microfinanzas',
'location' => 'Loja, Ecuador',
'lead' => 'Supervisó operaciones de 18 agencias a nivel nacional y regularizó procesos críticos inexistentes o incompletos.',
'bullets' => [
'Reconstruyó desde cero las bases de cálculo del seguro de desgravamen y regularizó pagos pendientes desde enero de 2021.',
'Elaboró el instructivo de constitución de garantías hipotecarias, proceso que no existía previamente en la institución.',
'Inició el proyecto de automatización de pagos por depósitos y transferencias.',
],
],
[
'period' => '2019 – 2021',
'title' => 'Actualización y formación profesional',
'company' => 'Formación independiente',
'location' => 'Ecuador',
'lead' => 'Etapa enfocada en fortalecimiento técnico y actualización profesional tras su salida de Mutualista Pichincha.',
'bullets' => [
'SAP FI (30 h).',
'Excel Empresarial (24 h).',
'Prevención de Lavado de Activos — UAFE (5 h).',
],
],
[
'period' => 'Ago. 2004 – May. 2019',
'title' => 'Oficial Operativa Regional / Jefa Operativa',
'company' => 'Mutualista Pichincha',
'location' => 'Loja, Ecuador',
'lead' => 'Trayectoria ascendente de 15 años desde Cajera hasta Jefa Operativa Regional.',
'bullets' => [
'Reconocida a nivel nacional por excelencia en control operativo.',
'La agencia se mantuvo consistentemente por encima del 80 % en gestión de excepciones de crédito hipotecario.',
'Administró proyecto inmobiliario institucional en Loja.',
'Gestionó alertas ALA/FT con 100 % de cumplimiento en comités trimestrales y auditorías internas sin observaciones significativas de forma sostenida.',
],
],
[
'period' => 'Ene. 2002 – Nov. 2003',
'title' => 'Cajera',
'company' => 'Cooperativa de Ahorro y Crédito El Porvenir',
'location' => 'Loja, Ecuador',
'lead' => 'Inicio de carrera en el frente operativo financiero con disciplina de caja y apoyo contable.',
'bullets' => [
'Gestión de caja sin faltantes ni sobrantes.',
'Contrato renovado de forma anticipada a los tres meses.',
'Apoyo al área de Contabilidad y reemplazo temporal de la contadora durante vacaciones.',
],
],
],
'education' => [
[
'year' => '2021',
'title' => 'Máster Universitario en Dirección y Planificación Financiera',
'institution' => 'Universidad Europea Miguel de Cervantes — Valladolid, España',
'detail' => 'Título reconocido por la SENECYT — Ecuador, febrero de 2023 | Reg. 2021/394270.',
],
[
'year' => '2007',
'title' => 'Ingeniera en Administración en Banca y Finanzas',
'institution' => 'Universidad Técnica Particular de Loja (UTPL)',
'detail' => '',
],
],
'certifications' => [
['year' => '2026', 'title' => 'Mujeres que Lideran: Alta Gerencia', 'issuer' => 'Foro UNIR — Universidad Internacional de La Rioja'],
['year' => '2025', 'title' => 'Programa Internacional de Habilidades Gerenciales (48 h)', 'issuer' => 'IDEASGROUP — Ecuador / México'],
['year' => '2025', 'title' => 'Transforma tu Trabajo con Inteligencia Artificial (12 h)', 'issuer' => 'Red de Instituciones Financieras de Desarrollo'],
['year' => '2025', 'title' => 'Excel: Básico a Dashboard (40 h)', 'issuer' => 'DMARURI Training & Consulting'],
['year' => '2024', 'title' => 'Administración por Procesos — BPM (64 h)', 'issuer' => 'Escuela Politécnica Nacional — CEC'],
['year' => '2023', 'title' => 'Formación de Formadores (80 h)', 'issuer' => 'Ministerio del Trabajo del Ecuador | Certificación vigente hasta 2028'],
['year' => '2023', 'title' => 'Power BI (40 h) | Indicadores Clave de Desempeño — KPI (12 h)', 'issuer' => 'Liderazgo Capacitación & Eventos'],
['year' => '2020', 'title' => 'Gestión Financiera ERP SAP FI — Key User (30 h)', 'issuer' => 'Socio SAP certificado'],
['year' => '2020', 'title' => 'Prevención de Lavado de Activos y Financiamiento de Delitos', 'issuer' => 'UAFE — Ecuador'],
['year' => '2013', 'title' => 'Tributación Integral (36 h)', 'issuer' => 'Universidad Técnica Particular de Loja'],
],
'competencies' => [
'Finanzas y Operaciones' => [
['name' => 'Gestión de operaciones financieras', 'pct' => 98],
['name' => 'Conciliación bancaria', 'pct' => 97],
['name' => 'Originación de crédito', 'pct' => 95],
['name' => 'Gestión de cartera', 'pct' => 95],
['name' => 'Control interno', 'pct' => 96],
['name' => 'Tesorería operativa', 'pct' => 94],
['name' => 'Microfinanzas', 'pct' => 97],
['name' => 'ALA/FT', 'pct' => 95],
],
'Tecnología y Procesos' => [
['name' => 'Transformación digital', 'pct' => 92],
['name' => 'BPM', 'pct' => 88],
['name' => 'Integración de APIs / Web Services','pct' => 82],
['name' => 'SAP FI', 'pct' => 80],
['name' => 'Excel avanzado y dashboards', 'pct' => 90],
['name' => 'Power BI', 'pct' => 85],
['name' => 'IA aplicada', 'pct' => 78],
['name' => 'Core financiero', 'pct' => 93],
],
'Liderazgo' => [
['name' => 'Equipos de hasta 48 personas', 'pct' => 95],
['name' => 'Remuneración variable', 'pct' => 90],
['name' => 'Gestión del cambio', 'pct' => 92],
['name' => 'Formación de formadores', 'pct' => 88],
['name' => 'Diversidad, equidad e inclusión', 'pct' => 87],
],
],
'volunteer' => [
'title' => 'Voluntaria — Hogar San Camilo de Lellis',
'year' => '2025',
'body' => 'Hogar de niñas con discapacidad | Loja, Ecuador. Alineado a la visión y misión de responsabilidad social de la Fundación FACES Microfinanzas.',
],
'additional' => [
'Idiomas' => 'Español nativo | Inglés básico en formación activa',
'Disponibilidad' => 'Inmediata | Presencial / Híbrida / Remota',
'C.I.' => '1103591648',
'Fecha de nacimiento'=> '3 de agosto de 1980',
'Web profesional' => 'pflores.linkedcorp.net',
],
];
}
@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
// ---------------------------------------------------------------------------
// Visit tracking — Apex CV theme
// ---------------------------------------------------------------------------
function apex_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 apex_track_visit(): void
{
if (! apex_should_track_visit()) {
return;
}
$requestUri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
$visitorHash = md5($requestUri . '|' . apex_get_client_ip() . '|' . gmdate('Y-m-d-H'));
$transientKey = 'apex_visit_' . $visitorHash;
if (get_transient($transientKey)) {
return;
}
set_transient($transientKey, '1', HOUR_IN_SECONDS);
$total = (int) get_option('apex_total_visits', 0);
update_option('apex_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, '_apex_views', true);
update_post_meta($frontPage, '_apex_views', $views + 1);
}
return;
}
$postId = get_queried_object_id();
if ($postId > 0) {
$views = (int) get_post_meta($postId, '_apex_views', true);
update_post_meta($postId, '_apex_views', $views + 1);
}
}
add_action('template_redirect', 'apex_track_visit');
function apex_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';
}
// ---------------------------------------------------------------------------
// Dashboard widget
// ---------------------------------------------------------------------------
function apex_register_visits_dashboard_widget(): void
{
wp_add_dashboard_widget(
'apex_visits_widget',
__('Apex CV: Visitas', 'apex'),
'apex_render_visits_dashboard_widget'
);
}
add_action('wp_dashboard_setup', 'apex_register_visits_dashboard_widget');
function apex_render_visits_dashboard_widget(): void
{
$total = (int) get_option('apex_total_visits', 0);
$frontPage = (int) get_option('page_on_front');
$frontViews = $frontPage > 0 ? (int) get_post_meta($frontPage, '_apex_views', true) : 0;
?>
<div class="apex-visits-widget">
<p><strong><?php esc_html_e('Visitas totales registradas:', 'apex'); ?></strong> <?php echo esc_html((string) $total); ?></p>
<p><strong><?php esc_html_e('Visitas a la portada:', 'apex'); ?></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.', 'apex'); ?></p>
</div>
<?php
}
// ---------------------------------------------------------------------------
// Post/page views column in admin
// ---------------------------------------------------------------------------
function apex_add_views_column(array $columns): array
{
$columns['apex_views'] = __('Visitas', 'apex');
return $columns;
}
add_filter('manage_pages_columns', 'apex_add_views_column');
add_filter('manage_posts_columns', 'apex_add_views_column');
function apex_render_views_column(string $column, int $postId): void
{
if ($column !== 'apex_views') {
return;
}
echo esc_html((string) ((int) get_post_meta($postId, '_apex_views', true)));
}
add_action('manage_pages_custom_column', 'apex_render_views_column', 10, 2);
add_action('manage_posts_custom_column', 'apex_render_views_column', 10, 2);
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
// Fallback template — the theme uses front-page.php for all CV content.
get_header();
?>
<div class="ax-main">
<div class="ax-content-wrap">
<?php if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>
<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
<h1><?php the_title(); ?></h1>
<div class="entry-content">
<?php the_content(); ?>
</div>
</article>
<?php endwhile; ?>
<?php else : ?>
<p><?php esc_html_e('No se encontró contenido.', 'apex'); ?></p>
<?php endif; ?>
</div>
</div>
<?php get_footer(); ?>
@@ -0,0 +1,9 @@
/*
Theme Name: Apex CV
Theme URI: https://pflores.linkedcorp.net
Description: Tema oscuro y elegante. Sidebar fijo izquierdo, paleta dark con acento cian.
Version: 1.0.0
Requires at least: 6.8
Requires PHP: 8.1
Text Domain: apex
*/
+1
View File
@@ -0,0 +1 @@
/home/mgonzalez/desarrollo/web/pflores.linkedcorp.net/frontend/lumina-cv-theme
+1
View File
@@ -0,0 +1 @@
/home/mgonzalez/desarrollo/web/pflores.linkedcorp.net/frontend/persona-cv-theme