# Agents - Guía de Desarrollo

## Descripción del Proyecto

Sitio web personal de **Saúl Morales Pacheco** construido con **Symfony 8.0.5** + **Webpack Encore**.

- **Framework**: Symfony 8.0.5
- **PHP**: 8.4.18
- **Frontend**: Webpack Encore, ES6+, CSS Modules
- **Entorno**: Sin base de datos, sin Docker
- **Propósito**: Portfolio personal, blog, sistema de contacto y donaciones
- **Multilenguaje**: Español/Inglés (Symfony Translation)
- **Testing**: PHPUnit 9.6

---

## 🚀 Inicio Rápido

### Requisitos Previos

- **PHP 8.4** o superior
- **Composer 2.x**
- **Node.js 18+** y npm/yarn
- Extensiones PHP: `ext-ctype`, `ext-iconv`, `ext-json`, `ext-mbstring`, `ext-xml`

### Instalación

```bash
# Clonar repositorio
git clone <repo-url> saulmoralespa.com
cd saulmoralespa.com

# Instalar dependencias PHP
composer install

# Instalar dependencias Node
npm install
# o yarn install

# Configurar variables de entorno
cp .env .env.local
# Editar .env.local con tus valores (ver sección Configuración)

# Compilar assets frontend
npm run dev
# o para watch mode: npm run watch

# Limpiar caché
php bin/console cache:clear
```

### Ejecutar la Aplicación

```bash
# Servidor de desarrollo de Symfony
symfony server:start

# O usando el servidor PHP integrado
php -S localhost:8000 -t public/
```

Accede a: `http://localhost:8000`

### Ejecutar Tests

```bash
# Todos los tests
php bin/phpunit

# Tests específicos
php bin/phpunit tests/Controller/PageControllerTest.php
```

---

## 📁 Estructura del Proyecto

