=== OPNV Doctor ===
Contributors: notoriusvision
Donate link: https://opnv.systems/
Tags: performance, health, css, javascript, governance, opnv
Requires at least: 6.0
Tested up to: 6.8
Requires PHP: 8.3
Stable tag: 4.9.42
License: Proprietary
License URI: https://opnv.systems/

Motor de gobernanza operativa para WordPress: salud, recursos CSS/JS, Style Center, puertas de publicación y puente opcional con OPNV WP.

== Descripción ==

OPNV Doctor es un plugin independiente de la flota WordPress.

Distribuido bajo las marcas **Notorius Vision** y **OPNV Systems** (sin personalidad jurídica).
Titular y propietaria del programa: **Soledad Sánchez Mohamed**.

* Detecta → Diagnostica → Prioriza → Repara → Verifica → Aprende
* Kill-switch de CSS/JS no usados (con handles protegidos)
* Style Center (tipografía, menú, cabecera)
* Publish Gates (seguridad antes de publicar cambios)
* Bridge opcional con OPNV WP (sin require_once cruzado)
* Especialistas: cron, runtime, artifact, WAF, SEO, seguridad, traducción, etc.

Web: https://opnv.systems/
Soporte: sos@opnv.systems
Agencia: https://notoriusvision.com/
Legal: `eula.txt` · `license.txt` (ES) · `eula-en.txt` · `license-en.txt` (EN)
Historial canónico: `CHANGELOG.md`

= Autoría y titularidad =

* Titular / propietaria: Soledad Sánchez Mohamed
* Marcas: Notorius Vision · OPNV Systems (titular comercial: Soledad Sánchez Mohamed)

== Instalación ==

1. Sube la carpeta `opnv-doctor` a `/wp-content/plugins/`
2. Activa el plugin desde «Plugins»
3. Abre **OPNV Doctor** en el menú lateral
4. Revisa Problemas y Puertas de publicación antes de desactivar assets

== Preguntas frecuentes ==

= ¿Depende de OPNV WP? =

No. Es independiente. Si OPNV WP está presente, se conecta vía hooks y opciones compartidas.

= ¿Quién es la titular? =

Soledad Sánchez Mohamed. Notorius Vision / OPNV Systems son solo marcas de distribución.

= ¿Puedo desactivar CSS de WordPress core? =

No. Los handles de núcleo y de OPNV están protegidos.

== Changelog ==

= 4.9.42 =
**Queue Doctor y Runtime Activity — visión de la cola Edge (Artifact/route). Scope: OPNV-DOCTOR edge observability.**

* El especialista **Cola / Worker** ya no es ciego a la cola Edge: además de `opnv_jobs` (Core) observa `opnv_edge_queue` (rutas `route_rebuild`). Antes veía "Core vacía = sano" mientras la cola Edge acumulaba cientos de jobs.
* Nueva señal **`edge_queue_undrainable` (FAIL)**: cola Edge con pendientes **+ Edge Runtime desactivado** → los jobs **no pueden drenar** (`processQueue()` retorna temprano). Es la combinación real que dejó 362 jobs atascados sin aviso.
* Nueva señal **`edge_queue_stalled` (FAIL)**: cola Edge con pendientes y trabajo más antiguo > **6 h** (antes el umbral era 24 h y solo daba WARN).
* **Runtime Activity** cruza la Señal A (Edge ON/OFF) con la profundidad de la cola Edge: `edge_queue_undrainable` suma 30 al `frozen_score`, `queue_stuck` (6 h) +25 y `queue_pending` +10, de modo que el veredicto deja de quedar en `ACTIVE`.
* Sigue siendo **solo lectura**: Doctor observa, el overlay repara la cola.
* Arnés: `docs/governance/releases/opnv-doctor-4.9.42/harness.php` → **18/18 PASS**.

= 4.9.41 =
**Especialista CSS & Estilos del tema — Fase 3 (consolidación asistida y reversible). Scope: CSS-RECOVERY.**

