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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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 ? ' | ' . $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> ' . $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> ' . $h($v) . '</p>';
|
||||
}
|
||||
if ($web) {
|
||||
$addHtml .= '<p class="add-line"><strong>Web:</strong> ' . $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>
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
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
|
||||
*/
|
||||
@@ -0,0 +1 @@
|
||||
/home/mgonzalez/desarrollo/web/pflores.linkedcorp.net/frontend/lumina-cv-theme
|
||||
@@ -0,0 +1 @@
|
||||
/home/mgonzalez/desarrollo/web/pflores.linkedcorp.net/frontend/persona-cv-theme
|
||||
@@ -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`
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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'); ?>>
|
||||
<?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>
|
||||
</article>
|
||||
</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>
|
||||
|
||||
@@ -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 === '') {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -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);
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
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));
|
||||
}
|
||||
});
|
||||
@@ -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')); ?> · <?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'); ?>">↑</a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<?php wp_footer(); ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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>
|
||||
· <?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(); ?>
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
@@ -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');
|
||||
@@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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(); ?>
|
||||
@@ -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}
|
||||
}
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
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" });
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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'); ?>">⇧</a>
|
||||
<?php wp_footer(); ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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> · <?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')); ?> © <?php echo esc_html(date_i18n('Y')); ?> — <?php esc_html_e('Todos los derechos reservados', 'persona'); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php get_footer(); ?>
|
||||
@@ -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>';
|
||||
}
|
||||
@@ -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');
|
||||
@@ -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',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
});
|
||||
@@ -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(); ?>
|
||||
@@ -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
|
||||
*/
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 535 B |
@@ -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>
|
||||