AUD-20260925-008 VIGENTE

Evaluación Integral MVP — 7 Áreas Técnicas

FOLIO: AUD-20260925-008 Secuencia: Auditoría/Informe #008 del día 2026-09-25
📅 2026-09-25 11:50:00 COT 👤 Auditor: Anderson Mares (AMR TECH) ● VIGENTE
Veredicto global: GO CONDICIONADO — 7 agentes especializados, 84 hallazgos, 12 resueltos en caliente

¿Lista el MVP para lanzar y empezar a monetizar hoy?

Evaluación integral de las 7 áreas del sistema (tienda amrstore + WhatsApp CRM + infraestructura Hostinger) realizada con 7 agentes especializados en paralelo, con evidencia de código verificada línea por línea por el orquestador antes de publicar. Veredicto: el producto puede lanzarse HOY si se cierran los 7 puntos del Gap MVP — ninguno requiere más de unas horas de trabajo — porque el bot ya vende, el precio ya es correcto y la estabilidad crítica ya fue parcheada.

Seguridad
GO-CON-FIXES
6 CRITICAL→OK
Calidad PHP
GO-CON-FIXES
schema drift
Base de Datos
GO-CON-FIXES
migración parcial
Rendimiento
GO-CON-FIXES
delay 4s→1s OK
QA / Tests
GO-CON-FIXES
15/15 débiles
Accesibilidad
GO-CON-FIXES
WCAG 2.2 AA
Arquitectura
GO-CON-FIXES
sin cobro auto
Evento durante la auditoría: el commit f5705f7 «MVP critical fixes — 12 blockers resolved» se aplicó en caliente (11:36 COT) mientras los agentes auditar, resolviendo 6 hallazgos CRITICAL de seguridad. Todos los estados de este informe fueron re-verificados con grep contra el código actual antes de publicar.

Resumen Ejecutivo

Sí, se puede lanzar hoy, con la siguiente lectura de riesgo: el sistema funciona (bot cotiza, cierra, detecta loops, entrega productos digitales, panel operativo, alertas Slack) y los bugs que arruinaban dinero — precio $10.000 vs $12.000, detalle 404, loop pidiendo correo — ya están corregidos y desplegados en producción (commit 8d9efc8 + fixes posteriores).

Lo que impide un «GO» plano son 7 gaps de negocio/operación, no de feature: (1) no existe mecanismo de cobro verificable — el cliente paga manual con foto de comprobante; (2) notifyPaymentPending() nunca se dispara → cuando el bot dice «te paso los datos de pago», nadie recibe alerta; (3) login sin rate-limit ni hash, (4) webhook Meta sin firma HMAC, (5) PII/márgenes expuestos en endpoints públicos, (6) la suite de tests no cubre el bot (15/15 es una señal parcialmente vacía), (7) el deploy correctivo de hoy + migración 002 deben aplicarse en Hostinger.

Ninguno de los 7 excede unas horas. Con eso cerrado, el MVP entra a producción con control de calidad razonable y se puede empezar a cobrar el día 1; el roadmap de 2 semanas convierte «cobro manual» en «cobro con link».

Gap MVP — 7 puntos para lanzar HOY (antes de las ventas)

  1. 1. Aplicar en Hostinger migration_002_mvp_fixes.sql + desplegar los archivos de f5705f7/8575fc3 (y los de este informe). Sin esto, producción corre con código sin parchar y schema incompleto (web_leads, manual_takeover, índices).
  2. 2. Cablear AdminNotifier::notifyPaymentPending() en WebhookHandler (~3 líneas): cuando la respuesta contenga la frase de cierre «Te comparto los datos de pago», dispara Slack + push a la cola GoPlay. Es la pieza que convierte conversación en dinero.
  3. 3. Link/QR de pago en el mensaje de cierre: Wompi Payment Links (PSE/tarjeta/Nequi) o QR Nequi+Bancolombia con monto exacto. Elimina el paso «espera, te paso los datos».
  4. 4. Registro mínimo de pedido: tabla/JSON orders(phone, producto, monto, estado, created_at) escrito al cierre → conciliación sin leer el chat a mano.
  5. 5. Endurecer login + webhooks: password_hash/password_verify + rateLimit() (ya existe en admin-amrtech.php) en login.php; firma X-Hub-Signature-256 con hash_hmac en el puente whatsapp-webhook.php.
  6. 6. Cerrar fugas de datos: auth en GET /api/telemetry (teléfonos/IPs), quitar buyPrice (154 ocurrencias) de data/products.json, quitar consumer keys de WooCommerce del JS público, proteger data/ en .htaccess.
  7. 7. Pausa de red y velocidad: timeout 8 s → 15 s en whatsapp-webhook.php:326, CURLOPT_TIMEOUT en VectorService (2 líneas, hoy = ∞), confirmar backup del día + php -l + suite antes de subir.

