FOLIO: AUD-20261003-001 Secuencia: Auditoría #001 del día 2026-10-03
📅 2026-10-03 09:50:00 COT 👤 Auditor: Anderson Mares (AMR TECH) ● VIGENTE

AUDITORÍA INTEGRAL PRE-LANZAMIENTO

StreamingApps — Auditoría a fondo
QA · Seguridad · DBA · Deuda · Estándares · Patrones · UX/UI · A11y · Perf · DevOps

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).

7.6
/ 10 GLOBAL
APTO PARA LUNES*
*con 5 quick-wins

Veredicto ejecutivo

Apto con condiciones

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).

5 quick-wins antes del lunes

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 aceptable

Deuda técnica concentrada en 6 monolitos (1.4k–1.9k líneas) — no bloquea el lunes; planificar refactor por Módulo Post-Lanzamiento.

🔥 Incidencia del día — CRM WhatsApp no respondía

Reportada 2026-10-03 · resuelta y verificada E2E en el mismo día · commit 5472ac3

EtapaHallazgo
SíntomaTodo mensaje contestaba «En este momento no puedo procesar tu mensaje…» (fallback del catch genérico de WebhookHandler.php:546).
Causa raízDirectorio 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 1Ciega 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 2Setting 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ónE2E 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.

Scores por área

🧪 QA & Tests funcionales7.5
🛡️ Seguridad8.5
🗄️ DBA7.5
🧱 Deuda técnica6.5
📐 Estándares de desarrollo7.0
🧩 Patrones de diseño6.5
🎨 UX/UI8.0
♿ Accesibilidad (WCAG AA)7.0
⚡ Performance8.0
🚀 DevOps / Operación6.5

7.5 1 · QA y Tests

Suite propia PHP · 26 pruebas · ejecución en host

SevHallazgo — evidenciaFix
PASSSuite completa 26/26 (100%) — ejecutada hoy en host, JSON de resultados restaurado.—
PASSCobertura 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.—
HIGHSin 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).
MEDCasos 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.

8.5 2 · Seguridad (OWASP)

Headers, auth, inyección, uploads, rate-limit, secretos

SevHallazgo — evidenciaFix
OKHeaders 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.—
OKSQLi: 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.—
OKSecretos 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.—
OKapi-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)
OKCSP 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)
MEDWebhook 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.
LOWX-XSS-Protection: 1; mode=block — header obsoleto y contraproducente en navegadores modernos.Remover (CSP ya protege).

7.5 3 · DBA

SQLite (checkout) + MySQL 24 tablas (CRM) + backups

SevHallazgo — evidenciaFix
OKdata/checkout.sqlite: modo WAL activo; tablas orders + order_payments con UUID UNIQUE (autoindex) — lookups por uuid indexados.—
OKMySQL 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.—
OKBackups existentes: scripts/backup-hostinger.sh incluye mysqldump + pull de data/ en Makefile. Backup de settings: tabla ai_settings_bak_20260930.—
MEDIncidente 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).
LOWSQLite 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.

6.5 4 · Deuda Técnica

SevHallazgo — evidenciaFix
HIGH6 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).
MED28 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.
MEDHelper h() duplicado en 7 archivos (cada archivo PHP redeclara su propia función de escape).Mover a lib/helpers.php compartido con require.
LOWEcos 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.

7.0 5 · Estándares de Desarrollo (PSR-12 / PHP 8 / SOLID)

SevHallazgo — evidenciaFix
OKPrepared statements generalizados (157), JSON responses consistentes en APIs (ok/error), naming de acciones action=create|status|webhook uniforme entre api-checkout y api-wompi.—
MEDdeclare(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.
MEDDuplicació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.
LOWCSP/hardcoding de versiones de fuentes y estilos inline extensos en vistas (esperado en PHP vanilla sin build).Aceptar hasta post-lanzamiento.

6.5 6 · Patrones de Diseño

SevHallazgo — evidenciaFix
OKPatrones 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).—
HIGHCapa 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).
MEDConfig 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.
MEDDuplicació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.

8.0 7 · UX/UI

SevHallazgo — evidenciaFix
OKFlujo 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).—
OKE2E previo del repo: 12/12 flujos carrito/checkout; hoy verificación manual prod: landing/tienda/ficha/checkout/carrito todos 200 con payloads razonables.—
MEDComparativo 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.
LOWFicha de producto de prueba con IDs inexistentes devuelve 404 «crudo» (sin página 404 amigable de tienda).Vista 404 con «Volver a la tienda».

7.0 8 · Accesibilidad WCAG 2.2 AA

SevHallazgo — evidencia (HTML real de prod)Fix
OKBase 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 ✅.—
MED8 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).
MEDAutocomplete 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.
LOWBadges 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.

8.0 9 · Performance

Medido hoy contra producción (curl)

PÁGINA · status · peso · TTFB+total
/ (landing)…… 200 · 61 KB · 1.14s
/amrstore/ …… 200 · 312 KB · 0.89s
ficha …………… 200 · 14.5 KB · 0.47s
/checkout.php … 200 · 22 KB · 0.55s
/carrito.php … 200 · 14 KB · 0.47s
✅ Fortalezas: todos <1.2s, gzip (DEFLATE) activo, landing optimizada (61KB, antes ~312KB), fichas y checkout ligeros.
SevHallazgoFix
MEDHome 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).
LOWCSP sigue citando CDNs de script ya no usados — limpieza doble (seguridad + honestidad del CSP).Ver hallazgo Seguridad-4.

6.5 10 · DevOps / Operación

SevHallazgo — evidenciaFix
HIGHDeploy 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.
MEDContenedor 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).
MEDObservabilidad 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).
OKGit hygiene: dev→staging→main respetado, push dual GitHub+GitLab, tests antes de cada push, md5 de deploy verificado 7/7 hoy.—

Checklist obligatorio antes del lunes

Metodología y transparencia

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.

Auditor: Anderson Mares — AMR TECH
Folio AUD-20261003-001 · Informe #48 · 2026-10-03 09:50 COT
ESTADO: VIGENTE · APTO CON QUICK-WINS