```
saulmoralespa.com/
├── assets/                    # Assets frontend (source)
│   ├── app.js                # Entry point principal
│   ├── js/                   # Módulos JavaScript
│   │   ├── app.js           # Inicialización
│   │   ├── contact.js       # Formulario de contacto (AJAX)
│   │   ├── cursor.js        # Custom cursor effect
│   │   ├── nav.js           # Navegación y cambio de idioma
│   │   └── reveal.js        # Scroll animations
│   └── styles/              # Módulos CSS
│       ├── app.css          # Stylesheet principal
│       ├── _tokens.css      # Design tokens (colores, fonts)
│       ├── _reset.css       # CSS reset
│       ├── _utilities.css   # Utilidades
│       ├── _nav.css         # Navegación
│       ├── _hero.css        # Sección hero
│       ├── _about.css       # Sección about
│       ├── _skills.css      # Sección skills
│       ├── _experience.css  # Experiencia laboral
│       ├── _portfolio.css   # Portfolio/proyectos
│       ├── _testimonials.css # Testimonios
│       ├── _contact.css     # Formulario de contacto
│       ├── _footer.css      # Footer
│       └── _responsive.css  # Media queries
├── bin/                       # Scripts ejecutables
│   ├── console               # Symfony console
│   └── phpunit               # PHPUnit runner
├── config/                    # Configuración de la aplicación
│   ├── packages/             # Configuración por bundle
│   │   ├── framework.yaml
│   │   ├── twig.yaml
│   │   ├── mailer.yaml
│   │   ├── security.yaml
│   │   ├── validator.yaml
│   │   ├── translation.yaml
│   │   └── webpack_encore.yaml
│   ├── routes/               # Definición de rutas
│   │   ├── framework.yaml
│   │   └── web_profiler.yaml (importante: prefijo /_wdt)
│   ├── routes.yaml           # Rutas principales
│   ├── services.yaml         # Definición de servicios
│   └── bundles.php           # Bundles registrados
├── public/                    # Directorio web público
│   ├── index.php             # Front controller
│   ├── build/                # Assets compilados (generado por Encore)
│   │   ├── app.js
│   │   ├── app.css
│   │   ├── manifest.json
│   │   └── entrypoints.json
│   ├── css/                  # Estilos legacy
│   ├── js/                   # JavaScript legacy
│   ├── img/                  # Imágenes
│   ├── fonts/                # Fuentes
│   ├── manifest.json         # PWA manifest
│   ├── robots.txt
│   ├── security.txt
│   └── sitemap.xml
├── src/
│   ├── Controller/           # Controladores HTTP
│   │   ├── PageController.php          # Rutas principales
│   │   ├── DonationController.php      # Sistema de donaciones
│   │   └── UnsubscribeController.php   # Desuscripciones
│   ├── Form/                 # Form Types
│   │   └── ContactType.php   # Formulario de contacto
│   ├── Entity/               # (Vacío - no usamos BD)
│   ├── Repository/           # (No usado sin BD)
│   ├── Service/              # Servicios de negocio
│   ├── EventSubscriber/      # Event listeners
│   └── Kernel.php            # Kernel de la aplicación
├── templates/                 # Plantillas Twig
│   ├── base.html.twig        # Layout base con Encore
│   ├── home/
│   │   └── index.html.twig   # Página principal
│   ├── donation/
│   │   └── index.html.twig
│   ├── unsubscribe/
│   │   └── index.html.twig
│   ├── test-encore.html.twig # Página de prueba
│   └── partials/             # Componentes reutilizables
│       ├── _nav.html.twig           # Navegación multilenguaje
│       ├── _hero.html.twig          # Hero section
│       ├── _about.html.twig         # About me
│       ├── _skills.html.twig        # Habilidades técnicas
│       ├── _experience.html.twig    # Experiencia
│       ├── _portfolio.html.twig     # Proyectos
│       ├── _testimonials.html.twig  # Testimonios
│       ├── _contact.html.twig       # Formulario
│       └── _footer.html.twig        # Footer
├── tests/                     # Tests unitarios/funcionales
│   ├── bootstrap.php
│   └── Controller/
│       └── PageControllerTest.php  # Tests completos del controlador
├── translations/              # Archivos de traducción i18n
│   ├── messages.es.yaml      # Español (default)
│   └── messages.en.yaml      # Inglés
├── var/                       # Cache y logs
│   ├── cache/                # Caché de Symfony
│   └── log/                  # Logs de la aplicación
├── vendor/                    # Dependencias de Composer (generado)
├── node_modules/              # Dependencias de Node (generado)
├── composer.json              # Dependencias PHP
├── composer.lock
├── package.json               # Dependencias Node
├── package-lock.json
├── webpack.config.js          # Configuración Webpack Encore
├── phpunit.xml.dist           # Configuración PHPUnit
├── .env                       # Variables de entorno (template)
├── .env.local                 # Variables de entorno locales (gitignored)
├── README.md                  # Documentación principal
└── AGENTS.md                  # Esta guía
```

---

## 🎯 Funcionalidades Principales

### Rutas Disponibles

| Ruta | Método | Descripción | Controlador |
|------|--------|-------------|-------------|
| `/` | GET | Página principal (portfolio completo) | `PageController::index` |
| `/new_message` | POST | Envío de formulario de contacto (AJAX) | `PageController::newMessage` |
| `/test-encore` | GET | Página de prueba Webpack Encore | `PageController::testEncore` |
| `/unsubscribe/{campaign}/{email}` | GET | Desuscripción de newsletter | `UnsubscribeController::index` |
| `/webhookzoho` | POST | Webhook de Zoho CRM (requiere auth básica) | `PageController::webhookzoho` |
| `/webhookwhatsapp` | GET\|POST | Webhook de WhatsApp Cloud API | `PageController::webhookwhatsapp` |
| `/donation` | GET | Página de donaciones | `DonationController::index` |
| `/donation/confirmation` | POST | Confirmación de donación | `DonationController::confirmDonation` |

**Nota importante**: El Web Profiler está configurado con prefijo `/_wdt` y `/_profiler` para evitar conflictos con otras rutas.

### Controladores