* El especialista `css_recovery` ya **consolida** las variables CSS duplicadas de cada fichero.
* **Siempre crea un punto de recuperación** (tipo `css_slice`) **antes** de escribir: la operación es 100% reversible desde el almacén compartido.
* Consolidador puro y determinista (`OPNV_Doctor_Css_Consolidator`): dentro de cada bloque colapsa cada custom property `--*` a su valor efectivo de cascada (la última declaración). **Nunca toca declaraciones no personalizadas** ni reordena nada más.
* Nueva acción `consolidate_css` y señal `css_consolidable` (con acción) para consolidar en un clic desde el panel.
* **Confinamiento de rutas:** solo se escriben ficheros dentro de las raíces de tema y de `wp-content` (sin traversal).
* Arnés: sección Fase 3 (consolidador · antes/después · idempotencia · reversibilidad · confinamiento). Total **138/138 PASS**.

= 4.9.40 =
**Especialista CSS & Estilos del tema — Fase 2 (punto de recuperación). Scope: CSS-RECOVERY.**

* El especialista `css_recovery` ahora puede **crear un punto de recuperación CSS** antes de consolidar (nuevo tipo `css_slice` en el almacén compartido).
* El punto guarda las hojas reales (`style.css` del tema + `@import` locales + tokens locales) y el CSS personalizado nativo, para poder **deshacer** la consolidación.
* **Confinamiento de rutas:** solo se capturan/restauran ficheros dentro de las raíces de tema y de `wp-content` (sin traversal).
* Nueva señal `css_no_recovery_point` (INFO) con acción `create_recovery_point`; `fixable` pasa a `true`.
* **Todavía NO consolida CSS** (la consolidación reversible llega en la Fase 3).
* Arnés: sección Fase 2 (crear · restaurar · confinamiento · seguridad). Total **111/111 PASS**.

= 4.9.39 =
**Especialista CSS & Estilos del tema — Fase 1 (solo lectura). Scope: CSS-RECOVERY.**

* Nuevo especialista `css_recovery` (grupo Calidad del sitio): **inventaría** el CSS real de la web sin modificar nada.
* Fuentes que inspecciona: `style.css` del tema + `@import`, CSS personalizado nativo, CSS personalizado inline, tokens locales.
* Detecta: **orígenes múltiples**, variables **duplicadas**, variables **en conflicto**, `!important` intensivo, **hojas huérfanas** en uploads y **deriva** frente al conjunto canónico (filtro `opnv_doctor_css_canonical_tokens`).
* **Solo lectura:** no escribe ficheros ni crea puntos de recuperación (eso llega en la Fase 3, reversible).
* No es Penpot ni depende de ningún MCP. Peso de puntuación 0 (no altera el score de flota).
* Arnés: casos CSS-RECOVERY. Total **97/97 PASS**.

= 4.9.38 =
**Ayuda clara en Design Infrastructure. Scope: DOC-UX-001.**

* Nuevo panel «¿Qué es esto y cómo funciona? (explicado fácil)» antes de los ajustes (analogía casa = web · catálogo = Penpot · revisor = Doctor).
* Cada campo lleva número, ejemplo y ayuda llana: qué es, qué escribir y dónde encontrarlo.
* Se aclara que **Workspace** y **Proyecto** son solo rótulos (no se envían a Penpot) y que el MCP de Penpot Cloud no habla los métodos de Doctor.
* Solo UI/ayuda: sin cambios de lógica, esquema ni cron.

= 4.9.37 =
**Saneado de artefactos de diseño legados (no arrastrar el error). Scope: DESIGN-003.**

* **DESIGN-003 (P1 · residuo envenenado):** un `tokens.json` escrito por una versión anterior (< 4.9.35) con un envelope de error MCP (`-32000`) seguía leyéndose como árbol de tokens en todos los consumidores, arrastrando el error aunque el código ya estuviera corregido.
  - `Design_Token_Reader::read()` rechaza ahora, en el punto único de lectura, cualquier payload contaminado (`is_contaminated()`); el error nunca se propaga aunque el fichero siga en disco. Nuevo `is_file_contaminated()`.
  - **Autolimpieza al actualizar** (`opnv_doctor_maybe_migrate`, una vez por versión): elimina el `tokens.json` legado envenenado y sus CSS/Tailwind derivados (`cleanup_if_contaminated()` / `purge_artifacts()`). Nunca toca un snapshot sano.
  - **Acción manual** «Limpiar artefactos de diseño» en el panel Diseño, con aviso explícito `tokens_contaminated`.
  - El informe expone `tokens_contaminated`; el especialista emite FAIL con fix `cleanup_artifacts`.

