Documento técnico

Manual Editor – Especificación completa

Proyecto: /manual_editor/ · Editor WYSIWYG con TinyMCE, galería de imágenes y actualización de cursos_sence.manual.

1) Resumen ejecutivo

Este documento define todo lo necesario para que un desarrollador implemente el módulo Manual Editor sin consultas adicionales, respetando el flujo de trabajo y estándares del equipo.

2) Decisiones confirmadas

Sobre el botón “Buscar en…” y el disco local del usuario: por seguridad del navegador no existe API web para “recorrer” el disco del usuario sin su acción explícita. La implementación correcta es abrir el selector de archivos del navegador (input type="file") que permite escoger archivos o una carpeta (atributo webkitdirectory / directory en navegadores compatibles). El sistema mostrará en la galería las imágenes elegidas por el usuario. Si se requiere recorrido automático del disco, debe desarrollarse una app nativa (p. ej. Electron/desktop) o un helper fuera del navegador.

3) Objetivos funcionales

  1. Cargar manual por $curso_id y mostrarlo en el editor.
  2. Listar imágenes desde $carpeta_img como miniaturas arrastrables.
  3. Permitir subir imágenes (validar, optimizar, guardar, exponer URL relativa).
  4. Insertar imágenes por drag&drop en el contenido.
  5. Actualizar el campo manual con el HTML resultante (texto + <img>).
  6. Crear registro en manual_historial previo a la actualización.
  7. Mostrar notificaciones y logs claros de cada acción.

4) Arquitectura de archivos

Todos los archivos residen bajo /manual_editor/:

RutaRolNotas
/manual_editor/manual_editor.phpVista principal (UI 2 columnas). Orquesta solicitudes AJAX y TinyMCE.Incluye configuración en duro y recursos (jQuery, Bootstrap, TinyMCE).
/manual_editor/api_get_manual.phpGET: devuelve HTML del campo manual por curso_id.Usa include de conexión BD.
/manual_editor/api_update_manual.phpPOST JSON: sanea y actualiza cursos_sence.manual. Inserta en manual_historial antes del UPDATE.Incluye protección CSRF.
/manual_editor/api_listar_imagenes.phpGET: lista de imágenes bajo $carpeta_img.Incluye metadatos (ancho, alto, peso).
/manual_editor/api_subir_imagen.phpPOST multipart/form-data: valida, optimiza y guarda imagen.Devuelve ruta relativa.
/manual_editor/helpers_imagenes.phpFunciones de validación, normalización y optimización (Imagick → GD).Control de límites y nombres de archivo.
/manual_editor/helpers_html.phpSaneado del HTML con whitelist de etiquetas/atributos y normalización de URLs relativas.Bloquea scripts/estilos en línea y eventos.
/manual_editor/README_NOTAS.txtNotas operativas y troubleshooting.Opcional.
/manual_editor/tinymce/…Distribución local de TinyMCE (como en la captura).Se referencia /manual_editor/tinymce/js/tinymce/tinymce.min.js.
/manual_editor/img/Carpeta de imágenes del módulo.Se puede subdividir por curso en el futuro.

5) Configuración (en duro, reutilizable)

Las siguientes variables se definirán al inicio de manual_editor.php y se reutilizarán (vía include) en los endpoints:

VariableValor por defectoDescripción
$carpeta_img"/manual_editor/img/"Ruta relativa pública donde se listan/guardan imágenes.
$permitidos_ext['jpg','jpeg','png','gif']Extensiones permitidas.
$limite_peso_mb4Tamaño máximo de subida (MB) antes de optimización/reeescala.
$max_dim_px2560Dimensión máxima por lado. Si se excede, se reescala.
$usar_imagicktrue si disponibleFallback automático a GD si Imagick no está.
$csrf_activotrueProtege los POST con token.

Todos los endpoints deben include el mismo archivo de configuración para mantener consistencia.

6) UI / UX

Columna izquierda – Galería

  • Encabezado con ruta activa ($carpeta_img) y conteo de imágenes.
  • Grid de miniaturas con “drag handle” para arrastrar hacia el editor.
  • Botón Subir imagen (abre input file); valida y envía a api_subir_imagen.php.
  • Botón Buscar en…:
    • Abre el selector de archivos del navegador (permite elegir archivos o carpeta completa con webkitdirectory).
    • Las imágenes seleccionadas se previsualizan en galería local y pueden subirse individualmente o por lotes (enviando cada una).
  • Filtro por nombre (simple, lado cliente) y paginación si hay muchas imágenes.

