feat: sitio CV completo v1.0.0
- 4 temas WordPress: persona-cv (activo), lumina, apex-cv, cvolio - Plugin pflores-cv-fields con CMB2 (100% gratuito) — admin editable - Generador PDF dinámico con Chromium headless (?cv_pdf=1) - Datos del CV de Patricia Flores cargados en CMB2 - URL dinámica con soporte proxy SSL (NPM/X-Forwarded-Proto) - WP Mail SMTP instalado (pendiente config SMTP) - Documentación completa: PROMPT_IA.md, DEPLOY_WEB.md, PRODUCCION.md, VERSIONADO.md
This commit is contained in:
+756
@@ -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
|
||||
```
|
||||
Reference in New Issue
Block a user