= 4.9.36 =
**Endurecimiento de auditoría de diseño (reachability vs protocolo MCP). Scope: RED-001.**

* **RED-001 (P1 · falso «ONLINE» + payload envenenado):** las sondas de salud trataban cualquier HTTP `>=400 <500` como salud positiva y un payload `jsonrpc/error/ok` podía contarse como tokens. Observado en producción (`sant-francesc.eu`): `tokens.json` con un envelope `-32000` y el panel mostrando «MCP/Penpot ONLINE» con 0 tokens.
  - `reachable()` pasa a ser **solo transporte** (`2xx–4xx`, excluye `404/410`): prueba que el endpoint existe, **no** que sea compatible.
  - Nuevo `protocol_status()`: sondea el handshake MCP (`initialize`) y expone `serverInfo`; un endpoint que responde con error ⇒ `initialized=false`.
  - Nuevo `looks_like_mcp_meta()`: un envelope `jsonrpc`/`error`/`ok` **jamás** se cuenta como árbol de tokens.
  - `penpot_reachable()` exige `2xx–3xx` (un `400` del stream MCP ya no es «Penpot ONLINE»).
  - El informe incluye `mcp_protocol` y el aviso `UNCERTIFIED` distingue «responde pero no es un MCP compatible con Doctor».
  - La reachability de transporte **nunca** certifica conformidad de diseño (DESIGN-001 intacto).
  - Arnés de laboratorio: **53/53 → 64/64**.

= 4.9.35 =
**Hotfix de certificación — Design System (MCP) y verificación de `.user.ini`. Scope estricto: DESIGN-001, DESIGN-002, COMP-001.**

* **DESIGN-001 (P0 · falso positivo crítico):** la auditoría Penpot/MCP podía certificar «Visual Compliance 100% SANO» cuando MCP respondía con un error JSON-RPC (`-32000 Not Acceptable`). El error se contaba como «tokens» y, con 0 deriva, producía conformidad positiva.
  - El cliente MCP envía `Accept: application/json, text/event-stream` y decodifica respuestas JSON **o** SSE (`data:`).
  - Precedencia de error: un envelope `error` nunca se cuenta, persiste ni certifica. Sin payload válido se reutiliza la última copia buena (`stale_cache`) o se falla en cerrado — jamás se sobrescribe un `tokens.json` válido con basura.
  - Nuevo estado **`UNCERTIFIED` / «SIN CERTIFICAR»**: cuando MCP falla o no hay tokens, Doctor deja de emitir SANO/100% y lo declara explícitamente.
* **DESIGN-002 (P0 · seguridad):** la URL MCP con `userToken` se mostraba en claro y se guardaba en `wp_options` y en los informes.
  - El campo pasa a `type="password"` con máscara (`********abcd`); guardar el valor enmascarado o vacío **conserva** el secreto (round-trip seguro).
  - `mcp_url`/`penpot_url`/`project_url` y los mensajes de error se **redactan** antes de persistir, renderizar o exportar.
* **COMP-001 (P1 · verificación diferida):** escribir `.user.ini` reportaba éxito sin comprobar el valor efectivo (`.user.ini` aplica en la petición siguiente).
  - Nuevo flujo **WRITE → VERIFY → REPORT** con estados `PATCH_WRITTEN` / `APPLIED` / `NOT_APPLIED` / `UNKNOWN`; verificación diferida en la siguiente petición admin y botón «Comprobar .user.ini» que muestra el `zlib.output_compression` efectivo.