- **PageController**: Página principal, formulario de contacto, webhooks
- **DonationController**: Sistema de donaciones
- **UnsubscribeController**: Gestión de desuscripciones

### Formularios

- **ContactType**: Formulario de contacto con validación CSRF y reCAPTCHA
  - Campos: name, email, message
  - Validaciones: min/max length, email format, HTML sanitization
  - Protección: CSRF token, reCAPTCHA v3

### Servicios Frontend

El proyecto usa **Webpack Encore** para gestionar assets:

**JavaScript Modules** (`assets/js/`):
- `app.js` - Inicialización general
- `contact.js` - Manejo AJAX del formulario de contacto
- `cursor.js` - Efecto de cursor personalizado
- `nav.js` - Navegación y cambio de idioma (localStorage + cookies)
- `reveal.js` - Animaciones al hacer scroll (Intersection Observer)

**CSS Modules** (`assets/styles/`):
- Arquitectura modular por componentes
- Design tokens centralizados (_tokens.css)
- Mobile-first responsive design (_responsive.css)
- **DonationController**: Sistema de donaciones
- **UnsubscribeController**: Gestión de desuscripciones

---

## ⚙️ Configuración

### Variables de Entorno (.env.local)

Crea un archivo `.env.local` basado en `.env` con estos valores:

```env
###> symfony/framework-bundle ###
APP_ENV=dev                     # dev | prod | test
APP_SECRET=your-secret-key      # Genera con: php bin/console secrets:generate-keys
APP_DEBUG=1                     # 1 en dev, 0 en prod
###< symfony/framework-bundle ###

###> symfony/mailer ###
MAILER_DSN=smtp://user:pass@smtp.example.com:587
# Ejemplo Gmail: smtp://user@gmail.com:password@smtp.gmail.com:587
# Ejemplo Mailgun: mailgun://KEY:SECRET@default
SEND_TO_EMAIL=your-email@example.com
###< symfony/mailer ###

###> Google reCAPTCHA v3 ###
RECAPTCHA_SECRET_KEY=6LcXXXXXXXXXXXXXXXXXXXXXXXXXXX
# Obtener en: https://www.google.com/recaptcha/admin
# Tipo: reCAPTCHA v3
# Clave pública va en templates/base.html.twig y contact.js
###< Google reCAPTCHA v3 ###

###> Google Drive (CV Download) ###
FILE_ID_CV_DRIVE_GOOGLE=1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ID del archivo de Google Drive
# Obtener de: https://drive.google.com/file/d/[FILE_ID]/view
###< Google Drive (CV Download) ###

###> Zoho CRM Webhook ###
USER_ZOHO_WEBHOOK=username
PASSWORD_ZOHO_WEBHOOK=password
# Autenticación básica HTTP para webhook
###< Zoho CRM Webhook ###

###> WhatsApp Cloud API ###
TOKEN_WEBHOOK_WHATSAPP=your-webhook-verification-token
ACCESS_TOKEN_WHATSAPP=EAXXXXXXXXXXXXXXXXXXXXXXXXXXXX
FROM_PHONE_NUMBER_WHATSAPP_ID=123456789012345
FLOW_TOKEN=FLOW_TOKEN_XXXXXXXXXX
FLOW_ID=123456789012345
# Obtener en: https://developers.facebook.com/apps/
# Producto: WhatsApp Business API
###< WhatsApp Cloud API ###

###> WordPress Blog Integration ###
SITE_WP_POSTS=https://blog.saulmoralespa.com
# URL del sitio WordPress para consumir API REST
# Endpoint usado: /wp-json/wp/v2/posts
###< WordPress Blog Integration ###

###> App Configuration ###
APP_TIMEZONE=America/Bogota
###< App Configuration ###
```

### Configuración de servicios.yaml

El archivo `config/services.yaml` define parámetros globales:

```yaml
parameters:
    recaptcha_secret_key: "%env(RECAPTCHA_SECRET_KEY)%"
    send_to_email: "%env(SEND_TO_EMAIL)%"
    file_id_cv_drive_google: "%env(FILE_ID_CV_DRIVE_GOOGLE)%"
    site_wp_posts: "%env(SITE_WP_POSTS)%"
    # ... otros parámetros
```