Columna derecha – Editor

  • Instancia de TinyMCE con plugins: link, lists, image, code, paste.
  • Barra de herramientas mínima: formato (negrita/itálica), listas, encabezados, enlace, imagen, código.
  • Drag&Drop: al soltar una miniatura dentro del editor, insertar <img src="RUTA_REL" alt="" /> en el caret.
  • Previsualización inmediata: el editor es WYSIWYG (lo que se ve es lo que se guarda).
  • Botón Actualizar:
    • Obtiene HTML del editor → saneado servidor → inserta en manual_historial → actualiza cursos_sence.manual.
    • Feedback con toast/alert y logs de consola.

7) Arrastrar y soltar (comportamiento)

  1. La miniatura establece en dataTransfer una carga con la ruta relativa (p. ej. /manual_editor/img/curso_123_1717400000.jpg).
  2. El área del editor acepta el drop y usa la API de TinyMCE para insertar un nodo <img> en la posición del cursor.
  3. Antes de insertar, se valida que la ruta:
    • No contenga ../ (path traversal).
    • Empiece por $carpeta_img.
    • Termine en una extensión permitida.

8) API – Contratos

8.1. GET /manual_editor/api_get_manual.php?curso_id={ID}

DescripciónDevuelve el HTML actual del campo manual de cursos_sence.
Parámetroscurso_id (entero, requerido)
Respuesta 200
{
  "ok": true,
  "curso_id": 123,
  "html": "<p>...</p>"
}
Errores
{
  "ok": false,
  "error_code": "NOT_FOUND" | "INVALID_ID" | "DB_ERROR",
  "message": "..."
}

8.2. GET /manual_editor/api_listar_imagenes.php

DescripciónLista imágenes disponibles bajo $carpeta_img.
ParámetrosOpcionales: page (entero ≥1), q (string para filtrar por nombre)
Respuesta 200
{
  "ok": true,
  "base": "/manual_editor/img/",
  "page": 1,
  "total": 42,
  "imagenes": [
    {"nombre":"curso_123_1717400000.jpg","ruta_rel":"/manual_editor/img/curso_123_1717400000.jpg","ancho":1200,"alto":800,"peso":340212},
    ...
  ]
}

8.3. POST /manual_editor/api_subir_imagen.php

DescripciónSube una imagen, valida, optimiza y la guarda bajo $carpeta_img.
HeadersContent-Type: multipart/form-data
Camposarchivo (file, requerido), curso_id (entero, opcional para nombre), csrf_token (string, requerido si CSRF activo)
Respuesta 200
{
  "ok": true,
  "ruta_rel": "/manual_editor/img/curso_123_1717400000.jpg",
  "width": 1200, "height": 800, "size": 240132
}
Errores
{
  "ok": false,
  "error_code": "NO_FILE" | "EXT_NOT_ALLOWED" | "MIME_NOT_ALLOWED" | "TOO_LARGE" | "OPTIMIZE_FAIL" | "SAVE_FAIL" | "CSRF_FAIL",
  "message": "..."
}

8.4. POST /manual_editor/api_update_manual.php

DescripciónSanea HTML, inserta en manual_historial y actualiza el campo manual.
HeadersContent-Type: application/json
Body
{
  "curso_id": 123,
  "html": "<p>...<img src="/manual_editor/img/....jpg" alt="">...</p>",
  "csrf_token": "..."
}
Respuesta 200
{
  "ok": true,
  "updated": true,
  "historial_id": 9876
}
Errores
{
  "ok": false,
  "error_code": "INVALID_ID" | "EMPTY_HTML" | "SANITIZE_FAIL" | "DB_ERROR" | "CSRF_FAIL",
  "message": "..."
}

9) Base de datos

9.1. Tabla existente

9.2. Tabla de historial (nueva)

Definición funcional (sin entregar DDL):

CampoTipo sugeridoDescripción
idINT AUTO_INCREMENTPK
curso_idINTReferencia a cursos_sence.id
htmlLONGTEXTSnapshot previo al UPDATE
creado_enDATETIMEFecha/hora del registro
ip_origenVARCHAR(45)IPv4/IPv6 del cliente
user_agentVARCHAR(255)Navegador/OS