= 4.9.34 =
* Nuevo especialista «Cola / Worker» (Salud núcleo), observador de solo lectura de la cola persistente del overlay (`opnv_jobs`): pendientes, en ejecución, cuarentena (poison), antigüedad del pendiente más viejo y latido del worker.
* Ruta de datos por capas: seam `opnv_doctor_queue_report` → fallback de solo lectura a `opnv_jobs` / `opnv_job_poison` → estado `unknown` explícito. Nunca se asume cola vacía.
* Solo lectura por diseño (`fixable => false`): `diagnose()` / `simulate()` informan; `fix()` no repara. Sin reencolado ni limpieza de zombies desde Doctor: la reparación corresponde al overlay (Overlay = owner, Doctor = observador).
* Si el heartbeat de Cron está frío, el latido del worker baja a INFO y la tarjeta Cron enlaza a esta cuando el reloj va bien.

= 4.9.33 =
* Huérfanas / sin visitas: el especialista deja de presentar un recuento truncado como si fuera el total del sitio. Si el proveedor del seam declara `scanned_all = false`, el mensaje dice cuántas publicaciones se analizaron de cuántas hay y avisa de que el informe es PARCIAL; el dato viaja en `data.scanned_all`.
* El `limit` que Doctor pide al proveedor (300) acota solo la MUESTRA de `items`; los recuentos que difunde un proveedor deben ser siempre sus totales. Documentado en el contrato del seam.
* Escaneo de respaldo propio (sin OPNV WP): recorre todo el contenido publicado en vez de los primeros 300, con techo de seguridad y muestra de 20 IDs; añade `total_scanned` / `total_published` / `scanned_all`.
* Corrección: la insignia de origen solo aparece en el especialista que declara consumir un seam de informe. Otros especialistas usan `data.source` con otro significado (p. ej. «OPNV_VERSION») y se etiquetaban por error como «Informe vía OPNV_VERSION».

= 4.9.31 =
* Huérfanas / sin visitas: trazabilidad del origen del informe. El especialista captura y expone el campo `source` que declare el proveedor del seam `opnv_doctor_orphan_report` (`opnv-wp` cuando lo sirve OPNV WP); si no hay proveedor, cae a `doctor-native`. Sin `source`, Doctor no presume nada y no se atribuye el dato.
* Panel de especialistas: la tarjeta muestra ahora una insignia de origen («Informe vía OPNV WP» / «Escaneo propio de Doctor»).
* Compatibilidad de plataforma: nuevo ítem en el explorador de contratos y en el checklist de auditoría — «Proveedor de informe de huérfanas» (`has_filter('opnv_doctor_orphan_report')`), que hace el acoplamiento auto-verificable.
* Sin cambio de comportamiento del diagnóstico: mismo informe, misma severidad; solo se añade el origen.

= 4.9.30 =
* Nuevos especialistas registrados: Performance Specialist (performance_psi) e Images Specialist (images). Performance Specialist lee señales PSI/CrUX medidas que entrega el Hub (las credenciales PSI son solo del Hub); Images Specialist prepara propuestas image_replacement, una por imagen, con evidencia de bytes y estimaciones marcadas como tal. Ninguno altera la puntuación de flota.

= 4.9.29 =
* Especialista Huérfanas / sin visitas: eliminada la dependencia de una clase de OPNV WP inexistente (OrphanPostsScanner). Ahora el reporte canónico se inyecta mediante el filter `opnv_doctor_orphan_report` (integration seam hooks/options); si ningún proveedor responde un reporte válido, Doctor escanea por sí mismo en solo lectura. Sin cambio de comportamiento por defecto.

= 4.9.28 =
* Especialista Content: la limpieza de markup scrapeado de Google (data-hveid / data-ved / kno-rdesc) se delega a OPNV WP cuando está activo y expone GoogleMarkupCleaner. Doctor lo detecta y avisa, pero no duplica la reparación; sin OPNV WP sigue limpiando como antes.

= 4.9.26 =
* Dashboard principal: corregido el clamp de ancho (el margen estrecho y centrado se anulaba por una regla posterior). Vuelve a centrarse y estrecharse.

= 4.9.25 =
* Especialistas «Salud núcleo»: layout híbrido a 2 columnas. Las tarjetas con hallazgos (y Base de datos · Cron) pasan a ancho completo para que las tablas de diagnóstico no se compriman; las tarjetas sin hallazgos se emparejan de 2 en 2.
* Encabezados de grupo (Salud núcleo / Calidad del sitio / Análisis profundo) con barra de acento y descripción alineada.