### Configuración de Webpack Encore

El archivo `webpack.config.js` configura la compilación de assets:

```javascript
Encore
    .setOutputPath('public/build/')
    .setPublicPath('/build')
    .addEntry('app', './assets/app.js')
    .enableSingleRuntimeChunk()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())
    .configureBabelPresetEnv((config) => {
        config.useBuiltIns = 'usage';
        config.corejs = '3.36';
    });
```

### Importante: Web Profiler

Para evitar conflictos de rutas, el Web Profiler debe tener prefijos en `config/routes/web_profiler.yaml`:

```yaml
when@dev:
    web_profiler_wdt:
        resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
        prefix: /_wdt  # ⚠️ Importante: previene conflictos con /test-encore, etc.

    web_profiler_profiler:
        resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
        prefix: /_profiler
```

Sin el prefijo `/_wdt`, el profiler captura TODAS las rutas de un solo segmento como `/test-encore`, `/new_message`, etc.

---

## 🛠️ Comandos Útiles

### Comandos de Symfony

```bash
# Ver información de la aplicación
php bin/console about

# Listar todas las rutas
php bin/console debug:router

# Probar coincidencia de ruta
php bin/console router:match /test-encore
php bin/console router:match /new_message --method=POST

# Listar servicios disponibles
php bin/console debug:container

# Ver configuración de un servicio
php bin/console debug:container mailer

# Limpiar caché
php bin/console cache:clear

# Calentar caché
php bin/console cache:warmup

# Ver variables de entorno
php bin/console debug:container --env-vars

# Ver listeners de eventos
php bin/console debug:event-dispatcher

# Ver traducciones
php bin/console debug:translation es
php bin/console debug:translation en

# Validar configuración YAML
php bin/console lint:yaml config/
php bin/console lint:yaml translations/

# Validar plantillas Twig
php bin/console lint:twig templates/
```

### Comandos de Webpack Encore

```bash
# Compilar assets una vez (desarrollo)
npm run dev
# o
yarn dev

# Compilar y vigilar cambios (watch mode)
npm run watch
# o
yarn watch

# Compilar para producción (minificado, optimizado)
npm run build
# o
yarn build

# Ver configuración de Webpack
npx encore dump

# Limpiar archivos build
rm -rf public/build/*
```

### Comandos de Composer

```bash
# Instalar dependencias
composer install

# Instalar sin dev (producción)
composer install --no-dev --optimize-autoloader

# Actualizar dependencias
composer update

# Actualizar solo Symfony
composer update "symfony/*"

# Auditoría de seguridad
composer audit

# Ver dependencias instaladas
composer show

# Ver dependencias de un paquete
composer show symfony/framework-bundle

# Autoload optimizado (producción)
composer dump-autoload --optimize --classmap-authoritative

# Verificar validez de composer.json
composer validate

# Ver qué paquetes dependen de X
composer depends symfony/twig-bundle

# Ver paquetes desactualizados
composer outdated
```

### Comandos de Testing

```bash
# Ejecutar todos los tests
php bin/phpunit

# Ejecutar test específico
php bin/phpunit tests/Controller/PageControllerTest.php

# Ejecutar un método de test específico
php bin/phpunit --filter testContactFormSubmitsSuccessfully

# Tests con cobertura (requiere xdebug)
php bin/phpunit --coverage-html var/coverage

# Tests con output verbose
php bin/phpunit --verbose

# Lista de tests disponibles
php bin/phpunit --list-tests
```

### Comandos de Desarrollo

```bash
# Crear un nuevo controlador
php bin/console make:controller NombreController

# Crear un nuevo formulario
php bin/console make:form NombreFormType

# Crear un nuevo comando
php bin/console make:command app:nombre-comando

# Crear un test
php bin/console make:test

# Ver rutas de un controlador específico
php bin/console debug:router --show-controllers | grep PageController

# Limpiar cache, logs y archivos temporales
rm -rf var/cache/* var/log/*
```

