32 KiB
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 7.0
│ ├── wp-config.php # Config dinámica (URL, auto-updates OFF)
│ ├── favicon.ico # Foto de Patricia, circular, 16/32/48px
│ └── wp-content/
│ ├── plugins/
│ │ ├── cmb2/ # CMB2 — campos personalizados (GRATIS)
│ │ ├── wp-mail-smtp/ # Email SMTP (pendiente config)
│ │ └── 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/
│ ├── apex-cv/ # Directorio REAL — Dark, cian, sidebar fijo
│ ├── persona-cv -> ../../../../frontend/persona-cv-theme # symlink relativo
│ ├── lumina -> ../../../../frontend/lumina-cv-theme # symlink relativo
│ └── cvolio -> ../../../../frontend/cvfolio-theme # symlink relativo
├── frontend/
│ ├── persona-cv-theme/ # ACTIVO — Personage-style, rojo, Raleway
│ │ ├── style.css
│ │ ├── functions.php
│ │ ├── header.php # Nav + botón "Descargar CV PDF"
│ │ ├── footer.php
│ │ ├── front-page.php # Plantilla principal del CV
│ │ ├── index.php
│ │ ├── assets/css/theme.css
│ │ ├── assets/js/theme.js
│ │ ├── 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
│ │ └── visits.php
│ ├── lumina-cv-theme/ # Claro, teal+ámbar, sidebar izquierdo
│ └── cvfolio-theme/ # Oscuro, glassmorphism original
├── otros/ # Archivos de referencia (foto, QR, docx original)
├── docs/
│ └── instalacion-wordpress.md
├── .env # Local, no versionado
├── .env.example
├── VERSION
├── CHANGELOG.md
├── VERSIONADO.md
├── PRODUCCION.md
├── DEPLOY_WEB.md
├── PROMPT_IA.md # ESTE ARCHIVO
└── README.md
Temas:
apex-cvvive enwp-content/themes/apex-cv/como directorio real (no symlink). Los otros 3 temas usan symlinks con ruta relativa (../../../../frontend/...) para funcionar tanto en desarrollo como en producción sin cambios.
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
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
# 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:
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:
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 conphp -r "echo serialize(['cmb2/init.php','pflores-cv-fields/pflores-cv-fields.php']);".
6. Cambiar el tema activo
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:
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:
[
'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
.envcomoWP_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():
// 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():
'hero' => [
// ... campos existentes ...
'twitter' => $perfil['pf_twitter'] ?? '',
],
Paso 3 — Usarlo en el front-page.php de cada tema:
$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():
$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():
$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:
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):
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
- Directorio: Crear directamente en
backend/wordpress/wp-content/themes/NOMBRE-cv/con subdirectoriosassets/{css,js,images,docs}einc/.⚠ No usar symlink — el tema
apex-cvdemostró que los archivos enfrontend/desaparecen cuando WordPress activa/desactiva temas. El directorio real dentro dewp-content/themes/es la única forma estable. - Text Domain: usar
nombre(minúsculas, sin guiones). - Prefijo de funciones:
nombre_(ej:nova_setup(),nova_render_nav()). - Prefijo CSS:
nv-(2-3 letras, sin conflictos conpn-,lm-,ax-,cv-). - Copiar assets: copiar imágenes y docs desde cualquier tema existente:
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
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
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 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)
<!-- 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
# 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:
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():
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:
<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:
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):
- Se activa con
get_query_var('cv_pdf') === '1' - Llama a
pflores_get_cv_data()para obtener todos los datos actuales - Genera HTML limpio con CSS embebido (formato A4, 2 columnas)
- Escribe el HTML en un archivo temporal
- Ejecuta Chromium headless:
chromium --headless --no-sandbox --print-to-pdf=... file://... - Lee el PDF resultante y lo devuelve con headers de descarga
- 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):
// 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
// 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
curl -s http://127.0.0.1:8081/ | grep -c "Patricia Soledad"
# Debe retornar >= 1
Ver el tema activo
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
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)
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
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
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
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 corruptos o rotos | Los symlinks en git usan ruta relativa (../../../../frontend/...) — si están rotos, 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
- No usar ACF (Advanced Custom Fields). Tiene Repeater solo en versión de pago. Usar CMB2.
- No instalar plugins de pago. Todo debe ser 100 % gratuito.
- No tocar el bloque de auto-updates en
wp-config.php. - Verificar serialización PHP antes de cualquier
UPDATEenactive_pluginso similar. - Limpiar caché después de modificar datos en BD directamente.
- La foto de perfil es
assets/images/patricia-flores-profile.jpegen cada tema — NO usar el placeholder SVG como defecto. - El CV en docx (
CV_Patricia_Flores_Sanchez_ES_FINAL.docx) es solo para descarga — el contenido de la web se gestiona por CMB2. - El PDF se genera dinámicamente con Chromium desde
/?cv_pdf=1— no hay PDF estático. - 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. - 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
# 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:
git config credential.helper store
# La primera vez las guarda; las siguientes son automáticas
Guardar también en .token_gitea (local, nunca versionar):
# 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.phpbackend/start-wordpress.sh,docs/,*.md,VERSIONCV_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:
# 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 deploytiene 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 en127.0.0.1. NPM llega desde fuera del loopback — si queda en127.0.0.1el 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)
# 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
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:
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:
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