10) TinyMCE

Se utilizará la distribución local provista (ver captura): /manual_editor/tinymce/js/tinymce/tinymce.min.js. Alternativamente puede emplearse CDN oficial si se desea.

11) Seguridad y saneado

11.1. Saneado HTML (servidor)

11.2. Subida de imágenes

11.3. CSRF

12) Monitoreo y logging

12.1. Cliente (consola)

Todos los eventos relevantes deben registrarse con prefijo MANUAL_EDITOR:

EventoPayload
LOAD_OK{curso_id, bytes_html}
LOAD_FAIL{error_code, message}
IMAGES_LIST_OK{count, page}
UPLOAD_OK{ruta_rel, size, width, height}
UPLOAD_FAIL{error_code, message}
DROP_OK{ruta_rel}
DROP_BLOCKED{reason}
UPDATE_OK{historial_id, bytes_html}
UPDATE_FAIL{error_code, message}

12.2. Servidor

13) Catálogo de errores (códigos estándar)

CódigoDescripciónEndpoint
INVALID_IDID ausente o no numéricotodos
NOT_FOUNDNo existe el curso o no tiene manualget_manual
DB_ERRORError de base de datostodos
NO_FILENo se recibió archivosubir_imagen
EXT_NOT_ALLOWEDExtensión prohibidasubir_imagen
MIME_NOT_ALLOWEDMIME real no es imagen válidasubir_imagen
TOO_LARGESupera límite y no posible optimizarsubir_imagen
OPTIMIZE_FAILFallo al optimizarsubir_imagen
SAVE_FAILFallo al guardar archivosubir_imagen
EMPTY_HTMLContenido vacío o solo espaciosupdate_manual
SANITIZE_FAILEl saneado detectó contenido no permitidoupdate_manual
CSRF_FAILToken inválido o ausentesubir_imagen, update_manual

14) Rendimiento

15) Cabeceras y endurecimiento

16) Recursos obligatorios (front)

17) Plan de pruebas

  1. Carga inicial: con curso_id válido/ inválido; manual vacío / con contenido pesado.
  2. Listado imágenes: sin archivos / con 1000+ archivos (paginación); filtro por nombre.
  3. Subida:
    • Archivos válidos en cada formato permitido.
    • Archivo > 4 MB y > 2560 px (verificar optimización).
    • MIME falso / extensión cambiada.
  4. Drag&Drop: insertar 1, 10 y 100 imágenes; verificar rutas relativas y saneado.
  5. Actualización: guardar cambios, confirmar inserción en manual_historial y UPDATE en cursos_sence.
  6. CSRF: token ausente/incorrecto → rechazar.
  7. Accesibilidad: foco, teclas de navegación, textos alternativos en <img>.

18) Criterios de aceptación

19) Plan de implementación por etapas (sin escribir código aún)

  1. Esqueleto: crear estructura de archivos y bloque de configuración único.
  2. UI base: layout Bootstrap 2 columnas, carga de TinyMCE y botones.
  3. GET manual: endpoint + render en editor + logs LOAD_OK/FAIL.
  4. Galería: endpoint listar + grid + dragstart.
  5. Drop en TinyMCE: insertar <img> seguro (validaciones ruta).
  6. Subida: endpoint multipart + validaciones + optimización + refresco galería.
  7. Update: saneado server, historial, UPDATE BD + toasts + logs.
  8. CSRF: integrar en subir y update.
  9. Pulido: paginación, filtro, lazy images, mensajes UX.
  10. QA: ejecutar plan de pruebas y checklist de aceptación.

20) Estándares y encabezados obligatorios de código

Cuando autorices la codificación, todos los archivos PHP del módulo iniciarán con el bloque de depuración y el include a la conexión que definiste:

Este documento no incluye código del módulo (PHP/JS/SQL). Al recibir tu autorización explícita, se implementará en trozos por archivos y endpoints, siguiendo exactamente esta especificación.

21) Anexos

A) Glosario rápido

B) Compatibilidad del botón “Buscar…”

Para seleccionar carpetas completas, usar el atributo webkitdirectory (Chrome/Edge/Opera/Brave). En Firefox no está soportado: usar selección múltiple de archivos. En todos los casos, la lectura del disco solo ocurre después de que el usuario selecciona explícitamente la carpeta/archivos.