= 4.9.24 =
* Especialistas: Seguridad sube a la primera fila; Base de datos y Cron a doble ancho.
* Cron manual centrado (max-width 920px) y contención de desbordes.
* Dashboard principal ~20% más estrecho y centrado.

= 4.9.22 =
* Autoría unificada: el encabezado del plugin e info.txt muestran «Notorius Vision / OPNV Systems» (la titularidad legal del código no cambia)
* i18n pot/po/mo sincronizados con la nueva autoría (incluye variantes traducidas)

= 4.9.21 =
* Content Doctor: detecta y limpia markup scrapeado de Google (data-hveid / data-ved / kno-rdesc) de forma reversible
* Nuevo punto de recuperación `content_slice` (create + restore)
* El chequeo de markup ya no depende del sondeo de portada

= 4.9.19 =
* Gates: logo NV más grande

= 4.9.18 =
* Repair stay-on-page + banner / «Ver resumen»; copy servidor genérico (Pila Hostinger OK intacta)
* Hero 2 columnas; nav spacing; i18n pot/po/mo regenerados

= 4.9.17 =
* Recomendaciones: size/fonts más claras; duplicados → Reparar/Recursos

= 4.9.16 =
* Panel informativo Bug Reporter (documentar ≠ detectar)

= 4.9.15 =
* FAB Bug Reporter más alto (no solapa cookies)

= 4.9.14 =
* Fix layout admin Doctor (no reset #wpcontent) + Bug Reporter barra admin / FAB visible

= 4.9.13 =
* Bug Reporter MVP: FAB en front, informes locales (URL + pasos + captura + entorno), inbox en Doctor

= 4.9.11 =
* Titularidad: Soledad Sánchez Mohamed; marcas Notorius Vision / OPNV Systems
* EULA / license ES+EN v1.2; Author header e info.txt alineados
* CHANGELOG.md canónico; readme deja de estar congelado en 4.8.3
* UI Info + badges de versión dinámicos

= 4.9.10 =
* Cron: hooks Roma/Stella/Agency opcionales en FREE/PRO (críticos solo Agency+)
* Caché Hostinger LiteSpeed+HCDN: advisory OK, sin penalizar
* Compresión: no penaliza dual stack Hostinger / edge esperado
* Conflict Engine: `hostinger_cache_ok` como recommend_only

= 4.9.9 =
* Cron de sistema genérico (Hostinger/cPanel/Plesk/generic) + detect_php_binary
* MANUAL_SYSTEM_CRON.md; UI «Cron de sistema (manual)»
* hostinger_guide deprecado → system_cron_guide

= 4.9.8 =
* Guía cron Hostinger + receta + último hit en Especialistas / Actividad runtime
* Truth strip enlaza a la guía; no reescribe DISABLE_WP_CRON en wp-config

= 4.9.7 =
* Cron: cura operativa (drain + Edge); nunca edita wp-config
* enable_wp_cron → alias run_due_now
* Safe Mode UI = warning (no crítico falso); strip salud visual

= 4.9.6 =
* Bridge wp_edge en publish_status (DISABLE_WP_CRON, remediación, opnv_version)
* Eventos opnv_doctor_remediation_applied refrescan status Doctor

= 4.9.5 =
* RECiCLA + UI ES; pantalla Ignorados; plataforma ES
* Fix sanitize_settings (ignored_findings / disable_xmlrpc)
* Seguridad flota: ignore license.txt / wp-config-sample; XML-RPC off

= 4.9.3–4.9.4 =
* Serie RECiCLA ES / Ignorados / i18n (detalle patch [ver CHANGELOG.md])

= 4.8.3 =
* Signal A (Runtime OFF) vs Signal B (DoctorArtifactBridge missing)
* Runtime Activity Audit · Diagnostic Confidence
* Ver CHANGELOG.md / historial 4.8.x–4.5.0

== Upgrade Notice ==

= 4.9.11 =
Legal ownership + changelog catch-up. Empaquetar y desplegar cuando el changelog esté revisado.
