<h2>📋 DOCUMENTACIÓN ARQUITECTÓNICA COMPLETA</h2>
<h2>Sistema de Notificaciones POO – v2.1.0</h2>
<p><strong>Autor:</strong> Senior Developer / Software Architect<br />
<strong>Fecha:</strong> Marzo 2026<br />
<strong>Estado:</strong> ✅ Análisis completado – Todos los ficheros revisados</p>
<hr />
<h2>🗂️ 1. DIAGRAMA DE DIRECTORIOS Y ESTRUCTURA</h2>
<pre><code>/050033/ (ROOT – Aplicación Principal)
│
├── 📄 .htaccess # Seguridad principal (Rewrites, Headers CSP, Rate Limiting)
├── 📄 index.php # Frontend UI (HTML5/CSS3/JS – Dark Mode, AJAX, CSRF)
├── 📄 api.php # Backend API Endpoint (AJAX Handler + Queue Logic + Hooks)
│
├── 📁 config/ # 🔒 PROTEGIDO (Deny all)
│ ├── 📄 .htaccess # Protección absoluta de configuración
│ ├── 📄 config.php # Configuración centralizada (Email, SMTP, Sesiones, CSRF)
│ └── 📄 queue.php # Configuración del sistema de colas (APCu, DB, Timeouts)
│
├── 📁 classes/ # 🔒 PROTEGIDO (Deny all) – Núcleo OOP
│ ├── 📄 .htaccess # Protección de código fuente
│ │
│ ├── 🧩 Núcleo de Notificaciones
│ │ ├── 📄 Sendable.php # Interface: contrato para notificaciones
│ │ ├── 📄 NotificationException.php # Excepción personalizada con mensajes user-friendly
│ │ ├── 📄 LoggerTrait.php # Trait horizontal: logging con rotación
│ │ ├── 📄 Notification.php # Clase abstracta base (validación, sanitización)
│ │ ├── 📄 EmailNotification.php # Implementación Email (SMTP/mail(), HTML/Texto)
│ │ ├── 📄 SMSNotification.php # Implementación SMS (simulación/real)
│ │ └── 📄 NotificationFactory.php # Factory Pattern: creación dinámica de notificaciones
│ │
│ ├── 🧩 Sistema de Hooks/Events
│ │ ├── 📄 HookInterface.php # Interface para listeners
│ │ └── 📄 HookManager.php # Singleton: gestión de eventos con prioridades
│ │
│ └── 🧩 Sistema de Colas (Queue System)
│ ├── 📄 QueueEntity.php # Entidad: representa un email en cola
│ ├── 📄 QueueRepository.php # Repository Pattern: acceso a BD con PDO Singleton
│ ├── 📄 QueueManager.php # Lógica de negocio: enqueue, process, retry, dead-letter
│ └── 📄 QueueWorker.php # Worker portable: CLI/HTTP, batch processing, stats
│
├── 📁 cli/ # 🔒 PROTEGIDO – Scripts ejecutables
│ ├── 📄 .htaccess # Protección de scripts CLI
│ └── 📄 process_queue.php # Entry point para worker (CRON/EasyCron)
│
├── 📁 logs/ # 🔒 PROTEGIDO – Solo escritura local
│ ├── 📄 .htaccess # Deny all + Allow local para logs
│ ├── 📄 notifications.log # Log de operaciones de notificaciones
│ ├── 📄 php_errors.log # Errores de PHP
│ └── 📄 queue_worker.log # Log del worker de cola
│
└── 📁 vendor/ # 🔒 PROTEGIDO – Dependencias
└── 📁 phpmailer/phpmailer/ # Librería SMTP profesional
└── 📁 src/
├── 📄 Exception.php
├── 📄 PHPMailer.php
└── 📄 SMTP.php</code></pre>
<hr />
<h2>🏗️ 2. ARQUITECTURA GENERAL DEL SISTEMA</h2>
<pre><code class=»language-mermaid language-none language-mermaid» data-language=»none»>graph TB
subgraph «Frontend Layer»
UI[index.php] –>|AJAX POST + CSRF| API[api.php]
end
subgraph «API Layer – api.php»
API –>|Validación CSRF| CSRF{CSRF Valid?}
CSRF –>|No| ERR403[403 Forbidden]
CSRF –>|Sí| SANIT[Sanitización Inputs]
SANIT –> FACT[NotificationFactory]
end
subgraph «Notification Core»
FACT –>|create()| NOTIF[Notification Abstract]
NOTIF –> EMAIL[EmailNotification]
NOTIF –> SMS[SMSNotification]
EMAIL –>|enviar()| SMTP[PHPMailer SMTP]
EMAIL –>|enviar()| MAIL[mail() nativo]
SMS –>|enviar()| SMS_API[API SMS Externa/Simulación]
end
subgraph «Queue System (Opcional)»
API –>|QUEUE_ENABLED=true| QM[QueueManager]
QM –>|enqueueEmail()| QR[QueueRepository]
QR –>|INSERT| DB[(MySQL Database)]
QM –>|processEmail()| QW[QueueWorker]
QW –>|dequeue()| QR
QW –>|sendActualEmail()| EMAIL
QM –>|Dead Letter| QDL[queue_dead_letter]
end
subgraph «Cross-Cutting Concerns»
HOOKS[HookManager Singleton] -.->|fire()| API
HOOKS -.->|fire()| QM
HOOKS -.->|fire()| QW
LOGGER[LoggerTrait] -.->|logOperation()| EMAIL
LOGGER -.->|logOperation()| SMS
APCu[(APCu Cache)] -.->|Stats| QM
APCu -.->|Stats| QW
end
subgraph «External Execution»
CRON[CRON/EasyCron] –>|*/1 * * * *| CLI[cli/process_queue.php]
CLI –>|HTTP with secret| QW
CLI –>|CLI direct| QW
end
style UI fill:#e1f5fe
style API fill:#fff3e0
style DB fill:#e8f5e9
style APCu fill:#f3e5f5</code></pre>
<hr />
<h2>🔍 3. ANÁLISIS DETALLADO FICHERO POR FICHERO</h2>
<h3>3.1 <code>config/config.php</code> – Configuración Centralizada</h3>
<table>
<thead>
<tr>
<th>Sección</th>
<th>Propósito</th>
<th>Detalles Clave</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Seguridad de Sesión</strong></td>
<td>Configuración segura de sesiones PHP</td>
<td><code>cookie_httponly=true</code>, <code>cookie_secure</code> dinámico según HTTPS, <code>SameSite=Lax</code>, <code>use_strict_mode=true</code></td>
</tr>
<tr>
<td><strong>CSRF Token</strong></td>
<td>Protección contra CSRF</td>
<td>Token de 64 hex chars, regeneración cada 24h, almacenamiento en <code>$_SESSION</code></td>
</tr>
<tr>
<td><strong>Constantes de Entorno</strong></td>
<td>Configuración portable</td>
<td><code>APP_ENV</code>, <code>ROOT_PATH</code>, <code>LOGS_PATH</code> con fallback a <code>getenv()</code></td>
</tr>
<tr>
<td><strong>Configuración Email</strong></td>
<td>Método y formato de envío</td>
<td><code>EMAIL_METHOD</code> (smtp/mail), <code>EMAIL_FORMAT</code> (html/text), desde/reply-to configurables</td>
</tr>
<tr>
<td><strong>Configuración SMTP</strong></td>
<td>Credenciales y parámetros SMTP</td>
<td>Host, puerto, TLS/SSL, auth, timeout, debug conditional por entorno</td>
</tr>
<tr>
<td><strong>Plantillas HTML</strong></td>
<td>Generación de emails HTML</td>
<td>CSS inline, template base, footer dinámico, conversión HTML→PlainText</td>
</tr>
<tr>
<td><strong>Seguridad App</strong></td>
<td>Claves y rate limiting</td>
<td><code>APP_KEY</code> para hashing, límites de sesión y requests por IP</td>
</tr>
<tr>
<td><strong>Validación Producción</strong></td>
<td>Checks de seguridad en prod</td>
<td>Warns si APP_KEY por defecto o credenciales SMTP vacías</td>
</tr>
<tr>
<td><strong>Funciones Auxiliares</strong></td>
<td>Helpers reutilizables</td>
<td><code>env()</code>, <code>isEmailConfigured()</code>, <code>getPhpMailerConfig()</code>, <code>generateEmailHTML()</code>, <code>htmlToPlainText()</code></td>
</tr>
</tbody>
</table>
<p><strong>💡 Por qué se creó:</strong> Centralizar toda la configuración sensible y operativa en un único punto, permitiendo despliegues portables mediante variables de entorno y facilitando la gestión de diferentes entornos (dev/prod).</p>
<hr />
<h3>3.2 <code>config/queue.php</code> – Configuración del Sistema de Colas</h3>
<table>
<thead>
<tr>
<th>Constante/Función</th>
<th>Propósito</th>
<th>Valor por Defecto</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>QUEUE_ENABLED</code></td>
<td>Activar/desactivar sistema de colas</td>
<td><code>true</code></td>
</tr>
<tr>
<td><code>QUEUE_EXECUTION_METHOD</code></td>
<td>Método de ejecución: cli/http/auto</td>
<td><code>’auto'</code></td>
</tr>
<tr>
<td><code>QUEUE_HTTP_SECRET_KEY</code></td>
<td>Clave para autenticar ejecuciones HTTP</td>
<td>64 hex chars aleatorios</td>
</tr>
<tr>
<td><code>QUEUE_DEFAULT_PRIORITY</code></td>
<td>Prioridad por defecto (1=máx, 10=mín)</td>
<td><code>5</code></td>
</tr>
<tr>
<td><code>QUEUE_MAX_ATTEMPTS</code></td>
<td>Reintentos antes de dead-letter</td>
<td><code>3</code></td>
</tr>
<tr>
<td><code>QUEUE_RETRY_DELAY</code></td>
<td>Espera entre reintentos (segundos)</td>
<td><code>300</code> (5 min)</td>
</tr>
<tr>
<td><code>QUEUE_BATCH_SIZE</code></td>
<td>Emails a procesar por ejecución</td>
<td><code>50</code></td>
</tr>
<tr>
<td><code>QUEUE_ITEM_TIMEOUT</code></td>
<td>Timeout por email individual</td>
<td><code>30s</code></td>
</tr>
<tr>
<td><code>QUEUE_EXECUTION_TIMEOUT</code></td>
<td>Timeout total de ejecución del worker</td>
<td><code>120s</code></td>
</tr>
<tr>
<td><code>QUEUE_USE_APCU</code></td>
<td>Usar APCu para cache de estadísticas</td>
<td><code>true</code> si extensión cargada</td>
</tr>
<tr>
<td><code>QUEUE_APCU_PREFIX/TTL</code></td>
<td>Prefijo y TTL para claves APCu</td>
<td><code>’queue_'</code>, <code>300s</code></td>
</tr>
<tr>
<td><code>isHttpExecution()</code></td>
<td>Detectar si se ejecuta vía HTTP</td>
<td>Lógica basada en <code>QUEUE_EXECUTION_METHOD</code> + <code>PHP_SAPI</code></td>
</tr>
<tr>
<td><code>validateQueueSecretKey()</code></td>
<td>Validar clave secreta HTTP</td>
<td><code>hash_equals()</code> para timing-safe comparison</td>
</tr>
</tbody>
</table>
<p><strong>💡 Por qué se creó:</strong> Separar la configuración de colas permite ajustar el comportamiento del worker sin tocar el código, soportando múltiples métodos de ejecución (CLI nativo, EasyCron, servicios externos) y optimizando recursos mediante caché APCu.</p>
<hr />
<h3>3.3 <code>classes/Sendable.php</code> – Interface de Contrato</h3>
<pre><code class=»language-php language-php» data-language=»php»>interface Sendable {
public function enviar(): bool; // Ejecuta el envío, retorna éxito
public function validar(): void; // Valida datos antes de enviar, lanza excepción si falla
}</code></pre>
<p><strong>💡 Por qué se creó:</strong> Aplicar el principio de inversión de dependencias (SOLID). Cualquier clase que implemente <code>Sendable</code> garantiza una interfaz uniforme, permitiendo al <code>NotificationFactory</code> crear notificaciones sin conocer su implementación concreta.</p>
<hr />
<h3>3.4 <code>classes/NotificationException.php</code> – Excepción Personalizada</h3>
<table>
<thead>
<tr>
<th>Método</th>
<th>Propósito</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>__construct($message, $userMessage, $code, $previous)</code></td>
<td>Inicializa con mensaje técnico, mensaje amigable para usuario, código de error y excepción anterior</td>
</tr>
<tr>
<td><code>getUserFriendlyMessage()</code></td>
<td>Retorna mensaje sanitizado para mostrar al usuario final</td>
</tr>
<tr>
<td><code>getErrorCode()</code></td>
<td>Retorna código numérico para logging/monitoreo</td>
</tr>
<tr>
<td><code>getTechnicalMessage()</code></td>
<td>Retorna mensaje técnico completo para desarrolladores</td>
</tr>
</tbody>
</table>
<p><strong>💡 Por qué se creó:</strong> Separar claramente los mensajes de error técnicos (para logs) de los mensajes amigables (para UI), mejorando la experiencia de usuario y facilitando el debugging. Los códigos de error permiten categorizar fallos para métricas y alertas.</p>
<hr />
<h3>3.5 <code>classes/LoggerTrait.php</code> – Trait de Logging Horizontal</h3>
<table>
<thead>
<tr>
<th>Método</th>
<th>Propósito</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>logOperation($type, $target, $status, $context)</code></td>
<td>Escribe entrada de log con timestamp, IP, request ID, contexto JSON</td>
</tr>
<tr>
<td><code>sanitizeLogData()</code></td>
<td>Sanitiza datos para logs: elimina caracteres de control, limita longitud, escapa HTML</td>
</tr>
<tr>
<td><code>buildLogMessage()</code></td>
<td>Construye formato estructurado de log: <code>[TIMESTAMP] [ID:XXX] [IP:XXX] Type: X | To: Y | Status: Z \| Context: {…}</code></td>
</tr>
<tr>
<td><code>rotateLogIfNeeded()</code></td>
<td>Rota archivo de log si supera 10MB, mantiene máximo 5 archivos históricos</td>
</tr>
<tr>
<td><code>getClientIP()</code></td>
<td>Obtiene IP real del cliente considerando proxies (Cloudflare, X-Forwarded-For)</td>
</tr>
</tbody>
</table>
<p><strong>💡 Por qué se creó:</strong> Proporcionar logging consistente, seguro y performante a todas las clases de notificación sin duplicar código. La rotación automática previene el llenado de disco, y la sanitización previene inyección de logs.</p>
<hr />
<h3>3.6 <code>classes/Notification.php</code> – Clase Abstracta Base</h3>
<pre><code class=»language-php language-php» data-language=»php»>abstract class Notification implements Sendable {
use LoggerTrait;
// Propiedades protegidas compartidas
protected string $receptor;
protected string $mensaje;
// Constructor con validación y sanitización centralizada
public function __construct(string $receptor, string $mensaje) { … }
// Sanitización de entrada: strip_tags selectivo, limpieza de caracteres de control
private function sanitizarEntrada(string $data): string { … }
// Métodos abstractos que deben implementar las clases hijas
abstract public function validar(): void;
abstract public function enviar(): bool;
// Getters y __toString() para debugging
}</code></pre>
<p><strong>💡 Por qué se creó:</strong> Aplicar el patrón Template Method. Centraliza validaciones comunes (longitud máxima 5000 chars, sanitización), logging y propiedades base, reduciendo duplicación y asegurando consistencia en todas las notificaciones.</p>
<hr />
<h3>3.7 <code>classes/EmailNotification.php</code> – Implementación Email</h3>
<table>
<thead>
<tr>
<th>Método</th>
<th>Propósito</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>__construct($receptor, $mensaje, $asunto, $footerPersonalizado)</code></td>
<td>Inicializa con sanitización de asunto (máx 255 chars, sin saltos de línea)</td>
</tr>
<tr>
<td><code>validar()</code></td>
<td>Valida formato email con <code>filter_var()</code>, verifica DNS del dominio (MX/A records)</td>
</tr>
<tr>
<td><code>enviar()</code></td>
<td>Orquesta el envío: valida, selecciona método (SMTP/mail), loggea resultado, maneja excepciones</td>
</tr>
<tr>
<td><code>enviarViaMailNative()</code></td>
<td>Dispatch a envío HTML o texto plano según <code>EMAIL_FORMAT</code></td>
</tr>
<tr>
<td><code>enviarTextoViaMailNative()</code></td>
<td>Envío simple con headers MIME básicos</td>
</tr>
<tr>
<td><code>enviarHtmlViaMailNative()</code></td>
<td>Envío multipart/alternative (texto + HTML) con boundary generado</td>
</tr>
<tr>
<td><code>enviarViaSMTP()</code></td>
<td>Configuración completa de PHPMailer: auth, encryption, timeouts, attachments</td>
</tr>
<tr>
<td><code>adjuntarArchivo()</code></td>
<td>Método fluido para añadir adjuntos (solo SMTP), con validación de existencia y permisos</td>
</tr>
</tbody>
</table>
<p><strong>💡 Por qué se creó:</strong> Soportar envío de emails profesional con fallback a <code>mail()</code> nativo, formato HTML/texto configurable, y capacidad de adjuntos. La validación DNS previene rebotes por dominios inválidos.</p>
<hr />
<h3>3.8 <code>classes/SMSNotification.php</code> – Implementación SMS</h3>
<table>
<thead>
<tr>
<th>Método</th>
<th>Propósito</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>__construct($receptor, $mensaje, $codigoPais)</code></td>
<td>Valida y normaliza código de país (formato <code>+XX</code>)</td>
</tr>
<tr>
<td><code>validarCodigoPais()</code></td>
<td>Asegura formato <code>+1-3 dígitos</code>, fallback a <code>+34</code> si inválido</td>
</tr>
<tr>
<td><code>validar()</code></td>
<td>Valida longitud de teléfono (7-15 dígitos), longitud de mensaje (≤160 chars para SMS estándar)</td>
</tr>
<tr>
<td><code>enviar()</code></td>
<td>Orquesta envío: valida, construye número completo, ejecuta simulación o envío real, loggea</td>
</tr>
<tr>
<td><code>simularEnvioSMS()</code></td>
<td>Mock para desarrollo: loggea intento sin enviar, retorna <code>true</code></td>
</tr>
<tr>
<td><code>enviarSMSReal()</code></td>
<td>Placeholder para integración con API SMS real (Twilio, Vonage, etc.)</td>
</tr>
</tbody>
</table>
<p><strong>💡 Por qué se creó:</strong> Proporcionar una implementación de SMS con la misma interfaz que Email, facilitando pruebas en desarrollo mediante modo simulación y preparada para integración futura con proveedores reales.</p>
<hr />
<h3>3.9 <code>classes/NotificationFactory.php</code> – Patrón Factory</h3>
<pre><code class=»language-php language-php» data-language=»php»>class NotificationFactory {
private const NOTIFICATION_TYPES = [
‘email’ => EmailNotification::class,
‘sms’ => SMSNotification::class
];
public static function create(string $tipo, string $receptor, string $mensaje, array $options = []): Notification {
// 1. Validar tipo soportado
// 2. Verificar existencia de la clase
// 3. Instanciar con parámetros específicos por tipo
// 4. Manejar excepciones de forma consistente
}
public static function tipoValido(string $tipo): bool { … }
public static function getTiposDisponibles(): array { … }
public static function claseExiste(string $tipo): bool { … }
}</code></pre>
<p><strong>💡 Por qué se creó:</strong> Aplicar el patrón Factory para desacoplar la creación de objetos concretos del código cliente. Permite añadir nuevos tipos de notificación (push, WhatsApp) sin modificar el código que las solicita, cumpliendo el principio Open/Closed.</p>
<hr />
<h3>3.10 <code>classes/HookInterface.php</code> y <code>HookManager.php</code> – Sistema de Eventos</h3>
<p><strong>HookInterface:</strong></p>
<pre><code class=»language-php language-php» data-language=»php»>interface HookInterface {
public function handle(array $data): void; // Método único para procesar evento
}</code></pre>
<p><strong>HookManager (Singleton):</strong><br />
| Método | Propósito |<br />
|——–|———–|<br />
| <code>getInstance()</code> | Acceso global al único gestor de hooks |<br />
| <code>listen($hookName, HookInterface $listener, int $priority)</code> | Registra listener con prioridad (menor número = mayor prioridad) |<br />
| <code>fire($hookName, array $data = [])</code> | Ejecuta todos los listeners registrados para un hook, en orden de prioridad |<br />
| <code>hasFired($hookName)</code> | Consulta si un hook ya fue ejecutado en esta request |<br />
| <code>getExecutedHooks()</code> / <code>clearHistory()</code> | Debug y testing |</p>
<p><strong>💡 Por qué se creó:</strong> Implementar un sistema de eventos/extensibilidad tipo WordPress. Permite a módulos externos reaccionar a acciones del sistema (ej: <code>queue.after_enqueue</code> para enviar notificación push) sin modificar el código core.</p>
<hr />
<h3>3.11 <code>classes/Queue/QueueEntity.php</code> – Entidad de Cola</h3>
<p><strong>Propiedades clave:</strong></p>
<ul>
<li><code>queue_id</code>: UUID v4 para identificación única</li>
<li><code>status</code>: ENUM(<code>pending</code>, <code>processing</code>, <code>sent</code>, <code>failed</code>, <code>dead</code>)</li>
<li><code>priority</code>: 1-10 para ordenamiento en dequeue</li>
<li><code>attempts</code>/<code>max_attempts</code>: Control de reintentos</li>
<li><code>scheduled_at</code>: Envío programado (delayed jobs)</li>
<li><code>headers</code>: JSON para metadata personalizada</li>
</ul>
<p><strong>💡 Por qué se creó:</strong> Representar un email en cola como objeto inmutable con métodos de acceso, facilitando la serialización a BD y la lógica de negocio en <code>QueueManager</code>.</p>
<hr />
<h3>3.12 <code>classes/Queue/QueueRepository.php</code> – Repository Pattern con PDO Singleton</h3>
<table>
<thead>
<tr>
<th>Método</th>
<th>Propósito</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>getInstance()</code> / <code>getPdo()</code></td>
<td>Singleton de conexión PDO con configuración optimizada</td>
</tr>
<tr>
<td><code>enqueue(QueueEntity $email)</code></td>
<td>INSERT en <code>email_queue</code> con prepared statements</td>
</tr>
<tr>
<td><code>dequeue(int $limit)</code></td>
<td>SELECT con <code>FOR UPDATE SKIP LOCKED</code> para concurrencia segura</td>
</tr>
<tr>
<td><code>updateStatus()</code></td>
<td>UPDATE con campos dinámicos según estado (<code>processed_at</code>, <code>sent_at</code>, <code>error_message</code>)</td>
</tr>
<tr>
<td><code>moveToDeadLetter()</code></td>
<td>INSERT en <code>email_dead_letter</code> + UPDATE status a ‘dead'</td>
</tr>
<tr>
<td><code>logProcessing()</code></td>
<td>INSERT en <code>queue_processing_log</code> para auditoría detallada</td>
</tr>
<tr>
<td><code>checkRateLimit()</code></td>
<td>Implementación de rate limiting en BD con ventana deslizante</td>
</tr>
</tbody>
</table>
<p><strong>💡 Por qué se creó:</strong> Aislar el acceso a datos, permitiendo cambiar de MySQL a otro motor sin afectar la lógica de negocio. El uso de <code>SKIP LOCKED</code> permite múltiples workers procesando la misma cola sin bloqueos.</p>
<hr />
<h3>3.13 <code>classes/Queue/QueueManager.php</code> – Lógica de Negocio de Colas</h3>
<pre><code class=»language-php language-php» data-language=»php»>class QueueManager {
// Enqueue: crea entidad, dispara hooks, guarda en BD, actualiza stats APCu
public function enqueueEmail(…): ?string { … }
// Process: valida rate limit, actualiza status a ‘processing’,
// llama a sendActualEmail, maneja éxito/fallo/reintentos
public function processEmail(QueueEntity $email, string $workerId): bool { … }
// Wrapper que instancia EmailNotification y llama a enviar()
private function sendActualEmail(QueueEntity $email): bool { … }
// Manejo de fallos: incrementa attempts, decide retry vs dead-letter, loggea
private function handleFailure(…): bool { … }
// Logging estructurado de cada operación
private function logProcessing(…): void { … }
// Actualización atómica de estadísticas en APCu
private function updateApcuStats(string $action): void { … }
}</code></pre>
<p><strong>💡 Por qué se creó:</strong> Centralizar la lógica compleja de gestión de colas: priorización, reintentos exponenciales, dead-letter queue, rate limiting, y métricas en tiempo real. Separa claramente la lógica de negocio del acceso a datos (Repository) y de la ejecución (Worker).</p>
<hr />
<h3>3.14 <code>classes/Queue/QueueWorker.php</code> – Worker Portable CLI/HTTP</h3>
<table>
<thead>
<tr>
<th>Método</th>
<th>Propósito</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>__construct($verboseLogging)</code></td>
<td>Inicializa dependencias, genera worker_id único, registra startTime</td>
</tr>
<tr>
<td><code>generateWorkerId()</code></td>
<td>Crea ID único: <code>worker-{CLI/HTTP}-{hostname}-{pid}-{timestamp}</code></td>
</tr>
<tr>
<td><code>run()</code></td>
<td>Método principal: configura límites, verifica rate limit, dequeue batch, procesa emails, retorna stats</td>
</tr>
<tr>
<td><code>processEmail()</code></td>
<td>Wrapper con try/catch para aislar fallos de un email sin detener el batch</td>
</tr>
<tr>
<td><code>configureExecutionLimits()</code></td>
<td>Ajusta <code>set_time_limit</code>, <code>memory_limit</code>, timezone, logging para ejecución segura</td>
</tr>
<tr>
<td><code>isExecutionTimeout()</code></td>
<td>Verifica si se superó <code>QUEUE_EXECUTION_TIMEOUT</code> para salida limpia</td>
</tr>
<tr>
<td><code>getResult()</code></td>
<td>Construye respuesta estructurada con métricas de ejecución</td>
</tr>
<tr>
<td><code>updateApcuStats()</code></td>
<td>Actualiza contadores globales en APCu para dashboard/monitoreo</td>
</tr>
<tr>
<td><code>log()</code></td>
<td>Logging condicional: stdout para CLI, error_log para HTTP</td>
</tr>
<tr>
<td><code>getRealTimeStats()</code> / <code>cleanupOldEmails()</code></td>
<td>Métodos utilitarios para monitoreo y mantenimiento</td>
</tr>
</tbody>
</table>
<p><strong>💡 Por qué se creó:</strong> Proporcionar un worker que funcione idénticamente en CLI (CRON nativo) y HTTP (EasyCron, servicios serverless), con manejo robusto de timeouts, concurrencia y logging adaptable al contexto de ejecución.</p>
<hr />
<h3>3.15 <code>cli/process_queue.php</code> – Entry Point para Worker</h3>
<p><strong>Flujo principal:</strong></p>
<ol>
<li>Define <code>QUEUE_EXECUTION_CONTEXT</code> y <code>ROOT_PATH</code></li>
<li><strong>Si es HTTP</strong>: valida <code>?key=SECRET</code> contra <code>QUEUE_HTTP_SECRET_KEY</code> (timing-safe)</li>
<li>Carga configuraciones y clases requeridas con verificación de existencia</li>
<li>Instancia <code>QueueWorker</code> y ejecuta <code>run()</code></li>
<li>Formatea salida: JSON para HTTP, texto formateado para CLI</li>
<li>Manejo centralizado de excepciones con códigos de salida apropiados</li>
</ol>
<p><strong>💡 Por qué se creó:</strong> Proporcionar un punto de entrada seguro y portable para ejecutar el worker, con validación de autenticación para ejecuciones HTTP y salida adaptada al contexto (JSON para APIs, texto legible para terminal).</p>
<hr />
<h3>3.16 <code>api.php</code> – Endpoint AJAX Principal</h3>
<p><strong>Flujo de procesamiento:</strong></p>
<pre><code>1. Configuración inicial: polyfills PHP 7.4, función enviarRespuestaJson()
2. Carga de config.php y queue.php con validación de constantes críticas
3. Configuración de entorno: display_errors, error_log según APP_ENV
4. Inicio de sesión segura con parámetros hardened
5. Carga dinámica de clases con verificación de existencia
6. Helper functions: getHeadersCompatibles(), sanitizarInput(), validarCSRF()
7. Validación de método HTTP (solo POST)
8. Validación CSRF: headers X-CSRF-TOKEN o POST, comparación con hash_equals()
9. Sanitización y validación de inputs: type, to, message
10. Validación de tipo de notificación vía NotificationFactory::tipoValido()
11. Fire hook ‘api.before_process’ para extensibilidad
12. DECISIÓN CRÍTICA:
├── Si EMAIL + QUEUE_ENABLED=true:
│ ├── QueueManager::enqueueEmail()
│ ├── Fire hooks de cola
│ └── Respuesta 202 Accepted con queue_id
└── Si no (SMS o queue desactivado):
├── NotificationFactory::create()
├── Notification::enviar() síncrono
└── Respuesta 200 OK con resultado
13. Manejo centralizado de excepciones: NotificationException (400) vs Throwable (500)
14. Logging detallado de errores con contexto para debugging</code></pre>
<p><strong>💡 Por qué se creó:</strong> Actuar como controlador frontal seguro que orquesta la recepción de solicitudes AJAX, validación de seguridad, enrutamiento lógico (síncrono vs cola) y respuesta estructurada, manteniendo separación de responsabilidades con las clases de negocio.</p>
<hr />
<h3>3.17 <code>index.php</code> – Frontend UI Profesional</h3>
<p><strong>Características técnicas:</strong></p>
<ul>
<li><strong>HTML5 Semántico</strong>: <code><main></code>, <code><form novalidate></code>, ARIA labels, roles</li>
<li><strong>CSS3 Avanzado</strong>: Variables CSS, gradientes, backdrop-filter, animaciones keyframes, media queries para mobile/reduced-motion/high-contrast</li>
<li><strong>Dark Mode Nativo</strong>: Tema oscuro por defecto con variables CSS theming</li>
<li><strong>JavaScript Modular</strong>: IIFE <code>App</code> con encapsulación, event listeners, fetch API con AbortController</li>
<li><strong>UX Profesional</strong>: <ul>
<li>Validación en tiempo real con feedback visual</li>
<li>Contador de caracteres con warning visual al 90%</li>
<li>Spinner de carga en botón, deshabilitación durante request</li>
<li>Modal de resultado con iconos contextuales y foco accesible</li>
<li>Soporte para teclado (Escape cierra modal, focus management)</li>
</ul>
</li>
<li><strong>Seguridad Frontend</strong>: <ul>
<li>CSRF token inyectado desde PHP, enviado en header <code>X-CSRF-TOKEN</code></li>
<li>Sanitización de placeholders y hints</li>
<li>Pattern HTML5 para validación básica de email/teléfono</li>
</ul>
</li>
</ul>
<p><strong>💡 Por qué se creó:</strong> Proporcionar una interfaz de usuario moderna, accesible y segura que comunique claramente el estado del sistema (configuración SMTP/HTML visible) y guíe al usuario en el envío de notificaciones, con feedback inmediato y manejo elegante de errores.</p>
<hr />
<h3>3.18 Archivos <code>.htaccess</code> – Seguridad en Capas</h3>
<table>
<thead>
<tr>
<th>Archivo</th>
<th>Reglas Clave</th>
<th>Propósito</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Root</strong></td>
<td><code>Options -Indexes</code>, Security Headers (CSP, X-Frame-Options, etc.), Rewrite rules para bloquear acceso a carpetas sensibles, PHP config hardened</td>
<td>Protección general de la aplicación, prevención de información leakage, hardening de headers HTTP</td>
</tr>
<tr>
<td><strong>config/</strong></td>
<td><code>Require all denied</code> para Apache 2.4+, bloqueo de ejecución PHP, bloqueo de descarga de archivos sensibles</td>
<td>Protección absoluta de configuración: ni siquiera el servidor web debe servir estos archivos</td>
</tr>
<tr>
<td><strong>classes/</strong></td>
<td><code>Deny From All</code>, bloqueo de archivos sensibles, unset X-Powered-By</td>
<td>Prevenir acceso directo a código fuente, evitar exposición de versión de PHP</td>
</tr>
<tr>
<td><strong>logs/</strong></td>
<td><code>Require all denied</code> + <code>Require local</code> para archivos de log</td>
<td>Permitir escritura desde PHP local pero bloquear acceso web externo</td>
</tr>
</tbody>
</table>
<p><strong>💡 Por qué se crearon:</strong> Implementar defensa en profundidad (defense in depth). Incluso si hay una vulnerabilidad en la aplicación, los archivos sensibles no deben ser accesibles vía HTTP. Los headers de seguridad previenen ataques comunes (XSS, clickjacking, MIME sniffing).</p>
<hr />
<h2>🗄️ 4. ESQUEMA DE BASE DE DATOS</h2>
<pre><code class=»language-mermaid language-none language-mermaid» data-language=»none»>erDiagram
email_queue {
BIGINT UNSIGNED id PK
CHAR(36) queue_id UK
VARCHAR(255) recipient
VARCHAR(255) subject
TEXT body_text
TEXT body_html
JSON headers
ENUM status «pending|processing|sent|failed|dead»
TINYINT priority «1-10»
TINYINT attempts
TINYINT max_attempts
DATETIME scheduled_at
DATETIME processed_at
DATETIME sent_at
DATETIME failed_at
TEXT error_message
DATETIME created_at
DATETIME updated_at
}
queue_rate_limits {
INT UNSIGNED id PK
VARCHAR(100) limit_key UK
INT UNSIGNED limit_value
INT UNSIGNED window_seconds
INT UNSIGNED current_count
DATETIME window_start
DATETIME updated_at
}
email_dead_letter {
BIGINT UNSIGNED id PK
CHAR(36) original_queue_id FK
VARCHAR(255) recipient
VARCHAR(255) subject
TEXT body_text
TEXT body_html
TEXT failure_reason
TINYINT total_attempts
DATETIME created_at
}
queue_processing_log {
BIGINT UNSIGNED id PK
CHAR(36) queue_id FK
CHAR(36) worker_id
VARCHAR(50) action
VARCHAR(20) status
INT UNSIGNED duration_ms
TEXT error_message
JSON context
DATETIME created_at
}
email_queue ||–o{ queue_processing_log : «logs»
email_queue ||–o{ email_dead_letter : «moves to on permanent failure»</code></pre>
<p><strong>Índices estratégicos:</strong></p>
<ul>
<li><code>email_queue(status, priority, scheduled_at)</code>: Optimiza dequeue con ordenamiento</li>
<li><code>email_queue(scheduled_at, status)</code>: Para jobs programados</li>
<li><code>queue_rate_limits(limit_key)</code>: Lookup rápido de límites</li>
<li><code>queue_processing_log(queue_id, worker_id, created_at)</code>: Auditoría y debugging</li>
</ul>
<p><strong>💡 Por qué este diseño:</strong> Separar la cola activa (<code>email_queue</code>) de los fallos permanentes (<code>email_dead_letter</code>) permite mantener la tabla principal ligera para operaciones frecuentes. La tabla de logs permite auditoría sin impactar performance de la cola. El rate limiting en BD permite coordinación entre múltiples workers.</p>
<hr />
<h2>🔄 5. DIAGRAMAS DE FLUJO</h2>
<h3>5.1 Flujo de Solicitud de Notificación</h3>
<pre><code class=»language-mermaid language-none language-mermaid» data-language=»none»>sequenceDiagram
participant U as Usuario
participant UI as index.php (Frontend)
participant API as api.php
participant CSRF as Validación CSRF
participant FACT as NotificationFactory
participant QUEUE as QueueManager
participant DB as MySQL
participant EMAIL as EmailNotification
participant SMTP as PHPMailer/SMTP
U->>UI: Completa formulario (tipo, destinatario, mensaje)
UI->>API: POST /api.php con CSRF token + datos
API->>CSRF: validarCSRF(clientToken, sessionToken)
CSRF–>>API: ✅ Válido / ❌ 403 Forbidden
API->>API: sanitizarInput() para type, to, message
API->>FACT: NotificationFactory::tipoValido($type)
FACT–>>API: ✅ Tipo soportado
API->>API: Fire hook ‘api.before_process’
alt QUEUE_ENABLED=true AND type=email
API->>QUEUE: QueueManager::enqueueEmail()
QUEUE->>DB: INSERT INTO email_queue (status=’pending’)
DB–>>QUEUE: ✅ queue_id generado
QUEUE->>QUEUE: updateApcuStats(‘enqueued’)
QUEUE–>>API: Retorna queue_id
API–>>UI: 202 Accepted {success:true, queue_id:»…», status:»queued»}
UI–>>U: Modal: «Email encolado para envío diferido»
Note right of DB: Worker procesará asíncronamente
else Envío Síncrono
API->>FACT: NotificationFactory::create($type, $to, $msg, $options)
FACT–>>API: Instancia EmailNotification/SMSNotification
API->>EMAIL: $notification->enviar()
EMAIL->>EMAIL: validar() (formato email, DNS, longitud)
EMAIL->>SMTP: PHPMailer::send() o mail() nativo
SMTP–>>EMAIL: ✅ Enviado / ❌ Error
EMAIL->>EMAIL: logOperation() en notifications.log
EMAIL–>>API: Retorna bool éxito
API–>>UI: 200 OK {success:true, message:»¡Enviado!»}
UI–>>U: Modal de éxito + reset formulario
end</code></pre>
<h3>5.2 Flujo de Procesamiento de Cola (Worker)</h3>
<pre><code class=»language-mermaid language-none language-mermaid» data-language=»none»>flowchart TD
Start[Worker Inicia: cli/process_queue.php] –> Auth{¿HTTP con key válida?}
Auth –>|❌| Exit403[403 Forbidden + JSON error]
Auth –>|✅| Load[Load config + classes]
Load –> Init[QueueWorker::__construct()] Init –> Config[configureExecutionLimits: timeout, memory, timezone]
Config –> RateCheck{QueueManager::isRateLimited?}
RateCheck –>|✅ Limitado| ExitRate[Log + exit: ‘rate_limited’]
RateCheck –>|❌ OK| Dequeue[QueueRepository::dequeue BATCH_SIZE]
Dequeue –> Empty{¿Emails pendientes?}
Empty –>|❌ No| ExitEmpty[Log + exit: ‘no_pending’]
Empty –>|✅ Sí| ProcessLoop[For each email in batch]
ProcessLoop –> Timeout{¿Execution timeout?}
Timeout –>|✅ Sí| BreakLoop[Break loop, return partial stats]
Timeout –>|❌ No| Process[QueueManager::processEmail]
Process –> StatusUpdate[UPDATE status=’processing’]
StatusUpdate –> RateLimitDB{checkRateLimit smtp_per_minute}
RateLimitDB –>|❌ Limitado| Fail[handleFailure: retry]
RateLimitDB –>|✅ OK| Send[sendActualEmail: EmailNotification::enviar] Send –> Result{¿Éxito?}
Result –>|✅ Sí| MarkSent[UPDATE status=’sent’, sent_at=NOW] MarkSent –> LogSuccess[logProcessing: ‘send’/’success’] LogSuccess –> StatsSent[updateApcuStats ‘sent’] StatsSent –> NextEmail[Next email in batch]
Result –>|❌ No| HandleFail[handleFailure] HandleFail –> IncAttempts[Increment attempts counter] IncAttempts –> MaxAttempts{attempts >= max_attempts?}
MaxAttempts –>|✅ Sí| DeadLetter[moveToDeadLetter + UPDATE status=’dead’] DeadLetter –> LogDead[logProcessing: ‘dead_letter’/’failed’] LogDead –> StatsDead[updateApcuStats ‘dead’]
MaxAttempts –>|❌ No| Retry[UPDATE status=’pending’ con error_message] Retry –> LogRetry[logProcessing: ‘retry’/’failed’] LogRetry –> StatsFailed[updateApcuStats ‘failed’]
NextEmail –> ProcessLoop
BreakLoop –> Finalize
StatsDead –> Finalize
StatsFailed –> Finalize
Finalize[Build result array with stats] –> UpdateAPCu[updateApcuStats with batch totals] UpdateAPCu –> Output{¿CLI o HTTP?}
Output –>|CLI| TextOutput[Formatted text summary to stdout] Output –>|HTTP| JSONOutput[JSON response with X-Worker-ID header]
TextOutput –> Exit0[exit(0)]
JSONOutput –> Exit0</code></pre>
<hr />
<h2>🔐 6. ANÁLISIS DE SEGURIDAD</h2>
<h3>6.1 Capas de Protección Implementadas</h3>
<table>
<thead>
<tr>
<th>Capa</th>
<th>Mecanismo</th>
<th>Propósito</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Transporte</strong></td>
<td>HTTPS detection + <code>cookie_secure</code> dinámico</td>
<td>Prevenir robo de sesión en tránsito</td>
</tr>
<tr>
<td><strong>Sesión</strong></td>
<td><code>httponly</code>, <code>SameSite=Lax</code>, <code>use_strict_mode</code>, regeneración CSRF cada 24h</td>
<td>Prevenir XSS, CSRF, session fixation</td>
</tr>
<tr>
<td><strong>CSRF</strong></td>
<td>Token aleatorio 64 hex chars, validación con <code>hash_equals()</code>, envío en header</td>
<td>Prevenir solicitudes cross-site forgery</td>
</tr>
<tr>
<td><strong>Input</strong></td>
<td>Sanitización en 3 niveles: frontend (pattern HTML5), backend (<code>strip_tags</code> selectivo, <code>preg_replace</code>), clases (<code>Notification::sanitizarEntrada()</code>)</td>
<td>Prevenir XSS, inyección de logs, truncamiento malicioso</td>
</tr>
<tr>
<td><strong>Output</strong></td>
<td><code>htmlspecialchars()</code> en mensajes de usuario, escaping en logs, JSON con <code>JSON_UNESCAPED_UNICODE</code></td>
<td>Prevenir XSS reflejado, inyección en logs</td>
</tr>
<tr>
<td><strong>Archivos</strong></td>
<td><code>.htaccess</code> con <code>Deny all</code> en carpetas sensibles, bloqueo de ejecución PHP en config/classes</td>
<td>Prevenir acceso directo a código/configuración</td>
</tr>
<tr>
<td><strong>Headers HTTP</strong></td>
<td>CSP estricto, <code>X-Frame-Options</code>, <code>X-Content-Type-Options</code>, <code>Referrer-Policy</code></td>
<td>Prevenir clickjacking, MIME sniffing, leakage de referer</td>
</tr>
<tr>
<td><strong>Autenticación Worker HTTP</strong></td>
<td>Clave secreta aleatoria, validación con <code>hash_equals()</code> (timing-safe)</td>
<td>Prevenir ejecución no autorizada del worker vía web</td>
</tr>
<tr>
<td><strong>Base de Datos</strong></td>
<td>Prepared statements con PDO, <code>FOR UPDATE SKIP LOCKED</code> para concurrencia segura</td>
<td>Prevenir SQL injection, race conditions en cola</td>
</tr>
<tr>
<td><strong>Logging</strong></td>
<td>Sanitización de datos de log, rotación automática, escritura con <code>LOCK_EX</code></td>
<td>Prevenir inyección de logs, llenado de disco, corrupción por concurrencia</td>
</tr>
</tbody>
</table>
<h3>6.2 Puntos Críticos a Monitorear</h3>
<ol>
<li><strong>Credenciales SMTP en config.php</strong>: Aunque se cargan desde <code>getenv()</code>, asegúrate de que las variables de entorno no se loggeen accidentalmente.</li>
<li><strong>QUEUE_HTTP_SECRET_KEY</strong>: Si se expone, permite ejecución remota del worker. Rotar periódicamente.</li>
<li><strong>APP_KEY en producción</strong>: El valor por defecto está hardcodeado; cambiar en despliegue real.</li>
<li><strong>Rate limiting</strong>: Configurar <code>queue_rate_limits</code> según límites reales de tu proveedor SMTP para evitar bloqueos.</li>
<li><strong>Permisos de logs</strong>: Asegurar que el usuario PHP pueda escribir en <code>logs/</code> pero que no sea accesible vía web.</li>
</ol>
<hr />
<h2>⚙️ 7. CONFIGURACIÓN PARA DESPLIEGUE</h2>
<h3>7.1 Variables de Entorno Recomendadas</h3>
<pre><code class=»language-bash language-bash» data-language=»bash»># Entorno
APP_ENV=production # development | production
APP_KEY=cambia_esta_clave_por_una_segura_de_64_chars
# Email
EMAIL_METHOD=smtp # smtp | mail
EMAIL_FORMAT=html # html | text
EMAIL_FROM_ADDRESS=noreply@tudominio.com
EMAIL_FROM_NAME=»Mi Aplicación»
EMAIL_REPLY_TO=soporte@tudominio.com
# SMTP (si EMAIL_METHOD=smtp)
SMTP_HOST=smtp.proveedor.com
SMTP_PORT=587
SMTP_SECURE=tls # tls | ssl
SMTP_AUTH=true
SMTP_USERNAME=usuario@tudominio.com
SMTP_PASSWORD=contraseña_segura
SMTP_TIMEOUT=30
SMTP_VERIFY_CERTS=true
# Cola
QUEUE_ENABLED=true
QUEUE_EXECUTION_METHOD=auto # cli | http | auto
QUEUE_HTTP_SECRET_KEY=generar_con_bin2hex_random_bytes_32
QUEUE_DEFAULT_PRIORITY=5
QUEUE_MAX_ATTEMPTS=3
QUEUE_RETRY_DELAY=300
QUEUE_BATCH_SIZE=50
QUEUE_EXECUTION_TIMEOUT=120
QUEUE_USE_APCU=true
# Base de Datos
DB_HOST=localhost
DB_NAME=notificaciones_db
DB_USER=app_user
DB_PASS=contraseña_fuerte
DB_CHARSET=utf8mb4</code></pre>
<h3>7.2 Configuración de CRON (Ejemplo)</h3>
<pre><code class=»language-bash language-bash» data-language=»bash»># Ejecutar worker cada minuto (recomendado para QUEUE_BATCH_SIZE=50)
*/1 * * * * /usr/bin/php /var/www/050033/cli/process_queue.php >> /var/log/queue_worker.log 2>&1
# O para EasyCron / servicios HTTP:
# URL: https://tudominio.com/050033/cli/process_queue.php?key=TU_CLAVE_SECRETA
# Método: GET
# Frecuencia: */1 * * * *</code></pre>
<h3>7.3 Optimización de APCu</h3>
<pre><code class=»language-ini language-ini» data-language=»ini»>; En php.ini o configuración del pool FPM
apc.enabled=1
apc.enable_cli=0 ; Solo para web/worker HTTP
apc.stat=1 ; Detectar cambios en archivos
apc.ttl=7200 ; TTL por defecto 2 horas
apc.user_ttl=300 ; TTL para datos de usuario (nuestras stats)
apc.smart=0 ; Sin optimización automática
apc.preload_path=»» ; Sin preload
apc.write_lock=1 ; Prevenir corrupción en escrituras concurrentes
apc.slam_defense=1 ; Prevenir thundering herd</code></pre>
<hr />
<h2>📊 8. MÉTRICAS Y MONITOREO</h2>
<h3>8.1 Estadísticas Disponibles vía APCu</h3>
<pre><code class=»language-php language-php» data-language=»php»>// Clave: ‘queue_stats’
[
‘enqueued’ => int, // Total emails encolados (session)
‘sent’ => int, // Total enviados exitosamente
‘failed’ => int, // Total fallidos (reintentando o dead)
‘dead’ => int, // Total movidos a dead-letter
‘last_update’ => int, // Timestamp última actualización
‘last_run’ => string, // Fecha/hora última ejecución worker
‘last_worker’ => string // ID del último worker ejecutado
]</code></pre>
<h3>8.2 Consultas Útiles para Dashboard</h3>
<pre><code class=»language-sql language-sql» data-language=»sql»>– Emails por estado
SELECT status, COUNT(*) as count,
MIN(created_at) as oldest_pending
FROM email_queue
GROUP BY status;
— Tasa de éxito por hora
SELECT
DATE_FORMAT(sent_at, ‘%Y-%m-%d %H:00’) as hour,
COUNT(*) as sent_count
FROM email_queue
WHERE status = ‘sent’
AND sent_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
GROUP BY hour
ORDER BY hour;
— Emails en dead-letter para análisis
SELECT
failure_reason,
COUNT(*) as count,
MAX(created_at) as last_occurrence
FROM email_dead_letter
GROUP BY failure_reason
ORDER BY count DESC;
— Tiempo promedio de procesamiento
SELECT
AVG(duration_ms) as avg_duration_ms,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY duration_ms) as p95_ms
FROM queue_processing_log
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 HOUR);</code></pre>
<hr />
<h2>🚀 9. RECOMENDACIONES DE MEJORA</h2>
<h3>9.1 Alto Impacto / Bajo Esfuerzo</h3>
<ul>
<li>[ ] <strong>Añadir health check endpoint</strong>: <code>GET /api.php?health=1</code> que verifique conexión a BD, APCu, y configuración SMTP, retornando JSON con estado.</li>
<li>[ ] <strong>Logging estructurado JSON</strong>: Cambiar <code>LoggerTrait</code> para output en JSON, facilitando ingestión en ELK/Splunk.</li>
<li>[ ] <strong>Métricas Prometheus</strong>: Exponer contador de emails procesados vía endpoint <code>/metrics</code> para scraping.</li>
</ul>
<h3>9.2 Medio Impacto / Medio Esfuerzo</h3>
<ul>
<li>[ ] <strong>Soporte para attachments en cola</strong>: Serializar adjuntos en <code>QueueEntity</code> (base64 o referencia a path seguro) para envío diferido con archivos.</li>
<li>[ ] <strong>Prioridad dinámica</strong>: Permitir que hooks modifiquen prioridad antes de encolar (<code>queue.before_enqueue</code>).</li>
<li>[ ] <strong>Backoff exponencial</strong>: En <code>handleFailure</code>, calcular <code>RETRY_DELAY * 2^attempts</code> en lugar de delay fijo.</li>
</ul>
<h3>9.3 Alto Impacto / Alto Esfuerzo</h3>
<ul>
<li>[ ] <strong>Migrar a PSR-4 autoloading</strong>: Reemplazar <code>require_once</code> manual por Composer autoloader, facilitando testing y mantenimiento.</li>
<li>[ ] <strong>Soporte para múltiples colas</strong>: Permitir definir colas separadas para email/SMS/push con configuraciones independientes.</li>
<li>[ ] <strong>Dashboard de administración</strong>: Interfaz web para visualizar cola, reintentar emails fallidos, purgar dead-letter, con autenticación adicional.</li>
</ul>
<h3>9.4 Seguridad Adicional</h3>
<ul>
<li>[ ] <strong>Rate limiting por IP en api.php</strong>: Implementar ventana deslizante en APCu/BD para prevenir abuso del endpoint.</li>
<li>[ ] <strong>Firma de webhooks</strong>: Si se añaden hooks que llaman a URLs externas, firmar payloads con HMAC para verificación.</li>
<li>[ ] <strong>Rotación automática de QUEUE_HTTP_SECRET_KEY</strong>: Script CLI para generar y actualizar clave, con notificación a sistemas de despliegue.</li>
</ul>
<hr />
<h2>✅ 10. CHECKLIST DE VALIDACIÓN PRE-PRODUCCIÓN</h2>
<ul>
<li>[ ] <code>APP_KEY</code> cambiado del valor por defecto</li>
<li>[ ] Credenciales SMTP validadas y con permisos mínimos necesarios</li>
<li>[ ] <code>QUEUE_HTTP_SECRET_KEY</code> generado aleatoriamente y no hardcodeado</li>
<li>[ ] Permisos de carpetas: <code>logs/</code> writable por usuario PHP, no accessible vía web</li>
<li>[ ] <code>.htaccess</code> funcionando: probar acceso directo a <code>/config/config.php</code> → debe dar 403</li>
<li>[ ] Headers de seguridad presentes: verificar con <code>curl -I</code> que se envían CSP, X-Frame-Options, etc.</li>
<li>[ ] Worker probado en CLI: <code>php cli/process_queue.php</code> debe ejecutar sin errores</li>
<li>[ ] Worker probado en HTTP: <code>curl «https://…/process_queue.php?key=XXX»</code> debe validar clave</li>
<li>[ ] Prueba de envío email: formulario → API → cola → worker → SMTP → bandeja de entrada</li>
<li>[ ] Prueba de fallo: email a dominio inválido → debe ir a dead-letter tras 3 intentos</li>
<li>[ ] Logs rotando: generar >10MB en <code>notifications.log</code> y verificar creación de <code>.1</code>, <code>.2</code>, etc.</li>
<li>[ ] APCu cacheando stats: verificar con <code>apcu_fetch(‘queue_stats’)</code> que se actualizan tras procesamiento</li>
</ul>
<hr />
<blockquote>
<p>📌 <strong>Nota Final del Arquitecto</strong>:<br />
Este sistema demuestra una arquitectura madura, con separación clara de responsabilidades, patrones de diseño bien aplicados (Factory, Repository, Singleton, Template Method), y consideraciones de seguridad en múltiples capas. La capacidad de operar en modo síncrono o asíncrono (cola) lo hace flexible para diferentes volúmenes de tráfico. La portabilidad entre CLI y HTTP para el worker es una decisión técnica acertada para entornos de hosting compartido o serverless. </p>
<p><strong>Próximo paso recomendado</strong>: Implementar tests unitarios para las clases core (<code>NotificationFactory</code>, <code>QueueManager</code>) usando PHPUnit, y tests de integración para el flujo completo API→Cola→Worker→Email.</p>
</blockquote>
<p>¿Te gustaría que profundice en algún componente específico, genere código para alguna de las recomendaciones, o elabore diagramas adicionales (ej: secuencia para reintentos, estado de QueueEntity)? 🛠️</p>
