Propuesta integral – Dashboard de publicaciones por curso

Documento funcional para implementar un dashboard que, dado ?curso_id=###, genera vistas previas por red social, permite compartir/publicar mediante servicios externos por red y registra histórico. Todo lo “social” vive dentro de /social/. La raíz solo contiene el dashboard principal.

Objetivo

Centralizar la operación de difusión de cursos: vista previa realista, generación de URLs con UTM, publicación directa (cuando la red lo permite) vía servicios externos y auditoría mediante histórico.

Entrada: ?curso_id OG/Twitter listos UTM por red/medio Servicios externos por red Histórico auditable

Arquitectura & estructura de carpetas

Árbol de proyecto

/                              ← raíz del sitio
└─ dashboard_publicaciones.php ← Único archivo en raíz
└─ /social/                    ← Todo lo del proyecto vive aquí
   ├─ /config/                 ← variables y credenciales (protegido)
   │   ├─ env.example
   │   └─ env.local            ← valores reales (producción)
   ├─ /storage/                ← archivos de soporte (protegido)
   │   ├─ /logs/               ← trazas, auditoría simple (txt/json)
   │   └─ /tmp/                ← temporales / caché de imágenes
   ├─ /shared/                 ← utilidades comunes (helpers)
   ├─ /fb/                     ← servicio Facebook Pages
   ├─ /ig/                     ← servicio Instagram Business
   ├─ /li/                     ← servicio LinkedIn Pages
   ├─ /x/                      ← servicio X (Twitter)
   ├─ /tg/                     ← servicio Telegram
   ├─ /slack/                  ← opcional
   └─ /discord/                ← opcional
        

El dashboard, al iniciar, verifica y crea (si faltan) estas carpetas dentro de /social/ y valida permisos de escritura en /social/storage/.

Flujo funcional (alto nivel)

1) Ingreso

  • Usuario entra a /dashboard_publicaciones.php?curso_id=44.
  • El dashboard valida curso_id, crea estructura de /social/ si falta y consulta el curso.
  • Construye: título, descripción “limpia” ≤ 250 + sufijo, imagen OG, URL canónica.

2) Previews & acciones

  • Muestra tarjetas (Facebook, LinkedIn, X, WhatsApp, Telegram, Instagram Bio/Story, Paid).
  • Acciones por tarjeta: Copiar URL (con UTM), Copiar caption, Depuradores, Publicar.
  • “Publicar” llama via HTTP a su servicio en /social/{red}/publish.

3) Respuesta & histórico

  • Si la red responde OK, muestra permalink/ID y guarda fila en histórico.
  • Si falla, muestra el error y guarda auditoría.
  • Tabla inferior: histórico filtrado por curso_id.

4) Supervisión

  • Estados: publicado, programado, borrador.
  • Permalinks clicables para ver el post real.
  • Descarga CSV simple del histórico (opcional).

Contratos de servicios externos por red

Formato común (HTTP POST JSON)

CampoTipoDescripción
curso_idnumberID del curso.
networkstringfacebook | instagram | linkedin | twitter | telegram | slack | discord
mediumstringorganic | story_sticker | bio | dm | paid_social | …
campaignstringp.ej. curso_44_lanzamiento_2025w40
utm_contentstring?p.ej. sticker_v1, profile_link
link_urlstringURL canónica + UTM.
captionstringTexto ya limpio y ≤ 250 con sufijo.
image_urlstring?Imagen OG o creativa (según red).
requested_bystringUsuario operador.

Cada servicio gestiona sus tokens/IDs y devuelve un JSON con ok, permalink, external_post_id y message.

Facebook Pages /social/fb/publish

  • Publicación recomendada: link post.
  • Credenciales en /social/config.
  • Devuelve: permalink_url, post_id.

Instagram Business /social/ig/publish

  • Feed (imagen + caption). Stories: acción manual (sticker).
  • Devuelve: id y permalink.

LinkedIn Pages /social/li/publish

  • Publicación con enlace (preview OG).
  • Devuelve: URN / activity y permalink.

X (Twitter) /social/x/publish

  • Tuit con caption + link_url.
  • Devuelve: tweet_id y URL del tuit.

Telegram /social/tg/publish

  • Mensaje a canal: imagen opcional + texto + link.
  • Devuelve: message_id y enlace del canal (si público).

Modelo de datos

Tablas nuevas

1) Histórico de publicaciones

CREATE TABLE IF NOT EXISTS social_posts_log (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  curso_id BIGINT NOT NULL,
  network VARCHAR(32) NOT NULL,         -- facebook | instagram | linkedin | twitter | telegram | slack | discord | ...
  medium VARCHAR(32) NOT NULL,          -- organic | story_sticker | bio | dm | paid_social | ...
  campaign VARCHAR(128) NOT NULL,       -- ej: curso_44_lanzamiento_2025w40
  content_tag VARCHAR(64) NULL,         -- ej: sticker_v1, profile_link, feed_ad, reel_ad
  shared_url TEXT NOT NULL,             -- URL final con UTM
  caption TEXT NULL,                    -- texto usado
  external_post_id VARCHAR(128) NULL,   -- id del post/mensaje en la plataforma
  permalink TEXT NULL,                  -- url pública del post si aplica
  published_by VARCHAR(128) NULL,       -- usuario operador
  status VARCHAR(24) NOT NULL DEFAULT 'publicado', -- publicado | programado | borrador
  error_message TEXT NULL,              -- si falló la publicación
  created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_curso (curso_id),
  INDEX idx_network (network),
  INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        

2) (Opcional) Integraciones por red si decides persistir tokens/IDs en BD

