{"id":3637,"date":"2026-03-19T11:43:31","date_gmt":"2026-03-19T11:43:31","guid":{"rendered":"https:\/\/mboprinters.com\/?page_id=3637"},"modified":"2026-03-19T11:43:32","modified_gmt":"2026-03-19T11:43:32","slug":"app-posts-e-imagenes","status":"publish","type":"page","link":"https:\/\/mboprinters.com\/fr\/app-posts-e-imagenes","title":{"rendered":"app posts e imagenes"},"content":{"rendered":"<p><!DOCTYPE html><br \/>\n<html lang=\"es\"><br \/>\n<head><br \/>\n  <meta charset=\"UTF-8\" \/><br \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/><br \/>\n  <title>Bomedia Content Dashboard<\/title>\n  <link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\" \/>\n  <link href=\"https:\/\/fonts.googleapis.com\/css2?family=DM+Sans:wght@300;400;500;600&#038;family=DM+Mono:wght@400;500&#038;display=swap\" rel=\"stylesheet\" \/>\n<style>\n    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n    :root {\n      --bg:       #f4f3ef;\n      --surface:  #ffffff;\n      --surface2: #edece8;\n      --border:   #e0dfd9;\n      --text:     #1a1917;\n      --muted:    #6b6962;\n      --accent:   #d4540a;\n      --accent2:  #f0e9df;\n      --green:    #1a6645;\n      --green-bg: #e3f3ec;\n      --amber:    #7a4a00;\n      --amber-bg: #fff0d4;\n      --red:      #9e1c1c;\n      --red-bg:   #fdeaea;\n      --shadow:   0 2px 12px rgba(0,0,0,.07);\n      --radius:   14px;\n      --radius-sm:8px;\n    }\n    body { font-family: 'DM Sans', sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; padding: 28px 20px 60px; }\n    .container { max-width: 1100px; margin: 0 auto; display: flex; flex-direction: column; gap: 24px; }\n    .card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }\n    .card-body  { padding: 24px; }\n    .card-title { font-size: 1.1rem; font-weight: 600; display: flex; align-items: center; gap: 8px; }\n    .header-top { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 20px; }\n    .header-top h1 { font-size: clamp(1.6rem, 4vw, 2.1rem); font-weight: 600; letter-spacing: -.02em; line-height: 1.15; }\n    .logo-dot { color: var(--accent); }\n    .header-desc { font-size: .875rem; color: var(--muted); line-height: 1.6; max-width: 680px; margin-top: 6px; }\n    .stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-top: 20px; }\n    .stat-box { background: var(--surface2); border-radius: var(--radius-sm); padding: 14px 16px; }\n    .stat-label { font-size: .75rem; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }\n    .stat-value { font-size: 1.45rem; font-weight: 600; margin-top: 4px; font-family: 'DM Mono', monospace; }\n    .stat-value.sm { font-size: .85rem; font-weight: 500; font-family: 'DM Sans', sans-serif; color: var(--muted); line-height: 1.4; margin-top: 6px; }\n    .alert { border-radius: var(--radius-sm); padding: 12px 16px; font-size: .875rem; line-height: 1.5; display: flex; gap: 10px; align-items: flex-start; }\n    .alert-green { background: var(--green-bg); color: var(--green); border: 1px solid #b5dfc9; }\n    .alert-red   { background: var(--red-bg);   color: var(--red);   border: 1px solid #f3c0c0; }\n    .alert svg   { flex-shrink: 0; margin-top: 1px; }\n    .tabs-nav { display: flex; gap: 4px; background: var(--surface2); border-radius: 10px; padding: 4px; width: fit-content; }\n    .tab-btn { background: none; border: none; padding: 8px 22px; font-size: .875rem; font-weight: 500; font-family: inherit; color: var(--muted); cursor: pointer; border-radius: 8px; transition: background .15s, color .15s; }\n    .tab-btn.active { background: var(--surface); color: var(--text); box-shadow: 0 1px 4px rgba(0,0,0,.1); }\n    .controls { display: grid; grid-template-columns: 1fr 110px auto; gap: 10px; align-items: center; }\n    @media (max-width: 600px) { .controls { grid-template-columns: 1fr; } }\n    .input-wrap { position: relative; }\n    .input-icon { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--muted); pointer-events: none; }\n    input, textarea, select { width: 100%; font-family: inherit; font-size: .875rem; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 9px 12px; background: var(--surface); color: var(--text); outline: none; transition: border-color .15s, box-shadow .15s; }\n    input:focus, textarea:focus, select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(212,84,10,.12); }\n    .input-wrap input { padding-left: 34px; }\n    textarea { min-height: 160px; resize: vertical; line-height: 1.6; }\n    .btn { display: inline-flex; align-items: center; gap: 7px; padding: 9px 18px; border-radius: var(--radius-sm); font-family: inherit; font-size: .875rem; font-weight: 500; cursor: pointer; border: none; white-space: nowrap; transition: opacity .15s, transform .1s; }\n    .btn:active { transform: scale(.97); }\n    .btn:disabled { opacity: .5; cursor: not-allowed; }\n    .btn-primary { background: var(--text); color: #fff; }\n    .btn-primary:hover:not(:disabled) { background: #333; }\n    .btn-outline { background: var(--surface); color: var(--text); border: 1px solid var(--border); }\n    .btn-outline:hover:not(:disabled) { background: var(--surface2); }\n    .btn-accent  { background: var(--accent); color: #fff; }\n    .btn-accent:hover:not(:disabled) { background: #b84408; }\n    .btn-green   { background: var(--green); color: #fff; }\n    .btn-green:hover:not(:disabled) { background: #145536; }\n    .posts-grid { display: flex; flex-direction: column; gap: 16px; }\n    .post-card { border-radius: var(--radius); overflow: hidden; transition: box-shadow .2s; }\n    .post-card:hover { box-shadow: 0 4px 20px rgba(0,0,0,.1); }\n    .post-card-head { padding: 18px 20px 0; display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }\n    .post-meta { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }\n    .badge { display: inline-flex; align-items: center; font-size: .72rem; font-weight: 600; padding: 3px 9px; border-radius: 20px; border: 1px solid transparent; }\n    .badge-approved  { background: var(--green-bg); color: var(--green); border-color: #b5dfc9; }\n    .badge-published { background: var(--surface2); color: var(--muted); border-color: var(--border); }\n    .badge-draft, .badge-pending, .badge-default { background: var(--amber-bg); color: var(--amber); border-color: #f5d88a; }\n    .badge-outline { background: transparent; border-color: var(--border); color: var(--muted); }\n    .post-headline { font-size: 1.05rem; font-weight: 600; line-height: 1.35; }\n    .post-date     { font-size: .8rem; color: var(--muted); margin-top: 4px; }\n    .post-card-body { padding: 14px 20px 20px; display: flex; flex-direction: column; gap: 12px; }\n    .post-text { font-size: .875rem; line-height: 1.7; color: #3a3730; white-space: pre-wrap; }\n    .post-hashtags { background: var(--surface2); border-radius: var(--radius-sm); padding: 10px 14px; font-size: .82rem; color: var(--muted); line-height: 1.5; }\n    .images-label { font-size: .8rem; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; display: flex; align-items: center; gap: 6px; }\n    .images-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; }\n    .img-thumb { border-radius: var(--radius-sm); overflow: hidden; border: 1px solid var(--border); background: var(--surface2); aspect-ratio: 4\/3; display: block; }\n    .img-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .2s; }\n    .img-thumb:hover img { transform: scale(1.03); }\n    .images-empty { border: 1.5px dashed var(--border); border-radius: var(--radius-sm); padding: 14px; font-size: .82rem; color: var(--muted); }\n    \/* Search results *\/\n    .search-results-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 14px; }\n    .img-result-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; transition: box-shadow .2s; }\n    .img-result-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,.1); }\n    .img-result-thumb { aspect-ratio: 4\/3; overflow: hidden; background: var(--surface2); display: block; }\n    .img-result-thumb img { width: 100%; height: 100%; object-fit: cover; transition: transform .2s; }\n    .img-result-thumb:hover img { transform: scale(1.05); }\n    .img-result-info { padding: 10px 12px; }\n    .img-result-name { font-size: .78rem; font-weight: 500; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n    .img-result-tags { font-size: .72rem; color: var(--muted); margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n    .img-result-meta { display: flex; gap: 4px; margin-top: 6px; flex-wrap: wrap; }\n    .tag-pill { font-size: .68rem; background: var(--surface2); border-radius: 4px; padding: 2px 6px; color: var(--muted); }\n    \/* Search controls *\/\n    .search-bar { display: grid; grid-template-columns: 1fr auto auto auto; gap: 10px; align-items: center; }\n    @media (max-width: 700px) { .search-bar { grid-template-columns: 1fr; } }\n    .filter-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 10px; margin-top: 12px; }\n    .filter-label { font-size: .75rem; color: var(--muted); margin-bottom: 4px; }\n    .gen-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; }\n    .gen-desc { font-size: .875rem; color: var(--muted); line-height: 1.6; margin-bottom: 16px; }\n    .empty-state { text-align: center; padding: 48px 20px; color: var(--muted); font-size: .9rem; }\n    .empty-icon  { font-size: 2.2rem; margin-bottom: 10px; }\n    @keyframes spin { to { transform: rotate(360deg); } }\n    .spin { animation: spin .7s linear infinite; }\n    @keyframes fadeUp { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:none; } }\n    .fade-up { animation: fadeUp .3s ease both; }\n    .divider { border: none; border-top: 1px solid var(--border); margin: 4px 0; }\n    .webhook-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n    .webhook-row input { flex: 1; min-width: 200px; font-family: 'DM Mono', monospace; font-size: .8rem; }\n    .results-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }\n    .results-count { font-size: .85rem; color: var(--muted); }\n  <\/style>\n<p><\/head><br \/>\n<body><\/p>\n<div class=\"container fade-up\" id=\"app\">\n  <!-- Header --><\/p>\n<div class=\"card\">\n<div class=\"card-body\">\n<div class=\"header-top\">\n<div style=\"flex:1\">\n<h1>Bomedia<span class=\"logo-dot\">.<\/span> Content Dashboard<\/h1>\n<p class=\"header-desc\">Panel para consultar posts programados, aprobarlos, lanzar generaciones y buscar im\u00e1genes del \u00edndice.<\/p>\n<\/p><\/div>\n<\/p><\/div>\n<div class=\"stats-row\">\n<div class=\"stat-box\">\n<div class=\"stat-label\">Webhook Make<\/div>\n<div class=\"webhook-row\" style=\"margin-top:6px\">\n            <input type=\"text\" id=\"webhookInput\" value=\"https:\/\/hook.eu2.make.com\/6xfh7ui8sdyahint8kgx9m77vhb10ee4\" \/><br \/>\n            <button class=\"btn btn-outline\" onclick=\"saveWebhook()\" title=\"Guardar\" style=\"padding:8px 12px\"><br \/>\n              <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path d=\"M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z\"\/><polyline points=\"17 21 17 13 7 13 7 21\"\/><polyline points=\"7 3 7 8 15 8\"\/><\/svg><br \/>\n            <\/button>\n          <\/div>\n<\/p><\/div>\n<div class=\"stat-box\">\n<div class=\"stat-label\">Posts cargados<\/div>\n<div class=\"stat-value\" id=\"postCount\">0<\/div>\n<\/p><\/div>\n<div class=\"stat-box\">\n<div class=\"stat-label\">Im\u00e1genes encontradas<\/div>\n<div class=\"stat-value\" id=\"imgCount\">\u2014<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div id=\"globalMsg\" style=\"margin-top:14px;display:none\"><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>  <!-- Tabs --><\/p>\n<div class=\"tabs-nav\">\n    <button class=\"tab-btn active\" onclick=\"switchTab('posts', this)\">Posts<\/button><br \/>\n    <button class=\"tab-btn\" onclick=\"switchTab('images', this)\">\ud83d\udd0d Buscar im\u00e1genes<\/button><br \/>\n    <button class=\"tab-btn\" onclick=\"switchTab('generate', this)\">Generar<\/button>\n  <\/div>\n<p>  <!-- TAB: Posts --><\/p>\n<div id=\"tab-posts\">\n<div class=\"card\">\n<div class=\"card-body\">\n<div class=\"controls\">\n<div class=\"input-wrap\">\n            <svg class=\"input-icon\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><circle cx=\"11\" cy=\"11\" r=\"8\"\/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"\/><\/svg><br \/>\n            <input type=\"text\" id=\"searchInput\" placeholder=\"Buscar por titular, texto, estado o hashtags\" oninput=\"renderPosts()\" \/>\n          <\/div>\n<p>          <input type=\"number\" id=\"limitInput\" value=\"10\" min=\"1\" max=\"200\" placeholder=\"L\u00edmite\" \/><br \/>\n          <button class=\"btn btn-primary\" id=\"loadBtn\" onclick=\"loadPosts()\"><br \/>\n            <svg id=\"refreshIcon\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path d=\"M23 4v6h-6\"\/><path d=\"M20.49 15a9 9 0 11-2.12-9.36L23 10\"\/><\/svg><br \/>\n            Cargar posts<br \/>\n          <\/button>\n        <\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div class=\"posts-grid\" id=\"postsGrid\">\n<div class=\"card\">\n<div class=\"empty-state\">\n<div class=\"empty-icon\">\ud83d\udccb<\/div>\n<p>No hay posts cargados todav\u00eda.<\/p><\/div>\n<\/div><\/div>\n<\/p><\/div>\n<p>  <!-- TAB: Buscar im\u00e1genes --><\/p>\n<div id=\"tab-images\" style=\"display:none\">\n<div class=\"card\">\n<div class=\"card-body\">\n<div class=\"card-title\" style=\"margin-bottom:16px\">\n          <svg width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" viewBox=\"0 0 24 24\"><circle cx=\"11\" cy=\"11\" r=\"8\"\/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"\/><\/svg><br \/>\n          Buscar im\u00e1genes en el \u00edndice\n        <\/div>\n<div class=\"search-bar\">\n<div class=\"input-wrap\">\n            <svg class=\"input-icon\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><circle cx=\"11\" cy=\"11\" r=\"8\"\/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"\/><\/svg><br \/>\n            <input type=\"text\" id=\"imgSearchInput\" placeholder=\"Ej: wood varnish, medallas, bec1go sample\u2026\" onkeydown=\"if(event.key==='Enter') searchImages()\" \/>\n          <\/div>\n<p>          <input type=\"number\" id=\"imgLimitInput\" value=\"9\" min=\"1\" max=\"50\" placeholder=\"M\u00e1x\" style=\"width:80px\" \/><br \/>\n          <button class=\"btn btn-accent\" id=\"imgSearchBtn\" onclick=\"searchImages()\"><br \/>\n            <svg id=\"imgSearchIcon\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><circle cx=\"11\" cy=\"11\" r=\"8\"\/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"\/><\/svg><br \/>\n            Buscar<br \/>\n          <\/button><br \/>\n          <button class=\"btn btn-outline\" onclick=\"clearImageSearch()\" title=\"Limpiar\">\u2715<\/button>\n        <\/div>\n<div class=\"filter-row\">\n<div>\n<div class=\"filter-label\">Tipo de imagen<\/div>\n<p>            <select id=\"filterMachineType\"><option value=\"\">Todos los tipos<\/option><option value=\"application_output\">application_output \u2013 resultado impreso<\/option><option value=\"flatbed_uv_printer\">flatbed_uv_printer \u2013 foto de m\u00e1quina flatbed<\/option><option value=\"desktop_uv_printer\">desktop_uv_printer \u2013 foto de m\u00e1quina desktop<\/option><option value=\"mini_uv_printer\">mini_uv_printer \u2013 foto de m\u00e1quina mini<\/option><option value=\"uv_printer_catalog\">uv_printer_catalog \u2013 cat\u00e1logo \/ brochure<\/option><option value=\"id_card_uv_printer\">id_card_uv_printer \u2013 foto de impresora tarjetas<\/option><\/select>\n          <\/div>\n<div>\n<div class=\"filter-label\">Contexto<\/div>\n<p>            <select id=\"filterContext\"><option value=\"\">Todos<\/option><option value=\"printed_sample\">printed_sample \u2013 muestra impresa<\/option><option value=\"machine_photo\">machine_photo \u2013 foto de m\u00e1quina<\/option><option value=\"logo_graphic\">logo_graphic \u2013 logo \/ gr\u00e1fico<\/option><option value=\"brochure_download\">brochure_download \u2013 brochure descargable<\/option><\/select>\n          <\/div>\n<div>\n<div class=\"filter-label\">Modelo<\/div>\n<p>            <select id=\"filterModel\"><option value=\"\">Todos los modelos<\/option><option value=\"bec1go\">BEC1GO<\/option><option value=\"3000u_pro\">artis 3000U Pro (Freebird)<\/option><option value=\"5000u\">artis 5000U<\/option><option value=\"prov6\">artisJet ProV6<\/option><option value=\"young\">artisJet Young<\/option><option value=\"trust_6090\">artisJet Trust 6090<\/option><option value=\"proud\">artisJet Proud<\/option><option value=\"catalog_generic\">catalog_generic \u2013 sin modelo espec\u00edfico<\/option><\/select>\n          <\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div id=\"imgResultsWrap\" style=\"display:none\">\n<div class=\"card\">\n<div class=\"card-body\">\n<div class=\"results-header\">\n            <span class=\"results-count\" id=\"imgResultsCount\"><\/span>\n          <\/div>\n<div class=\"search-results-grid\" id=\"imgResultsGrid\"><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<div id=\"imgEmptyState\" class=\"card\" style=\"display:none\">\n<div class=\"empty-state\">\n<div class=\"empty-icon\">\ud83d\udd0e<\/div>\n<p>No se encontraron im\u00e1genes para esa b\u00fasqueda.<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>  <!-- TAB: Generate --><\/p>\n<div id=\"tab-generate\" style=\"display:none\">\n<div class=\"gen-grid\">\n<div class=\"card\">\n<div class=\"card-body\">\n<div class=\"card-title\">\n            <svg width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\"\/><line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"\/><line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"\/><line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"\/><\/svg><br \/>\n            Generaci\u00f3n autom\u00e1tica\n          <\/div>\n<hr class=\"divider\" style=\"margin:12px 0\" \/>\n<p class=\"gen-desc\">Lanza la generaci\u00f3n del siguiente post usando la l\u00f3gica de calendario y rotaci\u00f3n del escenario en Make.<\/p>\n<p>          <button class=\"btn btn-accent\" id=\"genAutoBtn\" onclick=\"generateAutomatic()\"><br \/>\n            <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"\/><\/svg><br \/>\n            Generar post autom\u00e1tico<br \/>\n          <\/button>\n        <\/div>\n<\/p><\/div>\n<div class=\"card\">\n<div class=\"card-body\">\n<div class=\"card-title\">\n            <svg width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" viewBox=\"0 0 24 24\"><line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"\/><polygon points=\"22 2 15 22 11 13 2 9 22 2\"\/><\/svg><br \/>\n            Generaci\u00f3n por prompt\n          <\/div>\n<hr class=\"divider\" style=\"margin:12px 0\" \/>\n          <textarea id=\"promptInput\">Create a LinkedIn post in English about UV flatbed printing for retail displays, with a professional and technical tone.<\/textarea><\/p>\n<div style=\"margin-top:10px\">\n            <button class=\"btn btn-accent\" id=\"genPromptBtn\" onclick=\"generateFromPrompt()\"><br \/>\n              <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"\/><\/svg><br \/>\n              Generar desde prompt<br \/>\n            <\/button>\n          <\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/div>\n<p><script>\n  let WEBHOOK_URL = document.getElementById('webhookInput').value;\n  function saveWebhook() {\n    WEBHOOK_URL = document.getElementById('webhookInput').value.trim();\n    showMsg('Webhook actualizado.', 'green');\n  }\n  let allPosts = [];<\/p>\n<p>  \/\/ \u2500\u2500 Normalize posts \u2500\u2500\n  function normalizePost(raw) {\n    if (!raw || typeof raw !== 'object') return null;\n    if (raw.headline || raw.publish_date || raw.row_number) {\n      return {\n        row_number: raw.row_number ?? raw.__ROW_NUMBER__ ?? null,\n        publish_date: raw.publish_date ?? '',\n        status: raw.status ?? '',\n        topic_type: raw.topic_type ?? '',\n        headline: raw.headline ?? '',\n        body_text: raw.body_text ?? '',\n        hashtags: raw.hashtags ?? '',\n        image_1_drive_file_id: raw.image_1_drive_file_id ?? '',\n        image_2_drive_file_id: raw.image_2_drive_file_id ?? '',\n        image_3_drive_file_id: raw.image_3_drive_file_id ?? '',\n        image_1_drive_url: raw.image_1_drive_url ?? '',\n        image_2_drive_url: raw.image_2_drive_url ?? '',\n        image_3_drive_url: raw.image_3_drive_url ?? '',\n      };\n    }\n    return {\n      row_number: raw.__ROW_NUMBER__ ?? null,\n      publish_date: raw['0'] ?? '',\n      status: raw['1'] ?? '',\n      topic_type: raw['2'] ?? '',\n      headline: raw['3'] ?? '',\n      body_text: raw['4'] ?? '',\n      hashtags: raw['5'] ?? '',\n      image_1_drive_file_id: raw['7'] ?? '',\n      image_2_drive_file_id: raw['8'] ?? '',\n      image_3_drive_file_id: raw['9'] ?? '',\n      image_1_drive_url: raw['11'] ?? '',\n      image_2_drive_url: raw['12'] ?? '',\n      image_3_drive_url: raw['13'] ?? '',\n    };\n  }\n  function normalizeList(data) {\n    if (!data) return [];\n    if (Array.isArray(data)) return data.map(normalizePost).filter(Boolean);\n    if (Array.isArray(data.posts)) return data.posts.map(normalizePost).filter(Boolean);\n    if (typeof data.posts === 'object' && data.posts !== null && data.posts.__ROW_NUMBER__)\n      return [normalizePost(data.posts)].filter(Boolean);\n    if (typeof data === 'object' && data.__ROW_NUMBER__)\n      return [normalizePost(data)].filter(Boolean);\n    return [];\n  }<\/p>\n<p>  \/\/ \u2500\u2500 Normalize image results \u2500\u2500\n  function normalizeImageResult(raw) {\n    if (!raw || typeof raw !== 'object') return null;\n    \/\/ Map numeric keys from aggregator\n    return {\n      drive_file_id: raw['1'] ?? raw.drive_file_id ?? '',\n      file_name:     raw['2'] ?? raw.file_name ?? '',\n      drive_url:     raw['4'] ?? raw.drive_url ?? '',\n      machine_model: raw['11'] ?? raw.machine_model ?? '',\n      machine_type:  raw['12'] ?? raw.machine_type ?? '',\n      context:       raw['13'] ?? raw.context ?? '',\n      tags:          raw['16'] ?? raw.tags ?? '',\n      notes:         raw['19'] ?? raw.notes ?? '',\n    };\n  }\n  function normalizeImageList(data) {\n    if (!data) return [];\n    const arr = data.images ?? data;\n    if (Array.isArray(arr)) return arr.map(normalizeImageResult).filter(Boolean);\n    return [];\n  }<\/p>\n<p>  \/\/ \u2500\u2500 API \u2500\u2500\n  async function callWebhook(payload) {\n    const res = await fetch(WEBHOOK_URL, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application\/json' },\n      body: JSON.stringify(payload),\n    });\n    const text = await res.text();\n    try { return JSON.parse(text); } catch { return { raw: text, ok: res.ok }; }\n  }<\/p>\n<p>  \/\/ \u2500\u2500 Tabs \u2500\u2500\n  function switchTab(name, btn) {\n    ['posts','images','generate'].forEach(t => {\n      document.getElementById('tab-' + t).style.display = t === name ? '' : 'none';\n    });\n    document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));\n    btn.classList.add('active');\n  }<\/p>\n<p>  \/\/ \u2500\u2500 Helpers \u2500\u2500\n  function extractDriveId(url) {\n    if (!url) return null;\n    const m1 = url.match(\/\\\/d\\\/([a-zA-Z0-9_-]{10,})\/);\n    if (m1) return m1[1];\n    const m2 = url.match(\/[?&]id=([a-zA-Z0-9_-]{10,})\/);\n    if (m2) return m2[1];\n    return null;\n  }\n  function driveImgSrc(url, fileId) {\n    const id = fileId || extractDriveId(url);\n    if (id) return `https:\/\/drive.google.com\/thumbnail?id=${id}&sz=w600`;\n    return url || '';\n  }\n  function driveHref(url, fileId) {\n    const id = fileId || extractDriveId(url);\n    if (id) return `https:\/\/drive.google.com\/file\/d\/${id}\/view`;\n    return url || '#';\n  }\n  function badgeClass(status) {\n    const v = (status || '').toLowerCase();\n    if (v === 'approved')  return 'badge-approved';\n    if (v === 'published') return 'badge-published';\n    return 'badge-default';\n  }\n  function esc(str) {\n    return String(str ?? '').replace(\/&\/g,'&amp;').replace(\/<\/g,'&lt;').replace(\/>\/g,'&gt;').replace(\/\"\/g,'&quot;');\n  }\n  function showMsg(msg, type) {\n    const el = document.getElementById('globalMsg');\n    el.style.display = 'block';\n    el.innerHTML = `<\/p>\n<div class=\"alert alert-${type === 'green' ? 'green' : 'red'}\">\n      <svg width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\">\n        ${type === 'green'\n          ? '<path d=\"M22 11.08V12a10 10 0 11-5.93-9.14\"\/><polyline points=\"22 4 12 14.01 9 11.01\"\/>'\n          : '<circle cx=\"12\" cy=\"12\" r=\"10\"\/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"\/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"\/>'}\n      <\/svg>\n      ${esc(msg)}\n    <\/div>\n<p>`;\n    setTimeout(() => { el.style.display = 'none'; }, 5000);\n  }\n  function setLoading(btnId, iconId, loading) {\n    const btn = document.getElementById(btnId);\n    if (!btn) return;\n    btn.disabled = loading;\n    const icon = document.getElementById(iconId);\n    if (icon) icon.classList.toggle('spin', loading);\n  }<\/p>\n<p>  \/\/ \u2500\u2500 Render posts \u2500\u2500\n  function renderPosts() {\n    const q = document.getElementById('searchInput').value.trim().toLowerCase();\n    const filtered = q\n      ? allPosts.filter(p => [p.headline, p.body_text, p.topic_type, p.status, p.hashtags].join(' ').toLowerCase().includes(q))\n      : allPosts;\n    document.getElementById('postCount').textContent = allPosts.length;\n    const grid = document.getElementById('postsGrid');\n    if (!filtered.length) {\n      grid.innerHTML = '<\/p>\n<div class=\"card\">\n<div class=\"empty-state\">\n<div class=\"empty-icon\">\ud83d\udccb<\/div>\n<p>No hay posts que coincidan.<\/p><\/div>\n<\/div>\n<p>';\n      return;\n    }\n    grid.innerHTML = filtered.map(post => {\n      const images = [\n        { url: post.image_1_drive_url, fileId: post.image_1_drive_file_id },\n        { url: post.image_2_drive_url, fileId: post.image_2_drive_file_id },\n        { url: post.image_3_drive_url, fileId: post.image_3_drive_file_id },\n      ].filter(img => img.url || img.fileId);\n      const imagesHtml = images.length\n        ? `<\/p>\n<div class=\"images-grid\">${images.map((img, i) => {\n            const src = driveImgSrc(img.url, img.fileId);\n            const href = driveHref(img.url, img.fileId);\n            return `<a class=\"img-thumb\" href=\"${esc(href)}\" target=\"_blank\" rel=\"noreferrer\">\n              <img decoding=\"async\" src=\"${esc(src)}\" alt=\"Imagen ${i+1}\" loading=\"lazy\" onerror=\"this.style.display='none'\" \/>\n            <\/a>`;\n          }).join('')}<\/div>\n<p>`\n        : `<\/p>\n<div class=\"images-empty\">No hay im\u00e1genes en esta fila.<\/div>\n<p>`;\n      const isApproved = (post.status || '').toLowerCase() === 'approved';\n      return `<\/p>\n<div class=\"card post-card fade-up\" id=\"post-${esc(post.row_number)}\">\n<div class=\"post-card-head\">\n<div style=\"flex:1\">\n<div class=\"post-meta\">\n              <span class=\"badge ${badgeClass(post.status)}\">${esc(post.status || 'unknown')}<\/span>\n              ${post.topic_type ? `<span class=\"badge badge-outline\">${esc(post.topic_type)}<\/span>` : ''}\n              ${post.row_number != null ? `<span class=\"badge badge-outline\">fila ${esc(post.row_number)}<\/span>` : ''}\n            <\/div>\n<div class=\"post-headline\">${esc(post.headline || 'Sin titular')}<\/div>\n<div class=\"post-date\">${esc(post.publish_date || 'Sin fecha')}<\/div>\n<\/p><\/div>\n<p>          <button class=\"btn ${isApproved ? 'btn-outline' : 'btn-green'}\"\n            id=\"approve-btn-${esc(post.row_number)}\"\n            onclick=\"approvePost(${JSON.stringify(post.row_number)})\"\n            ${!post.row_number || isApproved ? 'disabled' : ''}>\n            <svg width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><path d=\"M22 11.08V12a10 10 0 11-5.93-9.14\"\/><polyline points=\"22 4 12 14.01 9 11.01\"\/><\/svg>\n            ${isApproved ? 'Aprobado' : 'Aprobar'}\n          <\/button>\n        <\/div>\n<div class=\"post-card-body\">\n<p class=\"post-text\">${esc(post.body_text)}<\/p>\n<p>          ${post.hashtags ? `<\/p>\n<div class=\"post-hashtags\">${esc(post.hashtags)}<\/div>\n<p>` : ''}<\/p>\n<div>\n<div class=\"images-label\">\n              <svg width=\"13\" height=\"13\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"\/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"\/><polyline points=\"21 15 16 10 5 21\"\/><\/svg>\n              Im\u00e1genes candidatas\n            <\/div>\n<div style=\"margin-top:8px\">${imagesHtml}<\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>`;\n    }).join('');\n  }<\/p>\n<p>  \/\/ \u2500\u2500 Image search \u2500\u2500\n  async function searchImages() {\n    const query       = document.getElementById('imgSearchInput').value.trim();\n    const machineType = document.getElementById('filterMachineType').value;\n    const context     = document.getElementById('filterContext').value;\n    const model       = document.getElementById('filterModel').value;\n    const limit       = parseInt(document.getElementById('imgLimitInput').value) || 9;<\/p>\n<p>    if (!query && !machineType && !context && !model) {\n      showMsg('Escribe al menos una palabra clave o selecciona un filtro.', 'red');\n      return;\n    }<\/p>\n<p>    setLoading('imgSearchBtn', 'imgSearchIcon', true);\n    document.getElementById('imgResultsWrap').style.display = 'none';\n    document.getElementById('imgEmptyState').style.display = 'none';<\/p>\n<p>    const payload = { action: 'search_images', query: query || '', limit };\n    if (machineType) payload.machine_type = machineType;\n    if (context)     payload.context      = context;\n    if (model)       payload.model        = model;<\/p>\n<p>    try {\n      const data = await callWebhook(payload);\n      const images = normalizeImageList(data);\n      document.getElementById('imgCount').textContent = images.length;<\/p>\n<p>      if (!images.length) {\n        document.getElementById('imgEmptyState').style.display = '';\n        return;\n      }<\/p>\n<p>      document.getElementById('imgResultsCount').textContent = `${images.length} imagen${images.length !== 1 ? 'es' : ''} encontrada${images.length !== 1 ? 's' : ''}`;\n      document.getElementById('imgResultsGrid').innerHTML = images.map(img => {\n        const src  = driveImgSrc(img.drive_url, img.drive_file_id);\n        const href = driveHref(img.drive_url, img.drive_file_id);\n        const tags = (img.tags || '').split(',').map(t => t.trim()).filter(Boolean).slice(0, 4);\n        return `<\/p>\n<div class=\"img-result-card fade-up\">\n          <a class=\"img-result-thumb\" href=\"${esc(href)}\" target=\"_blank\" rel=\"noreferrer\">\n            <img decoding=\"async\" src=\"${esc(src)}\" alt=\"${esc(img.file_name)}\" loading=\"lazy\" onerror=\"this.parentElement.style.background='var(--surface2)'\" \/>\n          <\/a><\/p>\n<div class=\"img-result-info\">\n<div class=\"img-result-name\" title=\"${esc(img.file_name)}\">${esc(img.file_name || '\u2014')}<\/div>\n<div class=\"img-result-tags\">${esc(img.tags || '\u2014')}<\/div>\n<div class=\"img-result-meta\">\n              ${img.machine_model ? `<span class=\"tag-pill\">${esc(img.machine_model)}<\/span>` : ''}\n              ${img.context       ? `<span class=\"tag-pill\">${esc(img.context)}<\/span>` : ''}\n            <\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>`;\n      }).join('');\n      document.getElementById('imgResultsWrap').style.display = '';\n    } catch (e) {\n      showMsg(e.message || 'Error al buscar im\u00e1genes.', 'red');\n    } finally {\n      setLoading('imgSearchBtn', 'imgSearchIcon', false);\n    }\n  }<\/p>\n<p>  function clearImageSearch() {\n    document.getElementById('imgSearchInput').value = '';\n    document.getElementById('filterMachineType').value = '';\n    document.getElementById('filterContext').value = '';\n    document.getElementById('filterModel').value = '';\n    document.getElementById('imgResultsWrap').style.display = 'none';\n    document.getElementById('imgEmptyState').style.display = 'none';\n    document.getElementById('imgCount').textContent = '\u2014';\n  }<\/p>\n<p>  \/\/ \u2500\u2500 Posts actions \u2500\u2500\n  async function loadPosts() {\n    setLoading('loadBtn', 'refreshIcon', true);\n    try {\n      const limit = parseInt(document.getElementById('limitInput').value) || 10;\n      const data = await callWebhook({ action: 'list_posts', limit });\n      allPosts = normalizeList(data);\n      renderPosts();\n      showMsg(`Cargados ${allPosts.length} posts.`, 'green');\n    } catch (e) {\n      showMsg(e.message || 'No se pudieron cargar los posts.', 'red');\n    } finally {\n      setLoading('loadBtn', 'refreshIcon', false);\n    }\n  }\n  async function generateAutomatic() {\n    const btn = document.getElementById('genAutoBtn');\n    btn.disabled = true; btn.textContent = 'Generando\u2026';\n    try {\n      await callWebhook({ action: 'generate_post' });\n      showMsg('Post generado correctamente.', 'green');\n      await loadPosts();\n    } catch (e) {\n      showMsg(e.message || 'No se pudo generar el post.', 'red');\n    } finally {\n      btn.disabled = false;\n      btn.innerHTML = `<svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"\/><\/svg> Generar post autom\u00e1tico`;\n    }\n  }\n  async function generateFromPrompt() {\n    const promptVal = document.getElementById('promptInput').value.trim();\n    if (!promptVal) return;\n    const btn = document.getElementById('genPromptBtn');\n    btn.disabled = true; btn.textContent = 'Generando\u2026';\n    try {\n      await callWebhook({ action: 'generate_post_from_prompt', prompt: promptVal });\n      showMsg('Post generado desde prompt correctamente.', 'green');\n      await loadPosts();\n    } catch (e) {\n      showMsg(e.message || 'No se pudo generar el post desde prompt.', 'red');\n    } finally {\n      btn.disabled = false;\n      btn.innerHTML = `<svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" viewBox=\"0 0 24 24\"><polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\"\/><\/svg> Generar desde prompt`;\n    }\n  }\n  async function approvePost(rowNumber) {\n    if (!rowNumber) return;\n    const btn = document.getElementById(`approve-btn-${rowNumber}`);\n    if (btn) { btn.disabled = true; btn.textContent = '\u2026'; }\n    try {\n      await callWebhook({ action: 'approve_post', row_number: rowNumber, status: 'approved' });\n      showMsg(`Fila ${rowNumber} aprobada.`, 'green');\n      allPosts = allPosts.map(p => p.row_number === rowNumber ? { ...p, status: 'approved' } : p);\n      renderPosts();\n    } catch (e) {\n      showMsg(e.message || 'No se pudo aprobar el post.', 'red');\n      if (btn) { btn.disabled = false; btn.textContent = 'Aprobar'; }\n    }\n  }\n<\/script><br \/>\n<\/body><br \/>\n<\/html><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Bomedia Content Dashboard Bomedia. Content Dashboard Panel para consultar posts programados, aprobarlos, lanzar generaciones y buscar im\u00e1genes del \u00edndice. Webhook Make Posts cargados 0 Im\u00e1genes encontradas \u2014 Posts \ud83d\udd0d Buscar im\u00e1genes Generar Cargar posts \ud83d\udccb No hay posts cargados todav\u00eda.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-3637","page","type-page","status-publish","hentry"],"translation":{"provider":"WPGlobus","version":"3.0.0","language":"fr","enabled_languages":["es","en","de","fr"],"languages":{"es":{"title":true,"content":true,"excerpt":false},"en":{"title":false,"content":false,"excerpt":false},"de":{"title":false,"content":false,"excerpt":false},"fr":{"title":false,"content":false,"excerpt":false}}},"_links":{"self":[{"href":"https:\/\/mboprinters.com\/fr\/wp-json\/wp\/v2\/pages\/3637","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mboprinters.com\/fr\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/mboprinters.com\/fr\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/mboprinters.com\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mboprinters.com\/fr\/wp-json\/wp\/v2\/comments?post=3637"}],"version-history":[{"count":1,"href":"https:\/\/mboprinters.com\/fr\/wp-json\/wp\/v2\/pages\/3637\/revisions"}],"predecessor-version":[{"id":3638,"href":"https:\/\/mboprinters.com\/fr\/wp-json\/wp\/v2\/pages\/3637\/revisions\/3638"}],"wp:attachment":[{"href":"https:\/\/mboprinters.com\/fr\/wp-json\/wp\/v2\/media?parent=3637"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}