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
+53 -3
View File
@@ -1,8 +1,58 @@
backend/wordpress/wp-config-local.php
# ── Secretos y entorno ──────────────────────────────────────
.env
.env.*
!.env.example
.token_gitea
LOCAL_DEPLOY_CONTEXT.md
# ── WordPress core (no versionar — viene de wordpress.org) ──
backend/wordpress/wp-admin/
backend/wordpress/wp-includes/
backend/wordpress/wp-activate.php
backend/wordpress/wp-blog-header.php
backend/wordpress/wp-comments-post.php
backend/wordpress/wp-cron.php
backend/wordpress/wp-links-opml.php
backend/wordpress/wp-load.php
backend/wordpress/wp-login.php
backend/wordpress/wp-mail.php
backend/wordpress/wp-settings.php
backend/wordpress/wp-signup.php
backend/wordpress/wp-trackback.php
backend/wordpress/xmlrpc.php
backend/wordpress/index.php
backend/wordpress/readme.html
backend/wordpress/license.txt
# ── wp-content: solo versionar lo propio ────────────────────
backend/wordpress/wp-content/uploads/
backend/wordpress/wp-content/cache/
backend/wordpress/wp-content/upgrade/
backend/wordpress/wp-content/backups/
.env
.token_gitea
backend/wordpress/wp-content/debug.log
backend/wordpress/wp-content/languages/
# Temas por defecto de WordPress (no son nuestros)
backend/wordpress/wp-content/themes/twentytwentythree/
backend/wordpress/wp-content/themes/twentytwentyfour/
backend/wordpress/wp-content/themes/twentytwentyfive/
# Plugins descargados de WordPress.org (no son nuestros)
backend/wordpress/wp-content/plugins/cmb2/
backend/wordpress/wp-content/plugins/wp-mail-smtp/
backend/wordpress/wp-content/plugins/advanced-custom-fields/
backend/wordpress/wp-content/plugins/index.php
# ── Sistema / editor ────────────────────────────────────────
.DS_Store
Thumbs.db
.idea/
.vscode/*
!.vscode/extensions.json
*.swp
*.swo
.claude/
# ── Archivos temporales ──────────────────────────────────────
/tmp/
*.tmp
+38
View File
@@ -0,0 +1,38 @@
# Changelog — pflores.linkedcorp.net
Formato: `X.Y.Z` según `VERSIONADO.md`
---
## [1.0.0] — 2026-06-05
### Lanzamiento inicial completo
**Temas WordPress (4)**
- `persona-cv` — Personage/Pixflow style: hero fullscreen, secciones alternas, timeline centrado, barras de skills, Raleway + Open Sans. Tema activo.
- `lumina` — Diseño claro alegre, paleta teal+ámbar, sidebar izquierdo, foto circular, Plus Jakarta Sans.
- `apex-cv` — Dark theme serio, sidebar fijo izquierdo, acento cian eléctrico, Space Grotesk.
- `cvolio` — Glassmorphism oscuro, paleta café/tostado, sidebar derecho.
**Gestión de contenido**
- Plugin propio `pflores-cv-fields` con CMB2 (100 % gratuito).
- Panel admin **"CV Patricia Flores"** con 5 sub-páginas editables: Perfil, Experiencia, Formación, Competencias, Métricas/Impacto.
- Todos los datos del CV editables sin tocar código.
**Generador de PDF**
- Endpoint `/?cv_pdf=1` genera PDF dinámico con Chromium headless.
- Diseño fiel al documento DOCX original: columna única, navy #1B3A5C, secciones en mayúsculas con borde inferior.
- Se actualiza automáticamente al cambiar datos en el admin.
- Botón "Descargar CV PDF" en todos los temas apunta al mismo endpoint.
**Infraestructura**
- Servidor PHP integrado en `0.0.0.0:8081` (accesible desde red local).
- URL dinámica en `wp-config.php` — funciona con cualquier IP y detrás de proxy SSL (NPM).
- Auto-updates de WordPress desactivados.
- WP Mail SMTP instalado (pendiente configuración SMTP).
**Contenido del CV cargado**
- 5 cargos con 23 bullets completos.
- 2 títulos académicos, 10 certificaciones.
- 3 grupos de competencias (15 habilidades con porcentajes).
- Métricas, highlights, impacto, voluntariado y datos adicionales.
Binary file not shown.
+90
View File
@@ -0,0 +1,90 @@
# Deploy Web — pflores.linkedcorp.net
## Previo a cualquier push
- [ ] `git status` — sin `.env` ni `.token_gitea` en staging
- [ ] Site responde: `curl -s http://127.0.0.1:8081/ | grep -c "Patricia Soledad"` → debe retornar ≥ 1
- [ ] PDF funciona: `curl -sI http://127.0.0.1:8081/?cv_pdf=1 | grep Content-Type` → `application/pdf`
- [ ] Si hubo cambios funcionales: actualizar `VERSION` y `CHANGELOG.md`
## Pasos — ciclo normal de cambios
```bash
# 1. Ver qué cambió
git status
git diff --stat
# 2. Agregar archivos (selectivo, nunca git add -A sin revisar)
git add frontend/persona-cv-theme/
git add frontend/lumina-cv-theme/
git add frontend/cvfolio-theme/
git add backend/wordpress/wp-content/themes/apex-cv/
git add backend/wordpress/wp-content/plugins/pflores-cv-fields/
git add backend/wordpress/wp-config.php
git add README.md CHANGELOG.md VERSION PROMPT_IA.md
# ... agregar solo lo que corresponde
# 3. Commit
git commit -m "fix: descripción del cambio"
# Prefijos: feat: / fix: / docs: / chore: / style:
# 4. Push a Gitea
git push origin main
```
## Credenciales para git push
Las credenciales de Gitea se guardan en `.token_gitea` (nunca versionado):
```text
# Token gitea
usuario: mgonzalez
token: [tu personal access token de gitea.linkedcorp.net]
# Gitea web
user: mgonzalez
passwd: [contraseña de acceso]
```
Si Git pide usuario y contraseña al hacer push, usar:
- **Usuario:** `mgonzalez`
- **Contraseña:** el personal access token (no la contraseña web)
Crear token en Gitea: `https://gitea.linkedcorp.net/user/settings/applications`
### Guardar credenciales para no escribirlas cada vez
```bash
git config credential.helper store
# La primera vez que hagas push te pedirá usuario+token y los guardará
```
## Remoto configurado
```
origin https://gitea.linkedcorp.net/mgonzalez/pflores.linkedcorp.net.git
```
## No tocar durante el deploy
- `.env` — credenciales de BD y servidor
- Base de datos (no hacer migrate sin confirmar)
- El proceso PHP del servidor (no reiniciar en producción sin verificar)
## Si el servidor necesita actualizar el código
Este proyecto corre directamente desde el directorio de trabajo. Si hay un clon separado en producción:
```bash
# En el servidor de producción
cd /ruta/al/proyecto
git pull origin main
# Reiniciar el proceso PHP si es necesario
```
## Verificación post-push
```bash
# Confirmar que Gitea recibió los cambios
# Ir a: https://gitea.linkedcorp.net/mgonzalez/pflores.linkedcorp.net
```
+85
View File
@@ -0,0 +1,85 @@
# Producción — pflores.linkedcorp.net
## Estado actual
- Versión: `1.0.0`
- Estado: `operativo`
- Última validación: `2026-06-05`
## Acceso público
- URL: `https://pflores.linkedcorp.net`
- Admin: `https://pflores.linkedcorp.net/wp-admin`
- PDF: `https://pflores.linkedcorp.net/?cv_pdf=1`
## Servidor
- IP local: `192.168.222.40`
- Puerto WordPress: `8081` (PHP built-in server, `0.0.0.0:8081`)
- Proxy: Nginx Proxy Manager → `http://192.168.222.40:8081`
- SSL: certificado en NPM, Force SSL activado, `X-Forwarded-Proto: https`
## Proceso WordPress
```bash
# Ver si está corriendo
lsof -i tcp:8081
# Arrancar
bash /home/mgonzalez/desarrollo/web/pflores.linkedcorp.net/backend/start-wordpress.sh &
# Detener
kill $(lsof -ti tcp:8081)
```
## Base de datos
- Host: `192.168.222.20`
- Base: `paty.wordpress`
- Usuario: `paty.wordpress`
- Prefijo de tablas: `cv_`
- Credenciales completas: ver `.env`
## Plugins activos
| Plugin | Archivo | Función |
|--------|---------|---------|
| CMB2 | `cmb2/init.php` | Campos personalizados del CV |
| Pflores CV Fields | `pflores-cv-fields/pflores-cv-fields.php` | Admin + PDF |
| WP Mail SMTP | `wp-mail-smtp/wp_mail_smtp.php` | Email (pendiente config) |
Verificar plugins activos:
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "SELECT option_value FROM cv_options WHERE option_name='active_plugins';" 2>/dev/null
```
## Tema activo
- Slug: `persona-cv`
- Fuente: `frontend/persona-cv-theme/` (symlink)
## Repositorio Git
- Remoto: `https://gitea.linkedcorp.net/mgonzalez/pflores.linkedcorp.net.git`
- Rama principal: `main`
- Credenciales: `~/.gitconfig` o `.token_gitea` (no versionado)
## Comandos de verificación rápida
```bash
# Site responde
curl -s http://127.0.0.1:8081/ | grep -c "Patricia Soledad"
# PDF funciona
curl -sI http://127.0.0.1:8081/?cv_pdf=1 | grep Content-Type
# Limpiar caché WP si algo no se actualiza
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "DELETE FROM cv_options WHERE option_name LIKE '_site_transient_%' OR option_name LIKE '_transient_%';" 2>/dev/null
```
## Pendientes
- [ ] Configurar SMTP en WP Mail SMTP (Gmail u otro proveedor)
- [ ] Revisar si el proceso WordPress arranca automáticamente al reiniciar el servidor
+756
View File
@@ -0,0 +1,756 @@
# PROMPT_IA — Guía completa del proyecto pflores.linkedcorp.net
> Documento de referencia para asistentes de IA. Léelo completo antes de cualquier tarea.
> Última actualización: junio 2026.
---
## 1. ¿Qué es este proyecto?
Sitio web personal de **hoja de vida / currículum ejecutivo** de **Patricia Soledad Flores Sánchez**, gerente de operaciones financieras con 22+ años en banca y microfinanzas (Ecuador).
- URL pública: `https://pflores.linkedcorp.net`
- Stack: **WordPress 6.x** + PHP 8.4 + MariaDB + **CMB2** (campos personalizados, 100 % gratuito)
- Se ejecuta con el servidor PHP incorporado (`php -S`) en el puerto **8081**
- SSL terminado por **Nginx Proxy Manager** → envía `X-Forwarded-Proto: https`
- IP local del servidor: `192.168.222.40`
---
## 2. Estructura de directorios
```
pflores.linkedcorp.net/
├── backend/
│ ├── start-wordpress.sh # Arrancar el servidor PHP
│ └── wordpress/ # Instalación WordPress
│ ├── wp-config.php # Config dinámica (URL, auto-updates OFF)
│ └── wp-content/
│ ├── plugins/
│ │ ├── cmb2/ # CMB2 — campos personalizados (GRATIS)
│ │ └── pflores-cv-fields/
│ │ ├── pflores-cv-fields.php # Menú admin + campos CMB2
│ │ ├── pdf-generator.php # Generador PDF con Chromium
│ │ └── populate-data.php # Sembrador inicial (ejecutar 1 vez)
│ └── themes/
│ ├── persona-cv/ # ACTIVO — Personage-style, rojo, timeline central
│ ├── apex-cv/ # Dark, cian, sidebar fijo izquierdo
│ ├── lumina -> ../../frontend/lumina-cv-theme # symlink
│ ├── cvolio -> ../../frontend/cvfolio-theme # symlink
│ └── ...
├── frontend/
│ ├── persona-cv-theme/ # Fuente del tema activo (usa symlink persona-cv)
│ │ ├── style.css # Solo cabecera del tema
│ │ ├── functions.php # Setup, enqueue, helpers
│ │ ├── header.php # Header con nav y botón PDF
│ │ ├── footer.php
│ │ ├── front-page.php # PLANTILLA PRINCIPAL — toda la página CV
│ │ ├── index.php # Fallback
│ │ ├── assets/css/theme.css # Estilos completos
│ │ ├── assets/js/theme.js # JS (nav, scroll spy, animaciones)
│ │ ├── assets/images/ # patricia-flores-profile.jpeg, qr.png
│ │ ├── assets/docs/ # CV_Patricia_Flores_Sanchez_ES_FINAL.docx
│ │ └── inc/
│ │ ├── cv-data.php # Fallback hardcodeado (función persona_cv_data())
│ │ ├── customizer.php # Campos Customizer (mínimos)
│ │ └── visits.php # Contador de visitas
│ ├── lumina-cv-theme/ # Tema claro teal+ámbar, sidebar izquierdo
│ ├── cvfolio-theme/ # Tema oscuro glassmorphism (original)
│ └── apex-cv-theme/ # OBSOLETO — usar wp-content/themes/apex-cv
├── docs/
│ └── instalacion-wordpress.md
├── .env # Variables de entorno (DB, puerto, etc.)
├── PROMPT_IA.md # ESTE ARCHIVO
└── README.md
```
> **NOTA IMPORTANTE:** El tema `apex-cv` vive directamente en
> `backend/wordpress/wp-content/themes/apex-cv/` (directorio real, no symlink)
> porque el directorio `frontend/apex-cv-theme/` tiene un problema de persistencia.
> Los demás temas activos usan symlinks desde `wp-content/themes/` hacia `frontend/`.
---
## 3. Base de datos
```
Host: 192.168.222.20
DB: paty.wordpress
Usuario: paty.wordpress
Password: paty.wordpress
Prefijo: cv_ → tabla principal: cv_options
```
Conexión de prueba:
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0
```
---
## 4. Arrancar / reiniciar el servidor
```bash
# Ver si está corriendo
lsof -i tcp:8081
# Matar proceso existente
kill $(lsof -ti tcp:8081)
# Arrancar (lee variables de .env automáticamente)
bash backend/start-wordpress.sh &
```
El servidor escucha en `0.0.0.0:8081` (todas las interfaces) gracias a `WP_HOST=0.0.0.0` en `.env`.
---
## 5. Plugins activos y su función
| Plugin | Propósito | Gratuito |
|--------|-----------|----------|
| `cmb2/init.php` | Campos personalizados en el admin (repeaters, options pages) | ✓ GPL |
| `pflores-cv-fields/pflores-cv-fields.php` | Páginas de opciones + función `pflores_get_cv_data()` + PDF | ✓ propio |
**Activar / verificar plugins en la BD:**
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "SELECT option_value FROM cv_options WHERE option_name='active_plugins';"
```
El valor correcto debe ser:
```
a:2:{i:0;s:13:"cmb2/init.php";i:1;s:39:"pflores-cv-fields/pflores-cv-fields.php";}
```
Si no, corregirlo:
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "UPDATE cv_options SET option_value='a:2:{i:0;s:13:\"cmb2/init.php\";i:1;s:39:\"pflores-cv-fields/pflores-cv-fields.php\";}' WHERE option_name='active_plugins';"
```
> ⚠ La serialización PHP es estricta: `s:N:` donde N es la longitud exacta en bytes.
> Siempre genera el valor con `php -r "echo serialize(['cmb2/init.php','pflores-cv-fields/pflores-cv-fields.php']);"`.
---
## 6. Cambiar el tema activo
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "UPDATE cv_options SET option_value='SLUG_TEMA' WHERE option_name IN('template','stylesheet');"
```
| Slug | Tema |
|------|------|
| `persona-cv` | **Activo por defecto** — Personage-style, secciones alternas, rojo |
| `lumina` | Claro, teal+ámbar, sidebar izquierdo, foto circular |
| `apex-cv` | Oscuro, cian eléctrico, sidebar fijo |
| `cvolio` | Oscuro glassmorphism, paleta café/tostado |
---
## 7. Arquitectura de datos — CMB2
Todos los datos del CV se almacenan en `wp_options` (prefijo `cv_`) como arrays PHP serializados. Se acceden con `get_option('clave')`.
| Clave `wp_options` | Contenido |
|--------------------|-----------|
| `pflores_perfil` | Nombre, cargo, intro, resumen, contacto, foto |
| `pflores_experiencia` | Array de cargos con bullets |
| `pflores_formacion` | Títulos académicos + certificaciones |
| `pflores_competencias` | Grupos de habilidades con porcentajes |
| `pflores_extras` | Métricas, highlights, impacto, voluntariado |
**Función central de lectura:**
```php
pflores_get_cv_data(): array
// Retorna la misma estructura que todos los temas esperan.
// Si CMB2 no está disponible, devuelve pflores_cv_fallback_data().
```
**Estructura del array retornado:**
```php
[
'hero' => ['name','role','role2','intro','location','email',
'phone','linkedin','availability','focus','languages',
'born','id','web','eyebrow'],
'photo' => 'URL de la imagen de perfil (o vacío)',
'summary' => ['title','body'],
'metrics' => [['value','suffix','label'], ...], // 5 contadores
'highlights' => [['value','suffix','label'], ...], // 4 logros grandes
'impact' => [['title','body'], ...], // 3 áreas
'experience' => [['period','title','company','location','lead','bullets'=>[...]], ...],
'education' => [['year','title','institution','detail'], ...],
'certifications' => [['year','title','issuer'], ...],
'competencies' => ['Grupo' => [['name','pct'], ...], ...],
'volunteer' => ['title','year','body'],
'additional' => ['Idiomas'=>'...','Disponibilidad'=>'...','C.I.'=>'...', ...],
]
```
---
## 8. Editar datos del CV desde el admin
URL del admin: `https://pflores.linkedcorp.net/wp-admin`
- Usuario: `pflores`
- Contraseña: en `.env` como `WP_ADMIN_PASSWORD`
En el menú lateral aparece **"CV Patricia Flores"** con sub-páginas:
- **Perfil** → datos personales, foto, disponibilidad
- **Experiencia** → cargos (repeater, arrastrable)
- **Formación** → títulos + certificaciones
- **Competencias** → grupos de habilidades con porcentajes
- **Métricas / Impacto** → contadores, logros, áreas de impacto, voluntariado
---
## 9. Agregar un nuevo campo al CV
### 9a. Campo simple (texto, email, URL)
**Paso 1** — Registrar el campo en `pflores-cv-fields.php` dentro de la función `pflores_register_pages()`:
```php
// Ejemplo: añadir "Twitter/X" al perfil
$perfil->add_field([
'id' => 'pf_twitter',
'name' => 'Twitter / X URL',
'type' => 'text_url',
]);
```
**Paso 2** — Añadir la lectura en `pflores_get_cv_data()`:
```php
'hero' => [
// ... campos existentes ...
'twitter' => $perfil['pf_twitter'] ?? '',
],
```
**Paso 3** — Usarlo en el `front-page.php` de cada tema:
```php
$twitter = $hero['twitter'] ?? '';
if ($twitter) : ?>
<a href="<?php echo esc_url($twitter); ?>">Twitter</a>
<?php endif; ?>
```
**Paso 4** — Si el campo debe aparecer en el PDF, añadirlo en `pdf-generator.php` dentro de `pflores_build_cv_html()`.
---
### 9b. Nuevo grupo repetible (como experiencia o certificaciones)
**Paso 1** — Registrar un nuevo CMB2 box con group en `pflores_register_pages()`:
```php
$premios = new_cmb2_box([
'id' => 'pflores_premios',
'title' => 'Premios y reconocimientos',
'object_types' => ['options-page'],
'option_key' => 'pflores_premios', // nombre de la opción en BD
'parent_slug' => 'pflores_perfil', // sub-menú bajo el menú principal
'menu_title' => 'Premios',
'save_button' => 'Guardar cambios',
'capability' => 'manage_options',
]);
$gr = $premios->add_field([
'id' => 'pf_awards',
'type' => 'group',
'options' => ['group_title' => 'Premio {#}', 'add_button' => 'Añadir premio',
'remove_button' => 'Eliminar', 'sortable' => true, 'closed' => true],
]);
$premios->add_group_field($gr, ['id' => 'year', 'name' => 'Año', 'type' => 'text']);
$premios->add_group_field($gr, ['id' => 'title', 'name' => 'Título', 'type' => 'text']);
$premios->add_group_field($gr, ['id' => 'org', 'name' => 'Organización','type' => 'text']);
```
**Paso 2** — Leer los datos en `pflores_get_cv_data()`:
```php
$premiosOpts = get_option('pflores_premios', []);
$awards = [];
foreach ((array) ($premiosOpts['pf_awards'] ?? []) as $item) {
$awards[] = ['year' => $item['year'] ?? '', 'title' => $item['title'] ?? '', 'org' => $item['org'] ?? ''];
}
// Agregar 'awards' => $awards al array retornado
```
**Paso 3** — Renderizarlo en `front-page.php` de cada tema.
**Paso 4** — Poblar datos iniciales añadiendo al `populate-data.php`:
```php
update_option('pflores_premios', [
'pf_awards' => [
['year' => '2025', 'title' => 'Premio Excelencia', 'org' => 'Fundación FACES'],
],
]);
```
---
## 10. Modificar datos existentes
**Opción A** — Desde el admin de WordPress (recomendado):
- Ir a `wp-admin` → **CV Patricia Flores** → sub-página correspondiente → editar → Guardar.
**Opción B** — Directo en BD (para migraciones o correcciones masivas):
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 -e "
UPDATE cv_options SET option_value = 'Nuevo valor' WHERE option_name = 'pflores_perfil';
"
```
> Para campos dentro de arrays (repeaters), es más fácil editar desde el admin o re-ejecutar `populate-data.php`.
**Re-ejecutar el poblador de datos** (si se necesita resetear todo):
```
https://pflores.linkedcorp.net/wp-content/plugins/pflores-cv-fields/populate-data.php?key=pflores2024
```
> ⚠ Esto sobreescribe TODOS los datos con los valores del archivo `populate-data.php`.
---
## 11. Crear un nuevo tema
### Convenciones obligatorias
1. **Directorio**: Crear en `frontend/NOMBRE-cv-theme/` con subdirectorios `assets/{css,js,images,docs}` e `inc/`.
2. **Symlink**: `ln -s /ruta/absoluta/frontend/NOMBRE-cv-theme /ruta/wordpress/wp-content/themes/NOMBRE-cv`
3. **Text Domain**: usar `nombre` (minúsculas, sin guiones).
4. **Prefijo de funciones**: `nombre_` (ej: `nova_setup()`, `nova_render_nav()`).
5. **Prefijo CSS**: `nv-` (2-3 letras, sin conflictos con `pn-`, `lm-`, `ax-`, `cv-`).
### Archivos mínimos requeridos
```
NOMBRE-cv-theme/
├── style.css # Cabecera del tema (Theme Name, Text Domain, Version...)
├── functions.php # apex_setup, enqueue, nav helpers
├── header.php # <html><head> + nav + botón PDF
├── footer.php # </body></html>
├── front-page.php # TODA la lógica y HTML del CV
├── index.php # Fallback vacío o mínimo
└── inc/
├── cv-data.php # Función NOMBRE_cv_data() con fallback hardcodeado
├── customizer.php # Opcional, puede estar vacío
└── visits.php # Contador de visitas (copiar de otro tema y renombrar funciones)
```
### Estructura obligatoria de `functions.php`
```php
<?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';
function NOMBRE_setup(): void { /* add_theme_support, register_nav_menus */ }
add_action('after_setup_theme', 'NOMBRE_setup');
function NOMBRE_enqueue_assets(): void {
// Cargar Google Font + CSS + JS
wp_enqueue_style('NOMBRE-fonts', 'https://fonts.googleapis.com/...', [], null);
wp_enqueue_style('NOMBRE-theme', get_template_directory_uri() . '/assets/css/theme.css', ['NOMBRE-fonts'], '1.0.0');
wp_enqueue_script('NOMBRE-theme', get_template_directory_uri() . '/assets/js/theme.js', [], '1.0.0', true);
}
add_action('wp_enqueue_scripts', 'NOMBRE_enqueue_assets');
function NOMBRE_render_nav(): string { /* nav links con IDs de sección */ }
```
### Estructura obligatoria de `front-page.php`
```php
<?php
declare(strict_types=1);
get_header();
// ► SIEMPRE usar esta línea para obtener los datos:
$cv = function_exists('pflores_get_cv_data') ? pflores_get_cv_data() : NOMBRE_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'];
// Foto de perfil: ACF primero, si no la imagen local
$acfPhoto = $cv['photo'] ?? '';
$profileImage = $acfPhoto ?: get_template_directory_uri() . '/assets/images/patricia-flores-profile.jpeg';
// Archivos
$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';
// Variables de conveniencia
$heroName = $hero['name'];
$heroRole = $hero['role'];
$heroEmail = $hero['email'];
$heroPhone = $hero['phone'];
$heroLocation = $hero['location'];
$linkedin = $hero['linkedin'];
/* ... HTML del tema aquí ... */
get_footer();
```
### Iteración sobre competencias (compatibilidad CMB2 + fallback)
Los temas deben manejar ambos formatos de competencias:
```php
<?php foreach ($competencies as $group => $items) : ?>
<h3><?php echo esc_html($group); ?></h3>
<?php foreach ($items as $item) :
// ► SIEMPRE usar esta normalización:
$skillName = is_array($item) ? ($item['name'] ?? '') : (string) $item;
$skillPct = is_array($item) ? ($item['pct'] ?? 80) : 80;
?>
<span><?php echo esc_html($skillName); ?></span>
<?php // Si el tema muestra barras de progreso: ?>
<div style="width:<?php echo esc_attr((string)$skillPct); ?>%"></div>
<?php endforeach; ?>
<?php endforeach; ?>
```
### Botón de descarga PDF (obligatorio en todos los temas)
```php
<!-- En header.php o sidebar, reemplazar cualquier botón de "Imprimir" por: -->
<a class="btn-pdf" href="<?php echo esc_url(home_url('/?cv_pdf=1')); ?>" target="_blank">
Descargar CV PDF
</a>
```
### `inc/cv-data.php` — función de fallback
Debe contener **todos los datos reales** de Patricia Flores, idénticos a los que están en la BD. Copiar la función de otro tema y renombrar a `NOMBRE_cv_data()`.
> ⚠ Las competencias del fallback deben tener formato array:
> `['name' => 'Gestión operativa', 'pct' => 95]`
### Activar el nuevo tema
```bash
# 1. Crear el symlink
ln -s /ruta/absoluta/frontend/NOMBRE-cv-theme \
/ruta/absoluta/backend/wordpress/wp-content/themes/NOMBRE-cv
# 2. Activar en la BD
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "UPDATE cv_options SET option_value='NOMBRE-cv' WHERE option_name IN('template','stylesheet');"
```
> ⚠ **Evitar el slug `apex`** (existe un tema comercial con ese nombre en WordPress.org).
> Usar siempre slugs compuestos: `nova-cv`, `slate-cv`, `forma-cv`, etc.
---
## 12. Crear / modificar menús de navegación
Los menús de navegación son listas de anclas (`#seccion`) dentro del mismo tema. No se usan menús de WordPress por defecto.
### Modificar los ítems del menú en un tema
En `functions.php` de cada tema, hay una función `NOMBRE_render_nav()`:
```php
function persona_render_nav(): string {
$items = [
'#sobre-mi' => 'Sobre mí',
'#impacto' => 'Impacto',
'#experiencia' => 'Experiencia',
'#formacion' => 'Formación',
'#competencias' => 'Competencias',
'#certificaciones' => 'Certificaciones',
'#contacto' => 'Contacto',
];
// Para añadir un ítem: '#premios' => 'Premios',
// Para eliminar: quitar la línea correspondiente
// Para reordenar: cambiar el orden del array
...
}
```
El `href` debe coincidir con el `id` de la sección en `front-page.php`:
```html
<section id="premios"> <!-- El ID debe ser igual al href sin el # -->
```
### Usar menús de WordPress (avanzado)
Si se registra un menú en `functions.php`:
```php
register_nav_menus(['primary' => __('Menú principal', 'persona')]);
```
Se puede asignar desde `wp-admin` → **Apariencia → Menús** y el tema lo detectará automáticamente si `has_nav_menu('primary')` retorna `true`.
---
## 13. Generador de PDF
**Endpoint:** `https://pflores.linkedcorp.net/?cv_pdf=1`
**Cómo funciona** (`pdf-generator.php`):
1. Se activa con `get_query_var('cv_pdf') === '1'`
2. Llama a `pflores_get_cv_data()` para obtener todos los datos actuales
3. Genera HTML limpio con CSS embebido (formato A4, 2 columnas)
4. Escribe el HTML en un archivo temporal
5. Ejecuta Chromium headless: `chromium --headless --no-sandbox --print-to-pdf=... file://...`
6. Lee el PDF resultante y lo devuelve con headers de descarga
7. Limpia los archivos temporales
**Modificar el layout del PDF:**
Todo el diseño está en la función `pflores_build_cv_html()` dentro de `pdf-generator.php`. Modificar el CSS dentro de la variable `$css` o el HTML de cada sección.
**Añadir una nueva sección al PDF** (ej: Premios):
```php
// En pflores_build_cv_html(), después de certificaciones:
$awardsHtml = '';
foreach ($awards as $a) {
$awardsHtml .= '<div class="cert-item">...';
}
// Y en el HTML final:
'<p class="sec-title">Premios y reconocimientos</p>' . $awardsHtml .
```
---
## 14. Archivo `.env`
```
WP_ENV=local
WP_HOST=0.0.0.0
WP_PORT=8081
WP_URL=http://192.168.222.40:8081
DB_NAME=paty.wordpress
DB_USER=paty.wordpress
DB_PASSWORD=paty.wordpress
DB_HOST=192.168.222.20
DB_PREFIX=cv_
WP_ADMIN_USER=pflores
WP_ADMIN_PASSWORD=HXk4boxuI1QYum3jKL
```
---
## 15. Configuración `wp-config.php` clave
```php
// Auto-updates DESACTIVADAS (protege archivos de temas y plugins locales)
define('AUTOMATIC_UPDATER_DISABLED', true);
define('WP_AUTO_UPDATE_CORE', false);
// URL dinámica — funciona con cualquier IP o dominio, y detrás de proxy SSL (NPM)
$_wpIsHttps = /* detecta X-Forwarded-Proto: https */;
define('WP_HOME', $_wpProto . '://' . $_wpHost);
define('WP_SITEURL', $_wpProto . '://' . $_wpHost);
```
---
## 16. Convenciones de código
| Elemento | Convención |
|----------|-----------|
| PHP | `declare(strict_types=1)` en todos los archivos |
| Funciones WordPress | Usar `esc_html()`, `esc_url()`, `esc_attr()` en toda salida HTML |
| Prefijos | Funciones y opciones siempre con prefijo del plugin/tema para evitar conflictos |
| Comentarios | Solo cuando el "por qué" no es obvio. Sin comentarios de "qué hace" |
| Serialización PHP en SQL | Generar con `php -r "echo serialize([...]);"` — nunca manual |
| Auto-updates WP | **Siempre desactivadas** — no tocar `wp-config.php` en ese bloque |
---
## 17. Tareas comunes — referencia rápida
### Verificar que todo funciona
```bash
curl -s http://127.0.0.1:8081/ | grep -c "Patricia Soledad"
# Debe retornar >= 1
```
### Ver el tema activo
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "SELECT option_value FROM cv_options WHERE option_name='template';" 2>/dev/null
```
### Ver todos los datos del CV en BD
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "SELECT option_name, LENGTH(option_value) as bytes FROM cv_options WHERE option_name LIKE 'pflores_%' ORDER BY option_name;" 2>/dev/null
```
### Limpiar caché de WordPress (transients)
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "DELETE FROM cv_options WHERE option_name LIKE '_site_transient_%' OR option_name LIKE '_transient_%';" 2>/dev/null
```
### Verificar los 4 temas en un loop
```bash
for slug in persona-cv lumina apex-cv cvolio; do
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "UPDATE cv_options SET option_value='$slug' WHERE option_name IN('template','stylesheet');" 2>/dev/null
sleep 0.4
count=$(curl -s http://127.0.0.1:8081/ | grep -c "Patricia Soledad" || true)
echo "[$slug] Patricia aparece: $count veces"
done
# Restaurar:
mysql ... -e "UPDATE cv_options SET option_value='persona-cv' WHERE option_name IN('template','stylesheet');"
```
### Probar la descarga del PDF
```bash
curl -sI http://127.0.0.1:8081/?cv_pdf=1 | grep -E "Content-Type|Content-Disposition"
# Debe mostrar: application/pdf y attachment; filename="CV_Patricia_Flores_Sanchez.pdf"
```
### Comprobar PHP syntax de un tema
```bash
php -l /ruta/al/tema/front-page.php
php -l /ruta/al/tema/functions.php
```
---
## 18. Errores frecuentes y soluciones
| Error | Causa probable | Solución |
|-------|---------------|----------|
| Página en blanco o "error crítico" | PHP fatal en el tema | Ver `debug.log`, buscar la línea del error |
| CSS sin cargar / página sin estilos | Mixed content (HTTP vs HTTPS) | Verificar `wp-config.php` — bloque de proxy SSL |
| CMB2 / plugin no carga | Serialización incorrecta en `active_plugins` | Regenerar con `php -r "echo serialize([...]);"` |
| `pflores_get_cv_data()` retorna vacío | Datos no guardados en BD | Re-ejecutar `populate-data.php?key=pflores2024` + limpiar caché |
| Apex-cv vacío o sin archivos | Tema apex-cv depende de directorio real, no symlink | Los archivos van en `wp-content/themes/apex-cv/`, no en `frontend/` |
| PDF vacío o error 500 | Chromium no encontrado o falla | `which chromium chromium-browser google-chrome` |
| `update_option` no guarda | Caché de WordPress | Limpiar transients (ver sección 17) |
---
## 19. Acerca de los temas — resumen visual
| Tema | Estilo | Layout | Fuente | Acento |
|------|--------|--------|--------|--------|
| `persona-cv` | Personage/Pixflow, secciones alternas | Full-width, nav sticky top | Raleway + Open Sans | Rojo `#c0392b` |
| `lumina` | Claro, moderno, alegre | Sidebar IZQUIERDO fijo | Plus Jakarta Sans | Teal `#0d9488` + Ámbar |
| `apex-cv` | Oscuro, serio, editorial | Sidebar IZQUIERDO fijo | Space Grotesk | Cian `#22d3ee` + Dorado |
| `cvolio` | Oscuro glassmorphism | Hero dividido + sidebar DERECHO | Manrope + Cormorant | Café/tostado |
---
## 20. Notas importantes para la IA asistente
1. **No usar ACF** (Advanced Custom Fields). Tiene Repeater solo en versión de pago. Usar CMB2.
2. **No instalar plugins de pago**. Todo debe ser 100 % gratuito.
3. **No tocar el bloque de auto-updates** en `wp-config.php`.
4. **Verificar serialización PHP** antes de cualquier `UPDATE` en `active_plugins` o similar.
5. **Limpiar caché** después de modificar datos en BD directamente.
6. **La foto de perfil** es `assets/images/patricia-flores-profile.jpeg` en cada tema — NO usar el placeholder SVG como defecto.
7. **El CV en docx** (`CV_Patricia_Flores_Sanchez_ES_FINAL.docx`) es solo para descarga — el contenido de la web se gestiona por CMB2.
8. **El PDF** se genera dinámicamente con Chromium desde `/?cv_pdf=1` — no hay PDF estático.
9. **Los 4 temas leen de CMB2** mediante `pflores_get_cv_data()`. Si esa función no existe (plugin inactivo), cada tema usa su propio fallback hardcodeado.
10. **Siempre verificar** con `curl -s http://127.0.0.1:8081/ | grep -c "Patricia Soledad"` que el sitio responde correctamente después de cualquier cambio.
---
## 21. Git y Gitea — versionar y subir
### Repositorio
- **Remoto:** `https://gitea.linkedcorp.net/mgonzalez/pflores.linkedcorp.net.git`
- **Rama:** `main`
- **Credenciales:** archivo local `.token_gitea` (nunca versionado)
### Flujo estándar de commit + push
```bash
# 1. Revisar qué cambió
git status
git diff --stat
# 2. Agregar archivos — SELECTIVO (nunca git add -A sin revisar)
git add frontend/ # temas propios
git add backend/wordpress/wp-content/plugins/pflores-cv-fields/
git add backend/wordpress/wp-content/themes/apex-cv/
git add backend/wordpress/wp-config.php
git add CHANGELOG.md VERSION README.md PROMPT_IA.md
# 3. Confirmar que .env y .token_gitea NO están en staging
git status | grep -E "\.env|\.token" # debe retornar vacío
# 4. Commit
git commit -m "tipo: descripción corta"
# Prefijos: feat: / fix: / docs: / chore: / style:
# 5. Push
git push origin main
```
### Credenciales para push
Al hacer `git push` por primera vez pedirá usuario y contraseña:
- **Usuario:** `mgonzalez`
- **Contraseña:** Personal Access Token (NO la contraseña web de Gitea)
Crear token: `https://gitea.linkedcorp.net/user/settings/applications`
Para no escribir credenciales cada vez:
```bash
git config credential.helper store
# La primera vez las guarda; las siguientes son automáticas
```
Guardar también en `.token_gitea` (local, nunca versionar):
```text
# Token gitea
usuario: mgonzalez
token: [token de gitea.linkedcorp.net]
```
### Qué SE versiona vs qué NO
**SE versiona:**
- `frontend/` (todos los temas propios)
- `backend/wordpress/wp-content/plugins/pflores-cv-fields/`
- `backend/wordpress/wp-content/themes/apex-cv/`
- `backend/wordpress/wp-config.php`
- `backend/start-wordpress.sh`, `docs/`, `*.md`, `VERSION`
- `CV_Patricia_Flores_Sanchez_ES_FINAL.docx`, imágenes del CV
**NO se versiona:**
- `.env`, `.token_gitea`, `LOCAL_DEPLOY_CONTEXT.md`
- WordPress core (`wp-admin/`, `wp-includes/`)
- Plugins de terceros (`cmb2/`, `wp-mail-smtp/`)
- Temas por defecto de WP (`twenty*/`)
- `wp-content/debug.log`, `uploads/`, `languages/`
### Versionar antes de hacer push
Ver `VERSIONADO.md` para decidir si subir `X`, `Y` o `Z`:
```bash
# Actualizar VERSION
echo "1.0.1" > VERSION
# Anotar cambio en CHANGELOG.md
# Luego commit y push normal
```
+80 -18
View File
@@ -1,27 +1,89 @@
# CVolio WP
# pflores.linkedcorp.net
Tema WordPress moderno para hoja de vida, preparado para pruebas locales en este directorio.
Sitio web de currículum ejecutivo de **Patricia Soledad Flores Sánchez** — Gerente / Jefa de Operaciones Financieras, sector financiero y microfinanzas.
## Stack
- **CMS:** WordPress 6.x (PHP 8.4, servidor PHP integrado)
- **Base de datos:** MariaDB en `192.168.222.20`
- **Campos del CV:** CMB2 (100 % gratuito, GPL)
- **PDF:** Chromium headless (`/?cv_pdf=1`)
- **Email:** WP Mail SMTP (configurable)
- **Deploy:** Debian 12 · Nginx Proxy Manager → `192.168.222.40:8081`
- **URL pública:** `https://pflores.linkedcorp.net`
## Estructura
- `frontend/cvfolio-theme`: tema editable.
- `backend/wordpress`: instalación local de WordPress.
- `docs/`: notas de instalación y uso.
## Estado actual
- WordPress descargado en local.
- Tema base responsive creado.
- Puerto sugerido preparado: `8081`.
- Pendiente para ejecución completa: extensiones PHP de WordPress y una base MariaDB/MySQL disponible.
```
pflores.linkedcorp.net/
├── frontend/
│ ├── persona-cv-theme/ ← ACTIVO — Personage-style, rojo, timeline central
│ ├── lumina-cv-theme/ ← Claro, teal + ámbar, sidebar izquierdo
│ └── cvfolio-theme/ ← Oscuro, glassmorphism original
├── backend/
│ ├── start-wordpress.sh
│ └── wordpress/
│ ├── wp-config.php
│ └── wp-content/
│ ├── plugins/pflores-cv-fields/ ← plugin propio: CMB2 + PDF
│ └── themes/apex-cv/ ← tema oscuro cian
├── docs/
├── .env ← local, no versionado
├── .env.example
├── VERSION
├── CHANGELOG.md
├── PROMPT_IA.md ← guía completa para IA
└── README.md
```
## Arranque rápido
1. Copia `.env.example` a `.env`.
2. Ajusta credenciales de base de datos.
3. Revisa `docs/instalacion-wordpress.md`.
4. Ejecuta `backend/start-wordpress.sh`.
```bash
# 1. Copiar variables de entorno
cp .env.example .env
# 2. Editar .env con credenciales reales
# 3. Arrancar el servidor WordPress
bash backend/start-wordpress.sh &
# 4. Abrir http://127.0.0.1:8081
```
## Git
El servidor escucha en `0.0.0.0:8081` (accesible desde la red local).
El repositorio fue inicializado localmente. Falta enlazar remoto si luego quieres subirlo a Gitea.
## Temas disponibles
| Slug | Descripción | Estado |
|------|-------------|--------|
| `persona-cv` | Personage-style, rojo, secciones alternas, Raleway | **Activo** |
| `lumina` | Claro, teal+ámbar, sidebar izq., Plus Jakarta Sans | Disponible |
| `apex-cv` | Oscuro, cian eléctrico, sidebar fijo, Space Grotesk | Disponible |
| `cvolio` | Oscuro glassmorphism, café/tostado, Manrope | Disponible |
Cambiar tema activo:
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0 \
-e "UPDATE cv_options SET option_value='SLUG' WHERE option_name IN('template','stylesheet');"
```
## Editar el CV
Acceder al admin: `https://pflores.linkedcorp.net/wp-admin`
- Usuario: `pflores`
- Menú: **CV Patricia Flores** → sub-páginas por sección
Los datos se almacenan en CMB2 (opciones de WordPress). Cada cambio se refleja automáticamente en la web y en el PDF descargable.
## Descargar CV en PDF
El PDF se genera dinámicamente con Chromium headless:
```
GET /?cv_pdf=1 → descarga CV_Patricia_Flores_Sanchez.pdf
```
## Documentación relacionada
- `PROMPT_IA.md` — guía completa para asistentes de IA y desarrolladores
- `DEPLOY_WEB.md` — pasos para versionar y desplegar
- `PRODUCCION.md` — estado del servidor de producción
- `VERSIONADO.md` — reglas de versiones
- `CHANGELOG.md` — historial de cambios
- `docs/instalacion-wordpress.md` — configuración local
+1
View File
@@ -0,0 +1 @@
1.0.0
+50
View File
@@ -0,0 +1,50 @@
# Versionado — pflores.linkedcorp.net
## Esquema: `X.Y.Z`
| Nivel | Cuándo subirlo |
|-------|---------------|
| `X` | Rediseño completo, cambio de CMS, rotura de compatibilidad mayor |
| `Y` | Nuevo tema completo, nueva sección del CV, nuevo módulo funcional |
| `Z` | Correcciones visuales, ajuste de texto, nuevo campo, fix de bug |
## Qué se versiona
- `frontend/` — todos los temas WordPress propios
- `backend/wordpress/wp-content/plugins/pflores-cv-fields/` — plugin propio
- `backend/wordpress/wp-content/themes/apex-cv/` — tema en directorio real
- `backend/wordpress/wp-config.php` — sin credenciales (usa variables de entorno)
- `backend/start-wordpress.sh`
- `docs/`
- `README.md`, `PROMPT_IA.md`, `CHANGELOG.md`, `VERSION`, `VERSIONADO.md`
- `PRODUCCION.md`, `DEPLOY_WEB.md`
- `.env.example`
- `.gitignore`
- Archivos de assets del CV: `CV_Patricia_Flores_Sanchez_ES_FINAL.docx`, imágenes QR/perfil
## Qué NO se versiona
- `.env` — credenciales locales
- `.token_gitea` — token de acceso Gitea
- `backend/wordpress/wp-admin/` y `wp-includes/` — núcleo de WordPress
- `backend/wordpress/wp-content/plugins/cmb2/` — plugin de terceros descargado
- `backend/wordpress/wp-content/plugins/wp-mail-smtp/` — plugin de terceros
- `backend/wordpress/wp-content/themes/twenty*/` — temas por defecto
- `backend/wordpress/wp-content/uploads/` — media subida
- `backend/wordpress/wp-content/debug.log`
- `.DS_Store`, `.claude/`
- `LOCAL_DEPLOY_CONTEXT.md` — contexto local con credenciales
## Checklist antes de cada commit
1. `git status` — revisar qué se incluye
2. Verificar que `.env` y `.token_gitea` NO están en staging
3. Si hay cambios funcionales → actualizar `VERSION` y `CHANGELOG.md`
4. Mensaje de commit claro: `feat:`, `fix:`, `chore:`, `docs:`
## Checklist antes de subir a Gitea (`git push`)
1. Todo el checklist de commit cumplido
2. Site funcionando: `curl -s http://127.0.0.1:8081/ | grep -c "Patricia Soledad"`
3. PDF funcionando: `curl -sI http://127.0.0.1:8081/?cv_pdf=1 | grep Content-Type`
4. Ver `DEPLOY_WEB.md` para pasos completos
+2 -1
View File
@@ -17,9 +17,10 @@ WP_HOST="${WP_HOST:-127.0.0.1}"
required_modules=("mysqli" "dom")
missing_modules=()
php_modules="$(php -m | tr '[:upper:]' '[:lower:]')"
for module in "${required_modules[@]}"; do
if ! php -m | grep -qi "^${module}$"; then
if ! printf '%s\n' "$php_modules" | grep -qx "$module"; then
missing_modules+=("$module")
fi
done
+26 -3
View File
@@ -25,11 +25,34 @@ define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
if (getenv('WP_URL')) {
define('WP_HOME', getenv('WP_URL'));
define('WP_SITEURL', getenv('WP_URL'));
// Desactivar actualizaciones automáticas para proteger archivos de temas y plugins locales
define('AUTOMATIC_UPDATER_DISABLED', true);
define('WP_AUTO_UPDATE_CORE', false);
// URL dinámica con soporte de reverse proxy (NPM, Nginx, Traefik…).
// NPM termina SSL y reenvía como HTTP, pero manda X-Forwarded-Proto: https.
$_wpIsHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|| (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')
|| (!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on');
// X-Forwarded-Host contiene el dominio público cuando hay proxy.
// Fallback a HTTP_HOST (que NPM también pasa correctamente).
$_wpHost = $_SERVER['HTTP_X_FORWARDED_HOST']
?? $_SERVER['HTTP_HOST']
?? '127.0.0.1:8081';
$_wpProto = $_wpIsHttps ? 'https' : 'http';
define('WP_HOME', $_wpProto . '://' . $_wpHost);
define('WP_SITEURL', $_wpProto . '://' . $_wpHost);
// Necesario para que WordPress confíe en el header del proxy
// y no entre en bucle de redirección HTTP→HTTPS.
if ($_wpIsHttps) {
$_SERVER['HTTPS'] = 'on';
}
unset($_wpIsHttps, $_wpHost, $_wpProto);
if (! defined('ABSPATH')) {
define('ABSPATH', __DIR__ . '/');
}
@@ -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
+33 -19
View File
@@ -1,31 +1,45 @@
# Instalación local
## Puerto
## Acceso
El primer puerto libre detectado fue `8081`.
| Desde | URL |
|---|---|
| Este equipo | `http://127.0.0.1:8081` |
| Red local | `http://192.168.222.40:8081` |
| Admin WP | `http://192.168.222.40:8081/wp-admin` |
## Requisitos faltantes en este entorno
## Arranque
Para que WordPress quede ejecutable aquí mismo, el sistema necesita:
```bash
bash backend/start-wordpress.sh
```
- PHP con `mysqli` o `pdo_mysql`
- PHP con `dom/xml`
- Recomendado: `mbstring`, `curl`, `gd`, `zip`
- Un servidor MariaDB/MySQL activo o credenciales de una base existente
El servidor escucha en `0.0.0.0:8081` (todas las interfaces). La URL se genera dinámicamente según el host de la petición, por lo que funciona desde cualquier IP sin cambiar configuración.
## Temas disponibles
| Slug | Descripción |
|---|---|
| `persona-cv` | **Activo** — Una página, Personage-style, rojo, timeline central |
| `lumina` | Claro, teal+ámbar, sidebar izquierdo |
| `apex-cv` | Oscuro, cian eléctrico, sidebar fijo izquierdo |
| `cvolio` | Oscuro clásico, glassmorphism, paleta café |
Para cambiar de tema:
```sql
UPDATE cv_options SET option_value = 'slug-del-tema'
WHERE option_name IN ('template','stylesheet');
```
## Archivos clave
- Configuración principal: `backend/wordpress/wp-config.php`
- Tema editable: `frontend/cvfolio-theme`
- Configuración: `backend/wordpress/wp-config.php`
- Temas: `frontend/*/`
- Variables de entorno: `.env`
- Script de arranque: `backend/start-wordpress.sh`
## Uso del tema
## Requisitos
1. Activa el tema `CVolio`.
2. Crea una página de inicio y asígnala como portada.
3. Edita el contenido principal con bloques de WordPress.
4. Personaliza cabecera, contacto y habilidades desde `Apariencia > Personalizar`.
## Siguiente paso recomendado
Instalar las extensiones PHP faltantes y levantar MariaDB local, o apuntar WordPress a una base existente.
- PHP con `mysqli` y `dom`
- Recomendado: `mbstring`, `curl`, `gd`, `zip`
- Base de datos MariaDB en `192.168.222.20`
+645 -115
View File
@@ -1,16 +1,20 @@
:root {
--bg: #f4efe7;
--bg-strong: #eadfd1;
--surface: rgba(255, 255, 255, 0.78);
--surface-strong: #fffdf8;
--text: #1f1d1a;
--muted: #6f685f;
--line: rgba(31, 29, 26, 0.1);
--accent: #d4582b;
--accent-dark: #9f3814;
--accent-soft: #f6c9ae;
--shadow: 0 24px 60px rgba(89, 57, 32, 0.12);
--radius-lg: 28px;
--bg: #ece8e1;
--bg-soft: #f7f5f0;
--surface: rgba(255, 255, 255, 0.82);
--surface-strong: #ffffff;
--surface-dark: #10161d;
--text: #17202a;
--muted: #5d6773;
--line: rgba(23, 32, 42, 0.1);
--accent: #9a7b4f;
--accent-strong: #7b5f39;
--accent-soft: #ece2d1;
--deep: #13202d;
--success: #2f6c60;
--shadow: 0 24px 60px rgba(16, 22, 29, 0.08);
--radius-xl: 32px;
--radius-lg: 24px;
--radius-md: 18px;
--radius-sm: 999px;
--container: 1180px;
@@ -28,12 +32,11 @@ html {
body {
margin: 0;
font-family: "Outfit", sans-serif;
color: var(--text);
font-family: "Manrope", sans-serif;
background:
radial-gradient(circle at top left, rgba(212, 88, 43, 0.14), transparent 34%),
radial-gradient(circle at right 20%, rgba(135, 148, 255, 0.12), transparent 24%),
linear-gradient(180deg, #f6f1e8 0%, #f9f6f1 48%, #efe5d8 100%);
radial-gradient(circle at top left, rgba(154, 123, 79, 0.15), transparent 26%),
linear-gradient(180deg, #f8f6f2 0%, #f0ece5 46%, #e6e0d5 100%);
}
a {
@@ -41,13 +44,18 @@ a {
text-decoration: none;
}
img {
max-width: 100%;
}
.site-bg {
position: fixed;
inset: 0;
background-image: linear-gradient(rgba(31, 29, 26, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(31, 29, 26, 0.03) 1px, transparent 1px);
background-size: 24px 24px;
mask-image: radial-gradient(circle at center, black 45%, transparent 90%);
background:
linear-gradient(rgba(19, 32, 45, 0.028) 1px, transparent 1px),
linear-gradient(90deg, rgba(19, 32, 45, 0.028) 1px, transparent 1px);
background-size: 28px 28px;
mask-image: radial-gradient(circle at center, black 38%, transparent 86%);
pointer-events: none;
}
@@ -67,19 +75,19 @@ a {
}
.site-header {
padding: 1.2rem 0 0;
padding-top: 1.2rem;
}
.site-header__inner {
display: flex;
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1rem 1.25rem;
border: 1px solid var(--line);
padding: 0.95rem 1.2rem;
border: 1px solid rgba(19, 32, 45, 0.08);
border-radius: var(--radius-sm);
background: rgba(255, 253, 248, 0.78);
backdrop-filter: blur(18px);
background: rgba(255, 255, 255, 0.72);
backdrop-filter: blur(16px);
box-shadow: var(--shadow);
}
@@ -87,94 +95,182 @@ a {
h1,
h2,
h3 {
font-family: "Fraunces", serif;
font-family: "Cormorant Garamond", serif;
}
.site-brand {
font-size: 1.1rem;
font-size: 1.5rem;
font-weight: 700;
letter-spacing: 0.02em;
}
.site-nav-toggle {
display: none;
border: 0;
border-radius: var(--radius-sm);
background: var(--accent);
color: #fff;
padding: 0.85rem 1rem;
font: inherit;
.site-nav {
justify-self: center;
}
.site-nav__menu {
display: flex;
align-items: center;
gap: 1rem;
justify-content: center;
gap: 1.1rem;
list-style: none;
padding: 0;
margin: 0;
list-style: none;
}
.site-nav__menu a {
color: var(--muted);
font-weight: 500;
font-size: 0.95rem;
font-weight: 600;
}
.site-nav__menu a:hover,
.site-nav__menu a:focus-visible {
color: var(--text);
}
.site-nav-toggle,
.site-print,
.btn,
.cv-chip,
.skill-pill {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
border-radius: var(--radius-sm);
font: inherit;
}
.site-nav-toggle {
display: none;
border: 0;
padding: 0.75rem 1rem;
background: var(--deep);
color: #fff;
}
.site-print {
border: 1px solid rgba(19, 32, 45, 0.12);
background: rgba(255, 255, 255, 0.7);
color: var(--text);
padding: 0.7rem 1rem;
cursor: pointer;
}
.hero {
display: grid;
grid-template-columns: minmax(0, 1.55fr) minmax(300px, 0.95fr);
grid-template-columns: minmax(0, 1.6fr) minmax(320px, 0.9fr);
gap: 1.5rem;
padding: 2rem 0 1.5rem;
padding: 1.8rem 0;
}
.hero__copy,
.profile-card,
.content-card,
.side-card {
border: 1px solid rgba(31, 29, 26, 0.08);
border: 1px solid rgba(19, 32, 45, 0.08);
background: var(--surface);
backdrop-filter: blur(14px);
box-shadow: var(--shadow);
}
.hero__copy {
padding: 2rem;
position: relative;
overflow: hidden;
padding: 2.1rem;
border-radius: 40px;
}
.hero__copy::after {
content: "";
position: absolute;
right: -80px;
bottom: -80px;
width: 260px;
height: 260px;
border-radius: 50%;
background: radial-gradient(circle, rgba(154, 123, 79, 0.14), transparent 68%);
}
.eyebrow {
margin: 0 0 1rem;
font-size: 0.82rem;
margin: 0 0 0.9rem;
color: var(--accent-strong);
font-size: 0.8rem;
font-weight: 800;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--accent-dark);
}
.hero h1 {
margin: 0;
font-size: clamp(2.6rem, 5vw, 5.5rem);
line-height: 0.95;
font-size: clamp(3.1rem, 6vw, 5.5rem);
line-height: 0.92;
letter-spacing: -0.03em;
}
.hero__role {
margin: 1rem 0 0;
font-size: clamp(1.2rem, 2vw, 1.6rem);
color: var(--accent-dark);
color: var(--deep);
font-size: clamp(1.2rem, 2vw, 1.55rem);
font-weight: 700;
}
.hero__intro,
.entry-content,
.side-card p {
.side-card p,
.timeline__item p,
.demo-panel li,
.profile-card__label,
.profile-card__highlights span,
.metric-card span,
.achievement-card span {
color: var(--muted);
line-height: 1.7;
font-size: 1rem;
line-height: 1.75;
}
.hero__intro {
max-width: 58ch;
margin-top: 1rem;
}
.hero__metrics,
.hero__actions,
.chip-grid,
.skill-cloud {
.skill-cloud,
.contact-stack {
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
gap: 0.85rem;
}
.hero__metrics {
margin-top: 1.5rem;
}
.hero__metrics--wide .metric-card {
flex: 1 1 180px;
}
.metric-card {
min-width: 170px;
padding: 1rem 1.05rem;
border-radius: 22px;
background: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(19, 32, 45, 0.08);
}
.hero__metrics--wide {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.metric-card strong,
.achievement-card strong {
display: block;
color: var(--deep);
font-size: 1.5rem;
font-weight: 800;
}
.hero__actions {
@@ -184,59 +280,121 @@ h3 {
.btn,
.cv-chip,
.skill-pill {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 44px;
padding: 0.75rem 1.05rem;
border-radius: var(--radius-sm);
}
.btn {
border: 0;
cursor: pointer;
}
.btn--primary {
background: var(--accent);
background: var(--deep);
color: #fff;
font-weight: 600;
font-weight: 700;
}
.btn--secondary {
border: 1px solid rgba(19, 32, 45, 0.12);
background: rgba(255, 255, 255, 0.6);
color: var(--text);
}
.cv-chip,
.skill-pill {
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(31, 29, 26, 0.08);
color: var(--text);
border: 1px solid rgba(19, 32, 45, 0.08);
background: rgba(255, 255, 255, 0.78);
}
.profile-card {
border-radius: 30px;
padding: 1.5rem;
height: 100%;
padding: 1.45rem;
border-radius: 30px;
}
.profile-card__media {
margin-bottom: 1.2rem;
border-radius: 24px;
overflow: hidden;
background:
radial-gradient(circle at top left, rgba(154, 123, 79, 0.2), transparent 40%),
linear-gradient(180deg, #f9f5ef 0%, #ede4d7 100%);
}
.profile-card__label {
margin: 0 0 0.5rem;
color: var(--muted);
font-size: 0.95rem;
margin: 0 0 0.45rem;
font-size: 0.92rem;
}
.profile-card h2,
.side-card h2,
.entry-title {
.entry-title,
.demo-section h2 {
margin: 0 0 1rem;
font-size: clamp(1.5rem, 2vw, 2rem);
font-size: clamp(1.9rem, 3vw, 2.45rem);
line-height: 1.04;
}
.profile-card__highlights {
display: grid;
gap: 0.8rem;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid rgba(19, 32, 45, 0.08);
}
.profile-card__highlights strong {
display: block;
margin-top: 0.15rem;
}
.profile-card__contact {
display: flex;
flex-wrap: wrap;
gap: 0.65rem;
}
.profile-card--executive .profile-card__media--portrait {
aspect-ratio: 4 / 4.35;
}
.profile-card--executive .profile-card__media--portrait img {
width: 100%;
height: 100%;
object-fit: cover;
}
.content-wrap {
display: grid;
padding: 0 0 2rem;
padding-bottom: 2rem;
}
.content-wrap--front {
grid-template-columns: minmax(0, 1.5fr) minmax(280px, 0.8fr);
grid-template-columns: minmax(0, 1.48fr) minmax(290px, 0.82fr);
gap: 1.5rem;
}
.content-card {
border-radius: 32px;
padding: 2rem;
border-radius: var(--radius-xl);
}
.content-card--resume {
padding: 2.2rem;
}
.resume-section + .resume-section {
margin-top: 2.6rem;
}
.section-head {
display: grid;
gap: 0.35rem;
margin-bottom: 1.15rem;
}
.lead-copy {
font-size: 1.08rem;
}
.entry-content > *:first-child {
@@ -245,23 +403,213 @@ h3 {
.entry-content h2,
.entry-content h3 {
margin-top: 1.8rem;
margin-bottom: 0.75rem;
}
.entry-content .wp-block-group,
.entry-content .wp-block-columns,
.entry-content .wp-block-cover,
.entry-content .wp-block-media-text {
margin-top: 1.5rem;
margin-bottom: 1.5rem;
margin-top: 1.6rem;
margin-bottom: 0.8rem;
}
.entry-content .wp-block-group.cv-section {
padding: 1.5rem;
border-radius: 22px;
background: rgba(255, 255, 255, 0.58);
border: 1px solid rgba(19, 32, 45, 0.08);
}
.demo-section + .demo-section,
.demo-section + .demo-grid,
.demo-grid + .demo-section {
margin-top: 2rem;
}
.achievement-grid,
.demo-grid {
display: grid;
gap: 1rem;
}
.achievement-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.achievement-card,
.timeline__item,
.demo-panel {
padding: 1.25rem;
border: 1px solid rgba(19, 32, 45, 0.08);
border-radius: 24px;
background: rgba(255, 255, 255, 0.52);
border: 1px solid rgba(31, 29, 26, 0.08);
background: rgba(255, 255, 255, 0.68);
}
.achievement-card {
background: linear-gradient(180deg, rgba(19, 32, 45, 0.96) 0%, rgba(24, 39, 55, 0.96) 100%);
}
.achievement-grid--executive {
grid-template-columns: repeat(2, minmax(0, 1fr));
margin-bottom: 1rem;
}
.achievement-card strong,
.achievement-card span {
color: #fff;
}
.achievement-card--stat {
min-height: 172px;
}
.impact-grid,
.credentials-grid {
display: grid;
gap: 1rem;
}
.impact-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.impact-card,
.credential-card,
.cert-item {
padding: 1.25rem;
border: 1px solid rgba(19, 32, 45, 0.08);
border-radius: 22px;
background: rgba(255, 255, 255, 0.74);
}
.impact-card h3,
.credential-card h3,
.cert-item h3,
.competency-block h3 {
margin: 0 0 0.55rem;
font-size: 1.2rem;
}
.credentials-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.credential-card__year,
.cert-item__year {
display: inline-flex;
margin-bottom: 0.9rem;
padding: 0.3rem 0.7rem;
border-radius: var(--radius-sm);
background: var(--accent-soft);
color: var(--accent-strong);
font-size: 0.8rem;
font-weight: 800;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.credential-card__detail,
.side-card__meta,
.timeline__meta,
.cert-item p {
color: var(--muted);
}
.timeline__meta {
margin-top: -0.15rem;
margin-bottom: 0.75rem;
font-weight: 700;
}
.timeline__bullets {
margin: 0.9rem 0 0;
padding-left: 1rem;
color: var(--muted);
line-height: 1.7;
}
.timeline__bullets li + li {
margin-top: 0.45rem;
}
.cert-list {
display: grid;
gap: 0.9rem;
}
.cert-item {
display: grid;
grid-template-columns: 92px 1fr;
gap: 1rem;
align-items: start;
}
.cert-item__year {
justify-content: center;
width: 100%;
margin-bottom: 0;
}
.timeline {
position: relative;
display: grid;
gap: 1rem;
}
.timeline::before {
content: "";
position: absolute;
top: 0.5rem;
bottom: 0.5rem;
left: 0.75rem;
width: 1px;
background: rgba(154, 123, 79, 0.4);
}
.timeline__item {
position: relative;
padding-left: 2.25rem;
}
.timeline__item::before {
content: "";
position: absolute;
top: 1.2rem;
left: 0.35rem;
width: 0.8rem;
height: 0.8rem;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 0 6px rgba(154, 123, 79, 0.18);
}
.timeline__year {
display: inline-flex;
margin-bottom: 0.75rem;
padding: 0.36rem 0.78rem;
border-radius: var(--radius-sm);
background: var(--accent-soft);
color: var(--accent-strong);
font-size: 0.82rem;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.timeline__item h3 {
margin: 0 0 0.45rem;
font-size: 1.35rem;
}
.timeline__item--rich {
padding-right: 1.2rem;
}
.demo-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.demo-panel ul {
margin: 0;
padding-left: 1.05rem;
}
.demo-panel--accent {
background: linear-gradient(180deg, rgba(236, 226, 209, 0.75), rgba(255, 255, 255, 0.72));
}
.side-stack {
@@ -270,42 +618,157 @@ h3 {
align-content: start;
}
.side-stack--resume {
position: sticky;
top: 1.5rem;
align-self: start;
}
.side-card {
padding: 1.5rem;
border-radius: 26px;
padding: 1.45rem;
border-radius: 28px;
}
.side-card--business-card {
background: linear-gradient(180deg, rgba(19, 32, 45, 0.98) 0%, rgba(20, 31, 43, 0.98) 100%);
color: #fff;
}
.side-card--business-card .eyebrow,
.side-card--business-card p,
.side-card--business-card h2 {
color: #fff;
}
.competency-block + .competency-block {
margin-top: 1.25rem;
}
.skill-cloud {
gap: 0.75rem;
}
.skill-pill {
color: var(--text);
font-weight: 600;
}
.site-footer {
padding: 0 0 2.5rem;
padding-bottom: 2rem;
}
.site-footer__inner {
padding: 1rem 0;
padding: 0.8rem 0;
color: var(--muted);
text-align: center;
}
@media (max-width: 920px) {
.hero,
.content-wrap--front {
grid-template-columns: 1fr;
.info-list {
display: grid;
gap: 0.95rem;
margin: 0 0 1.2rem;
}
.info-list div {
display: grid;
gap: 0.2rem;
}
.info-list dt {
color: var(--accent-strong);
font-size: 0.82rem;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.info-list dd {
margin: 0;
color: var(--text);
}
.business-card {
padding: 1rem;
border-radius: 22px;
background:
radial-gradient(circle at top left, rgba(154, 123, 79, 0.35), transparent 34%),
linear-gradient(160deg, rgba(255, 255, 255, 0.09), rgba(255, 255, 255, 0.04));
border: 1px solid rgba(255, 255, 255, 0.12);
}
.business-card__top {
margin-bottom: 1rem;
}
.business-card__name {
margin: 0;
font-family: "Cormorant Garamond", serif;
font-size: 1.55rem;
font-weight: 700;
line-height: 1;
}
.business-card__role {
margin: 0.45rem 0 0;
color: rgba(255, 255, 255, 0.82);
font-size: 0.94rem;
line-height: 1.5;
}
.business-card__body {
display: grid;
grid-template-columns: 110px 1fr;
gap: 1rem;
align-items: center;
}
.business-card__qr {
padding: 0.55rem;
border-radius: 18px;
background: #fff;
}
.business-card__qr img {
display: block;
width: 100%;
height: auto;
}
.business-card__info {
display: grid;
gap: 0.35rem;
}
.business-card__info p,
.business-card__info a {
margin: 0;
color: rgba(255, 255, 255, 0.88);
word-break: break-word;
}
@media (max-width: 980px) {
.site-header__inner {
grid-template-columns: auto auto;
}
.site-nav-toggle {
display: inline-flex;
justify-self: end;
}
.site-print {
order: 4;
grid-column: 1 / -1;
justify-self: start;
}
.site-nav {
position: absolute;
top: calc(100% + 0.75rem);
right: 1rem;
left: 1rem;
grid-column: 1 / -1;
display: none;
padding: 1rem;
border: 1px solid var(--line);
border-radius: 20px;
background: rgba(255, 253, 248, 0.96);
box-shadow: var(--shadow);
padding: 0.95rem;
border-radius: 18px;
border: 1px solid rgba(19, 32, 45, 0.08);
background: rgba(255, 255, 255, 0.92);
}
.site-nav.is-open {
@@ -313,29 +776,96 @@ h3 {
}
.site-nav__menu {
flex-direction: column;
align-items: flex-start;
flex-direction: column;
}
.hero,
.content-wrap--front {
grid-template-columns: 1fr;
}
.achievement-grid {
grid-template-columns: 1fr;
}
.hero__metrics--wide {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.impact-grid,
.credentials-grid {
grid-template-columns: 1fr;
}
.side-stack--resume {
position: static;
}
}
@media (max-width: 640px) {
.site-header__inner,
.hero__copy,
.profile-card,
.content-card,
.side-card {
border-radius: 24px;
}
.site-header__inner,
.hero__copy,
.profile-card,
.content-card,
.side-card {
padding: 1.2rem;
border-radius: 22px;
}
.hero h1 {
font-size: 2.5rem;
font-size: 2.75rem;
}
.metric-card {
min-width: unset;
width: 100%;
}
.hero__metrics--wide {
grid-template-columns: 1fr;
}
.demo-grid {
grid-template-columns: 1fr;
}
.cert-item {
grid-template-columns: 1fr;
}
.business-card__body {
grid-template-columns: 1fr;
}
}
@media print {
.site-header,
.site-footer,
.site-bg,
.hero__actions,
.site-print,
.site-nav-toggle {
display: none !important;
}
body {
background: #fff;
}
.hero,
.content-wrap--front {
grid-template-columns: 1fr;
}
.hero__copy,
.profile-card,
.content-card,
.side-card,
.timeline__item,
.demo-panel,
.achievement-card {
box-shadow: none;
background: #fff;
}
}
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

@@ -1,6 +1,7 @@
document.addEventListener("DOMContentLoaded", () => {
const toggle = document.querySelector(".site-nav-toggle");
const nav = document.querySelector(".site-nav");
const printButtons = document.querySelectorAll("[data-cvolio-print]");
if (toggle && nav) {
toggle.addEventListener("click", () => {
@@ -9,4 +10,10 @@ document.addEventListener("DOMContentLoaded", () => {
nav.classList.toggle("is-open", !expanded);
});
}
printButtons.forEach((button) => {
button.addEventListener("click", () => {
window.print();
});
});
});
+254 -42
View File
@@ -4,64 +4,235 @@ declare(strict_types=1);
get_header();
$hero_name = get_theme_mod('cvolio_hero_name', get_bloginfo('name'));
$hero_role = get_theme_mod('cvolio_hero_role', __('Profesional creativo y estratega digital', 'cvolio'));
$hero_intro = get_theme_mod('cvolio_hero_intro', __('Presenta tu experiencia, logros, proyectos y propuesta de valor con un formato claro, elegante y listo para cualquier dispositivo.', 'cvolio'));
$hero_location = get_theme_mod('cvolio_contact_location', 'Guayaquil, Ecuador');
$hero_email = get_theme_mod('cvolio_contact_email', 'hola@ejemplo.com');
$hero_phone = get_theme_mod('cvolio_contact_phone', '+593 999 999 999');
$hero_cta_label = get_theme_mod('cvolio_hero_cta_label', __('Ver experiencia', 'cvolio'));
$hero_cta_url = get_theme_mod('cvolio_hero_cta_url', '#perfil');
$skills = array_filter(array_map('trim', explode(',', (string) get_theme_mod('cvolio_skills_list', 'UX Strategy, Liderazgo, Frontend, WordPress, SEO, Analitica'))));
$linkedin = get_theme_mod('cvolio_social_linkedin', '');
$github = get_theme_mod('cvolio_social_github', '');
$portfolio = get_theme_mod('cvolio_social_portfolio', '');
$cv = function_exists('pflores_get_cv_data') ? pflores_get_cv_data() : cvolio_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'];
$qrImage = get_template_directory_uri() . '/assets/images/patricia-flores-qr.png';
$publicUrl = 'https://pflores.linkedcorp.net';
$heroName = $hero['name'];
$heroRole = $hero['role'];
$heroIntro = $hero['intro'];
$heroLocation = $hero['location'];
$heroEmail = $hero['email'];
$heroPhone = $hero['phone'];
$heroCtaLabel = 'Ver trayectoria';
$heroCtaUrl = '#experiencia';
$linkedin = $hero['linkedin'];
// Compatibilidad CVolio: skills como array plano para pills
$skillsFlat = [];
foreach ($competencies as $groupItems) {
foreach ((array) $groupItems as $item) {
$skillsFlat[] = is_array($item) ? ($item['name'] ?? '') : (string) $item;
}
}
$skills = array_filter($skillsFlat);
$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';
?>
<section class="hero">
<section class="hero hero--executive">
<div class="hero__copy">
<p class="eyebrow"><?php esc_html_e('Hoja de vida digital', 'cvolio'); ?></p>
<h1><?php echo esc_html($hero_name); ?></h1>
<p class="hero__role"><?php echo esc_html($hero_role); ?></p>
<p class="hero__intro"><?php echo esc_html($hero_intro); ?></p>
<p class="eyebrow"><?php echo esc_html($hero['eyebrow']); ?></p>
<h1><?php echo esc_html($heroName); ?></h1>
<p class="hero__role"><?php echo esc_html($heroRole); ?></p>
<p class="hero__intro"><?php echo esc_html($heroIntro); ?></p>
<div class="hero__metrics hero__metrics--wide">
<?php foreach ($metrics as $metric) : ?>
<article class="metric-card">
<strong><?php echo esc_html($metric['value']); ?></strong>
<span><?php echo esc_html($metric['label']); ?></span>
</article>
<?php endforeach; ?>
</div>
<div class="hero__actions">
<a class="btn btn--primary" href="<?php echo esc_url($hero_cta_url); ?>">
<?php echo esc_html($hero_cta_label); ?>
<a class="btn btn--primary" href="<?php echo esc_url($heroCtaUrl); ?>">
<?php echo esc_html($heroCtaLabel); ?>
</a>
<?php echo wp_kses_post(cvolio_render_contact_link('email', $hero_email)); ?>
<a class="btn btn--secondary" href="<?php echo esc_url(home_url('/?cv_pdf=1')); ?>">
<?php esc_html_e('Descargar CV PDF', 'cvolio'); ?>
</a>
<button class="btn btn--ghost" type="button" data-cvolio-print>
<?php esc_html_e('Versión imprimible', 'cvolio'); ?>
</button>
</div>
</div>
<aside class="hero__panel">
<div class="profile-card">
<p class="profile-card__label"><?php esc_html_e('Disponible para', 'cvolio'); ?></p>
<h2><?php esc_html_e('Proyectos, consultoria y liderazgo de equipos', 'cvolio'); ?></h2>
<div class="chip-grid">
<?php echo wp_kses_post(cvolio_render_contact_link('phone', $hero_phone)); ?>
<span class="cv-chip"><?php echo esc_html($hero_location); ?></span>
<?php if ($linkedin) : ?><a class="cv-chip" href="<?php echo esc_url($linkedin); ?>" target="_blank" rel="noreferrer">LinkedIn</a><?php endif; ?>
<?php if ($github) : ?><a class="cv-chip" href="<?php echo esc_url($github); ?>" target="_blank" rel="noreferrer">GitHub</a><?php endif; ?>
<?php if ($portfolio) : ?><a class="cv-chip" href="<?php echo esc_url($portfolio); ?>" target="_blank" rel="noreferrer">Portfolio</a><?php endif; ?>
<div class="profile-card profile-card--executive">
<div class="profile-card__media profile-card__media--portrait">
<img src="<?php echo esc_url($profileImage); ?>" alt="<?php echo esc_attr($heroName); ?>">
</div>
<div class="profile-card__contact">
<?php echo wp_kses_post(cvolio_render_contact_link('email', $heroEmail)); ?>
<?php echo wp_kses_post(cvolio_render_contact_link('phone', $heroPhone)); ?>
<span class="cv-chip"><?php echo esc_html($heroLocation); ?></span>
<?php if ($linkedin) : ?>
<a class="cv-chip" href="<?php echo esc_url($linkedin); ?>" target="_blank" rel="noreferrer">LinkedIn</a>
<?php endif; ?>
</div>
<div class="profile-card__highlights">
<div>
<span><?php esc_html_e('Enfoque', 'cvolio'); ?></span>
<strong><?php echo esc_html($hero['focus']); ?></strong>
</div>
<div>
<span><?php esc_html_e('Disponibilidad', 'cvolio'); ?></span>
<strong><?php echo esc_html($hero['availability']); ?></strong>
</div>
</div>
</div>
</aside>
</section>
<section id="perfil" class="content-wrap content-wrap--front">
<div class="content-card">
<section class="content-wrap content-wrap--front">
<div class="content-card content-card--resume">
<section id="resumen" class="resume-section">
<div class="section-head">
<p class="eyebrow"><?php esc_html_e('Perfil profesional', 'cvolio'); ?></p>
<h2><?php echo esc_html($summary['title']); ?></h2>
</div>
<p class="lead-copy"><?php echo esc_html($summary['body']); ?></p>
</section>
<section id="impacto" class="resume-section">
<div class="section-head">
<p class="eyebrow"><?php esc_html_e('Impacto ejecutivo', 'cvolio'); ?></p>
<h2><?php esc_html_e('Resultados concretos con alcance nacional', 'cvolio'); ?></h2>
</div>
<div class="achievement-grid achievement-grid--executive">
<?php foreach ($highlights as $highlight) : ?>
<article class="achievement-card achievement-card--stat">
<strong><?php echo esc_html($highlight['value']); ?></strong>
<span><?php echo esc_html($highlight['label']); ?></span>
</article>
<?php endforeach; ?>
</div>
<div class="impact-grid">
<?php foreach ($impact as $item) : ?>
<article class="impact-card">
<h3><?php echo esc_html($item['title']); ?></h3>
<p><?php echo esc_html($item['body']); ?></p>
</article>
<?php endforeach; ?>
</div>
</section>
<section id="experiencia" class="resume-section">
<div class="section-head">
<p class="eyebrow"><?php esc_html_e('Experiencia profesional', 'cvolio'); ?></p>
<h2><?php esc_html_e('Trayectoria sólida en banca, operaciones y microfinanzas', 'cvolio'); ?></h2>
</div>
<div class="timeline timeline--executive">
<?php foreach ($experience as $item) : ?>
<article class="timeline__item timeline__item--rich">
<span class="timeline__year"><?php echo esc_html($item['period']); ?></span>
<div class="timeline__content">
<h3><?php echo esc_html($item['title']); ?></h3>
<p class="timeline__meta"><?php echo esc_html($item['company'] . ' | ' . $item['location']); ?></p>
<p><?php echo esc_html($item['lead']); ?></p>
<ul class="timeline__bullets">
<?php foreach ($item['bullets'] as $bullet) : ?>
<li><?php echo esc_html($bullet); ?></li>
<?php endforeach; ?>
</ul>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<section id="formacion" class="resume-section">
<div class="section-head">
<p class="eyebrow"><?php esc_html_e('Formación académica', 'cvolio'); ?></p>
<h2><?php esc_html_e('Base técnica y formación ejecutiva de alto nivel', 'cvolio'); ?></h2>
</div>
<div class="credentials-grid">
<?php foreach ($education as $item) : ?>
<article class="credential-card">
<span class="credential-card__year"><?php echo esc_html($item['year']); ?></span>
<h3><?php echo esc_html($item['title']); ?></h3>
<p><?php echo esc_html($item['institution']); ?></p>
<?php if ($item['detail'] !== '') : ?>
<p class="credential-card__detail"><?php echo esc_html($item['detail']); ?></p>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>
</section>
<section id="certificaciones" class="resume-section">
<div class="section-head">
<p class="eyebrow"><?php esc_html_e('Certificaciones relevantes', 'cvolio'); ?></p>
<h2><?php esc_html_e('Actualización continua en gerencia, procesos y analítica', 'cvolio'); ?></h2>
</div>
<div class="cert-list">
<?php foreach ($certifications as $item) : ?>
<article class="cert-item">
<span class="cert-item__year"><?php echo esc_html($item['year']); ?></span>
<div>
<h3><?php echo esc_html($item['title']); ?></h3>
<p><?php echo esc_html($item['issuer']); ?></p>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<?php while (have_posts()) : the_post(); ?>
<article <?php post_class('front-article'); ?>>
<div class="entry-content">
<?php the_content(); ?>
</div>
</article>
<?php if (trim((string) get_the_content()) !== '') : ?>
<section class="resume-section resume-section--custom">
<div class="section-head">
<p class="eyebrow"><?php esc_html_e('Contenido adicional', 'cvolio'); ?></p>
<h2><?php esc_html_e('Bloques editables desde WordPress', 'cvolio'); ?></h2>
</div>
<div class="entry-content">
<?php the_content(); ?>
</div>
</section>
<?php endif; ?>
<?php endwhile; ?>
</div>
<aside class="side-stack">
<aside class="side-stack side-stack--resume">
<section class="side-card side-card--competencies">
<p class="eyebrow"><?php esc_html_e('Competencias clave', 'cvolio'); ?></p>
<h2><?php esc_html_e('Capacidades ejecutivas y técnicas', 'cvolio'); ?></h2>
<?php foreach ($competencies as $group => $items) : ?>
<div class="competency-block">
<h3><?php echo esc_html($group); ?></h3>
<div class="skill-cloud">
<?php foreach ($items as $item) : ?>
<span class="skill-pill"><?php echo esc_html(is_array($item) ? ($item['name'] ?? '') : $item); ?></span>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</section>
<section class="side-card">
<p class="eyebrow"><?php esc_html_e('Fortalezas', 'cvolio'); ?></p>
<h2><?php esc_html_e('Habilidades clave', 'cvolio'); ?></h2>
<p class="eyebrow"><?php esc_html_e('Liderazgo', 'cvolio'); ?></p>
<h2><?php esc_html_e('Atributos diferenciales', 'cvolio'); ?></h2>
<div class="skill-cloud">
<?php foreach ($skills as $skill) : ?>
<span class="skill-pill"><?php echo esc_html($skill); ?></span>
@@ -70,9 +241,50 @@ $portfolio = get_theme_mod('cvolio_social_portfolio', '');
</section>
<section class="side-card">
<p class="eyebrow"><?php esc_html_e('Consejo de uso', 'cvolio'); ?></p>
<h2><?php esc_html_e('Edita esta portada con bloques', 'cvolio'); ?></h2>
<p><?php esc_html_e('Puedes crear secciones de experiencia, estudios, casos de exito, certificados o servicios usando Gutenberg y los patrones del tema.', 'cvolio'); ?></p>
<p class="eyebrow"><?php esc_html_e('Voluntariado', 'cvolio'); ?></p>
<h2><?php echo esc_html($volunteer['title']); ?></h2>
<p class="side-card__meta"><?php echo esc_html($volunteer['year']); ?></p>
<p><?php echo esc_html($volunteer['body']); ?></p>
</section>
<section id="contacto" class="side-card side-card--contact">
<p class="eyebrow"><?php esc_html_e('Información adicional', 'cvolio'); ?></p>
<h2><?php esc_html_e('Datos clave del perfil', 'cvolio'); ?></h2>
<dl class="info-list">
<?php foreach ($additional as $label => $value) : ?>
<div>
<dt><?php echo esc_html($label); ?></dt>
<dd><?php echo esc_html($value); ?></dd>
</div>
<?php endforeach; ?>
</dl>
<div class="contact-stack">
<?php echo wp_kses_post(cvolio_render_contact_link('email', $heroEmail)); ?>
<?php echo wp_kses_post(cvolio_render_contact_link('phone', $heroPhone)); ?>
</div>
</section>
<section class="side-card side-card--business-card">
<p class="eyebrow"><?php esc_html_e('Tarjeta de presentación', 'cvolio'); ?></p>
<h2><?php esc_html_e('Acceso rápido al perfil web', 'cvolio'); ?></h2>
<article class="business-card">
<div class="business-card__top">
<p class="business-card__name"><?php echo esc_html($heroName); ?></p>
<p class="business-card__role"><?php echo esc_html($heroRole); ?></p>
</div>
<div class="business-card__body">
<div class="business-card__qr">
<img src="<?php echo esc_url($qrImage); ?>" alt="<?php esc_attr_e('Código QR hacia el perfil web', 'cvolio'); ?>">
</div>
<div class="business-card__info">
<p><?php echo esc_html($heroLocation); ?></p>
<p><?php echo esc_html($heroPhone); ?></p>
<p><?php echo esc_html($heroEmail); ?></p>
<a href="<?php echo esc_url($publicUrl); ?>" target="_blank" rel="noreferrer"><?php echo esc_html($publicUrl); ?></a>
</div>
</div>
</article>
</section>
</aside>
</section>
+57 -1
View File
@@ -3,7 +3,9 @@
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/patterns.php';
require_once get_template_directory() . '/inc/visits.php';
function cvolio_setup(): void
{
@@ -24,7 +26,7 @@ function cvolio_enqueue_assets(): void
{
wp_enqueue_style(
'cvolio-fonts',
'https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=Fraunces:opsz,wght@9..144,600;9..144,700&display=swap',
'https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700;800&family=Cormorant+Garamond:wght@500;600;700&display=swap',
[],
null
);
@@ -58,6 +60,60 @@ function cvolio_body_classes(array $classes): array
}
add_filter('body_class', 'cvolio_body_classes');
function cvolio_redirect_default_sample_page(): void
{
if (! is_page()) {
return;
}
$samplePage = get_queried_object();
if (! $samplePage instanceof WP_Post) {
return;
}
if ($samplePage->post_name !== 'pagina-ejemplo') {
return;
}
wp_safe_redirect(home_url('/'), 301);
exit;
}
add_action('template_redirect', 'cvolio_redirect_default_sample_page', 1);
function cvolio_render_primary_menu(): string
{
if (has_nav_menu('primary')) {
return trim((string) wp_nav_menu([
'theme_location' => 'primary',
'container' => false,
'fallback_cb' => false,
'menu_class' => 'site-nav__menu',
'echo' => false,
]));
}
$items = [
'#resumen' => __('Resumen', 'cvolio'),
'#impacto' => __('Impacto', 'cvolio'),
'#experiencia' => __('Experiencia', 'cvolio'),
'#formacion' => __('Formación', 'cvolio'),
'#certificaciones' => __('Certificaciones', 'cvolio'),
'#contacto' => __('Contacto', 'cvolio'),
];
$html = '<ul class="site-nav__menu">';
foreach ($items as $href => $label) {
$html .= sprintf(
'<li><a href="%s">%s</a></li>',
esc_url($href),
esc_html($label)
);
}
$html .= '</ul>';
return $html;
}
function cvolio_render_contact_link(string $type, string $value): string
{
if ($value === '') {
+2 -8
View File
@@ -23,15 +23,9 @@ $brand = get_theme_mod('cvolio_hero_name', get_bloginfo('name'));
<span>Menu</span>
</button>
<nav id="site-nav" class="site-nav" aria-label="<?php esc_attr_e('Menu principal', 'cvolio'); ?>">
<?php
wp_nav_menu([
'theme_location' => 'primary',
'container' => false,
'fallback_cb' => false,
'menu_class' => 'site-nav__menu',
]);
?>
<?php echo cvolio_render_primary_menu(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</nav>
<a class="site-print" href="<?php echo esc_url(home_url('/?cv_pdf=1')); ?>" target="_blank"><?php esc_html_e('Descargar CV PDF', 'cvolio'); ?></a>
</div>
</header>
<main class="site-main">
+21 -10
View File
@@ -10,20 +10,31 @@ function cvolio_customize_register(WP_Customize_Manager $wp_customize): void
]);
$fields = [
'cvolio_hero_name' => ['label' => __('Nombre principal', 'cvolio'), 'default' => 'Patricia Flores'],
'cvolio_hero_role' => ['label' => __('Rol o titular', 'cvolio'), 'default' => 'Directora de proyectos, estratega digital y lider de producto'],
'cvolio_hero_intro' => ['label' => __('Introduccion', 'cvolio'), 'default' => 'Perfil profesional con enfoque humano, resultados medibles y una presentacion visual moderna.'],
'cvolio_hero_cta_label' => ['label' => __('Texto del boton', 'cvolio'), 'default' => 'Ver experiencia'],
'cvolio_hero_cta_url' => ['label' => __('Enlace del boton', 'cvolio'), 'default' => '#perfil'],
'cvolio_contact_location' => ['label' => __('Ubicacion', 'cvolio'), 'default' => 'Guayaquil, Ecuador'],
'cvolio_contact_email' => ['label' => __('Correo', 'cvolio'), 'default' => 'hola@ejemplo.com'],
'cvolio_contact_phone' => ['label' => __('Telefono', 'cvolio'), 'default' => '+593 999 999 999'],
'cvolio_skills_list' => ['label' => __('Habilidades separadas por coma', 'cvolio'), 'default' => 'Liderazgo, Comunicacion, WordPress, UX, SEO, Analitica'],
'cvolio_social_linkedin' => ['label' => __('LinkedIn', 'cvolio'), 'default' => ''],
'cvolio_hero_name' => ['label' => __('Nombre principal', 'cvolio'), 'default' => 'Patricia Soledad Flores Sánchez'],
'cvolio_hero_role' => ['label' => __('Rol o titular', 'cvolio'), 'default' => 'Gerente / Jefa de Operaciones Financieras | Sector financiero y microfinanzas'],
'cvolio_hero_intro' => ['label' => __('Introduccion', 'cvolio'), 'default' => 'Ejecutiva financiera con más de 22 años de trayectoria en banca y microfinanzas. Especialista en gestión operativa, transformación digital de procesos, control interno y liderazgo de equipos multidisciplinarios.'],
'cvolio_hero_cta_label' => ['label' => __('Texto del boton', 'cvolio'), 'default' => 'Ver trayectoria'],
'cvolio_hero_cta_url' => ['label' => __('Enlace del boton', 'cvolio'), 'default' => '#experiencia'],
'cvolio_contact_location' => ['label' => __('Ubicacion', 'cvolio'), 'default' => 'Loja, Ecuador'],
'cvolio_contact_email' => ['label' => __('Correo', 'cvolio'), 'default' => 'patricflorsanchez@gmail.com'],
'cvolio_contact_phone' => ['label' => __('Telefono', 'cvolio'), 'default' => '+593 99 469 8405'],
'cvolio_skills_list' => ['label' => __('Habilidades separadas por coma', 'cvolio'), 'default' => 'Liderazgo, Gestión de operaciones financieras, Control interno, Transformación digital, Microfinanzas, Gestión de cartera, SAP FI, Power BI'],
'cvolio_social_linkedin' => ['label' => __('LinkedIn', 'cvolio'), 'default' => 'https://www.linkedin.com/in/patricia-soledad-flores-s%C3%A1nchez-43b84418/'],
'cvolio_social_github' => ['label' => __('GitHub', 'cvolio'), 'default' => ''],
'cvolio_social_portfolio' => ['label' => __('Portfolio', 'cvolio'), 'default' => ''],
];
$wp_customize->add_setting('cvolio_profile_image', [
'default' => '',
'sanitize_callback' => 'esc_url_raw',
]);
$wp_customize->add_control(new WP_Customize_Image_Control($wp_customize, 'cvolio_profile_image', [
'label' => __('Foto de perfil', 'cvolio'),
'section' => 'cvolio_hero',
'settings' => 'cvolio_profile_image',
]));
foreach ($fields as $key => $field) {
$wp_customize->add_setting($key, [
'default' => $field['default'],
+189
View File
@@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
function cvolio_cv_data(): array
{
return [
'hero' => [
'eyebrow' => 'Currículum ejecutivo',
'name' => 'Patricia Soledad Flores Sánchez',
'role' => 'Gerente / Jefa de Operaciones Financieras | 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' => [
'Gestión de operaciones financieras',
'Conciliación bancaria',
'Originación de crédito',
'Gestión de cartera',
'Control interno',
'Tesorería operativa',
'Microfinanzas',
'ALA/FT',
],
'Tecnología y Procesos' => [
'Transformación digital',
'BPM',
'Integración de APIs / Web Services',
'SAP FI',
'Excel avanzado y dashboards',
'Power BI',
'IA aplicada',
'Core financiero',
],
'Liderazgo' => [
'Equipos de hasta 48 personas',
'Remuneración variable',
'Gestión del cambio',
'Formación de formadores',
'Diversidad, equidad e inclusión',
],
],
'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' => 'pflres.linkedcorp.net',
],
];
}
+118
View File
@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
function cvolio_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 cvolio_track_visit(): void
{
if (! cvolio_should_track_visit()) {
return;
}
$requestUri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
$visitorHash = md5($requestUri . '|' . cvolio_get_client_ip() . '|' . gmdate('Y-m-d-H'));
$transientKey = 'cvolio_visit_' . $visitorHash;
if (get_transient($transientKey)) {
return;
}
set_transient($transientKey, '1', HOUR_IN_SECONDS);
$total = (int) get_option('cvolio_total_visits', 0);
update_option('cvolio_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, '_cvolio_views', true);
update_post_meta($frontPage, '_cvolio_views', $views + 1);
}
return;
}
$postId = get_queried_object_id();
if ($postId > 0) {
$views = (int) get_post_meta($postId, '_cvolio_views', true);
update_post_meta($postId, '_cvolio_views', $views + 1);
}
}
add_action('template_redirect', 'cvolio_track_visit');
function cvolio_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 cvolio_register_visits_dashboard_widget(): void
{
wp_add_dashboard_widget(
'cvolio_visits_widget',
__('CVolio: Visitas', 'cvolio'),
'cvolio_render_visits_dashboard_widget'
);
}
add_action('wp_dashboard_setup', 'cvolio_register_visits_dashboard_widget');
function cvolio_render_visits_dashboard_widget(): void
{
$total = (int) get_option('cvolio_total_visits', 0);
$frontPage = (int) get_option('page_on_front');
$frontViews = $frontPage > 0 ? (int) get_post_meta($frontPage, '_cvolio_views', true) : 0;
?>
<div class="cvolio-visits-widget">
<p><strong><?php esc_html_e('Visitas totales registradas:', 'cvolio'); ?></strong> <?php echo esc_html((string) $total); ?></p>
<p><strong><?php esc_html_e('Visitas a la portada:', 'cvolio'); ?></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.', 'cvolio'); ?></p>
</div>
<?php
}
function cvolio_add_views_column(array $columns): array
{
$columns['cvolio_views'] = __('Visitas', 'cvolio');
return $columns;
}
add_filter('manage_pages_columns', 'cvolio_add_views_column');
add_filter('manage_posts_columns', 'cvolio_add_views_column');
function cvolio_render_views_column(string $column, int $postId): void
{
if ($column !== 'cvolio_views') {
return;
}
echo esc_html((string) ((int) get_post_meta($postId, '_cvolio_views', true)));
}
add_action('manage_pages_custom_column', 'cvolio_render_views_column', 10, 2);
add_action('manage_posts_custom_column', 'cvolio_render_views_column', 10, 2);
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,60 @@
document.addEventListener("DOMContentLoaded", () => {
const toggle = document.querySelector(".lm-nav-toggle");
const nav = document.querySelector(".lm-nav");
if (toggle && nav) {
toggle.addEventListener("click", () => {
const expanded = toggle.getAttribute("aria-expanded") === "true";
toggle.setAttribute("aria-expanded", String(!expanded));
nav.classList.toggle("is-open", !expanded);
});
document.addEventListener("click", (e) => {
if (!nav.contains(e.target) && !toggle.contains(e.target)) {
nav.classList.remove("is-open");
toggle.setAttribute("aria-expanded", "false");
}
});
}
document.querySelectorAll("[data-lumina-print]").forEach((btn) => {
btn.addEventListener("click", () => window.print());
});
const header = document.querySelector(".lm-header");
if (header) {
let lastY = window.scrollY;
window.addEventListener("scroll", () => {
const currentY = window.scrollY;
if (currentY > 80) {
header.style.boxShadow = "0 2px 16px rgba(0,0,0,.12)";
} else {
header.style.boxShadow = "";
}
lastY = currentY;
}, { passive: true });
}
const sections = document.querySelectorAll("[id]");
const navLinks = document.querySelectorAll(".lm-nav__list a[href^='#']");
if (navLinks.length && sections.length) {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
navLinks.forEach((link) => {
link.classList.toggle(
"is-active",
link.getAttribute("href") === `#${entry.target.id}`
);
});
}
});
},
{ rootMargin: "-20% 0px -70% 0px" }
);
sections.forEach((section) => observer.observe(section));
}
});
+18
View File
@@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
?>
</main>
<footer class="lm-footer">
<div class="lm-footer__inner">
<p class="lm-footer__copy">
<?php echo esc_html(get_bloginfo('name')); ?> &middot; <?php echo esc_html(date_i18n('Y')); ?>
</p>
<a class="lm-footer__top" href="#top" aria-label="<?php esc_attr_e('Volver al inicio', 'lumina'); ?>">&#8593;</a>
</div>
</footer>
<?php wp_footer(); ?>
</body>
</html>
+343
View File
@@ -0,0 +1,343 @@
<?php
declare(strict_types=1);
get_header();
$cv = function_exists('pflores_get_cv_data') ? pflores_get_cv_data() : lumina_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'];
$qrImage = get_template_directory_uri() . '/assets/images/patricia-flores-qr.png';
$publicUrl = 'https://pflores.linkedcorp.net';
$heroName = $hero['name'];
$heroRole = $hero['role'];
$heroIntro = $hero['intro'];
$heroLocation = $hero['location'];
$heroEmail = $hero['email'];
$heroPhone = $hero['phone'];
$heroCtaLabel = 'Ver trayectoria';
$heroCtaUrl = '#experiencia';
$linkedin = $hero['linkedin'];
$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';
?>
<!-- ================================================
HERO — gradiente teal centrado, foto circular
================================================ -->
<section class="lm-hero">
<div class="lm-hero__bg-pattern" aria-hidden="true"></div>
<div class="lm-hero__center">
<div class="lm-hero__avatar">
<img
src="<?php echo esc_url($profileImage); ?>"
alt="<?php echo esc_attr($heroName); ?>"
class="lm-hero__photo"
>
</div>
<p class="lm-eyebrow lm-eyebrow--light"><?php echo esc_html($hero['eyebrow']); ?></p>
<h1 class="lm-hero__name"><?php echo esc_html($heroName); ?></h1>
<p class="lm-hero__role"><?php echo esc_html($heroRole); ?></p>
<p class="lm-hero__intro"><?php echo esc_html($heroIntro); ?></p>
<div class="lm-hero__chips">
<span class="lm-chip lm-chip--hero">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
<?php echo esc_html($heroLocation); ?>
</span>
<?php if ($heroEmail) : ?>
<a class="lm-chip lm-chip--hero" href="mailto:<?php echo esc_attr(antispambot($heroEmail)); ?>">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
<?php echo esc_html($heroEmail); ?>
</a>
<?php endif; ?>
<?php if ($heroPhone) : ?>
<a class="lm-chip lm-chip--hero" href="tel:<?php echo esc_attr(preg_replace('/[^0-9+]/', '', $heroPhone)); ?>">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12 19.79 19.79 0 0 1 1.61 3.41 2 2 0 0 1 3.6 1.25h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L7.91 8.96a16 16 0 0 0 6.02 6.02l.96-.96a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 21.73 16.92z"/></svg>
<?php echo esc_html($heroPhone); ?>
</a>
<?php endif; ?>
<?php if ($linkedin) : ?>
<a class="lm-chip lm-chip--hero" href="<?php echo esc_url($linkedin); ?>" target="_blank" rel="noreferrer">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"/><rect x="2" y="9" width="4" height="12"/><circle cx="4" cy="4" r="2"/></svg>
LinkedIn
</a>
<?php endif; ?>
</div>
<div class="lm-hero__actions">
<a class="lm-btn lm-btn--amber" href="<?php echo esc_url($heroCtaUrl); ?>">
<?php echo esc_html($heroCtaLabel); ?>
</a>
<a class="lm-btn lm-btn--white" href="<?php echo esc_url(home_url('/?cv_pdf=1')); ?>">
<svg width="16" height="16" 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', 'lumina'); ?>
</a>
<button class="lm-btn lm-btn--ghost-light" type="button" data-lumina-print>
<?php esc_html_e('Versión imprimible', 'lumina'); ?>
</button>
</div>
</div>
<!-- Métricas strip -->
<div class="lm-hero__metrics">
<?php foreach ($metrics as $metric) : ?>
<div class="lm-metric">
<strong><?php echo esc_html($metric['value']); ?></strong>
<span><?php echo esc_html($metric['label']); ?></span>
</div>
<?php endforeach; ?>
</div>
<!-- Wave decorativa -->
<div class="lm-hero__wave" aria-hidden="true">
<svg viewBox="0 0 1440 80" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg"><path d="M0,40 C360,80 1080,0 1440,40 L1440,80 L0,80 Z" fill="var(--lm-bg)"/></svg>
</div>
</section>
<!-- ================================================
HIGHLIGHTS — 4 números de impacto
================================================ -->
<section class="lm-section lm-highlights-section" id="impacto">
<div class="lm-container">
<header class="lm-section-head lm-section-head--center">
<p class="lm-eyebrow"><?php esc_html_e('Resultados medibles', 'lumina'); ?></p>
<h2><?php esc_html_e('Impacto ejecutivo con alcance nacional', 'lumina'); ?></h2>
</header>
<div class="lm-highlights-grid">
<?php foreach ($highlights as $h) : ?>
<article class="lm-highlight-card">
<strong><?php echo esc_html($h['value']); ?></strong>
<span><?php echo esc_html($h['label']); ?></span>
</article>
<?php endforeach; ?>
</div>
<div class="lm-impact-row">
<?php foreach ($impact as $item) : ?>
<article class="lm-impact-card">
<div class="lm-impact-card__icon" aria-hidden="true">
<?php if ($item['title'] === 'Transformación digital') : ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
<?php elseif ($item['title'] === 'Automatización crítica') : ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"/><path d="M16.24 7.76a6 6 0 0 1 0 8.49M7.76 7.76a6 6 0 0 0 0 8.49"/></svg>
<?php else : ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
<?php endif; ?>
</div>
<h3><?php echo esc_html($item['title']); ?></h3>
<p><?php echo esc_html($item['body']); ?></p>
</article>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- ================================================
CUERPO PRINCIPAL — sidebar izquierdo + contenido
================================================ -->
<div class="lm-body lm-container">
<!-- SIDEBAR IZQUIERDO -->
<aside class="lm-sidebar">
<!-- Perfil rápido -->
<section class="lm-widget lm-widget--profile" id="resumen">
<p class="lm-eyebrow"><?php esc_html_e('Perfil profesional', 'lumina'); ?></p>
<h2><?php echo esc_html($summary['title']); ?></h2>
<p><?php echo esc_html($summary['body']); ?></p>
<dl class="lm-quick-info">
<div>
<dt><?php esc_html_e('Disponibilidad', 'lumina'); ?></dt>
<dd><?php echo esc_html($hero['availability']); ?></dd>
</div>
<div>
<dt><?php esc_html_e('Enfoque', 'lumina'); ?></dt>
<dd><?php echo esc_html($hero['focus']); ?></dd>
</div>
</dl>
</section>
<!-- Competencias -->
<section class="lm-widget lm-widget--skills">
<p class="lm-eyebrow"><?php esc_html_e('Competencias clave', 'lumina'); ?></p>
<h2><?php esc_html_e('Capacidades ejecutivas y técnicas', 'lumina'); ?></h2>
<?php foreach ($competencies as $group => $items) : ?>
<div class="lm-skill-group">
<h3><?php echo esc_html($group); ?></h3>
<div class="lm-pills">
<?php foreach ($items as $item) : ?>
<span class="lm-pill"><?php echo esc_html(is_array($item) ? ($item['name'] ?? '') : $item); ?></span>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</section>
<!-- Voluntariado -->
<section class="lm-widget">
<p class="lm-eyebrow"><?php esc_html_e('Voluntariado', 'lumina'); ?></p>
<h2><?php echo esc_html($volunteer['title']); ?></h2>
<p class="lm-widget__year"><?php echo esc_html($volunteer['year']); ?></p>
<p><?php echo esc_html($volunteer['body']); ?></p>
</section>
<!-- Datos adicionales -->
<section class="lm-widget" id="contacto">
<p class="lm-eyebrow"><?php esc_html_e('Información adicional', 'lumina'); ?></p>
<h2><?php esc_html_e('Datos clave del perfil', 'lumina'); ?></h2>
<dl class="lm-info-list">
<?php foreach ($additional as $label => $value) : ?>
<div>
<dt><?php echo esc_html($label); ?></dt>
<dd><?php echo esc_html($value); ?></dd>
</div>
<?php endforeach; ?>
</dl>
<div class="lm-contact-links">
<?php echo wp_kses_post(lumina_contact_link('email', $heroEmail)); ?>
<?php echo wp_kses_post(lumina_contact_link('phone', $heroPhone)); ?>
</div>
</section>
<!-- Tarjeta de presentación -->
<section class="lm-widget lm-widget--card">
<p class="lm-eyebrow"><?php esc_html_e('Tarjeta de presentación', 'lumina'); ?></p>
<h2><?php esc_html_e('Perfil digital', 'lumina'); ?></h2>
<article class="lm-bcard">
<div class="lm-bcard__header">
<p class="lm-bcard__name"><?php echo esc_html($heroName); ?></p>
<p class="lm-bcard__role"><?php echo esc_html($heroRole); ?></p>
</div>
<div class="lm-bcard__body">
<div class="lm-bcard__qr">
<img src="<?php echo esc_url($qrImage); ?>" alt="<?php esc_attr_e('Código QR hacia el perfil web', 'lumina'); ?>">
</div>
<div class="lm-bcard__info">
<p><?php echo esc_html($heroLocation); ?></p>
<p><?php echo esc_html($heroPhone); ?></p>
<p><?php echo esc_html($heroEmail); ?></p>
<a href="<?php echo esc_url($publicUrl); ?>" target="_blank" rel="noreferrer"><?php echo esc_html($publicUrl); ?></a>
</div>
</div>
</article>
</section>
</aside><!-- /.lm-sidebar -->
<!-- CONTENIDO PRINCIPAL -->
<div class="lm-content">
<!-- Experiencia -->
<section class="lm-section-inner" id="experiencia">
<header class="lm-section-head">
<p class="lm-eyebrow"><?php esc_html_e('Experiencia profesional', 'lumina'); ?></p>
<h2><?php esc_html_e('Trayectoria sólida en banca, operaciones y microfinanzas', 'lumina'); ?></h2>
</header>
<div class="lm-timeline">
<?php foreach ($experience as $job) : ?>
<article class="lm-timeline__item">
<div class="lm-timeline__marker" aria-hidden="true"></div>
<div class="lm-timeline__body">
<span class="lm-timeline__period"><?php echo esc_html($job['period']); ?></span>
<h3><?php echo esc_html($job['title']); ?></h3>
<p class="lm-timeline__meta">
<strong><?php echo esc_html($job['company']); ?></strong>
&middot; <?php echo esc_html($job['location']); ?>
</p>
<p class="lm-timeline__lead"><?php echo esc_html($job['lead']); ?></p>
<ul class="lm-timeline__bullets">
<?php foreach ($job['bullets'] as $bullet) : ?>
<li><?php echo esc_html($bullet); ?></li>
<?php endforeach; ?>
</ul>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<!-- Formación -->
<section class="lm-section-inner" id="formacion">
<header class="lm-section-head">
<p class="lm-eyebrow"><?php esc_html_e('Formación académica', 'lumina'); ?></p>
<h2><?php esc_html_e('Base técnica y formación ejecutiva de alto nivel', 'lumina'); ?></h2>
</header>
<div class="lm-edu-grid">
<?php foreach ($education as $edu) : ?>
<article class="lm-edu-card">
<span class="lm-edu-card__year"><?php echo esc_html($edu['year']); ?></span>
<div>
<h3><?php echo esc_html($edu['title']); ?></h3>
<p><?php echo esc_html($edu['institution']); ?></p>
<?php if ($edu['detail'] !== '') : ?>
<p class="lm-edu-card__detail"><?php echo esc_html($edu['detail']); ?></p>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<!-- Certificaciones -->
<section class="lm-section-inner" id="certificaciones">
<header class="lm-section-head">
<p class="lm-eyebrow"><?php esc_html_e('Certificaciones relevantes', 'lumina'); ?></p>
<h2><?php esc_html_e('Actualización continua en gerencia, procesos y analítica', 'lumina'); ?></h2>
</header>
<div class="lm-cert-list">
<?php foreach ($certifications as $cert) : ?>
<article class="lm-cert-item">
<span class="lm-cert-item__year"><?php echo esc_html($cert['year']); ?></span>
<div>
<h3><?php echo esc_html($cert['title']); ?></h3>
<p><?php echo esc_html($cert['issuer']); ?></p>
</div>
</article>
<?php endforeach; ?>
</div>
</section>
<!-- Contenido editable de WordPress -->
<?php while (have_posts()) : the_post(); ?>
<?php if (trim((string) get_the_content()) !== '') : ?>
<section class="lm-section-inner">
<header class="lm-section-head">
<p class="lm-eyebrow"><?php esc_html_e('Contenido adicional', 'lumina'); ?></p>
<h2><?php esc_html_e('Bloques editables desde WordPress', 'lumina'); ?></h2>
</header>
<div class="lm-entry__body">
<?php the_content(); ?>
</div>
</section>
<?php endif; ?>
<?php endwhile; ?>
</div><!-- /.lm-content -->
</div><!-- /.lm-body -->
<?php get_footer(); ?>
+133
View File
@@ -0,0 +1,133 @@
<?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';
function lumina_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', 'lumina'),
]);
}
add_action('after_setup_theme', 'lumina_setup');
function lumina_enqueue_assets(): void
{
wp_enqueue_style(
'lumina-fonts',
'https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap',
[],
null
);
wp_enqueue_style(
'lumina-theme',
get_template_directory_uri() . '/assets/css/theme.css',
['lumina-fonts'],
wp_get_theme()->get('Version')
);
wp_enqueue_script(
'lumina-theme',
get_template_directory_uri() . '/assets/js/theme.js',
[],
wp_get_theme()->get('Version'),
true
);
}
add_action('wp_enqueue_scripts', 'lumina_enqueue_assets');
function lumina_body_classes(array $classes): array
{
$classes[] = 'lumina-shell';
if (is_front_page()) {
$classes[] = 'lumina-front';
}
return $classes;
}
add_filter('body_class', 'lumina_body_classes');
function lumina_redirect_sample_page(): void
{
if (! is_page()) {
return;
}
$page = get_queried_object();
if (! $page instanceof WP_Post) {
return;
}
if ($page->post_name !== 'pagina-ejemplo') {
return;
}
wp_safe_redirect(home_url('/'), 301);
exit;
}
add_action('template_redirect', 'lumina_redirect_sample_page', 1);
function lumina_render_primary_menu(): string
{
if (has_nav_menu('primary')) {
return trim((string) wp_nav_menu([
'theme_location' => 'primary',
'container' => false,
'fallback_cb' => false,
'menu_class' => 'lm-nav__list',
'echo' => false,
]));
}
$items = [
'#resumen' => __('Perfil', 'lumina'),
'#impacto' => __('Impacto', 'lumina'),
'#experiencia' => __('Experiencia', 'lumina'),
'#formacion' => __('Formación', 'lumina'),
'#certificaciones' => __('Certificaciones', 'lumina'),
'#contacto' => __('Contacto', 'lumina'),
];
$html = '<ul class="lm-nav__list">';
foreach ($items as $href => $label) {
$html .= sprintf(
'<li><a href="%s">%s</a></li>',
esc_url($href),
esc_html($label)
);
}
$html .= '</ul>';
return $html;
}
function lumina_contact_link(string $type, string $value): string
{
if ($value === '') {
return '';
}
$href = match ($type) {
'email' => 'mailto:' . antispambot($value),
'phone' => 'tel:' . preg_replace('/[^0-9+]/', '', $value),
default => esc_url($value),
};
return sprintf(
'<a class="lm-chip" href="%s">%s</a>',
esc_url($href),
esc_html($value)
);
}
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
$brand = get_theme_mod('lm_hero_name', get_bloginfo('name'));
?>
<!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(); ?>
<header class="lm-header" id="top">
<div class="lm-header__inner">
<a class="lm-brand" href="<?php echo esc_url(home_url('/')); ?>">
<span class="lm-brand__dot"></span>
<?php echo esc_html($brand); ?>
</a>
<button class="lm-nav-toggle" type="button" aria-expanded="false" aria-controls="lm-nav" aria-label="<?php esc_attr_e('Abrir menú', 'lumina'); ?>">
<span></span><span></span><span></span>
</button>
<nav id="lm-nav" class="lm-nav" aria-label="<?php esc_attr_e('Menú principal', 'lumina'); ?>">
<?php echo lumina_render_primary_menu(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</nav>
<div class="lm-header__actions">
<a class="lm-btn lm-btn--outline-sm" href="<?php echo esc_url(home_url('/?cv_pdf=1')); ?>" target="_blank">
<svg width="13" height="13" 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"/><polyline points="10 9 9 9 8 9"/></svg>
<?php esc_html_e('Descargar CV PDF', 'lumina'); ?>
</a>
</div>
</div>
</header>
<main class="lm-main">
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
function lumina_customize_register(WP_Customize_Manager $wp_customize): void
{
$wp_customize->add_section('lumina_hero', [
'title' => __('Lumina CV: Presentación', 'lumina'),
'priority' => 30,
]);
$fields = [
'lm_hero_name' => ['label' => __('Nombre principal', 'lumina'), 'default' => 'Patricia Soledad Flores Sánchez'],
'lm_hero_role' => ['label' => __('Rol o titular', 'lumina'), 'default' => 'Gerente / Jefa de Operaciones Financieras | Sector financiero y microfinanzas'],
'lm_hero_intro' => ['label' => __('Introducción', 'lumina'), 'default' => '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.'],
'lm_hero_cta_label' => ['label' => __('Texto del botón', 'lumina'), 'default' => 'Ver trayectoria'],
'lm_hero_cta_url' => ['label' => __('Enlace del botón', 'lumina'), 'default' => '#experiencia'],
'lm_contact_location'=> ['label' => __('Ubicación', 'lumina'), 'default' => 'Loja, Ecuador'],
'lm_contact_email' => ['label' => __('Correo', 'lumina'), 'default' => 'patricflorsanchez@gmail.com'],
'lm_contact_phone' => ['label' => __('Teléfono', 'lumina'), 'default' => '+593 99 469 8405'],
'lm_social_linkedin' => ['label' => __('LinkedIn', 'lumina'), 'default' => 'https://www.linkedin.com/in/patricia-soledad-flores-s%C3%A1nchez-43b84418/'],
'lm_social_github' => ['label' => __('GitHub', 'lumina'), 'default' => ''],
];
$wp_customize->add_setting('lm_profile_image', [
'default' => '',
'sanitize_callback' => 'esc_url_raw',
]);
$wp_customize->add_control(new WP_Customize_Image_Control($wp_customize, 'lm_profile_image', [
'label' => __('Foto de perfil', 'lumina'),
'section' => 'lumina_hero',
'settings' => 'lm_profile_image',
]));
foreach ($fields as $key => $field) {
$isUrl = str_contains($key, 'url')
|| str_contains($key, 'linkedin')
|| str_contains($key, 'github');
$wp_customize->add_setting($key, [
'default' => $field['default'],
'sanitize_callback' => $isUrl ? 'esc_url_raw' : 'sanitize_text_field',
]);
$wp_customize->add_control($key, [
'label' => $field['label'],
'section' => 'lumina_hero',
'type' => $key === 'lm_hero_intro' ? 'textarea' : 'text',
]);
}
}
add_action('customize_register', 'lumina_customize_register');
+189
View File
@@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
function lumina_cv_data(): array
{
return [
'hero' => [
'eyebrow' => 'Currículum ejecutivo',
'name' => 'Patricia Soledad Flores Sánchez',
'role' => 'Gerente / Jefa de Operaciones Financieras | 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' => [
'Gestión de operaciones financieras',
'Conciliación bancaria',
'Originación de crédito',
'Gestión de cartera',
'Control interno',
'Tesorería operativa',
'Microfinanzas',
'ALA/FT',
],
'Tecnología y Procesos' => [
'Transformación digital',
'BPM',
'Integración de APIs / Web Services',
'SAP FI',
'Excel avanzado y dashboards',
'Power BI',
'IA aplicada',
'Core financiero',
],
'Liderazgo' => [
'Equipos de hasta 48 personas',
'Remuneración variable',
'Gestión del cambio',
'Formación de formadores',
'Diversidad, equidad e inclusión',
],
],
'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' => 'pflres.linkedcorp.net',
],
];
}
+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);
+28
View File
@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
get_header();
?>
<section class="lm-wrap">
<div class="lm-card">
<?php if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>
<article <?php post_class('lm-entry'); ?>>
<h1 class="lm-entry__title"><?php the_title(); ?></h1>
<div class="lm-entry__body">
<?php the_content(); ?>
</div>
</article>
<?php endwhile; ?>
<?php else : ?>
<article class="lm-entry lm-entry--empty">
<h1><?php esc_html_e('Empieza a construir tu perfil', 'lumina'); ?></h1>
<p><?php esc_html_e('Crea una página de inicio y usa Lumina CV como tema activo.', 'lumina'); ?></p>
</article>
<?php endif; ?>
</div>
</section>
<?php get_footer(); ?>
+11
View File
@@ -0,0 +1,11 @@
/*
Theme Name: Lumina CV
Theme URI: https://pflres.linkedcorp.net
Author: mgonzalez
Description: Tema WordPress moderno, alegre y profesional para hoja de vida ejecutiva. Diseño claro con paleta teal y ámbar, tipografía limpia y layout de sidebar izquierdo.
Version: 1.0.0
Requires at least: 6.8
Tested up to: 6.8
Requires PHP: 8.1
Text Domain: lumina
*/
@@ -0,0 +1,458 @@
/* =========================================================
Persona CV — Inspirado en Personage / Pixflow
Raleway + Open Sans | Secciones alternas | Timeline central
========================================================= */
:root {
--pn-accent: #c0392b;
--pn-accent-h: #a93226;
--pn-dark: #1a2332;
--pn-dark-2: #243045;
--pn-white: #ffffff;
--pn-gray: #f5f6f8;
--pn-text: #2d3436;
--pn-muted: #636e72;
--pn-border: #e0e3e8;
--pn-header-h: 70px;
--pn-container: 1140px;
--radius: 6px;
--shadow: 0 4px 20px rgba(0,0,0,.1);
--shadow-lg: 0 8px 40px rgba(0,0,0,.15);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;scroll-padding-top:var(--pn-header-h);font-size:16px}
body{font-family:'Open Sans',system-ui,sans-serif;color:var(--pn-text);background:var(--pn-white);-webkit-font-smoothing:antialiased;overflow-x:hidden}
img{display:block;max-width:100%}
a{color:var(--pn-accent);text-decoration:none;transition:color .2s,opacity .2s}
a:hover{color:var(--pn-accent-h)}
ul{list-style:none}
h1,h2,h3,h4{font-family:'Raleway',sans-serif;line-height:1.25;color:var(--pn-text)}
/* ── CONTENEDOR ── */
.pn-container{max-width:var(--pn-container);margin-inline:auto;padding-inline:2rem}
/* ── HEADER ── */
.pn-header{
position:fixed;top:0;left:0;right:0;z-index:100;
height:var(--pn-header-h);
background:rgba(255,255,255,.97);
border-bottom:1px solid var(--pn-border);
box-shadow:0 2px 12px rgba(0,0,0,.06);
transition:background .3s,box-shadow .3s;
}
.pn-header.is-scrolled{background:#fff;box-shadow:var(--shadow)}
.pn-header__inner{
max-width:var(--pn-container);margin-inline:auto;padding-inline:2rem;
height:100%;display:flex;align-items:center;gap:2rem;
}
.pn-brand{
display:flex;align-items:center;gap:.5rem;
font-family:'Raleway',sans-serif;font-size:.95rem;font-weight:700;
color:var(--pn-text);letter-spacing:.04em;text-decoration:none;white-space:nowrap;flex-shrink:0;
}
.pn-brand:hover{color:var(--pn-accent)}
.pn-brand__dot{width:8px;height:8px;border-radius:50%;background:var(--pn-accent);flex-shrink:0}
.pn-nav{margin-left:auto}
.pn-nav__list{display:flex;align-items:center;gap:.25rem}
.pn-nav__list li a{
display:block;padding:.4rem .75rem;
font-family:'Raleway',sans-serif;font-size:.72rem;font-weight:600;
letter-spacing:.1em;text-transform:uppercase;color:var(--pn-muted);
border-radius:var(--radius);transition:color .2s;text-decoration:none;
}
.pn-nav__list li a:hover,.pn-nav__list li a.is-active{color:var(--pn-accent)}
.pn-header__actions{flex-shrink:0}
.pn-hamburger{
display:none;flex-direction:column;justify-content:center;align-items:center;
gap:5px;width:40px;height:40px;background:none;border:none;cursor:pointer;margin-left:auto;
}
.pn-hamburger span{display:block;width:22px;height:2px;background:var(--pn-text);border-radius:2px;transition:transform .25s,opacity .25s}
/* ── BOTONES ── */
.pn-btn{
display:inline-flex;align-items:center;gap:.5rem;
padding:.75rem 1.75rem;border-radius:var(--radius);
font-family:'Raleway',sans-serif;font-size:.8rem;font-weight:700;
letter-spacing:.08em;text-transform:uppercase;cursor:pointer;
border:2px solid transparent;transition:all .25s;text-decoration:none;line-height:1;
}
.pn-btn svg{width:14px;height:14px;flex-shrink:0}
.pn-btn--accent{background:var(--pn-accent);color:#fff;border-color:var(--pn-accent)}
.pn-btn--accent:hover{background:var(--pn-accent-h);border-color:var(--pn-accent-h);color:#fff}
.pn-btn--white{background:transparent;color:#fff;border-color:rgba(255,255,255,.7)}
.pn-btn--white:hover{background:#fff;color:var(--pn-accent)}
.pn-btn--outline{background:transparent;color:var(--pn-accent);border-color:var(--pn-accent)}
.pn-btn--outline:hover{background:var(--pn-accent);color:#fff}
.pn-btn--sm{padding:.5rem 1.1rem;font-size:.72rem}
/* ── SECCIONES ── */
.pn-section{padding:5rem 0}
.pn-section--white{background:var(--pn-white)}
.pn-section--gray{background:var(--pn-gray)}
.pn-section--dark{background:var(--pn-dark)}
.pn-main{padding-top:var(--pn-header-h)}
/* ── CABECERA DE SECCIÓN ── */
.pn-section-head{text-align:center;margin-bottom:3.5rem}
.pn-eyebrow{
display:block;font-family:'Raleway',sans-serif;font-size:.72rem;
font-weight:600;letter-spacing:.2em;text-transform:uppercase;
color:var(--pn-muted);margin-bottom:1rem;
}
.pn-eyebrow--light{color:rgba(255,255,255,.5)}
.pn-section-head h2{
font-family:'Raleway',sans-serif;font-size:clamp(1.8rem,3vw,2.5rem);
font-weight:700;color:var(--pn-text);letter-spacing:.02em;margin-bottom:.75rem;
}
.pn-section-head__title--light,.pn-section--dark .pn-section-head h2{color:#fff}
.pn-section-bar{width:48px;height:3px;background:var(--pn-accent);border-radius:2px;margin:0 auto}
/* ── HERO ── */
.pn-hero{
position:relative;width:100%;height:100vh;min-height:600px;
display:flex;align-items:center;justify-content:center;overflow:hidden;
}
.pn-hero__bg{
position:absolute;inset:0;background-size:cover;background-position:center top;
filter:grayscale(20%) brightness(.4);transform:scale(1.05);
transition:transform 8s ease;
}
.pn-hero:hover .pn-hero__bg{transform:scale(1)}
.pn-hero__overlay{
position:absolute;inset:0;
background:linear-gradient(160deg,rgba(10,18,36,.85) 0%,rgba(192,57,43,.2) 100%);
}
.pn-hero__content{
position:relative;z-index:2;text-align:center;
display:flex;flex-direction:column;align-items:center;gap:1.25rem;
padding-inline:2rem;max-width:800px;
}
.pn-hero__avatar{
width:130px;height:130px;border-radius:50%;overflow:hidden;
border:4px solid rgba(255,255,255,.9);
box-shadow:0 0 0 8px rgba(192,57,43,.25),var(--shadow-lg);
flex-shrink:0;
}
.pn-hero__avatar img{width:100%;height:100%;object-fit:cover}
.pn-hero__eyebrow{
font-family:'Raleway',sans-serif;font-size:.72rem;font-weight:600;
letter-spacing:.25em;text-transform:uppercase;color:var(--pn-accent);
}
.pn-hero__name{
font-family:'Raleway',sans-serif;font-size:clamp(2.2rem,5vw,3.5rem);
font-weight:800;color:#fff;letter-spacing:.03em;line-height:1.1;
}
.pn-hero__roles{
font-family:'Raleway',sans-serif;font-size:clamp(.9rem,1.8vw,1.1rem);
font-weight:300;color:rgba(255,255,255,.8);letter-spacing:.05em;
display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:.5rem;
}
.pn-hero__roles-sep{color:var(--pn-accent);font-weight:400}
.pn-hero__chips{display:flex;flex-wrap:wrap;justify-content:center;gap:.75rem}
.pn-hero__chip{
display:inline-flex;align-items:center;gap:.4rem;
padding:.3rem .9rem;border-radius:var(--radius);
font-size:.75rem;font-weight:500;
background:rgba(255,255,255,.12);color:rgba(255,255,255,.85);
border:1px solid rgba(255,255,255,.2);backdrop-filter:blur(4px);
}
.pn-hero__chip--avail{color:#6fcf97;border-color:rgba(111,207,151,.4);background:rgba(111,207,151,.1)}
.pn-dot{width:6px;height:6px;border-radius:50%;background:#6fcf97;animation:pn-blink 2s ease-in-out infinite}
@keyframes pn-blink{0%,100%{opacity:1}50%{opacity:.3}}
.pn-hero__actions{display:flex;flex-wrap:wrap;justify-content:center;gap:.75rem}
.pn-hero__socials{display:flex;gap:.75rem;justify-content:center}
.pn-social-icon{
width:36px;height:36px;border-radius:50%;display:flex;align-items:center;justify-content:center;
background:rgba(255,255,255,.12);color:#fff;border:1px solid rgba(255,255,255,.25);
transition:background .2s,border-color .2s,color .2s;
}
.pn-social-icon:hover{background:var(--pn-accent);border-color:var(--pn-accent);color:#fff}
.pn-social-icon svg{width:15px;height:15px}
.pn-social-icon--dark{background:var(--pn-gray);color:var(--pn-text);border-color:var(--pn-border)}
.pn-social-icon--dark:hover{background:var(--pn-accent);color:#fff;border-color:var(--pn-accent)}
.pn-scroll-hint{
position:absolute;bottom:2rem;left:50%;transform:translateX(-50%);
width:26px;height:44px;border:2px solid rgba(255,255,255,.4);border-radius:13px;
display:flex;align-items:flex-start;justify-content:center;padding-top:6px;
}
.pn-scroll-hint span{
width:4px;height:8px;background:rgba(255,255,255,.7);border-radius:2px;
animation:pn-scroll 2s ease-in-out infinite;
}
@keyframes pn-scroll{0%{opacity:1;transform:translateY(0)}100%{opacity:0;transform:translateY(16px)}}
/* ── SOBRE MÍ ── */
.pn-about-grid{display:grid;grid-template-columns:280px 1fr;gap:4rem;align-items:start}
.pn-about-photo-col{text-align:center}
.pn-about-photo{
width:220px;height:220px;border-radius:50%;overflow:hidden;margin:0 auto 1.25rem;
box-shadow:0 0 0 6px var(--pn-gray),0 0 0 10px var(--pn-accent),var(--shadow-lg);
}
.pn-about-photo img{width:100%;height:100%;object-fit:cover}
.pn-about-name{font-family:'Raleway',sans-serif;font-size:1.15rem;font-weight:700;margin-bottom:.25rem}
.pn-about-role{font-size:.82rem;color:var(--pn-muted);line-height:1.5;margin-bottom:1rem}
.pn-about-socials{display:flex;justify-content:center;gap:.5rem}
.pn-about-text-col h3{
font-family:'Raleway',sans-serif;font-size:1.35rem;font-weight:700;
margin-bottom:1rem;color:var(--pn-text);
}
.pn-about-text-col p{font-size:.95rem;color:var(--pn-muted);line-height:1.8;margin-bottom:2rem}
.pn-personal-details{border-top:2px solid var(--pn-gray);padding-top:1.5rem;display:flex;flex-direction:column;gap:.75rem}
.pn-pd-row{display:grid;grid-template-columns:130px 1fr;gap:1rem;font-size:.875rem}
.pn-pd-row span{color:var(--pn-muted);font-weight:600;text-transform:uppercase;font-size:.72rem;letter-spacing:.06em;padding-top:2px}
.pn-pd-row strong{color:var(--pn-text);font-weight:500}
.pn-pd-row strong a{color:var(--pn-accent)}
/* ── CONTADORES ── */
.pn-counters{
display:grid;grid-template-columns:repeat(5,1fr);gap:1px;
background:rgba(255,255,255,.08);border-radius:var(--radius);overflow:hidden;
}
.pn-counter{
display:flex;flex-direction:column;align-items:center;gap:.75rem;
padding:2.5rem 1rem;text-align:center;background:transparent;
transition:background .2s;position:relative;
}
.pn-counter::after{
content:'';position:absolute;right:0;top:25%;bottom:25%;
width:1px;background:rgba(255,255,255,.1);
}
.pn-counter:last-child::after{display:none}
.pn-counter:hover{background:rgba(255,255,255,.04)}
.pn-counter__number{
font-family:'Raleway',sans-serif;font-size:clamp(2.2rem,4vw,3rem);
font-weight:800;color:var(--pn-accent);line-height:1;
}
.pn-counter__label{font-size:.78rem;color:rgba(255,255,255,.55);letter-spacing:.06em;text-transform:uppercase}
/* ── HIGHLIGHTS ── */
.pn-highlights-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1.5rem;margin-bottom:3rem}
.pn-highlight-card{
text-align:center;padding:2rem 1.5rem;background:#fff;
border:1px solid var(--pn-border);border-radius:var(--radius);
box-shadow:var(--shadow);transition:transform .2s,box-shadow .2s;position:relative;overflow:hidden;
}
.pn-highlight-card::before{
content:'';position:absolute;top:0;left:0;right:0;height:3px;background:var(--pn-accent);
}
.pn-highlight-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
.pn-highlight-card__value{
font-family:'Raleway',sans-serif;font-size:clamp(1.8rem,3vw,2.6rem);
font-weight:800;color:var(--pn-accent);line-height:1;margin-bottom:.75rem;
}
.pn-highlight-card p{font-size:.82rem;color:var(--pn-muted);line-height:1.5}
.pn-impact-row{display:grid;grid-template-columns:repeat(3,1fr);gap:1.5rem}
.pn-impact-card{
padding:2rem;background:#fff;border:1px solid var(--pn-border);border-radius:var(--radius);
box-shadow:var(--shadow);transition:transform .2s,box-shadow .2s;
}
.pn-impact-card:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg)}
.pn-impact-card__icon{
width:48px;height:48px;background:rgba(192,57,43,.08);border-radius:var(--radius);
color:var(--pn-accent);padding:.75rem;margin-bottom:1rem;
}
.pn-impact-card__icon svg{width:100%;height:100%}
.pn-impact-card h3{font-family:'Raleway',sans-serif;font-size:1rem;font-weight:700;margin-bottom:.5rem}
.pn-impact-card p{font-size:.875rem;color:var(--pn-muted);line-height:1.65}
/* ── TIMELINE CENTRADO ── */
.pn-timeline{position:relative;padding:1rem 0}
.pn-timeline::before{
content:'';position:absolute;left:50%;transform:translateX(-50%);
top:0;bottom:0;width:2px;background:var(--pn-border);
}
.pn-tl-entry{
display:flex;width:100%;position:relative;margin-bottom:3rem;
}
.pn-tl-entry--left{justify-content:flex-end;padding-right:calc(50% + 2.5rem)}
.pn-tl-entry--right{justify-content:flex-start;padding-left:calc(50% + 2.5rem)}
.pn-tl-content{
width:100%;max-width:440px;background:#fff;border:1px solid var(--pn-border);
border-radius:var(--radius);padding:1.75rem;box-shadow:var(--shadow);
transition:transform .2s,box-shadow .2s;
}
.pn-tl-content:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg)}
.pn-tl-dot{
position:absolute;left:50%;top:1.75rem;transform:translateX(-50%);
width:16px;height:16px;border-radius:50%;background:#fff;
border:3px solid var(--pn-accent);z-index:1;
box-shadow:0 0 0 4px rgba(192,57,43,.15);
}
.pn-tl-period{
display:inline-block;padding:.2rem .7rem;border-radius:var(--radius);
font-family:'Raleway',sans-serif;font-size:.68rem;font-weight:700;
letter-spacing:.06em;text-transform:uppercase;
background:rgba(192,57,43,.08);color:var(--pn-accent);margin-bottom:.75rem;
}
.pn-tl-content h3{font-family:'Raleway',sans-serif;font-size:1.05rem;font-weight:700;margin-bottom:.25rem}
.pn-tl-company{font-size:.83rem;font-weight:600;color:var(--pn-accent);margin-bottom:.5rem}
.pn-tl-company em{font-style:normal;color:var(--pn-muted);font-weight:400}
.pn-tl-lead{font-size:.875rem;color:var(--pn-muted);font-style:italic;line-height:1.65;margin-bottom:.75rem}
.pn-tl-bullets{display:flex;flex-direction:column;gap:.4rem}
.pn-tl-bullets li{font-size:.83rem;color:var(--pn-muted);line-height:1.6;padding-left:1rem;position:relative}
.pn-tl-bullets li::before{content:'▸';position:absolute;left:0;top:0;color:var(--pn-accent);font-size:.7rem;line-height:1.8}
/* ── FORMACIÓN ── */
.pn-edu-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:2rem}
.pn-edu-card{
display:flex;gap:1.5rem;align-items:flex-start;padding:2rem;
background:#fff;border:1px solid var(--pn-border);border-radius:var(--radius);
box-shadow:var(--shadow);transition:transform .2s,box-shadow .2s;
}
.pn-edu-card:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg)}
.pn-edu-card__year{
font-family:'Raleway',sans-serif;font-size:1.4rem;font-weight:800;
color:var(--pn-accent);flex-shrink:0;line-height:1;padding-top:2px;
}
.pn-edu-card h3{font-family:'Raleway',sans-serif;font-size:.95rem;font-weight:700;margin-bottom:.4rem;line-height:1.4}
.pn-edu-card__inst{font-size:.83rem;color:var(--pn-muted);line-height:1.5}
.pn-edu-card__detail{font-size:.78rem;color:var(--pn-accent);margin-top:.4rem;font-style:italic}
/* ── COMPETENCIAS / SKILLS ── */
.pn-skills-cols{display:grid;grid-template-columns:repeat(3,1fr);gap:3rem}
.pn-skills-group h3{
font-family:'Raleway',sans-serif;font-size:.9rem;font-weight:700;
letter-spacing:.06em;text-transform:uppercase;color:var(--pn-text);
margin-bottom:1.5rem;padding-bottom:.5rem;border-bottom:2px solid var(--pn-accent);
}
.pn-skill{margin-bottom:1.25rem}
.pn-skill__header{
display:flex;justify-content:space-between;align-items:center;
font-size:.82rem;color:var(--pn-text);margin-bottom:.4rem;
}
.pn-skill__header span:last-child{font-weight:700;color:var(--pn-accent);font-size:.78rem}
.pn-skill__bar{height:6px;background:var(--pn-border);border-radius:3px;overflow:hidden}
.pn-skill__fill{height:100%;background:var(--pn-accent);border-radius:3px;width:0%;transition:width .8s cubic-bezier(.4,0,.2,1)}
/* ── CERTIFICACIONES ── */
.pn-certs-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:1rem}
.pn-cert{
display:flex;gap:1.25rem;align-items:flex-start;
padding:1.25rem;background:#fff;border:1px solid var(--pn-border);
border-radius:var(--radius);transition:border-color .2s,box-shadow .2s;
}
.pn-cert:hover{border-color:var(--pn-accent);box-shadow:var(--shadow)}
.pn-cert__year{
font-family:'Raleway',sans-serif;font-size:.72rem;font-weight:700;
background:var(--pn-accent);color:#fff;padding:.25rem .5rem;
border-radius:var(--radius);flex-shrink:0;letter-spacing:.04em;
align-self:flex-start;
}
.pn-cert h3{font-size:.875rem;font-weight:600;color:var(--pn-text);margin-bottom:.25rem;line-height:1.4}
.pn-cert p{font-size:.78rem;color:var(--pn-muted)}
/* ── CONTACTO ── */
.pn-contact-grid{display:grid;grid-template-columns:1fr 360px;gap:4rem;align-items:start}
.pn-contact-list{display:flex;flex-direction:column;gap:1.25rem;margin-bottom:2.5rem}
.pn-contact-list li{display:flex;align-items:flex-start;gap:.875rem;color:rgba(255,255,255,.7);font-size:.9rem}
.pn-contact-list li a{color:rgba(255,255,255,.7);transition:color .2s}
.pn-contact-list li a:hover{color:var(--pn-accent)}
.pn-contact-list__icon{width:18px;height:18px;flex-shrink:0;color:var(--pn-accent);margin-top:1px}
.pn-contact-list__icon svg{width:100%;height:100%}
.pn-contact-extra{
padding:1.5rem;background:rgba(255,255,255,.05);border-radius:var(--radius);
border-left:3px solid var(--pn-accent);margin-bottom:2rem;
}
.pn-contact-extra h4{font-family:'Raleway',sans-serif;font-size:.72rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--pn-accent);margin-bottom:.5rem}
.pn-contact-extra__title{font-size:.9rem;font-weight:600;color:#fff;margin-bottom:.5rem}
.pn-contact-extra__title span{font-weight:400;color:rgba(255,255,255,.5);margin-left:.5rem}
.pn-contact-extra p:last-child{font-size:.83rem;color:rgba(255,255,255,.5);line-height:1.6}
.pn-extra-dl{display:flex;flex-direction:column;gap:.875rem}
.pn-extra-dl>div{display:grid;grid-template-columns:140px 1fr;gap:.5rem;padding-bottom:.875rem;border-bottom:1px solid rgba(255,255,255,.08)}
.pn-extra-dl>div:last-child{border-bottom:none}
.pn-extra-dl dt{font-size:.68rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:rgba(255,255,255,.3)}
.pn-extra-dl dd{font-size:.83rem;color:rgba(255,255,255,.65)}
.pn-bcard-label{font-family:'Raleway',sans-serif;font-size:.68rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--pn-accent);margin-bottom:1rem}
.pn-bcard{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);border-radius:var(--radius);overflow:hidden}
.pn-bcard__front{display:flex;align-items:center;gap:1rem;padding:1.25rem 1.5rem;border-bottom:1px solid rgba(255,255,255,.08)}
.pn-bcard__photo{width:52px;height:52px;border-radius:50%;overflow:hidden;flex-shrink:0;border:2px solid var(--pn-accent)}
.pn-bcard__photo img{width:100%;height:100%;object-fit:cover}
.pn-bcard__name{font-family:'Raleway',sans-serif;font-size:.92rem;font-weight:700;color:#fff;margin-bottom:.2rem}
.pn-bcard__role{font-size:.72rem;color:rgba(255,255,255,.5);line-height:1.4}
.pn-bcard__details{display:grid;grid-template-columns:80px 1fr;gap:1rem;padding:1.25rem 1.5rem}
.pn-bcard__qr img{width:80px;height:80px;border-radius:var(--radius);filter:invert(1) opacity(.85)}
.pn-bcard__info{display:flex;flex-direction:column;gap:3px;font-size:.75rem;color:rgba(255,255,255,.5)}
.pn-bcard__info a{color:var(--pn-accent);font-weight:500;word-break:break-all}
.pn-footer-bar{
margin-top:4rem;padding-top:2rem;border-top:1px solid rgba(255,255,255,.08);
text-align:center;font-size:.78rem;color:rgba(255,255,255,.3);letter-spacing:.04em;
}
.pn-wp-content{margin-top:2rem;padding:1.5rem;background:rgba(255,255,255,.05);border-radius:var(--radius);color:rgba(255,255,255,.7);font-size:.9rem;line-height:1.75}
/* ── SCROLL TOP ── */
.pn-scroll-top{
position:fixed;bottom:2rem;right:2rem;z-index:90;
width:44px;height:44px;background:var(--pn-accent);color:#fff;
border-radius:50%;display:flex;align-items:center;justify-content:center;
font-size:1.4rem;box-shadow:var(--shadow);
opacity:0;visibility:hidden;transform:translateY(10px);
transition:opacity .3s,visibility .3s,transform .3s;
}
.pn-scroll-top.is-visible{opacity:1;visibility:visible;transform:translateY(0)}
.pn-scroll-top:hover{background:var(--pn-accent-h);color:#fff}
/* ── RESPONSIVE 1024 ── */
@media(max-width:1024px){
.pn-counters{grid-template-columns:repeat(3,1fr)}
.pn-counter:nth-child(3)::after{display:none}
.pn-skills-cols{grid-template-columns:repeat(2,1fr)}
.pn-contact-grid{grid-template-columns:1fr}
.pn-about-grid{grid-template-columns:220px 1fr;gap:2.5rem}
.pn-highlights-grid{grid-template-columns:repeat(2,1fr)}
}
/* ── RESPONSIVE 768 ── */
@media(max-width:768px){
:root{--pn-header-h:60px}
.pn-section{padding:3.5rem 0}
.pn-hamburger{display:flex}
.pn-nav{
display:none;position:absolute;top:var(--pn-header-h);left:0;right:0;
background:#fff;border-bottom:1px solid var(--pn-border);
padding:1rem;box-shadow:var(--shadow);z-index:99;
}
.pn-nav.is-open{display:block}
.pn-nav__list{flex-direction:column;gap:.25rem}
.pn-nav__list li a{padding:.6rem 1rem;font-size:.8rem}
.pn-header__actions{display:none}
.pn-about-grid{grid-template-columns:1fr}
.pn-about-photo-col{max-width:280px;margin:0 auto}
.pn-timeline::before{left:20px}
.pn-tl-entry--left,.pn-tl-entry--right{padding:0 0 0 50px;justify-content:flex-start}
.pn-tl-dot{left:14px;transform:none}
.pn-tl-content{max-width:100%}
.pn-edu-grid{grid-template-columns:1fr}
.pn-skills-cols{grid-template-columns:1fr}
.pn-certs-grid{grid-template-columns:1fr}
.pn-impact-row{grid-template-columns:1fr}
.pn-highlights-grid{grid-template-columns:repeat(2,1fr)}
.pn-counters{grid-template-columns:repeat(2,1fr)}
.pn-counter::after{display:none}
.pn-counter:nth-child(odd)::after{display:block}
.pn-hero__avatar{width:100px;height:100px}
}
/* ── PRINT ── */
@media print{
.pn-header,.pn-scroll-top,.pn-scroll-hint,.pn-hero__actions{display:none!important}
.pn-hero{height:auto;min-height:0;padding:3rem 2rem}
.pn-hero__bg{display:none}
.pn-hero__overlay{background:var(--pn-dark)!important}
.pn-main{padding-top:0}
.pn-section{padding:2rem 0}
.pn-tl-content,.pn-highlight-card,.pn-edu-card{box-shadow:none;page-break-inside:avoid}
.pn-skill__fill{background:var(--pn-accent)!important}
a{color:inherit!important}
.pn-section--dark{background:#1a2332!important}
}
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,97 @@
document.addEventListener("DOMContentLoaded", () => {
/* ── Header scroll ── */
const header = document.querySelector(".pn-header");
const scrollTop = document.querySelector(".pn-scroll-top");
window.addEventListener("scroll", () => {
const y = window.scrollY;
if (header) header.classList.toggle("is-scrolled", y > 60);
if (scrollTop) scrollTop.classList.toggle("is-visible", y > 400);
}, { passive: true });
/* ── Mobile nav ── */
const hamburger = document.querySelector(".pn-hamburger");
const nav = document.querySelector(".pn-nav");
if (hamburger && nav) {
hamburger.addEventListener("click", () => {
const open = nav.classList.toggle("is-open");
hamburger.setAttribute("aria-expanded", String(open));
const s = hamburger.querySelectorAll("span");
if (s.length === 3) {
s[0].style.transform = open ? "rotate(45deg) translate(5px, 5px)" : "";
s[1].style.opacity = open ? "0" : "";
s[2].style.transform = open ? "rotate(-45deg) translate(5px, -5px)" : "";
}
});
document.addEventListener("click", e => {
if (!nav.contains(e.target) && !hamburger.contains(e.target)) {
nav.classList.remove("is-open");
hamburger.setAttribute("aria-expanded", "false");
hamburger.querySelectorAll("span").forEach(s => { s.style.transform = ""; s.style.opacity = ""; });
}
});
}
/* ── Nav active on scroll ── */
const navLinks = Array.from(document.querySelectorAll(".pn-nav__list a[href^='#']"));
const sections = Array.from(document.querySelectorAll("section[id]"));
if (navLinks.length && sections.length) {
const obs = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
const id = "#" + e.target.id;
navLinks.forEach(l => l.classList.toggle("is-active", l.getAttribute("href") === id));
}
});
}, { rootMargin: "-20% 0px -70% 0px" });
sections.forEach(s => obs.observe(s));
}
navLinks.forEach(l => l.addEventListener("click", () => {
if (nav) nav.classList.remove("is-open");
}));
/* ── Animated counters ── */
const counters = Array.from(document.querySelectorAll(".pn-counter__val"));
const counterObs = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
counterObs.unobserve(entry.target);
const el = entry.target;
const end = parseInt(el.dataset.count, 10);
const duration = 1800;
const step = Math.ceil(end / (duration / 16));
let current = 0;
const tick = () => {
current = Math.min(current + step, end);
el.textContent = current.toLocaleString("es-EC");
if (current < end) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
});
}, { threshold: 0.3 });
counters.forEach(c => counterObs.observe(c));
/* ── Skill bars animation ── */
const skillBars = Array.from(document.querySelectorAll(".pn-skill__fill"));
const skillObs = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (!entry.isIntersecting) return;
skillObs.unobserve(entry.target);
const bar = entry.target;
const pct = bar.dataset.pct;
setTimeout(() => { bar.style.width = pct + "%"; }, 100);
});
}, { threshold: 0.2 });
skillBars.forEach(b => skillObs.observe(b));
/* ── Print ── */
document.querySelectorAll("[data-persona-print]").forEach(b => b.addEventListener("click", () => window.print()));
/* ── Scroll top ── */
if (scrollTop) {
scrollTop.addEventListener("click", e => {
e.preventDefault();
window.scrollTo({ top: 0, behavior: "smooth" });
});
}
});
+6
View File
@@ -0,0 +1,6 @@
<?php declare(strict_types=1); ?>
</main>
<a class="pn-scroll-top" href="#pn-header" aria-label="<?php esc_attr_e('Volver arriba', 'persona'); ?>">&#8679;</a>
<?php wp_footer(); ?>
</body>
</html>
+382
View File
@@ -0,0 +1,382 @@
<?php
declare(strict_types=1);
get_header();
// Usa ACF si está disponible, si no cae al fallback hardcodeado del tema
$cv = function_exists('pflores_get_cv_data') ? pflores_get_cv_data() : persona_cv_data();
$hero = $cv['hero'];
$heroName = $hero['name'];
$heroRole = $hero['role'];
$heroEmail = $hero['email'];
$heroPhone = $hero['phone'];
$heroLocation = $hero['location'];
$linkedin = $hero['linkedin'];
$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://pflres.linkedcorp.net';
?>
<!-- ═══════════════════════════════════════════════════
HERO — PANTALLA COMPLETA
═══════════════════════════════════════════════════ -->
<section class="pn-hero" id="inicio">
<div class="pn-hero__bg" style="background-image:url('<?php echo esc_url($profileImage); ?>')"></div>
<div class="pn-hero__overlay"></div>
<div class="pn-hero__content">
<div class="pn-hero__avatar">
<img src="<?php echo esc_url($profileImage); ?>" alt="<?php echo esc_attr($heroName); ?>">
</div>
<p class="pn-hero__eyebrow"><?php echo esc_html($hero['eyebrow']); ?></p>
<h1 class="pn-hero__name"><?php echo esc_html($heroName); ?></h1>
<div class="pn-hero__roles">
<span><?php echo esc_html($heroRole); ?></span>
<span class="pn-hero__roles-sep">|</span>
<span><?php echo esc_html($hero['role2']); ?></span>
</div>
<div class="pn-hero__chips">
<span class="pn-hero__chip">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
<?php echo esc_html($heroLocation); ?>
</span>
<span class="pn-hero__chip pn-hero__chip--avail">
<span class="pn-dot" aria-hidden="true"></span>
<?php esc_html_e('Disponible inmediatamente', 'persona'); ?>
</span>
</div>
<div class="pn-hero__actions">
<a class="pn-btn pn-btn--accent" href="#sobre-mi"><?php esc_html_e('Ver perfil', 'persona'); ?></a>
<a class="pn-btn pn-btn--white" href="<?php echo esc_url(home_url('/?cv_pdf=1')); ?>">
<svg width="14" height="14" 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', 'persona'); ?>
</a>
</div>
<div class="pn-hero__socials">
<?php if ($linkedin) : ?>
<a href="<?php echo esc_url($linkedin); ?>" target="_blank" rel="noreferrer" aria-label="LinkedIn" class="pn-social-icon">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"/><rect x="2" y="9" width="4" height="12"/><circle cx="4" cy="4" r="2"/></svg>
</a>
<?php endif; ?>
<a href="mailto:<?php echo esc_attr(antispambot($heroEmail)); ?>" aria-label="Email" class="pn-social-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
</a>
<a href="tel:<?php echo esc_attr(preg_replace('/[^0-9+]/', '', $heroPhone)); ?>" aria-label="Teléfono" class="pn-social-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12 19.79 19.79 0 0 1 1.61 3.41 2 2 0 0 1 3.6 1.25h3a2 2 0 0 1 2 1.72c.128.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.96a16 16 0 0 0 6.02 6.02l.96-.96a2 2 0 0 1 2.11-.45c.907.339 1.85.572 2.81.7A2 2 0 0 1 21.73 16.92z"/></svg>
</a>
</div>
</div>
<a class="pn-scroll-hint" href="#sobre-mi" aria-label="<?php esc_attr_e('Ir al contenido', 'persona'); ?>">
<span></span>
</a>
</section>
<!-- ═══════════════════════════════════════════════════
SOBRE MÍ — blanco
═══════════════════════════════════════════════════ -->
<section class="pn-section pn-section--white" id="sobre-mi">
<div class="pn-container">
<div class="pn-section-head">
<span class="pn-eyebrow">── <?php esc_html_e('Conóceme', 'persona'); ?> ──</span>
<h2><?php esc_html_e('Sobre mí', 'persona'); ?></h2>
<div class="pn-section-bar"></div>
</div>
<div class="pn-about-grid">
<div class="pn-about-photo-col">
<div class="pn-about-photo">
<img src="<?php echo esc_url($profileImage); ?>" alt="<?php echo esc_attr($heroName); ?>">
</div>
<h3 class="pn-about-name"><?php echo esc_html($heroName); ?></h3>
<p class="pn-about-role"><?php echo esc_html($heroRole); ?></p>
<div class="pn-about-socials">
<?php if ($linkedin) : ?>
<a href="<?php echo esc_url($linkedin); ?>" target="_blank" rel="noreferrer" class="pn-social-icon pn-social-icon--dark"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"/><rect x="2" y="9" width="4" height="12"/><circle cx="4" cy="4" r="2"/></svg></a>
<?php endif; ?>
<a href="mailto:<?php echo esc_attr(antispambot($heroEmail)); ?>" class="pn-social-icon pn-social-icon--dark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg></a>
</div>
<a class="pn-btn pn-btn--accent pn-btn--sm" href="<?php echo esc_url(home_url('/?cv_pdf=1')); ?>" style="margin-top:1rem;width:100%;justify-content:center"><?php esc_html_e('Descargar CV PDF', 'persona'); ?></a>
</div>
<div class="pn-about-text-col">
<h3><?php echo esc_html($cv['summary']['title']); ?></h3>
<p><?php echo esc_html($cv['summary']['body']); ?></p>
<div class="pn-personal-details">
<div class="pn-pd-row"><span><?php esc_html_e('Ubicación', 'persona'); ?></span><strong><?php echo esc_html($heroLocation); ?></strong></div>
<div class="pn-pd-row"><span><?php esc_html_e('Correo', 'persona'); ?></span><strong><a href="mailto:<?php echo esc_attr(antispambot($heroEmail)); ?>"><?php echo esc_html($heroEmail); ?></a></strong></div>
<div class="pn-pd-row"><span><?php esc_html_e('Teléfono', 'persona'); ?></span><strong><?php echo esc_html($heroPhone); ?></strong></div>
<div class="pn-pd-row"><span><?php esc_html_e('Disponibilidad', 'persona'); ?></span><strong><?php echo esc_html($hero['availability']); ?></strong></div>
<div class="pn-pd-row"><span><?php esc_html_e('Idiomas', 'persona'); ?></span><strong><?php echo esc_html($hero['languages']); ?></strong></div>
<div class="pn-pd-row"><span><?php esc_html_e('Enfoque', 'persona'); ?></span><strong><?php echo esc_html($hero['focus']); ?></strong></div>
<div class="pn-pd-row"><span><?php esc_html_e('Web', 'persona'); ?></span><strong><?php echo esc_html($hero['web']); ?></strong></div>
</div>
</div>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════
CONTADORES — oscuro
═══════════════════════════════════════════════════ -->
<section class="pn-section pn-section--dark" id="impacto">
<div class="pn-container">
<div class="pn-counters">
<?php foreach ($cv['metrics'] as $m) : ?>
<div class="pn-counter" data-target="<?php echo esc_attr($m['value']); ?>">
<span class="pn-counter__number">
<span class="pn-counter__val" data-count="<?php echo esc_attr(preg_replace('/[^0-9]/', '', $m['value'])); ?>">0</span><?php echo esc_html($m['suffix'] ?? ''); ?>
</span>
<span class="pn-counter__label"><?php echo esc_html($m['label']); ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════
LOGROS DESTACADOS — gris claro
═══════════════════════════════════════════════════ -->
<section class="pn-section pn-section--gray">
<div class="pn-container">
<div class="pn-section-head">
<span class="pn-eyebrow">── <?php esc_html_e('Resultados medibles', 'persona'); ?> ──</span>
<h2><?php esc_html_e('Logros destacados', 'persona'); ?></h2>
<div class="pn-section-bar"></div>
</div>
<div class="pn-highlights-grid">
<?php foreach ($cv['highlights'] as $h) : ?>
<article class="pn-highlight-card">
<div class="pn-highlight-card__value">
<?php echo esc_html($h['value']); ?>
<?php if (!empty($h['suffix'])) : ?><span class="pn-highlight-card__suf"><?php echo esc_html($h['suffix']); ?></span><?php endif; ?>
</div>
<p><?php echo esc_html($h['label']); ?></p>
</article>
<?php endforeach; ?>
</div>
<div class="pn-impact-row">
<?php foreach ($cv['impact'] as $item) : ?>
<article class="pn-impact-card">
<div class="pn-impact-card__icon" aria-hidden="true">
<?php if (str_contains($item['title'], 'digital')) : ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
<?php elseif (str_contains($item['title'], 'Autom')) : ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="3"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"/><path d="M16.24 7.76a6 6 0 0 1 0 8.49M7.76 7.76a6 6 0 0 0 0 8.49"/></svg>
<?php else : ?>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
<?php endif; ?>
</div>
<h3><?php echo esc_html($item['title']); ?></h3>
<p><?php echo esc_html($item['body']); ?></p>
</article>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════
EXPERIENCIA — blanco, timeline centrado
═══════════════════════════════════════════════════ -->
<section class="pn-section pn-section--white" id="experiencia">
<div class="pn-container">
<div class="pn-section-head">
<span class="pn-eyebrow">── <?php esc_html_e('Trayectoria profesional', 'persona'); ?> ──</span>
<h2><?php esc_html_e('Experiencia', 'persona'); ?></h2>
<div class="pn-section-bar"></div>
</div>
<div class="pn-timeline">
<?php foreach ($cv['experience'] as $i => $job) : ?>
<article class="pn-tl-entry pn-tl-entry--<?php echo $i % 2 === 0 ? 'left' : 'right'; ?>">
<div class="pn-tl-content">
<span class="pn-tl-period"><?php echo esc_html($job['period']); ?></span>
<h3><?php echo esc_html($job['title']); ?></h3>
<p class="pn-tl-company">
<?php echo esc_html($job['company']); ?>
<em> &middot; <?php echo esc_html($job['location']); ?></em>
</p>
<p class="pn-tl-lead"><?php echo esc_html($job['lead']); ?></p>
<ul class="pn-tl-bullets">
<?php foreach ($job['bullets'] as $b) : ?>
<li><?php echo esc_html($b); ?></li>
<?php endforeach; ?>
</ul>
</div>
<div class="pn-tl-dot" aria-hidden="true"></div>
</article>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════
FORMACIÓN — gris
═══════════════════════════════════════════════════ -->
<section class="pn-section pn-section--gray" id="formacion">
<div class="pn-container">
<div class="pn-section-head">
<span class="pn-eyebrow">── <?php esc_html_e('Académica', 'persona'); ?> ──</span>
<h2><?php esc_html_e('Formación', 'persona'); ?></h2>
<div class="pn-section-bar"></div>
</div>
<div class="pn-edu-grid">
<?php foreach ($cv['education'] as $edu) : ?>
<article class="pn-edu-card">
<span class="pn-edu-card__year"><?php echo esc_html($edu['year']); ?></span>
<div>
<h3><?php echo esc_html($edu['title']); ?></h3>
<p class="pn-edu-card__inst"><?php echo esc_html($edu['institution']); ?></p>
<?php if ($edu['detail']) : ?>
<p class="pn-edu-card__detail"><?php echo esc_html($edu['detail']); ?></p>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════
COMPETENCIAS — blanco, barras de progreso
═══════════════════════════════════════════════════ -->
<section class="pn-section pn-section--white" id="competencias">
<div class="pn-container">
<div class="pn-section-head">
<span class="pn-eyebrow">── <?php esc_html_e('Capacidades', 'persona'); ?> ──</span>
<h2><?php esc_html_e('Competencias', 'persona'); ?></h2>
<div class="pn-section-bar"></div>
</div>
<div class="pn-skills-cols">
<?php foreach ($cv['competencies'] as $group => $items) : ?>
<div class="pn-skills-group">
<h3><?php echo esc_html($group); ?></h3>
<?php foreach ($items as $skill) :
$skillName = is_array($skill) ? ($skill['name'] ?? '') : (string) $skill;
$skillPct = is_array($skill) ? ($skill['pct'] ?? 80) : 80;
?>
<div class="pn-skill">
<div class="pn-skill__header">
<span><?php echo esc_html($skillName); ?></span>
<span><?php echo esc_html((string) $skillPct); ?>%</span>
</div>
<div class="pn-skill__bar">
<div class="pn-skill__fill" data-pct="<?php echo esc_attr((string) $skillPct); ?>" style="width:0%"></div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════
CERTIFICACIONES — gris
═══════════════════════════════════════════════════ -->
<section class="pn-section pn-section--gray" id="certificaciones">
<div class="pn-container">
<div class="pn-section-head">
<span class="pn-eyebrow">── <?php esc_html_e('Formación continua', 'persona'); ?> ──</span>
<h2><?php esc_html_e('Certificaciones', 'persona'); ?></h2>
<div class="pn-section-bar"></div>
</div>
<div class="pn-certs-grid">
<?php foreach ($cv['certifications'] as $cert) : ?>
<article class="pn-cert">
<span class="pn-cert__year"><?php echo esc_html($cert['year']); ?></span>
<div>
<h3><?php echo esc_html($cert['title']); ?></h3>
<p><?php echo esc_html($cert['issuer']); ?></p>
</div>
</article>
<?php endforeach; ?>
</div>
</div>
</section>
<!-- ═══════════════════════════════════════════════════
CONTACTO — oscuro
═══════════════════════════════════════════════════ -->
<section class="pn-section pn-section--dark" id="contacto">
<div class="pn-container">
<div class="pn-section-head">
<span class="pn-eyebrow pn-eyebrow--light">── <?php esc_html_e('Ponte en contacto', 'persona'); ?> ──</span>
<h2 class="pn-section-head__title--light"><?php esc_html_e('Contacto', 'persona'); ?></h2>
<div class="pn-section-bar"></div>
</div>
<div class="pn-contact-grid">
<div class="pn-contact-info">
<ul class="pn-contact-list">
<li>
<span class="pn-contact-list__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg></span>
<span><?php echo esc_html($heroLocation); ?></span>
</li>
<li>
<span class="pn-contact-list__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg></span>
<a href="mailto:<?php echo esc_attr(antispambot($heroEmail)); ?>"><?php echo esc_html($heroEmail); ?></a>
</li>
<li>
<span class="pn-contact-list__icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12 19.79 19.79 0 0 1 1.61 3.41 2 2 0 0 1 3.6 1.25h3a2 2 0 0 1 2 1.72c.128.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.96a16 16 0 0 0 6.02 6.02l.96-.96a2 2 0 0 1 2.11-.45c.907.339 1.85.572 2.81.7A2 2 0 0 1 21.73 16.92z"/></svg></span>
<a href="tel:<?php echo esc_attr(preg_replace('/[^0-9+]/', '', $heroPhone)); ?>"><?php echo esc_html($heroPhone); ?></a>
</li>
<?php if ($linkedin) : ?>
<li>
<span class="pn-contact-list__icon"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"/><rect x="2" y="9" width="4" height="12"/><circle cx="4" cy="4" r="2"/></svg></span>
<a href="<?php echo esc_url($linkedin); ?>" target="_blank" rel="noreferrer">LinkedIn</a>
</li>
<?php endif; ?>
</ul>
<div class="pn-contact-extra">
<h4><?php esc_html_e('Voluntariado', 'persona'); ?></h4>
<p class="pn-contact-extra__title"><?php echo esc_html($cv['volunteer']['title']); ?> <span><?php echo esc_html($cv['volunteer']['year']); ?></span></p>
<p><?php echo esc_html($cv['volunteer']['body']); ?></p>
</div>
<dl class="pn-extra-dl">
<?php foreach ($cv['additional'] as $label => $value) : ?>
<div><dt><?php echo esc_html($label); ?></dt><dd><?php echo esc_html($value); ?></dd></div>
<?php endforeach; ?>
</dl>
</div>
<div class="pn-bcard-col">
<p class="pn-bcard-label"><?php esc_html_e('Tarjeta de presentación', 'persona'); ?></p>
<div class="pn-bcard">
<div class="pn-bcard__front">
<div class="pn-bcard__photo">
<img src="<?php echo esc_url($profileImage); ?>" alt="<?php echo esc_attr($heroName); ?>">
</div>
<div>
<p class="pn-bcard__name"><?php echo esc_html($heroName); ?></p>
<p class="pn-bcard__role"><?php echo esc_html($heroRole); ?></p>
</div>
</div>
<div class="pn-bcard__details">
<div class="pn-bcard__qr">
<img src="<?php echo esc_url($qrImage); ?>" alt="<?php esc_attr_e('QR perfil web', 'persona'); ?>">
</div>
<div class="pn-bcard__info">
<p><?php echo esc_html($heroLocation); ?></p>
<p><?php echo esc_html($heroPhone); ?></p>
<p><?php echo esc_html($heroEmail); ?></p>
<a href="<?php echo esc_url($publicUrl); ?>" target="_blank" rel="noreferrer"><?php echo esc_html($publicUrl); ?></a>
</div>
</div>
</div>
</div>
</div>
<?php while (have_posts()) : the_post(); ?>
<?php if (trim((string) get_the_content()) !== '') : ?><div class="pn-wp-content"><?php the_content(); ?></div><?php endif; ?>
<?php endwhile; ?>
<div class="pn-footer-bar">
<p><?php echo esc_html(get_bloginfo('name')); ?> &copy; <?php echo esc_html(date_i18n('Y')); ?> &mdash; <?php esc_html_e('Todos los derechos reservados', 'persona'); ?></p>
</div>
</div>
</section>
<?php get_footer(); ?>
+67
View File
@@ -0,0 +1,67 @@
<?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';
function persona_setup(): void {
add_theme_support('title-tag');
add_theme_support('post-thumbnails');
add_theme_support('editor-styles');
add_theme_support('responsive-embeds');
register_nav_menus(['primary' => __('Menú principal', 'persona')]);
}
add_action('after_setup_theme', 'persona_setup');
function persona_enqueue_assets(): void {
wp_enqueue_style('persona-fonts',
'https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;500;600;700;800&family=Open+Sans:wght@300;400;600&display=swap',
[], null);
wp_enqueue_style('persona-theme',
get_template_directory_uri() . '/assets/css/theme.css',
['persona-fonts'], wp_get_theme()->get('Version'));
wp_enqueue_script('persona-theme',
get_template_directory_uri() . '/assets/js/theme.js',
[], wp_get_theme()->get('Version'), true);
}
add_action('wp_enqueue_scripts', 'persona_enqueue_assets');
function persona_body_classes(array $classes): array {
$classes[] = 'persona-shell';
if (is_front_page()) $classes[] = 'persona-front';
return $classes;
}
add_filter('body_class', 'persona_body_classes');
function persona_redirect_sample(): void {
if (!is_page()) return;
$p = get_queried_object();
if ($p instanceof WP_Post && $p->post_name === 'pagina-ejemplo') {
wp_safe_redirect(home_url('/'), 301); exit;
}
}
add_action('template_redirect', 'persona_redirect_sample', 1);
function persona_render_nav(): string {
if (has_nav_menu('primary')) {
return trim((string) wp_nav_menu([
'theme_location' => 'primary', 'container' => false,
'fallback_cb' => false, 'menu_class' => 'pn-nav__list', 'echo' => false,
]));
}
$items = [
'#sobre-mi' => 'Sobre mí',
'#impacto' => 'Impacto',
'#experiencia' => 'Experiencia',
'#formacion' => 'Formación',
'#competencias' => 'Competencias',
'#certificaciones' => 'Certificaciones',
'#contacto' => 'Contacto',
];
$html = '<ul class="pn-nav__list">';
foreach ($items as $href => $label) {
$html .= sprintf('<li><a href="%s">%s</a></li>', esc_url($href), esc_html($label));
}
return $html . '</ul>';
}
+34
View File
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);
$heroName = get_theme_mod('pn_hero_name', 'Patricia S. Flores Sánchez');
?>
<!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(); ?>
<header class="pn-header" id="pn-header">
<div class="pn-header__inner">
<a class="pn-brand" href="<?php echo esc_url(home_url('/')); ?>">
<span class="pn-brand__dot"></span><?php echo esc_html($heroName); ?>
</a>
<button class="pn-hamburger" type="button" aria-expanded="false" aria-controls="pn-nav" aria-label="<?php esc_attr_e('Menú', 'persona'); ?>">
<span></span><span></span><span></span>
</button>
<nav id="pn-nav" class="pn-nav" aria-label="<?php esc_attr_e('Menú principal', 'persona'); ?>">
<?php echo persona_render_nav(); // phpcs:ignore ?>
</nav>
<div class="pn-header__actions">
<a class="pn-btn pn-btn--sm pn-btn--outline" href="<?php echo esc_url(home_url('/?cv_pdf=1')); ?>" target="_blank">
<svg width="13" height="13" 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"/><polyline points="10 9 9 9 8 9"/></svg>
<?php esc_html_e('Descargar CV PDF', 'persona'); ?>
</a>
</div>
</div>
</header>
<main class="pn-main">
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
function persona_customize_register(WP_Customize_Manager $wp_customize): void {
$wp_customize->add_section('persona_hero', ['title' => __('Persona CV: Presentación', 'persona'), 'priority' => 30]);
$fields = [
'pn_hero_name' => ['label' => __('Nombre', 'persona'), 'default' => 'Patricia Soledad Flores Sánchez'],
'pn_hero_role' => ['label' => __('Rol', 'persona'), 'default' => 'Gerente / Jefa de Operaciones Financieras'],
'pn_contact_location' => ['label' => __('Ubicación', 'persona'), 'default' => 'Loja, Ecuador'],
'pn_contact_email' => ['label' => __('Correo', 'persona'), 'default' => 'patricflorsanchez@gmail.com'],
'pn_contact_phone' => ['label' => __('Teléfono', 'persona'), 'default' => '+593 99 469 8405'],
'pn_social_linkedin' => ['label' => __('LinkedIn URL', 'persona'), 'default' => 'https://www.linkedin.com/in/patricia-soledad-flores-s%C3%A1nchez-43b84418/'],
];
$wp_customize->add_setting('pn_profile_image', ['default' => '', 'sanitize_callback' => 'esc_url_raw']);
$wp_customize->add_control(new WP_Customize_Image_Control($wp_customize, 'pn_profile_image', [
'label' => __('Foto de perfil', 'persona'), 'section' => 'persona_hero', 'settings' => 'pn_profile_image',
]));
foreach ($fields as $key => $field) {
$isUrl = str_contains($key, 'linkedin');
$wp_customize->add_setting($key, ['default' => $field['default'], 'sanitize_callback' => $isUrl ? 'esc_url_raw' : 'sanitize_text_field']);
$wp_customize->add_control($key, ['label' => $field['label'], 'section' => 'persona_hero', 'type' => 'text']);
}
}
add_action('customize_register', 'persona_customize_register');
+157
View File
@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
function persona_cv_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. 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',
'languages' => 'Español nativo | Inglés básico en formación activa',
'born' => '3 de agosto de 1980',
'id' => '1103591648',
'web' => 'pflres.linkedcorp.net',
],
'metrics' => [
['value' => '22', 'suffix' => '+', 'label' => 'Años de experiencia'],
['value' => '26', 'suffix' => '', 'label' => 'Agencias lideradas'],
['value' => '48', 'suffix' => '', 'label' => 'Personas a cargo'],
['value' => '5', 'suffix' => 'M', 'label' => 'USD ops. mensuales'],
['value' => '28000', 'suffix' => '', 'label' => 'Clientes coordinados'],
],
'highlights' => [
['value' => '9/10', 'label' => 'Calificación excelente 4 trimestres consecutivos'],
['value' => '74%', 'label' => 'Reducción requerimientos soporte a Dirección'],
['value' => '1.68', 'suffix' => 'días', 'label' => 'Tiempo desembolso Q3 2025, mejor del año'],
['value' => '75%', 'label' => 'Reducción rotación de personal 2022–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).',
'Desembolsos por transferencia: 47,80 % del monto total en Q1 2026.',
'Redujo requerimientos de soporte un 74 %, de 1.489 a 382, mediante descentralización.',
'Automatizó el seguro de desgravamen con asientos contables automatizados.',
'Elevó el cumplimiento operativo nacional del 65 % al 88 %.',
'Redujo el tiempo de desembolso a 1,68 días en Q3 2025 (meta: máx. 2 días).',
'Recuperación de mora: 90,60 % en Q1 2026 — USD 3,06 M sobre USD 3,37 M.',
'Redujo la rotación del área del 41,46 % al 10,24 % (dic. 2022 – mar. 2026).',
'Participa en comités estratégicos, auditorías y representación institucional internacional.',
],
],
[
'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.',
'Elaboró el instructivo de garantías hipotecarias (proceso inexistente previamente).',
'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 de fortalecimiento técnico tras 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.',
'Agencia por encima del 80 % en gestión de excepciones de crédito hipotecario.',
'Administró proyecto inmobiliario institucional en Loja.',
'Cumplimiento ALA/FT al 100 % en comités y auditorías sin observaciones.',
],
],
[
'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 anticipadamente a los tres meses.',
'Apoyo a Contabilidad y reemplazo temporal de la contadora.',
],
],
],
'education' => [
[
'year' => '2021', 'title' => 'Máster Universitario en Dirección y Planificación Financiera',
'institution' => 'Universidad Europea Miguel de Cervantes — Valladolid, España',
'detail' => '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 | Vigente hasta 2028'],
['year' => '2023', 'title' => 'Power BI (40 h) | 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' => 95],
['name' => 'Control interno y cumplimiento', 'pct' => 95],
['name' => 'Gestión de cartera y microfinanzas', 'pct' => 90],
['name' => 'Conciliación bancaria / Tesorería', 'pct' => 90],
['name' => 'ALA/FT y prevención de riesgos', 'pct' => 88],
],
'Tecnología y Procesos' => [
['name' => 'Transformación digital / BPM', 'pct' => 85],
['name' => 'SAP FI', 'pct' => 78],
['name' => 'Excel avanzado y dashboards', 'pct' => 85],
['name' => 'Power BI', 'pct' => 75],
['name' => 'IA aplicada al trabajo', 'pct' => 70],
],
'Liderazgo' => [
['name' => 'Gestión de equipos (hasta 48 p.)', 'pct' => 95],
['name' => 'Gestión del cambio organizacional', 'pct' => 90],
['name' => 'Formación de formadores', 'pct' => 88],
['name' => 'Remuneración variable y carrera', 'pct' => 85],
],
],
'volunteer' => [
'title' => 'Voluntaria — Hogar San Camilo de Lellis',
'year' => '2025',
'body' => 'Hogar de niñas con discapacidad | Loja, Ecuador. Alineado a la 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' => 'pflres.linkedcorp.net',
],
];
}
+35
View File
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
function persona_should_track(): 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 persona_track_visit(): void {
if (!persona_should_track()) return;
$uri = isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : '';
$key = 'persona_v_' . md5($uri . '|' . persona_client_ip() . '|' . gmdate('Y-m-d-H'));
if (get_transient($key)) return;
set_transient($key, '1', HOUR_IN_SECONDS);
update_option('persona_total_visits', (int) get_option('persona_total_visits', 0) + 1, false);
if (is_front_page()) {
$fp = (int) get_option('page_on_front');
if ($fp > 0) update_post_meta($fp, '_persona_views', (int) get_post_meta($fp, '_persona_views', true) + 1);
return;
}
$id = get_queried_object_id();
if ($id > 0) update_post_meta($id, '_persona_views', (int) get_post_meta($id, '_persona_views', true) + 1);
}
add_action('template_redirect', 'persona_track_visit');
function persona_client_ip(): string {
foreach (['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'] as $k) {
if (!empty($_SERVER[$k])) return trim(explode(',', (string) $_SERVER[$k])[0]);
}
return '0.0.0.0';
}
add_action('wp_dashboard_setup', function() {
wp_add_dashboard_widget('persona_visits', __('Persona CV: Visitas', 'persona'), function() {
printf('<p><strong>%s</strong> %d</p>', esc_html__('Total:', 'persona'), (int) get_option('persona_total_visits', 0));
});
});
+12
View File
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
get_header();
?>
<section class="pn-section pn-section--white">
<div class="pn-container">
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>
<article <?php post_class(); ?>><h1><?php the_title(); ?></h1><?php the_content(); ?></article>
<?php endwhile; endif; ?>
</div>
</section>
<?php get_footer(); ?>
+11
View File
@@ -0,0 +1,11 @@
/*
Theme Name: Persona CV
Theme URI: https://pflres.linkedcorp.net
Author: mgonzalez
Description: Tema WordPress de una página, inspirado en Personage/Pixflow. Hero fullscreen, nav sticky, secciones alternas, timeline centrado, barras de skills, contadores animados. Raleway + Open Sans.
Version: 1.0.0
Requires at least: 6.8
Tested up to: 6.8
Requires PHP: 8.1
Text Domain: persona
*/
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

+253
View File
@@ -0,0 +1,253 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tarjeta de Presentación | Patricia Flores</title>
<style>
:root {
--bg: #f3efe7;
--card: #0f1923;
--card-soft: #162433;
--text: #f5f3ef;
--muted: rgba(245, 243, 239, 0.72);
--accent: #c6a56f;
--line: rgba(255, 255, 255, 0.12);
--radius: 28px;
--shadow: 0 28px 70px rgba(15, 25, 35, 0.22);
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 2rem;
background:
radial-gradient(circle at top left, rgba(198, 165, 111, 0.22), transparent 28%),
linear-gradient(180deg, #f8f5ef 0%, var(--bg) 100%);
font-family: "Segoe UI", Arial, sans-serif;
color: var(--text);
}
.card {
width: min(100%, 980px);
display: grid;
grid-template-columns: 1.15fr 0.85fr;
gap: 1.4rem;
padding: 1.5rem;
border-radius: 34px;
background: linear-gradient(145deg, var(--card) 0%, var(--card-soft) 100%);
box-shadow: var(--shadow);
}
.panel {
border: 1px solid var(--line);
border-radius: var(--radius);
background: rgba(255, 255, 255, 0.03);
overflow: hidden;
}
.identity {
display: grid;
grid-template-columns: 180px 1fr;
gap: 1.25rem;
padding: 1.25rem;
align-items: center;
}
.identity img {
width: 100%;
aspect-ratio: 1 / 1.1;
object-fit: cover;
border-radius: 22px;
border: 1px solid var(--line);
}
.eyebrow {
margin: 0 0 0.6rem;
color: var(--accent);
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
}
h1 {
margin: 0;
font: 700 clamp(2rem, 4vw, 3rem) Georgia, serif;
line-height: 0.95;
}
.role {
margin: 0.85rem 0 0;
color: var(--muted);
font-size: 1.02rem;
line-height: 1.55;
}
.highlights {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.8rem;
padding: 0 1.25rem 1.25rem;
}
.metric {
padding: 0.95rem;
border: 1px solid var(--line);
border-radius: 20px;
background: rgba(255, 255, 255, 0.03);
}
.metric strong {
display: block;
margin-bottom: 0.35rem;
color: var(--accent);
font-size: 1.25rem;
}
.metric span {
color: var(--muted);
font-size: 0.9rem;
line-height: 1.5;
}
.contact {
display: grid;
gap: 0.8rem;
padding: 1.25rem;
}
.contact a,
.contact p {
margin: 0;
color: var(--text);
text-decoration: none;
line-height: 1.5;
}
.contact p span {
display: block;
color: var(--muted);
font-size: 0.84rem;
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 0.15rem;
}
.qr-panel {
display: grid;
align-content: space-between;
padding: 1.25rem;
}
.qr-wrap {
display: grid;
gap: 1rem;
justify-items: center;
text-align: center;
}
.qr-wrap img {
width: min(100%, 250px);
padding: 0.9rem;
border-radius: 24px;
background: #fff;
}
.qr-wrap p {
margin: 0;
color: var(--muted);
line-height: 1.6;
}
.url {
color: var(--accent);
font-weight: 700;
word-break: break-word;
}
.footer {
padding-top: 1rem;
border-top: 1px solid var(--line);
color: var(--muted);
font-size: 0.92rem;
line-height: 1.6;
}
@media (max-width: 820px) {
.card,
.identity {
grid-template-columns: 1fr;
}
.highlights {
grid-template-columns: 1fr;
}
}
@media print {
body {
padding: 0;
background: #fff;
}
.card {
box-shadow: none;
}
}
</style>
</head>
<body>
<main class="card">
<section class="panel">
<div class="identity">
<img src="img-pflores.jpeg" alt="Patricia Soledad Flores Sánchez">
<div>
<p class="eyebrow">Tarjeta de presentación</p>
<h1>Patricia Soledad Flores Sánchez</h1>
<p class="role">Gerente / Jefa de Operaciones Financieras | Sector financiero y microfinanzas</p>
</div>
</div>
<div class="highlights">
<article class="metric">
<strong>22+</strong>
<span>Años de trayectoria en banca y microfinanzas.</span>
</article>
<article class="metric">
<strong>26</strong>
<span>Agencias bajo liderazgo operativo a nivel nacional.</span>
</article>
<article class="metric">
<strong>48</strong>
<span>Personas coordinadas en la estructura operativa.</span>
</article>
</div>
<div class="contact">
<p><span>Correo</span><a href="mailto:patricflorsanchez@gmail.com">patricflorsanchez@gmail.com</a></p>
<p><span>Teléfono</span><a href="tel:+593994698405">+593 99 469 8405</a></p>
<p><span>Ubicación</span>Loja, Ecuador</p>
<p><span>LinkedIn</span><a href="https://linkedin.com/in/patricia-soledad-flores-sanchez-43b84418">linkedin.com/in/patricia-soledad-flores-sanchez-43b84418</a></p>
</div>
</section>
<aside class="panel qr-panel">
<div class="qr-wrap">
<p class="eyebrow">Perfil web</p>
<img src="tarjeta-patricia-flores-qr.png" alt="Código QR hacia pflres.linkedcorp.net">
<p>Escanea el código QR para abrir el perfil profesional online.</p>
<p class="url">https://pflres.linkedcorp.net</p>
</div>
<div class="footer">
Ejecutiva financiera con enfoque en operaciones, transformación digital, control interno y liderazgo de equipos multidisciplinarios.
</div>
</aside>
</main>
</body>
</html>