Files
pflores.linkedcorp.net/PROMPT_IA.md
T

863 lines
32 KiB
Markdown

# 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
### Desarrollo
```
Host: 192.168.222.20
DB: paty.wordpress
Usuario: paty.wordpress
Password: paty.wordpress
Prefijo: cv_ → tabla principal: cv_options
```
```bash
mysql -u "paty.wordpress" -p"paty.wordpress" -h 192.168.222.20 "paty.wordpress" --ssl=0
```
### Producción
```
Host: 192.168.222.32
DB: paty.wordpress
Usuario: paty.wordpress
Password: (ver LOCAL_DEPLOY_CONTEXT.md)
Prefijo: cv_
```
> La BD de producción es una copia completa de la de desarrollo. Misma estructura, mismos datos del CV. Credenciales distintas.
---
## 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 **directamente** en `backend/wordpress/wp-content/themes/NOMBRE-cv/` con subdirectorios `assets/{css,js,images,docs}` e `inc/`.
> ⚠ **No usar symlink** — el tema `apex-cv` demostró que los archivos en `frontend/` desaparecen cuando WordPress activa/desactiva temas. El directorio real dentro de `wp-content/themes/` es la única forma estable.
2. **Text Domain**: usar `nombre` (minúsculas, sin guiones).
3. **Prefijo de funciones**: `nombre_` (ej: `nova_setup()`, `nova_render_nav()`).
4. **Prefijo CSS**: `nv-` (2-3 letras, sin conflictos con `pn-`, `lm-`, `ax-`, `cv-`).
5. **Copiar assets**: copiar imágenes y docs desde cualquier tema existente:
```bash
cp frontend/persona-cv-theme/assets/images/* backend/wordpress/wp-content/themes/NOMBRE-cv/assets/images/
cp frontend/persona-cv-theme/assets/docs/* backend/wordpress/wp-content/themes/NOMBRE-cv/assets/docs/
```
### 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: CMB2 primero, si no la imagen local del tema
$acfPhoto = $cv['photo'] ?? '';
$profileImage = $acfPhoto ?: get_template_directory_uri() . '/assets/images/patricia-flores-profile.jpeg';
// QR y URL pública
$qrImage = get_template_directory_uri() . '/assets/images/patricia-flores-qr.png';
$publicUrl = 'https://pflores.linkedcorp.net';
// ► NO usar $cvFile para botones de descarga — usar siempre home_url('/?cv_pdf=1')
// Variables de conveniencia del hero
$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
# El directorio ya existe en wp-content/themes/NOMBRE-cv/ — solo 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');"
```
### Versionar el nuevo tema
Al estar en `wp-content/themes/` directamente, añadir al staging explícitamente:
```bash
git add backend/wordpress/wp-content/themes/NOMBRE-cv/
git commit -m "feat: nuevo tema NOMBRE-cv"
```
> ⚠ **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/` |
| CSS/JS con 404 tras clonar en servidor nuevo | Symlinks de temas usan ruta absoluta del equipo de dev | Recrear symlinks con ruta absoluta del servidor real (ver `PRODUCCION.md` → Problemas conocidos) |
| 502 persistente en favicon u otros estáticos tras haberlo corregido | NPM **Cache Assets** cachéo la respuesta 502/404 anterior | En NPM: desactivar Cache Assets → guardar → reactivar → guardar. Para CV puede quedar desactivado. |
| 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
```
---
## 22. Servidor de producción
### Datos del servidor
- Host: `192.168.222.3`
- SSH: `deploy@192.168.222.3 -p 2203`
- `deploy` tiene sudo NOPASSWD
- Ruta app: `/opt/apps/pflores.linkedcorp.net`
- Servicio systemd: `pflores-web.service`
- Puerto: `0.0.0.0:8081`
- NPM proxy: `pflores.linkedcorp.net` → `192.168.222.3:8081`
> ⚠ El servicio **debe** escuchar en `0.0.0.0:8081`, no en `127.0.0.1`. NPM llega desde fuera del loopback — si queda en `127.0.0.1` el resultado es **502 Bad Gateway**.
Credenciales SSH: ver `LOCAL_DEPLOY_CONTEXT.md`
### Estructura en producción
```
/opt/apps/pflores.linkedcorp.net/
├── backend/wordpress/ ← WordPress + temas + plugins
├── frontend/ ← temas (lumina, persona-cv, cvolio via symlink)
├── scripts/deploy_prod_web.sh ← script de deploy
└── .env ← config producción (local, nunca versionar)
```
### Deploy recurrente (flujo completo)
```bash
# 1. En desarrollo: commit + push a Gitea (ver sección 21)
# 2. En el servidor de producción:
ssh deploy@192.168.222.3 -p 2203
cd /opt/apps/pflores.linkedcorp.net
bash scripts/deploy_prod_web.sh
```
El script hace: `git reset --hard` + verificar plugins + `systemctl restart` + verificar HTTP 200.
### Comandos útiles en producción
```bash
sudo systemctl status pflores-web
sudo systemctl restart pflores-web
sudo journalctl -u pflores-web -n 50 --no-pager
curl -s -o /dev/null -w "HTTP %{http_code}" http://127.0.0.1:8081/
curl -sI http://127.0.0.1:8081/?cv_pdf=1 | grep Content-Type
```
### Plugins de terceros (no en git)
Si desaparecen tras un `git reset --hard`, el script de deploy los reinstala automáticamente. También se pueden instalar manualmente:
```bash
cd /opt/apps/pflores.linkedcorp.net/backend/wordpress/wp-content/plugins
# CMB2
wget -q https://downloads.wordpress.org/plugin/cmb2.latest-stable.zip -O cmb2.zip && python3 -c "import zipfile; zipfile.ZipFile('cmb2.zip').extractall('.')" && rm cmb2.zip
# WP Mail SMTP
wget -q https://downloads.wordpress.org/plugin/wp-mail-smtp.latest-stable.zip -O wpmail.zip && python3 -c "import zipfile; zipfile.ZipFile('wpmail.zip').extractall('.')" && rm wpmail.zip
```
### WordPress core (no en git)
Si hay que reinstalar el core de WordPress en el servidor:
```bash
cd /opt/apps/pflores.linkedcorp.net/backend/wordpress
wget -q https://wordpress.org/wordpress-7.0.zip -O /tmp/wp.zip
python3 -c "
import zipfile,shutil,os
z=zipfile.ZipFile('/tmp/wp.zip')
z.extractall('/tmp/wp_extract')
src='/tmp/wp_extract/wordpress'
for d in ['wp-admin','wp-includes']:
if not os.path.exists(d): shutil.copytree(os.path.join(src,d),d)
for f in os.listdir(src):
if os.path.isfile(os.path.join(src,f)): shutil.copy2(os.path.join(src,f),f)
"
rm -rf /tmp/wp.zip /tmp/wp_extract
```