---

## 🔧 Servicios Integrados

### Mailer
- Sistema de envío de emails configurado via Symfony Mailer
- SMTP configurado en `.env`
- Utilizado en formulario de contacto

### WhatsApp Cloud API
- Integración con netflie/whatsapp-cloud-api
- Webhook configurado para recibir mensajes
- Flow de automatización configurado

### Zoho CRM
- Webhook para sincronización con Zoho
- Autenticación básica HTTP

### Google Drive
- Descarga de CV desde Google Drive
- ID del archivo configurado en `.env`

### WordPress Integration
- Consumo de API REST de WordPress
- Muestra últimos posts del blog

---

## 🎨 Frontend

### Assets
- **CSS**: Bootstrap 5, Font Awesome, estilos personalizados
- **JavaScript**: jQuery, Slick Carousel, Typed.js
- **Fonts**: Font Awesome, Pe-icon-7-stroke

### Plantillas Twig
- `templates/base.html.twig`: Plantilla base
- `templates/home/`: Página principal
- `templates/donation/`: Páginas de donación
- `templates/unsubscribe/`: Página de desuscripción

---

## 🧪 Testing

### Ejecutar Tests

```bash
# Ejecutar todos los tests
php bin/phpunit

# Ejecutar tests específicos
php bin/phpunit tests/Controller/PageControllerTest.php

# Ejecutar un método específico
php bin/phpunit --filter testContactFormSubmitsSuccessfully

# Tests con cobertura (requiere xdebug)
php bin/phpunit --coverage-html var/coverage

# Ver lista de tests
php bin/phpunit --list-tests
```

### Tests Implementados

#### PageControllerTest.php

Tests completos del controlador principal (`tests/Controller/PageControllerTest.php`):

**Tests de Homepage:**
- ✅ `testHomepageLoadsSuccessfully()` - Verifica que la homepage carga (status 200)
- ✅ `testHomepageContainsContactForm()` - Verifica presencia del formulario de contacto
- ✅ `testHomepageDisplaysInSpanishByDefault()` - Idioma por defecto español
- ✅ `testHomepageCanBeDisplayedInEnglish()` - Cambio a inglés con `?_locale=en`

**Tests del Formulario de Contacto:**
- ✅ `testContactFormWithInvalidNameFails()` - Valida nombre (min 2 caracteres)
- ✅ `testContactFormWithInvalidEmailFails()` - Valida formato de email
- ✅ `testContactFormWithShortMessageFails()` - Valida mensaje (min 60 caracteres)
- ✅ `testContactFormWithHtmlInMessageFails()` - Sanitización de HTML
- ✅ `testContactFormSubmitsSuccessfully()` - Envío exitoso con datos válidos
- ✅ `testContactFormReturnsErrorOnMailerFailure()` - Manejo de errores de mail
- ✅ `testContactFormValidatesCsrfToken()` - Validación CSRF
- ✅ `testContactFormRequiresAllFields()` - Campos requeridos
- ✅ `testContactFormWithLongMessageFails()` - Validación max length (1000 chars)
- ✅ `testContactFormResponseIsJson()` - Respuesta en formato JSON
- ✅ `testMultipleFormSubmissionsAreHandled()` - Múltiples envíos

**Tests de Integración con WordPress:**
- ✅ `testBlogPostsAreDisplayedWhenAvailable()` - Muestra posts del blog
- ✅ `testBlogSectionHandlesApiErrors()` - Manejo de errores del API

**Total**: 18 tests

### Estructura de un Test

```php
public function testContactFormSubmitsSuccessfully(): void
{
    // 1. Cargar la página
    $crawler = $this->client->request('GET', '/');
    
    // 2. Obtener el formulario
    $form = $crawler->selectButton('Enviar Mensaje')->form();
    
    // 3. Llenar datos
    $form['contact_form[name]'] = 'Juan Pérez';
    $form['contact_form[email]'] = 'juan@example.com';
    $form['contact_form[message]'] = str_repeat('Este es un mensaje de prueba. ', 10);
    
    // 4. Enviar (AJAX)
    $this->client->request(
        'POST',
        '/new_message',
        $form->getPhpValues(),
        [],
        ['HTTP_X-Requested-With' => 'XMLHttpRequest']
    );
    
    // 5. Verificar respuesta
    $this->assertResponseIsSuccessful();
    $response = json_decode($this->client->getResponse()->getContent(), true);
    $this->assertTrue($response['success']);
}
```