Hallazgos Consolidados (verificados en código)

CRITICAL HIGH MEDIUM LOW st-fixed = resuelto en f5705f7
CRITICAL RESUELTO Password del CRM hardcodeada en texto plano whatsapp-crm/public/auth.php:18

Evidencia original: $crm_password = "admin"; sin hash ni rate-limit. Estado actual: reemplazado por getenv('CRM_PASSWORD') con fail-fast si falta (commit f5705f7).

ABIERTO: login.php:6 sigue comparando en plano y sin rateLimit() ni session_regenerate_id() → ver Gap #5.

CRITICAL ABIERTO (parcial) XSS almacenado en el panel de mensajes whatsapp-crm/public/index.php:212,219

La impresión de texto ya usa escHtml() (:205, fix parcial), pero los botones «Aprender» siguen interpolando decodeURIComponent('${escapedText}') dentro de onclick: encodeURIComponent no escapa apóstrofes → un mensaje de cliente con ' rompe la cadena JS e inyecta código.

Fix: dejar de usar atributos onclick inline; adjuntar listeners con addEventListener + dataset.

CRITICAL ABIERTO Sin mecanismo de cobro verificable — 100% prepago manual whatsapp-crm/src/AIService.php:474 · api.php:1133

No hay tabla orders/payments, ni endpoint de pago, ni link Wompi/Nequi: el bot cierra con «Te comparto los datos de pago en un momento» (regla 11/15: prohibido enviar datos de pago) y Anderson responde a mano; orders_count cuenta clics, no ventas. Cero conciliación, cero facturación.

Fix: Gap #3 + #4 (links de pago + tabla mínima de pedidos).

CRITICAL ABIERTO La alerta de cierre de venta nunca se dispara whatsapp-crm/src/AdminNotifier.php:44

notifyPaymentPending() (Slack «CIERRE DE VENTA — envía datos de pago» + push a cola GoPlay) no tiene ni un call site en todo el repo; solo están cableados notifyEscalation y notifyConsulting. Impacto: venta moribunda en silencio + cola de entrega siempre vacía.

Fix: Gap #2.

CRITICAL RESUELTO (schema) / ABIERTO (install) Tres fuentes de verdad del schema + install.php usa SQL obsoleto whatsapp-crm/public/install.php:32 · db/init.sql vs init-full.sql

install.php sigue ejecutando init.sql (26 líneas, sin wamid/lead_data/channel/índices); web_leads no tenía CREATE TABLE y conversations carecía de manual_takeover/manual_takeover_at. Estado: migración 002 ya incluye web_leads + columnas + índices (fix en caliente), pero la ruta install.php sigue apuntando al SQL viejo → una instalación nueva quedaría rota.

Fix: que install.php ejecute init-full.sql + migraciones, y eliminar init.sql.

CRITICAL RESUELTO (parcial) Presupuesto de latencia: puente Meta de 8 s vs CRM que excedía ese presupuesto whatsapp-webhook.php:326 · WebhookHandler.php:323-331

El puente cURL espera 8 s; el CRM dormía hasta 4 s + 1 s/burbuja + cascada LLM de 4 modelos × 22 s timeout = respuestas perdidas (el dedupe casta el reintento a silencio). Estado: delay acotado a 1 s máximo (f5705f7) + fix del modelo Groq inválido (openai/gpt-oss-120b → llama-3.3-70b-versatile).

ABIERTO: timeout 8→15 s del puente, presupuesto duro de la cascada (8-10 s/ intento) y timeout en VectorService → Gap #7.

CRITICAL ABIERTO La suite 15/15 no cubre el bot — regresiones conocidas pasarían en verde tests/run_all_tests.php:66,90,248

