# 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-cv` vive en `wp-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 ``` ```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) : ?> Twitter ``` **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 # + nav + botón PDF ├── footer.php # ├── 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 $items) : ?>

``` ### Botón de descarga PDF (obligatorio en todos los temas) ```php Descargar CV PDF ``` ### `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
``` ### 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 .= '
...'; } // Y en el HTML final: '

Premios y reconocimientos

' . $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 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 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 ```