refactor: reorganizar openspec y planificacion con spec recaptcha

- renombrar openspec/ a _openspec/ (carpeta auxiliar)
- mover specs de features a changes/
- crear specs base: arquitectura-limpia, estandares-codigo, nomenclatura
- migrar _planificacion/ con design-system y roi-theme-template
- agregar especificacion recaptcha anti-spam (proposal, tasks, spec)
- corregir rutas y referencias en todas las specs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
FrankZamora
2026-01-08 15:30:45 -06:00
parent 0d6b6db108
commit 0f6387ab46
58 changed files with 15364 additions and 1171 deletions

View File

@@ -0,0 +1,341 @@
# Análisis de Spam en Formularios - ROI Theme
**Fecha de análisis:** 2026-01-08
**Problema:** Recepción de spam con datos aleatorios en formularios
## Características del Spam Detectado
- **Nombres:** Cadenas aleatorias (ej: `kxUcwkDPHRAnUbdRWnDx`, `SOTbwKzTcZhJfTRBYSTV`)
- **WhatsApp:** Cadenas aleatorias en lugar de números
- **Emails:** Emails reales (posiblemente robados de bases de datos)
- **Patrón:** Bots automatizados que llenan campos sin validación
- **Fuente identificada:** `newsletter-footer`
## Módulos de Formularios Identificados
### Formulario 1: Newsletter Footer
- **Handler**: `Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php`
- **Renderer**: `Public/Footer/Infrastructure/Ui/FooterRenderer.php`
- **Acción AJAX**: `roi_newsletter_subscribe`
- **Source en payload**: `newsletter-footer`
### Formulario 2: Contact Form (Sección + Modal)
- **Handler**: `Public/ContactForm/Infrastructure/Api/WordPress/ContactFormAjaxHandler.php`
- **Renderer**: `Public/ContactForm/Infrastructure/Ui/ContactFormRenderer.php`
- **Acción AJAX**: `roi_contact_form_submit`
- **Source en payload**: `contact-form`
---
## Hallazgos Durante la Revisión
### Archivos Identificados
**Schemas:**
- `Schemas/contact-form.json`
**JavaScript (Frontend):**
- `Assets/Js/footer-contact.js`
- `Assets/Js/modal-contact.js`
**PHP Backend - Newsletter (FUENTE DEL SPAM):**
- `Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php`
- `Public/Footer/Infrastructure/Ui/FooterRenderer.php`
---
## Análisis del Formulario Newsletter (PRINCIPAL AFECTADO)
### Ubicación
- **Handler AJAX**: `Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php`
- **Renderer HTML/JS**: `Public/Footer/Infrastructure/Ui/FooterRenderer.php`
- **Fuente en payload**: `newsletter-footer` ✓ (coincide con spam reportado)
### Medidas de Seguridad EXISTENTES ✅
| Medida | Estado | Archivo | Línea |
|--------|--------|---------|-------|
| Nonce WordPress | ✅ Implementado | NewsletterAjaxHandler.php | 46 |
| Rate Limiting (60s por IP) | ✅ Implementado | NewsletterAjaxHandler.php | 54-59 |
| Sanitización de inputs | ✅ Implementado | NewsletterAjaxHandler.php | 62-64 |
| Validación de email | ✅ Implementado | NewsletterAjaxHandler.php | 66 |
| Webhook URL oculta | ✅ Implementado | Nunca expuesta al cliente |
### VULNERABILIDADES CRÍTICAS ❌
| Vulnerabilidad | Impacto | Prioridad |
|----------------|---------|-----------|
| **NO hay honeypot** | Bots pasan sin detección | 🔴 ALTA |
| **NO hay CAPTCHA** | Sin verificación humana | 🔴 ALTA |
| **NO hay validación de nombre** | Acepta `kxUcwkDPHRAnUbdRWnDx` | 🔴 ALTA |
| **NO hay validación de WhatsApp** | Acepta `OGkrLENXqiQAaIYvCV` | 🔴 ALTA |
| **Rate limiting débil** | 60s es poco, bots rotan IPs | 🟡 MEDIA |
| **NO hay tiempo mínimo de envío** | Bots envían instantáneamente | 🟡 MEDIA |
### Campos del Formulario (FooterRenderer.php:336-343)
```html
<form id="roi-newsletter-form">
<input type="hidden" name="nonce" value="...">
<input type="text" name="name" placeholder="Nombre"> <!-- SIN VALIDACIÓN -->
<input type="email" name="email" placeholder="Email" required> <!-- SOLO HTML5 -->
<input type="tel" name="whatsapp" placeholder="WhatsApp"> <!-- SIN VALIDACIÓN -->
<button type="submit">Suscribirse</button>
</form>
```
### Patrón del Spam Detectado
Los datos de spam muestran:
- **Nombres**: Cadenas alfanuméricas aleatorias (20+ caracteres)
- **WhatsApp**: Cadenas alfanuméricas aleatorias (NO son números)
- **Emails**: Emails reales (posiblemente de bases de datos filtradas)
Esto indica **bots automatizados** que:
1. Bypassean la validación HTML5 (trivial)
2. Ignoran el rate limiting rotando IPs
3. Generan valores aleatorios para campos de texto
4. Usan emails reales para parecer legítimos
---
## Análisis del Formulario Contact Form (SEGUNDO FORMULARIO)
### Ubicación
- **Handler AJAX**: `Public/ContactForm/Infrastructure/Api/WordPress/ContactFormAjaxHandler.php`
- **Renderer**: `Public/ContactForm/Infrastructure/Ui/ContactFormRenderer.php`
- **Schema**: `Schemas/contact-form.json`
- **Fuente en payload**: `contact-form`
### Estado: ✅ ACTIVO (Clean Architecture)
Este formulario está implementado correctamente con Clean Architecture:
- Renderer genera HTML/CSS/JS dinámicamente
- Handler AJAX procesa envíos server-side
- Webhook URL guardada en BD (nunca expuesta al cliente)
### Medidas de Seguridad EXISTENTES ✅
| Medida | Estado | Archivo | Línea |
|--------|--------|---------|-------|
| Nonce WordPress | ✅ Implementado | ContactFormAjaxHandler.php | 47-48 |
| Rate Limiting (30s por IP) | ✅ Implementado | ContactFormAjaxHandler.php | 55-61 |
| Sanitización de inputs | ✅ Implementado | ContactFormAjaxHandler.php | 122-130 |
| Validación de email | ✅ Implementado | ContactFormAjaxHandler.php | 151-155 |
| Webhook URL oculta | ✅ Implementado | ContactFormAjaxHandler.php | 86 |
### VULNERABILIDADES CRÍTICAS ❌
| Vulnerabilidad | Impacto | Prioridad |
|----------------|---------|-----------|
| **NO hay honeypot** | Bots pasan sin detección | 🔴 ALTA |
| **NO hay CAPTCHA** | Sin verificación humana | 🔴 ALTA |
| **NO hay validación de nombre** | Acepta `kxUcwkDPHRAnUbdRWnDx` | 🔴 ALTA |
| **NO hay validación de WhatsApp** | Acepta `OGkrLENXqiQAaIYvCV` | 🔴 ALTA |
| **Rate limiting muy débil** | Solo 30s, bots rotan IPs | 🟡 MEDIA |
| **NO hay tiempo mínimo de envío** | Bots envían instantáneamente | 🟡 MEDIA |
### Campos del Formulario (ContactFormRenderer.php:278-320)
```html
<form id="roiContactForm" data-nonce="...">
<input type="text" name="fullName" placeholder="Nombre completo *" required> <!-- SIN VALIDACIÓN FORMATO -->
<input type="text" name="company" placeholder="Empresa">
<input type="tel" name="whatsapp" placeholder="WhatsApp *" required> <!-- SIN VALIDACIÓN FORMATO -->
<input type="email" name="email" placeholder="Correo electrónico *" required> <!-- SOLO required + is_email -->
<textarea name="message"></textarea>
</form>
```
### Nota sobre archivo Legacy (footer-contact.js)
El archivo `Assets/Js/footer-contact.js` es **código legacy NO utilizado**:
- NO está encolado en `Inc/enqueue-scripts.php`
- Tiene webhook URL hardcodeada (mala práctica)
- Fue reemplazado por el sistema Clean Architecture actual
---
## Resumen Comparativo de Ambos Formularios
| Característica | Newsletter Footer | Contact Form |
|----------------|-------------------|--------------|
| **Estado** | ✅ ACTIVO | ✅ ACTIVO |
| **Backend** | PHP AJAX | PHP AJAX |
| **Nonce** | ✅ Sí | ✅ Sí |
| **Rate Limit** | ✅ 60s | ✅ 30s |
| **Sanitización** | ✅ Sí | ✅ Sí |
| **Validación email** | ✅ is_email() | ✅ is_email() |
| **Validación nombre** | ❌ Ninguna | ❌ Solo required |
| **Validación WhatsApp** | ❌ Ninguna | ❌ Solo required |
| **Honeypot** | ❌ NO | ❌ NO |
| **CAPTCHA** | ❌ NO | ❌ NO |
| **Tiempo mínimo** | ❌ NO | ❌ NO |
### Conclusión: AMBOS formularios comparten las MISMAS vulnerabilidades críticas
---
## Soluciones Anti-Spam Propuestas
### PRIORIDAD 1: Newsletter Footer (Urgente)
#### 1.1 Honeypot Field (Implementación rápida, efectiva)
```html
<!-- Campo oculto que humanos no llenan pero bots sí -->
<input type="text" name="website_url" style="display:none !important" tabindex="-1" autocomplete="off">
```
Backend (`NewsletterAjaxHandler.php`):
```php
// Verificar honeypot
$honeypot = sanitize_text_field($_POST['website_url'] ?? '');
if (!empty($honeypot)) {
// Es bot - responder éxito falso para no alertar
wp_send_json_success(['message' => $successMsg]);
return;
}
```
#### 1.2 Validación de Contenido (Anti-gibberish)
```php
// Validar nombre: solo letras, espacios, acentos
$name = sanitize_text_field($_POST['name'] ?? '');
if (!empty($name) && !preg_match('/^[\p{L}\s\'-]{2,50}$/u', $name)) {
wp_send_json_error(['message' => 'Nombre inválido'], 422);
return;
}
// Validar WhatsApp: solo números, +, -, espacios
$whatsapp = sanitize_text_field($_POST['whatsapp'] ?? '');
if (!empty($whatsapp) && !preg_match('/^[\d\s\+\-\(\)]{10,20}$/', $whatsapp)) {
wp_send_json_error(['message' => 'Número de WhatsApp inválido'], 422);
return;
}
```
#### 1.3 Tiempo Mínimo de Envío
```php
// En el formulario HTML agregar:
<input type="hidden" name="form_timestamp" value="<?php echo time(); ?>">
// En el handler:
$timestamp = (int) ($_POST['form_timestamp'] ?? 0);
$minTime = 3; // segundos mínimos
if (time() - $timestamp < $minTime) {
// Envío demasiado rápido = bot
wp_send_json_success(['message' => $successMsg]); // Falso éxito
return;
}
```
#### 1.4 Rate Limiting Mejorado
```php
// Aumentar de 60s a 300s (5 minutos)
// Y agregar límite diario por IP
private function checkRateLimit(): bool
{
$ip = $this->getClientIP();
// Límite corto (5 minutos)
$shortKey = 'roi_newsletter_short_' . md5($ip);
if (get_transient($shortKey) !== false) {
return false;
}
set_transient($shortKey, time(), 300);
// Límite diario (máximo 5 por día)
$dailyKey = 'roi_newsletter_daily_' . md5($ip);
$dailyCount = (int) get_transient($dailyKey);
if ($dailyCount >= 5) {
return false;
}
set_transient($dailyKey, $dailyCount + 1, DAY_IN_SECONDS);
return true;
}
```
### PRIORIDAD 2: Opciones Adicionales
#### 2.1 Google reCAPTCHA v3 (Invisible)
- Sin fricción para usuarios
- Score basado en comportamiento
- Requiere cuenta Google reCAPTCHA
#### 2.2 Cloudflare Turnstile (Recomendado)
- Gratuito
- Sin tracking de Google
- Más privado que reCAPTCHA
#### 2.3 hCaptcha
- Alternativa a reCAPTCHA
- Mejor privacidad
- Versión gratuita disponible
---
## Plan de Implementación
### Fase 1: Crear Servicio Anti-Spam Compartido
1. Crear `Shared/Infrastructure/Services/AntiSpamValidator.php`
2. Implementar validaciones reutilizables:
- `validateHoneypot(string $value): bool`
- `validateMinimumTime(int $timestamp, int $minSeconds = 3): bool`
- `validateNameFormat(string $name): bool` (regex letras/espacios)
- `validateWhatsAppFormat(string $phone): bool` (regex números)
- `checkEnhancedRateLimit(string $ip, int $shortLimit, int $dailyLimit): bool`
### Fase 2: Aplicar a Newsletter Footer
1. Agregar honeypot + timestamp en `FooterRenderer.php`
2. Integrar AntiSpamValidator en `NewsletterAjaxHandler.php`
3. Probar que spam es rechazado
### Fase 3: Aplicar a Contact Form
1. Agregar honeypot + timestamp en `ContactFormRenderer.php`
2. Integrar AntiSpamValidator en `ContactFormAjaxHandler.php`
3. Probar que spam es rechazado
### Fase 4: Monitoreo y Mejoras (Opcional)
1. Agregar logging de intentos sospechosos
2. Evaluar CAPTCHA invisible si spam persiste
3. Dashboard de estadísticas anti-spam
---
## Archivos a Modificar
### Newsletter Footer
| Archivo | Cambio |
|---------|--------|
| `Public/Footer/Infrastructure/Ui/FooterRenderer.php` | Agregar honeypot + timestamp oculto |
| `Public/Footer/Infrastructure/Api/WordPress/NewsletterAjaxHandler.php` | Validaciones formato + honeypot check + rate limiting mejorado |
### Contact Form
| Archivo | Cambio |
|---------|--------|
| `Public/ContactForm/Infrastructure/Ui/ContactFormRenderer.php` | Agregar honeypot + timestamp oculto |
| `Public/ContactForm/Infrastructure/Api/WordPress/ContactFormAjaxHandler.php` | Validaciones formato + honeypot check + rate limiting mejorado |
### Código Reutilizable (Recomendado)
Crear un trait o clase helper compartida para evitar duplicación:
```
Shared/Infrastructure/Services/AntiSpamValidator.php
```
- Validación de honeypot
- Validación de tiempo mínimo
- Validación de formato nombre (regex)
- Validación de formato WhatsApp (regex)
- Rate limiting mejorado
---
## Referencias Serena
Las memorias existentes en `.serena/Memories/` documentan:
- `diagnostico-estructura-carpetas-admin.md` - Estructura de carpetas Admin
- `migracion-theme-options-tabla-personalizada.md` - Sistema de settings
- `ROI_THEME_CSS_LOADING_ANALYSIS.md` - Análisis de carga CSS
Esta memoria debe guardarse como referencia para implementación futura.