CREATE TABLE IF NOT EXISTS social_integrations (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  network VARCHAR(32) NOT NULL,         -- facebook | instagram | linkedin | twitter | telegram | ...
  account_label VARCHAR(128) NOT NULL,  -- nombre legible de la cuenta/página
  config_json JSON NOT NULL,            -- { page_id, access_token, ig_business_id, org_id, bot_token, chat_id, ... }
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  UNIQUE KEY uk_network_label (network, account_label)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        

La tabla de cursos ya existe (cursos_sence). Se consumen nombre_actividad, fundamentacion_tecnica, imagen_curso, id.

Reglas de negocio (contenido & UTMs)

Descripción social

  • Fuente: fundamentacion_tecnica (sin HTML, espacios colapsados).
  • Máximo 250 caracteres; si recorta: añadir sufijo “… Ingresa para más info” sin partir palabras.
  • Usar exactamente el mismo texto en og:description y twitter:description.

UTM por red

RedUTM sugerida
Facebook orgánico?utm_source=facebook&utm_medium=organic&utm_campaign=curso_{id}
LinkedIn orgánico?utm_source=linkedin&utm_medium=organic&utm_campaign=curso_{id}
X (Twitter)?utm_source=twitter&utm_medium=organic&utm_campaign=curso_{id}
Telegram?utm_source=telegram&utm_medium=share&utm_campaign=curso_{id}
WhatsApp (manual)?utm_source=whatsapp&utm_medium=share&utm_campaign=curso_{id}
Instagram Bio?utm_source=instagram&utm_medium=bio&utm_campaign=curso_{id}&utm_content=profile_link
Instagram Story?utm_source=instagram&utm_medium=story_sticker&utm_campaign=curso_{id}&utm_content=sticker_v1
Paid Social?utm_source={red}&utm_medium=paid_social&utm_campaign=curso_{id}&utm_content={ubicacion}

Especificación del Dashboard (UI/UX)

Encabezado

  • Campo bloqueado: curso_id leído del GET.
  • Ficha del curso: título, imagen, URL canónica, descripción (250 + sufijo), aviso de dimensiones de la imagen.
  • Alerta sutil si el dashboard tuvo que crear carpetas en /social/.

Grid de tarjetas por red

  • Mock de preview (imagen, título, descripción, enlace que abrirá la tarjeta).
  • Botones:
    • Copiar URL (con UTM de esa red/medio)
    • Copiar caption (250 + sufijo)
    • Depurador (FB/LinkedIn/Twitter)
    • Publicar (llama POST a /social/{red}/publish)
  • Tras publicar: mostrar permalink y bloquear botón.

Histórico inferior

  • Tabla: Fecha · Red · Medio · URL (copiar) · Caption (ver) · Publicado por · Estado · Enlace al post.
  • Filtros rápidos por red/medio.

Seguridad & despliegue

  • Proteger acceso público a /social/config y /social/storage (reglas servidor).
  • Tokens/IDs solo en servicios por red (no en el dashboard).
  • Logs rotativos sencillos en /social/storage/logs.
  • Validación de origen para endpoints /social/*/publish (CSRF simple o API key interna).
  • Tiempo de respuesta visual: spinner y mensajes claros.

Checklist de implementación

  1. Crear estructura de carpetas dentro de /social/.
  2. Crear tablas SQL (social_posts_log y opcional social_integrations).
  3. Asegurar OG/Twitter correctos en la ficha del curso (ya resuelto).
  4. Implementar dashboard minimal (solo lectura, previews, copiar/depurar).
  5. Crear servicios externos por red y cargar credenciales en /social/config/.
  6. Habilitar botón “Publicar” por red y registro en histórico.
  7. Probar end-to-end y validar en depuradores (Meta/LinkedIn/Twitter).

Plan de entrega (por etapas)

  1. Paso 1: Dashboard base (solo lectura) + creación automática de carpetas + previews + copiar/depurar.
  2. Paso 2: Histórico visible (lectura) + filtros.
  3. Paso 3: Servicios externos (FB, IG, LI, X, TG) con contrato común.
  4. Paso 4: Botón “Publicar” y escritura en social_posts_log.
  5. Paso 5: Salud de credenciales, CSV histórico (opcional) y detalles finos.

Cada paso se entrega como bloque independiente para pruebas supervisadas.

Notas finales

  • Instagram orgánico: los clics efectivos van por Bio y Story Sticker. El feed no hace clic en el caption.
  • WhatsApp: si no usas la Business API con opt-in, manejar como “Copiar mensaje”.
  • Para IG/LI puede rendir mejor postear imagen + caption que link post, pero el dashboard soporta ambos enfoques.

Con esta propuesta, un desarrollador puede montar el proyecto sin ambigüedades: carpetas, tablas, contratos, flujos y reglas de negocio están definidos.

¿Listo para avanzar? Cuando digas “autorizar paso 1”, te entrego el primer bloque de código (dashboard base) para pegar y probar.