{"version":3,"file":"StaticImageWidget-BHk7yXMe.cjs","sources":["../src/dashboard/widgets/StaticImageWidget.tsx"],"sourcesContent":["import React, { useState } from 'react';\nimport { useDashboardTranslation } from '../i18n';\nimport { ImageOff } from 'lucide-react';\nimport type { WidgetProps } from '../types';\nimport { useWidgetFetch } from '../hooks/useWidgetFetch';\nimport { WidgetSkeleton } from '../components/WidgetSkeletons';\nimport { useReducedMotion } from '../utils/useReducedMotion';\nimport { readObject } from '../utils/widgetConfig';\n\ninterface ImagePayload {\n  url: string;\n  title?: string;\n}\n\ninterface ImageWidgetConfig {\n  /** Texto alternativo. Sin él el widget es inutilizable con lector de pantalla. */\n  alt?: string;\n  /** `contain` por defecto — ver abajo. */\n  fit?: 'contain' | 'cover';\n  caption?: string;\n  /** Si está, la imagen navega. */\n  link?: string;\n}\n\n/**\n * Widget de imagen.\n *\n * <b>No es un widget de datos: es contexto visual.</b> Logo de la compañía activa,\n * plano de bodega, organigrama, QR de un enlace operativo, banner de campaña. Por\n * eso se rige por reglas distintas al resto: no tiene periodo, no tiene delta y en\n * la mayoría de los casos no debe refrescarse solo.\n *\n * Tres cambios respecto de la versión anterior:\n *\n *  - <b>`contain` es el default, no `cover`.</b> Un plano o un organigrama recortado\n *    pierde justamente su información. `cover` queda para banners y fotos\n *    decorativas, donde recortar no destruye contenido.\n *  - <b>Se van el zoom en hover y el degradado negro encima.</b> Una imagen que se\n *    agranda al pasar el mouse y lleva una banda oscura con texto es decoración que\n *    tapa el contenido — sobre un plano, lo tapa literalmente.\n *  - <b>El `alt` sale de la configuración</b>, no del nombre del widget: \"Ventas del\n *    periodo\" no describe una imagen.\n */\nconst StaticImageWidget: React.FC<WidgetProps> = ({ definition, period }) => {\n  const { t } = useDashboardTranslation();\n  const reducedMotion = useReducedMotion();\n  const [failed, setFailed] = useState(false);\n\n  const { data, loading, error } = useWidgetFetch<ImagePayload>(\n    definition.widget_id,\n    period,\n    definition.refresh_policy,\n    definition.field_mapping?.rename\n  );\n\n  const config = readObject(definition.config_schema, 'image') as ImageWidgetConfig | undefined;\n  const fit = config?.fit ?? 'contain';\n\n  /**\n   * Solo `http(s)`. La URL viene de la configuración del widget, que escribe un administrador del\n   * catálogo, y de ahí se pinta para todos los que ven el tablero.\n   *\n   * React 19 bloquea `javascript:` por su cuenta —sustituye el atributo por una expresión que\n   * lanza— pero el kit declara `react: \"^18.0.0 || ^19.0.0\"` en sus peers, y en React 18 eso es\n   * solo un aviso de desarrollo. Dos líneas de lista blanca cierran el caso para los dos.\n   */\n  const enlaceSeguro = config?.link && /^https?:\\/\\//i.test(config.link) ? config.link : undefined;\n  const alt = config?.alt ?? definition.descripcion ?? definition.nombre;\n  const caption = config?.caption;\n\n  if (loading) {\n    // Reserva el área para que la grilla no salte cuando la imagen entra.\n    return <WidgetSkeleton shape=\"image\" reducedMotion={reducedMotion} />;\n  }\n\n  if (error || failed || !data?.url) {\n    return (\n      <div\n        className=\"w-full h-full flex flex-col items-center justify-center gap-2 p-4 text-center\"\n        style={{ color: 'var(--dash-text-muted)' }}\n        data-testid=\"image-error\"\n      >\n        {/* Estado propio, nunca el icono roto del navegador. */}\n        <ImageOff className=\"w-7 h-7 opacity-60\" />\n        <span className=\"text-[13px]\">{t('dashboard.widget.imageUnavailable')}</span>\n      </div>\n    );\n  }\n\n  const image = (\n    <img\n      src={data.url}\n      alt={alt}\n      onError={() => setFailed(true)}\n      className=\"w-full h-full\"\n      // La relación de aspecto se preserva SIEMPRE: ninguna configuración permite\n      // deformar. `contain` deja ver el plano entero; `cover` recorta, y sólo se usa\n      // donde recortar no quita información.\n      style={{ objectFit: fit, borderRadius: 8, background: 'var(--dash-surface-page)' }}\n    />\n  );\n\n  return (\n    <div className=\"w-full h-full flex flex-col px-4 pb-4 gap-1.5\">\n      {/* Sin borde ni sombra propios: la tarjeta ya tiene borde, y una imagen\n          enmarcada dentro de un marco es ruido. */}\n      <div className=\"flex-1 min-h-0 overflow-hidden\">\n        {enlaceSeguro ? (\n          <a\n            href={enlaceSeguro}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"block w-full h-full focus:outline-2 focus:outline-offset-2\"\n            style={{ outlineColor: 'var(--dash-accent)' }}\n          >\n            {image}\n          </a>\n        ) : (\n          image\n        )}\n      </div>\n      {caption && (\n        <p\n          className=\"text-[12px] truncate flex-shrink-0\"\n          style={{ color: 'var(--dash-text-muted)' }}\n          title={caption}\n        >\n          {caption}\n        </p>\n      )}\n    </div>\n  );\n};\n\nexport default StaticImageWidget;\n"],"names":["StaticImageWidget","definition","period","t","useDashboardTranslation","reducedMotion","useReducedMotion","failed","setFailed","useState","data","loading","error","useWidgetFetch","config","readObject","fit","enlaceSeguro","alt","caption","jsx","WidgetSkeleton","jsxs","ImageOff","image"],"mappings":"+OA2CMA,EAA2C,CAAC,CAAE,WAAAC,EAAY,OAAAC,KAAa,CAC3E,KAAM,CAAE,EAAAC,CAAA,EAAMC,0BAAA,EACRC,EAAgBC,EAAAA,iBAAA,EAChB,CAACC,EAAQC,CAAS,EAAIC,EAAAA,SAAS,EAAK,EAEpC,CAAE,KAAAC,EAAM,QAAAC,EAAS,MAAAC,CAAA,EAAUC,EAAAA,eAC/BZ,EAAW,UACXC,EACAD,EAAW,eACXA,EAAW,eAAe,MAAA,EAGtBa,EAASC,EAAAA,WAAWd,EAAW,cAAe,OAAO,EACrDe,EAAMF,GAAQ,KAAO,UAUrBG,EAAeH,GAAQ,MAAQ,gBAAgB,KAAKA,EAAO,IAAI,EAAIA,EAAO,KAAO,OACjFI,EAAMJ,GAAQ,KAAOb,EAAW,aAAeA,EAAW,OAC1DkB,EAAUL,GAAQ,QAExB,GAAIH,EAEF,OAAOS,EAAAA,IAACC,EAAAA,eAAA,CAAe,MAAM,QAAQ,cAAAhB,CAAA,CAA8B,EAGrE,GAAIO,GAASL,GAAU,CAACG,GAAM,IAC5B,OACEY,EAAAA,KAAC,MAAA,CACC,UAAU,gFACV,MAAO,CAAE,MAAO,wBAAA,EAChB,cAAY,cAGZ,SAAA,CAAAF,EAAAA,IAACG,EAAAA,SAAA,CAAS,UAAU,oBAAA,CAAqB,QACxC,OAAA,CAAK,UAAU,cAAe,SAAApB,EAAE,mCAAmC,CAAA,CAAE,CAAA,CAAA,CAAA,EAK5E,MAAMqB,EACJJ,EAAAA,IAAC,MAAA,CACC,IAAKV,EAAK,IACV,IAAAQ,EACA,QAAS,IAAMV,EAAU,EAAI,EAC7B,UAAU,gBAIV,MAAO,CAAE,UAAWQ,EAAK,aAAc,EAAG,WAAY,0BAAA,CAA2B,CAAA,EAIrF,OACEM,EAAAA,KAAC,MAAA,CAAI,UAAU,gDAGb,SAAA,CAAAF,EAAAA,IAAC,MAAA,CAAI,UAAU,iCACZ,SAAAH,EACCG,EAAAA,IAAC,IAAA,CACC,KAAMH,EACN,OAAO,SACP,IAAI,sBACJ,UAAU,6DACV,MAAO,CAAE,aAAc,oBAAA,EAEtB,SAAAO,CAAA,CAAA,EAGHA,CAAA,CAEJ,EACCL,GACCC,EAAAA,IAAC,IAAA,CACC,UAAU,qCACV,MAAO,CAAE,MAAO,wBAAA,EAChB,MAAOD,EAEN,SAAAA,CAAA,CAAA,CACH,EAEJ,CAEJ"}