AUDITORÍA INTEGRAL PRE-LANZAMIENTO
Meta: salir en vivo el lunes con el sistema a estándar comercial. Incluye la incidencia CRM de WhatsApp resuelta hoy (causa raíz: prompts no desplegados).
Sin CRITICAL sin mitigar en producción. Todos los flujos de venta y pago verificados E2E hoy (suite 26/26, APIs con códigos correctos, CRM restaurado).
1) Rate-limit en api-checkout.php · 2) Reiniciar contenedor dev local · 3) Recortar CDNs muertos del CSP · 4) aria-hidden en iconos · 5) autocomplete en formularios.
Deuda técnica concentrada en 6 monolitos (1.4k–1.9k líneas) — no bloquea el lunes; planificar refactor por Módulo Post-Lanzamiento.
Reportada 2026-10-03 · resuelta y verificada E2E en el mismo día · commit 5472ac3
| Etapa | Hallazgo |
|---|---|
| Síntoma | Todo mensaje contestaba «En este momento no puedo procesar tu mensaje…» (fallback del catch genérico de WebhookHandler.php:546). |
| Causa raíz | Directorio whatsapp-crm/prompts/ vacío en producción (buyer.md, classifier.md, seller-humanize.md nunca se desplegaron — rsync manual selectivo). PromptLoader lanzaba RuntimeException: Prompt no encontrado: seller-humanize → fallback. El classificador caía en fail-open. |
| Colateral 1 | Ciega total: log_errors=Off en Hostinger + catch sin registrar → causa invisible. Fix: catch ahora registra clase/mensaje/trace (\Throwable) + .user.ini con log a archivo. |
| Colateral 2 | Setting payment_methods cifrado irrecuperable: QRs base64 superaban 65.535 chars de la columna TEXT → MySQL truncaba el ciphertext (tag GCM perdido) → 1.131 errores [CRYPTO] decrypt falló y el bot perdía el bloque [MEDIOS DE PAGO]. Fix: migración 009 TEXT→MEDIUMTEXT + valor re-creado desde config.json (cifrado verificado con self-test). |
| Verificación | E2E prod: webhook responde contenido real («¡Hola! Soy Anderson, de AMR TECH…»), 0 FATAL nuevos, 0 CRYPTO nuevos. Contactos de prueba purgados de la DB. Migración 009 commiteada. |
Suite propia PHP · 26 pruebas · ejecución en host
| Sev | Hallazgo — evidencia | Fix |
|---|---|---|
| PASS | Suite completa 26/26 (100%) — ejecutada hoy en host, JSON de resultados restaurado. | — |
| PASS | Cobertura real: Config, Dominios, Dropshipping (parser/precios/auditoría), Seguridad (sintaxis PHP+JS, JSON schema), Producción (landing 200 + validador Hostinger en vivo), TaskQueue, SupportTicket, WatchdogWAHA, Clasificador. | — |
| HIGH | Sin tests del core de ventas: ningún runTest cubre api-checkout.php (create/order), api-wompi.php (create/status/webhook), flujo carrito/checkout ni auth/CSRF del admin. Una regresión ahí no la detecta la suite. | Agregar 5 tests de regresión (create 422 whitelist, uuid 400, status fantasma, total server-side, whitelist de métodos). |
| MED | Casos límite API verificados hoy contra prod (a mano): uuid inválido → 400, método no-whitelist → 422, JSON malformado → 400, create vacío → 422, status wompi fantasma → 400. Ningún 500 ✅. | Formalizar como tests automáticos. |
Headers, auth, inyección, uploads, rate-limit, secretos
| Sev | Hallazgo — evidencia | Fix |
|---|---|---|
| OK | Headers de producción completos: HSTS preload, CSP total (frame-ancestors/base-uri/form-action 'self'), X-Frame-Options SAMEORIGIN, nosniff, referrer-policy estricto — verificados vía curl -I. | — |
| OK | SQLi: 157 prepare() vs 0 concatenaciones $_GET/$_POST dentro de SQL en el repo. Admin: password_verify + rate-limit login 5/300 + TOTP 2FA con sesión de dos pasos. | — |
| OK | Secretos fuera del cliente: grep sin llaves prv_test|SK_|ACCESS_TOKEN en HTML/JS; .htaccess bloquea .env, config.json, config_db.json, *.sqlite|*.bak. Upload de comprobantes valida mime_content_type + mapeo de extensión. | — |
| OK | api-checkout.php sin rate-limit → RESUELTO 2026-10-03: añadido rateLimit() por IP — create 20/60, comprobante 10/60, order 60/60, resto 60/60. Verificado local y prod: 20×400 → 5×429 exactos. | Hecho ✅ (commit pendiente de esta ronda) |
| OK | CSP con CDNs sobrantes en script-src → RESUELTO 2026-10-03 (corregido el hallazgo original): verificación por grep demostró que unpkg NO está muerto (Vue 3 activo en admin/dashboard/catalogo/puntos/pauta) y cdnjs solo sirve FA CSS (style-src, nunca JS). Se quitó únicamente cdn.tailwindcss.com (cero cargas reales) y cdnjs de script-src en .htaccess + env.php; se conservaron unpkg/GTM/Facebook. Verificado con curl -I en prod. | Hecho ✅ (corrección del alcance) |
| MED | Webhook CRM en fail-open HMAC: log diario [HMAC][WARN] Petición sin firma — con WAHA_WEBHOOK_SECRET configurado pero WEBHOOK_HMAC_STRICT sin activar, peticiones sin firma pasan. | Activar WEBHOOK_HMAC_STRICT=1 cuando todos los canales firmen. |
| LOW | X-XSS-Protection: 1; mode=block — header obsoleto y contraproducente en navegadores modernos. | Remover (CSP ya protege). |
SQLite (checkout) + MySQL 24 tablas (CRM) + backups
| Sev | Hallazgo — evidencia | Fix |
|---|---|---|
| OK | data/checkout.sqlite: modo WAL activo; tablas orders + order_payments con UUID UNIQUE (autoindex) — lookups por uuid indexados. | — |
| OK | MySQL CRM con buena higiene de índices: messages(contact_id,created_at), messages(contact_id,wamid), contacts.phone, dropshipping_products ×4, contact_evidence compuestos — sin scans obvios en tablas activas. | — |
| OK | Backups existentes: scripts/backup-hostinger.sh incluye mysqldump + pull de data/ en Makefile. Backup de settings: tabla ai_settings_bak_20260930. | — |
| MED | Incidente de hoy = fallo de容量: payment_methods en columna TEXT (65.535) con QRs base64 → truncado silencioso del ciphertext. Migración 009 aplicada hoy (MEDIUMTEXT), pero el patrón de guardar binarios en settings sigue frágil. | Mover QRs a archivos en data/pagos/ y guardar solo la URL (el admin ya sube QRs a disco). |
| LOW | SQLite PRAGMA foreign_keys=0 (default) — order_payments sin FK declarada hacia orders; consistencia depende de la aplicación. | FK + PRAGMA foreign_keys=ON en la conexión. |
| Sev | Hallazgo — evidencia | Fix |
|---|---|---|
| HIGH | 6 monolitos de 1.4k–1.9k líneas: admin-amrtech.php 1883, index.php 1790, catalogo-template.php 1789, config.php (CRM) 1763, dashboard-amrtech.php 1634, amrstore.php 1471 (~38.8k líneas PHP de primer nivel). | Plan post-lunes: extraer secciones a clases/servicios (admin: auth/orders/metrics). |
| MED | 28 catch {} vacíos y 20 supresiones @ en el repo — fallos silenciosos que dificultan diagnósticos (el incidente de hoy fue exactamente esto en su forma máxima). | Al menos loggear en catches de I/O y red. |
| MED | Helper h() duplicado en 7 archivos (cada archivo PHP redeclara su propia función de escape). | Mover a lib/helpers.php compartido con require. |
| LOW | Ecos de infraestructura abandonada: productos-dropi.php 1215, pauta.php 1120, catalogo-template.php 1789 con clústeres scraper de bajo uso (grafo: 12 clústeres, 6 paquetes sin fan-out). | Inventariar y decidir retiro vs mantenimiento. |
| Sev | Hallazgo — evidencia | Fix |
|---|---|---|
| OK | Prepared statements generalizados (157), JSON responses consistentes en APIs (ok/error), naming de acciones action=create|status|webhook uniforme entre api-checkout y api-wompi. | — |
| MED | declare(strict_types=1) inconsistente — presente en algunos archivos, ausente en la mayoría (los nuevos como api-wompi.php no lo declaran). | Adoptar por defecto en archivos nuevos; migrar gradual. |
| MED | Duplicación estructural: checkoutDb() y wompiDb() abren/conectan SQLite por separado con ALTER-runtime propios; loaders de .env reimplementados en ≥4 archivos (webhook.php, Database.php, env.php…). | Un único loader + una única capa de acceso SQLite. |
| LOW | CSP/hardcoding de versiones de fuentes y estilos inline extensos en vistas (esperado en PHP vanilla sin build). | Aceptar hasta post-lanzamiento. |
| Sev | Hallazgo — evidencia | Fix |
|---|---|---|
| OK | Patrones maduros en el CRM: Repository (ContactRepository…), Service (AIService, SupportTicketService, SequenceService), cascada de proveedores IA con fallback, rate-limit file-based reutilizable, EmergencyBrake/EvidenceLedger (observabilidad de decisión). | — |
| HIGH | Capa de negocio en la vista: admin-amrtech.php (1883), index.php y checkout.php mezclan SQL + lógica + HTML — violación de separación (SRP): cualquier cambio de esquema toca vistas. | Extraer OrderRepository/CheckoutService (el repo ya tiene el patrón en CRM — replicarlo en la raíz). |
| MED | Config dual sin fuente única: config.json (raíz) vs ai_settings (CRM) vs .env — hoy sincronizados a mano (el incidente de prompts/demostró el costo). | Documentar propiedad de cada setting + script de sync. |
| MED | Duplicación cliente: badge MANUAL/AUTOMÁTICO y tarjetas de método de pago implementados por separado en amrstore.php, checkout.php y admin. | Componente/template compartido. |
| Sev | Hallazgo — evidencia | Fix |
|---|---|---|
| OK | Flujo de compra completo y validado: errores inline específicos en checkout ("Ingresa un correo válido", "Los correos no coinciden", "Selecciona un método de pago", mensajes de fallo Wompi con reintento preservando carrito), estado vacío «Tu carrito está vacío», breadcrumb de pedido con estados claros (PAGO APROBADO / pago pendiente + botones de reintento). | — |
| OK | E2E previo del repo: 12/12 flujos carrito/checkout; hoy verificación manual prod: landing/tienda/ficha/checkout/carrito todos 200 con payloads razonables. | — |
| MED | Comparativo UX previo (informe 44, kfstreaming vs streamingapps) documentó gaps — verificar post-lanzamiento que las mejoras P0 sigan vigentes tras los cambios de esta semana. | Re-ejecutar checklist UX del informe 44. |
| LOW | Ficha de producto de prueba con IDs inexistentes devuelve 404 «crudo» (sin página 404 amigable de tienda). | Vista 404 con «Volver a la tienda». |
| Sev | Hallazgo — evidencia (HTML real de prod) | Fix |
|---|---|---|
| OK | Base sólida: lang="es" ✅ · un solo <h1> por página (home/tienda/checkout = 1/1/1) ✅ · 0 imágenes sin alt en tienda ✅ · labels: 8 inputs = 8 labels en checkout ✅ · viewport responsive ✅ · title descriptivo ✅. | — |
| MED | 8 iconos FontAwesome sin aria-hidden="true" en checkout (0 de 8 lo tienen) — lectores de pantalla anuncian ruido de iconos decorativos (criterio 1.1.1). | Añadir aria-hidden="true" a <i class="fa…"> decorativos (grep + sed masivo). |
| MED | Autocomplete parcial: solo 3 de 8 inputs del checkout declaran autocomplete (criterio 1.3.5 — prevenir/rellenar ayuda a usuarios con motor). | autocomplete="email|tel|cc-name" en name/email/tel. |
| LOW | Badges y labels a 10–11px — riesgo de contraste/legibilidad (criterio 1.4.4); verificar pares de color sobre #080b14. | Mínimo 12px para texto informativo. |
Medido hoy contra producción (curl)
| Sev | Hallazgo | Fix |
|---|---|---|
| MED | Home de tienda pesado (312KB) — catálogo completo renderizado en el HTML de una sola petición (DOM grande ⇒ main-thread en móvil). | Paginación/lazy-load de fichas o JSON + render JS (ya hay patrón en el repo). |
| LOW | CSP sigue citando CDNs de script ya no usados — limpieza doble (seguridad + honestidad del CSP). | Ver hallazgo Seguridad-4. |
| Sev | Hallazgo — evidencia | Fix |
|---|---|---|
| HIGH | Deploy manual por rsync selectivo = causa directa del incidente de hoy: los prompts nunca entraron en ninguna lista de archivos; no hay CI ni checklist de deploy. Riesgo repetición con cualquier archivo nuevo. | Script make deploy-full que rsync-ee por directorios completos (whatsapp-crm/ entero con excludes de .env/data) + checklist en README. |
| MED | Contenedor dev local degradado: localhost:8082 acepta TCP pero HTTP no responde (smoke local 000) — impedía pruebas E2E locales hoy; prod no se vio afectada. Intento de docker restart el 2026-10-03 falló: el socket de Docker Desktop está colgado (todo docker ps cuelga). | Reiniciar Docker Desktop desde la GUI (⚠️ pendiente — requiere acción manual). |
| MED | Observabilidad apagada: log_errors=Off global de Hostinger — corregido hoy con .user.ini (prod) y logging en el catch del webhook; falta que otros endpoints críticos (api-checkout, api.php) también logueen errores a archivo. | Mover .user.ini al docroot raíz (cubre todo el sitio). |
| OK | Git hygiene: dev→staging→main respetado, push dual GitHub+GitLab, tests antes de cada push, md5 de deploy verificado 7/7 hoy. | — |
Ejecución: orquestador principal en modo Auditor (Tier 3). Se intentó delegación a agentes especializados (security-reviewer, dba, php-reviewer, qa-tester, pr-test-analyzer, performance-optimizer, uiux, a11y-architect, e2e-runner) en oleadas de ≤2 — los subagentes devolvieron salidas degradadas (ecos de tool-calls, resultados fabricados, modelo qwen2.5 inexistente) por saturación del Ollama local; por ello la evidencia de este informe fue recogida y verificada directamente con comandos reproducibles.
Evidencia: suite php tests/run_all_tests.php (26/26), curl contra prod y local, grep/AST sobre el código fuente, sqlite3 PRAGMA, MySQL information_schema, lectura de error_log de Hostinger y E2E de webhook/checkout. Grafo de código: codebase-memory (695 nodos / 1464 aristas). Cobertura del grafo revisada para los paths citados.