### Mocking y Fixtures

El proyecto usa **KernelTestCase** con cliente HTTP para tests funcionales.

**Sin base de datos**: No se requieren fixtures de datos.

**Mocking de servicios externos**:
- Mailer puede ser mockeado si es necesario
- API de WordPress se puede mockear para tests aislados

### Cobertura de Código

Para generar reporte de cobertura (requiere Xdebug):

```bash
# Instalar xdebug
pecl install xdebug

# Habilitar en php.ini
zend_extension=xdebug.so
xdebug.mode=coverage

# Ejecutar con cobertura
php bin/phpunit --coverage-html var/coverage

# Ver reporte en navegador
open var/coverage/index.html
```

---

## 📦 Dependencias Principales

### Producción
- `symfony/framework-bundle`: Framework core
- `symfony/form`: Sistema de formularios
- `symfony/mailer`: Envío de emails
- `symfony/security-bundle`: Sistema de seguridad
- `symfony/validator`: Validación de datos
- `symfony/twig-bundle`: Motor de plantillas
- `twig/twig`: ^3.0
- `netflie/whatsapp-cloud-api`: ^2.1

### Desarrollo
- `symfony/maker-bundle`: Generadores de código
- `symfony/web-profiler-bundle`: Barra de debug
- `symfony/debug-bundle`: Herramientas de debug
- `phpunit/phpunit`: ^9.5

---

## 🔒 Seguridad

### Protecciones Implementadas
- CSRF Protection habilitado
- reCAPTCHA en formularios
- Validación de entrada de datos
- Sanitización de HTML

### Auditoría
```bash
# Verificar vulnerabilidades
composer audit

# Actualizar dependencias con parches de seguridad
composer update --with-all-dependencies
```

---

## 🚀 Despliegue a Producción

### Preparación

```bash
# Optimizar autoloader
composer install --no-dev --optimize-autoloader

# Limpiar caché de producción
APP_ENV=prod php bin/console cache:clear

# Calentar caché
APP_ENV=prod php bin/console cache:warmup

# Verificar configuración
APP_ENV=prod php bin/console about
```

### Variables de Entorno

Crear `.env.prod` o `.env.local` en producción:

```env
APP_ENV=prod
APP_DEBUG=0
APP_SECRET=<genera-un-secret-seguro>
# ... resto de variables
```

### Checklist de Producción

- [ ] Variables de entorno configuradas
- [ ] `APP_ENV=prod` y `APP_DEBUG=0`
- [ ] Dependencias instaladas sin dev
- [ ] Cache precalentado
- [ ] Permisos correctos en `var/` (escribible)
- [ ] HTTPS configurado
- [ ] Logs monitoreados (`var/log/`)

---

## 📝 Notas de Desarrollo

### Sin Base de Datos
Este proyecto no utiliza base de datos. Los datos se gestionan mediante:
- APIs externas (Zoho, WhatsApp, WordPress)
- Envío de emails
- Variables de entorno

### Sin Docker
El proyecto se ejecuta directamente con PHP sin contenedores Docker. Requisitos del sistema:
- PHP 8.2+ instalado
- Extensiones PHP necesarias
- Composer instalado globalmente

### Migraciones
No se requieren migraciones de base de datos. Si en el futuro se necesita persistencia, considerar:
- SQLite para almacenamiento local simple
- MySQL/PostgreSQL para datos estructurados
- Doctrine ORM (ya incluido como dependencia)

---

## 🐛 Troubleshooting

### Error 404 en rutas como /test-encore, /new_message

**Síntoma**: Al acceder a rutas definidas, se obtiene 404 Not Found.