Desglose real: php -l solo de la raíz (22 archivos; 36 de whatsapp-crm/ nunca se lintean), 3 tests tautológicos de dominios, 4 unitarias reales de Dropshipping, 2 checks de red. Cero imports de whatsapp-crm/src/*. Ningún test detecta el bug de precio $10.000 vs $12.000, el loop de correo, ni el detalle 404.

Fix: lint recursivo + paridad publicSellPrice + guard anti-correo + EmergencyBrake (ver roadmap).

HIGH ABIERTO Puente Meta sin validación de firma whatsapp-webhook.php (0 hash_hmac)

Cualquiera puede POSTear mensajes falsos al bot (o farmear tu token si va por query string). Nota: meta-webhook.php del CRM sí valida HMAC — el hueco está en el puente de WA.

HIGH ABIERTO PII y márgenes expuestos sin autenticación api.php:1084 · data/products.json (154 × buyPrice)

GET /api/telemetry devuelve teléfonos de referidos, audit logs e IPs/UA sin token; products.json público incluye buyPrice/margin → riesgo Ley 1581 + tus costos a la vista de la competencia.

HIGH ABIERTO Claves de WooCommerce embebidas en JS público middleware-frontend/assets/js/app.js:185

consumer_key=ck_…&consumer_secret=cs_… visibles para cualquier visitante → rotar claves y mover la llamada al servidor.

HIGH ABIERTO VectorService con curl sin timeout (=∞) whatsapp-crm/src/VectorService.php:19-42,79-114

0 ocurrencias de CURLOPT_TIMEOUT: un host de OpenAI/Pinecone lento congela el webhook indefinidamente (peor que toda la cascada). Fix: 3 líneas.

HIGH ABIERTO Dedupe casta a silencio el reintento que podría rescatar la respuesta WebhookHandler.php:180 · whatsapp-webhook.php:134

El wamid se persiste antes de generar la respuesta (y el puente marca el mensaje como visto antes de llamar al CRM): si el intento 1 se pasa de timeout, el intento 2 se descarta → silencio definitivo.

HIGH ABIERTO Polling del panel con N+1 cada 3 s ContactRepository.php:38-48 · index.php:683

1 JOIN + subconsulta correlacionada por contacto, sin LIMIT, cada 3 s: con 200 conversaciones ≈ 4.060 queries/min por panel abierto en MySQL compartido.

HIGH ABIERTO 210 enlaces de producto que recargan la landing completa amrstore.php:1096 · index.php:8-11

El grid genera href="/?id=…" pero ningún archivo procesa $_GET['id'] → cada «Ver producto» re-entrega 310 KB + ~95 imágenes en vez de la ficha. Conversión directa perdida.

HIGH ABIERTO CTAs públicos con contraste por debajo de WCAG (2.77:1 – 3.30:1) amrstore.php:576 · amrstore/producto.php:255

Botón primario «Ver productos» (blanco sobre #0ea5e9 = 2.77:1) y CTA «Pedir por WhatsApp» (3.30:1) — inlegibles para baja visión. Fix: gradiente desde #0369a1 (6.3:1) y fondo #15803d.

HIGH ABIERTO Inbox no operable por teclado + formularios sin labels index.php:638-641 · amrstore.php:1155-1157

Los contactos son <div onclick> sin tabindex/role/keydown (2.1.1 Keyboard) y los 3 inputs del formulario de referidos solo tienen placeholder, cero <label for> (3.3.2) → bloquea lectores de pantalla en el formulario de conversión.

HIGH ABIERTO Deploy de Makefile al destino equivocado y sin rollback Makefile:120,129

rsync … public_html/ con --delete apunta a la raíz, no a whatsapp-crm/main/ donde vive el CRM → puede crear un árbol muerto o borrar el productivo con su .env. Usar el flujo scp verificado que ya opera el equipo.

MEDIUM ABIERTO SPOF del canal de ingreso: app en un celular + token en query string CONTEXT-OPENCODE.md:143-148 · webhook.php:41

AutoResponder/WAHA sobre un número personal: batería/internet/baneo = 0 ventas. Existe meta-webhook.php (Cloud API) como canal alterno — activarlo es el failover barato.

MEDIUM ABIERTO get_chat muestra los 100 mensajes más antiguos MessageRepository.php:16-22 · get_chat.php:25

ORDER BY id ASC LIMIT 100: en chats >100 msgs el panel nunca muestra el final (incoherente con getRecentHistoryForAI, que usa DESC).

MEDIUM ABIERTO Monitoreo/backups con falsa seguridad scripts/monitor-setup.sh:25-29 · backup-hostinger.sh:12

El health check espera 401/503 (se obtiene aunque la BD/LLM estén caídas), los backups viven en el mismo servidor (sin offsite) y el restore nunca se probó. AdminNotifier::sendSlack falla en silencio si falta SLACK_WEBHOOK_URL.

MEDIUM ABIERTO Fórmula de precio +20% triplicada en 4 archivos CatalogService.php:254 · amrstore.php:1098 · producto.php:169 · productos-dropi.php:412

El bot ya usa CatalogService::publicSellPrice() (verificado: $10.000 → $12.000), pero la landing y otros scripts tienen copias propias → divergencia futura garantizada sin test de paridad.

MEDIUM RESUELTO Fuga de excepciones al cliente + modelo Groq inválido WebhookHandler.php:375 · AIService.php:316

Ambos corregidos en f5705f7: mensaje genérico al cliente y modelo llama-3.3-70b-versatile.

LOW ABIERTO Landing: render-blocking sin preconnect, imágenes S3 sin resize ni caché, 0 aria-hidden en iconos amrstore.php:469,1114,1128

75 imágenes S3 promedio 251 KB para cajas de 380 px, sin Cache-Control → LCP > 2.5 s en 4G; FontAwesome + Google Fonts bloquean pintura (~300-600 ms).

Fortalezas del Sistema (las 7 áreas coinciden)

Control de conversación muy por encima de un MVP: freno de emergencia anti-loop con takeover manual + alerta Slack, guard estructural que impide re-pedir datos ya capturados, historial corregido (últimos 20, no los primeros).
Precio unificado y verificado: publicSellPrice() con techo mil, reglas de discrepancia en el prompt y verificación en vivo ($10.000 → $12.000 en bot y landing).
Landing rápida de fábrica: sin frameworks, CSS ~17 KB, 310 KB → 19 KB gzip, render 133 ms, lazy-load en 94/95 imágenes, sin CLS.
Admin recién endurecido: admin-amrtech.php con rate limit + 2FA, webhook Meta con HMAC, src/ protegido, hash_equals para tokens.
Índices del hot path ya aplicados: messages.wamid, (direction, created_at), contacts.phone — dedupe y anti-loop no hacen full scan.
Semántica de landing correcta: lang, landmarks header/main/footer, jerarquía h1→h2→h3, alt descriptivos, rel="noopener", escaping con htmlspecialchars, targets ≥44 px.

Roadmap 2 Semanas — Pulir sobre la marcha y monetizar

Día 1-2 · Estabilizar: migración 002 + deploy de hoy en Hostinger, prueba manual del flujo completo (mensaje → cotización → cierre → cobro), corregir destino de Makefile.
Día 2-3 · Cobrar de verdad: links de pago Wompi o QR con monto exacto, frase de cierre con link, tabla orders, primer pago end-to-end medido.
Día 3-4 · Cablear dinero a Slack: notifyPaymentPending activo, prueba de que los webhooks (Ventas/Garantías/Stock) están vivos, falla ruidosa si falta config.
Día 4-6 · Observabilidad: health check que haga SELECT + 1 llamada LLM real, errores a Slack, dashboard de conversaciones/pedidos del día.
Día 6-8 · Escalabilidad: presupuesto duro 7-8 s en cascada LLM, timeout VectorService, dedup en AutoResponder, límite en webhook.php, polling del panel a 5-10 s.
Día 8-10 · Seguridad: login con hash + rate limit, firma Meta en el puente, token fuera de git/query, auth en /api/telemetry, buyPrice fuera del JSON público, rotar claves Woo.
Día 10-12 · Resiliencia: backup offsite + restore probado, runbook de rollback 5 pasos, CI real (lint recursivo que falle de verdad).
Día 13-14 · Entrega y medición: cola GoPlay activa, tests MVP (paridad de precio, guard anti-correo, EmergencyBrake, dedupe), primer reporte semanal conversión conversación→pago→entrega.

Metodología

7 agentes especializados ejecutados en paralelo (Seguridad, Calidad PHP, Base de Datos, Rendimiento, QA, Accesibilidad WCAG 2.2, Arquitectura & MVP), cada uno en modo solo-lectura sobre el repositorio, con salida estructurada obligatoria (veredicto GO/GO-CON-FIXES/NO-GO + hallazgos con archivo:línea + fortalezas + gap). Los 6 hallazgos CRITICAL más graves fueron re-verificados manualmente con grep/sed contra el código real por el orquestador (password, XSS, install sin auth, claves Woo, firma Meta, schema de conversations), y se detectó que el commit f5705f7 resolvía 6 blockers en caliente durante la auditoría — por lo que cada hallazgo lleva estado RESUELTO/ABIERTO según el código al momento de publicar.

Limitaciones declaradas: verificación estática (sin pentest dinámico, sin pen-testing de sesión) · los conteos de líneas corresponden al HEAD 8575fc3 · la suite de pruebas no se ejecutó como parte de esta auditoría (se ejecuta en el paso de deploy) · no se midieron Lighthouse reales en 4G (las cifras de rendimiento provienen del análisis de código y mediciones curl/CLI).