**Causa**: El Web Profiler sin prefijo captura todas las rutas de un solo segmento con su ruta `/{token}`.

**Solución**: Agregar prefijo en `config/routes/web_profiler.yaml`:

```yaml
when@dev:
    web_profiler_wdt:
        resource: '@WebProfilerBundle/Resources/config/routing/wdt.php'
        prefix: /_wdt  # ⚠️ Esto es CRUCIAL

    web_profiler_profiler:
        resource: '@WebProfilerBundle/Resources/config/routing/profiler.php'
        prefix: /_profiler
```

Luego limpiar caché:
```bash
php bin/console cache:clear
```

### Error 405 Method Not Allowed en formulario de contacto

**Síntoma**: El formulario POST a `/new_message` devuelve 405.

**Causa**: La ruta no acepta el método POST.

**Solución**: Verificar el atributo de ruta en `PageController.php`:

```php
#[Route('/new_message', name: 'new_message', methods: ['POST'])]
public function newMessage(Request $request, MailerInterface $mailer): JsonResponse
{
    // ...
}
```

O en `config/routes.yaml`:
```yaml
new_message:
    path: /new_message
    controller: 'App\Controller\PageController::newMessage'
    methods: POST  # ⚠️ Asegurar que esté POST
```

### Assets no se cargan (404 en /build/)

**Síntoma**: Los archivos CSS/JS no se encuentran en `/build/`.

**Causa**: Assets no compilados con Webpack Encore.

**Solución**:
```bash
# Instalar dependencias Node
npm install

# Compilar assets
npm run dev
# o para producción
npm run build
```

Verificar que existan:
- `public/build/app.js`
- `public/build/app.css`
- `public/build/manifest.json`
- `public/build/entrypoints.json`

### Error reCAPTCHA: "Invalid site key"

**Síntoma**: Error en consola del navegador sobre clave de reCAPTCHA inválida.

**Causas posibles**:
1. Clave pública incorrecta en el HTML
2. Dominio no autorizado en Google reCAPTCHA Console
3. Caché del navegador

**Solución**:

1. Verificar que las claves coincidan:
   - `templates/base.html.twig` o `home/index.html.twig` - clave pública
   - `assets/js/contact.js` - misma clave pública
   - `.env.local` - clave secreta

2. Verificar configuración en [Google reCAPTCHA Console](https://www.google.com/recaptcha/admin):
   - Tipo: reCAPTCHA v3
   - Dominios autorizados: `localhost`, tu dominio

3. Limpiar caché del navegador y recompilar assets:
```bash
npm run build
```

### Tests fallan: "Unreachable field _token"

**Síntoma**: Tests del formulario fallan con error de campo `_token` no encontrado.

**Causa**: El formulario no renderiza el token CSRF correctamente.

**Solución**: Verificar que el formulario use `{{ form_row(form._token) }}` o `{{ form_end(form) }}`.

En `templates/partials/_contact.html.twig`:
```twig
{{ form_start(form, {'attr': {'id': 'contact-form', 'class': 'contact__form', 'novalidate': 'novalidate'}}) }}
    {# ... campos del formulario ... #}
    {{ form_row(form._token) }}  {# ⚠️ Importante #}
{{ form_end(form, {'render_rest': false}) }}
```

### Error: "Cannot load routing file"

**Síntoma**: Error al cargar archivos de rutas.

**Solución**:
```bash
# Limpiar completamente la caché
rm -rf var/cache/*
php bin/console cache:clear
php bin/console cache:warmup
```

### Error de permisos en var/

**Síntoma**: Errores de escritura en `var/cache/` o `var/log/`.

**Solución**:
```bash
# Dar permisos de escritura
chmod -R 777 var/
# o más seguro
sudo chown -R www-data:www-data var/
chmod -R 775 var/
```

### Web Profiler no aparece

**Síntoma**: La barra de debug de Symfony no se muestra.

**Causa**: Environment incorrecto o debug deshabilitado.

**Solución**:
1. Verificar variables de entorno en `.env.local`:
```env
APP_ENV=dev
APP_DEBUG=1
```

2. Limpiar caché:
```bash
php bin/console cache:clear
```

3. Verificar que el bundle esté instalado:
```bash
composer require --dev symfony/web-profiler-bundle
```

### Error YAML en traducciones

**Síntoma**: "Unexpected characters near..." al cargar traducciones.

**Causa**: Comillas mal escapadas en archivos YAML.

**Solución**: Usar comillas simples dobles para strings con apóstrofes:

```yaml
# ❌ Incorrecto
hero.greeting: 'Hi, I'm'

# ✅ Correcto
hero.greeting: "Hi, I'm"
# o
hero.greeting: 'Hi, I''m'  # Escapar con doble comilla simple
```

Validar YAML:
```bash
php bin/console lint:yaml translations/
```

### Error: "Environment variable not found"

**Síntoma**: Variable de entorno no definida al ejecutar comandos.

**Solución**:
```bash
# Verificar variables de entorno disponibles
php bin/console debug:container --env-vars

# Asegurarse de que .env.local existe
ls -la .env*

# Verificar que la variable esté en .env o .env.local
cat .env.local | grep NOMBRE_VARIABLE
```

### Formulario de contacto devuelve error genérico

**Síntoma**: Siempre devuelve "Error al enviar el mensaje".

**Causas posibles**:
1. Configuración incorrecta del mailer
2. CSRF token inválido
3. Validación fallida

**Solución**:
1. Ver logs para detalles:
```bash
tail -f var/log/dev.log
```

2. Verificar configuración de mailer en `.env.local`:
```env
MAILER_DSN=smtp://user:pass@smtp.gmail.com:587
```

3. Probar mailer manualmente:
```bash
php bin/console debug:container mailer
```

4. Deshabilitar temporalmente reCAPTCHA para aislar el problema.

### Webpack Encore: Module not found

**Síntoma**: Error al compilar assets, módulos no encontrados.

**Solución**:
```bash
# Limpiar y reinstalar
rm -rf node_modules package-lock.json
npm install

# Verificar versiones de Node
node -v  # Debe ser 18+
npm -v

# Limpiar build anterior
rm -rf public/build/*
npm run dev
```

---

## 📚 Recursos

### Documentación Oficial
- [Symfony 8.0 Documentation](https://symfony.com/doc/8.0/index.html)
- [Twig 3.x Documentation](https://twig.symfony.com/doc/3.x/)
- [Webpack Encore Documentation](https://symfony.com/doc/current/frontend.html)
- [PHPUnit Documentation](https://phpunit.de/documentation.html)
- [Composer Documentation](https://getcomposer.org/doc/)

### APIs Integradas
- [WhatsApp Cloud API](https://developers.facebook.com/docs/whatsapp/cloud-api)
- [WordPress REST API](https://developer.wordpress.org/rest-api/)
- [Google reCAPTCHA v3](https://developers.google.com/recaptcha/docs/v3)
- [Google Drive API](https://developers.google.com/drive)

### Herramientas de Desarrollo
- [Symfony CLI](https://symfony.com/download) - Servidor local y herramientas
- [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) - Code style
- [PHPStan](https://phpstan.org/) - Análisis estático de código
- [Xdebug](https://xdebug.org/) - Debugging y cobertura de tests

### Enlaces del Proyecto
- **Sitio Principal**: https://www.saulmoralespa.com
- **Blog**: https://blog.saulmoralespa.com
- **Email**: info@saulmoralespa.com

---

## 👤 Autor

**Saúl Morales Pacheco**
- Full Stack Developer
- Especialista en PHP/Symfony y desarrollo backend
- Email: info@saulmoralespa.com
- Timezone: America/Bogota (UTC-5)

---

## 📄 Licencia

**Propietario** - Todos los derechos reservados © 2026 Saúl Morales Pacheco

---

**Última actualización**: 21 de Febrero de 2026  
**Versión**: 1.0.0  
**Symfony**: 8.0.5  
**PHP**: 8.4.18  
**Node.js**: 18+


