# Bug: adjunto de "Contact Request" daba 404 en el panel

## Síntoma

En `/admin/contact-requests`, el link "Attached File" de un registro con adjunto (ej.
`da22a882-a04f-4928-beaa-1394ced25f0c.png`) daba 404 al abrirlo.

## Causa raíz

`app/Livewire/ContactForm/ContactRequestPost.php:70` guarda el archivo con
`$this->file->storeAs('files-form', $nameFile)` — **sin especificar disco**, así que usa el
default (`FILESYSTEM_DISK=local` en `.env`). El archivo queda en `storage/app/files-form/...`,
NO en `storage/app/public/files-form/...`.

`app/Filament/Resources/ContactRequestResource.php:83` (antes del fix) armaba la URL con
`asset('storage/' . $record->file)`, que asume que el archivo vive bajo el disco `public`
(accesible vía el symlink `public/storage`). Como nunca estuvo ahí, 404 siempre — no es un
problema del symlink (existe y está bien) ni de CSS/cache, es un mismatch de disco entre guardado
y lectura.

`app/Mail/ContactMailable.php:69` (`Storage::path($this->file)`) también usa el disco default sin
especificarlo — **coincide** con dónde `storeAs()` guardó, así que el adjunto del correo sí
funciona. El bug era exclusivo del panel admin.

**Búsqueda del mismo patrón en el resto del proyecto**: es el único `storeAs()`/`store()` sin
disco explícito en todo `app/`. El resto (uploads de Filament en `PageBlocks.php`, `BlogResource.php`,
`MachineResource.php`, etc., y los `Storage::disk()` manuales de sale-ticket) siempre especifica
`'public'` de forma consistente. `TicketMailable.php:79` tiene el mismo `Storage::path()` sin
disco, pero su `$file` nunca se pasa con un valor real en ningún caller (`sendFormNotifications`
siempre lo deja en `null`) — código inerte, no un bug activo hoy.

## Decisión: disco `local` a propósito, no `public`

El adjunto lo sube gente externa vía formulario público sin login — moverlo a disco `public`
haría el archivo accesible por URL directa a cualquiera con el nombre (UUID), sin autenticación.
Se decidió mantenerlo en `local` (privado) y resolver el acceso desde el panel, no desde el
storage.

## Fix

Sin rutas nuevas en `routes/web.php` ni Controllers — todo dentro del propio recurso de Filament
(`ContactRequestResource.php`), corriendo por las rutas Livewire ya autenticadas del panel:

- **Preview**: el `ImageEntry::make('file')` lee el archivo del disco `local` server-side
  (`Storage::disk('local')->get()`) y lo embebe como `data:{mime};base64,...` en `getStateUsing()`.
  Confirmado en el código de Filament (`ImageEntry::getImageUrl()`) que un `data:` URI se usa tal
  cual, sin pasar por ningún disco/URL — por eso no hace falta que el disco `local` sepa generar
  URLs públicas.
- **Descarga**: un `->hintAction()` (todo `Entry` de Filament lo soporta, `HasHintActions`) corre
  `response()->download(Storage::disk('local')->path(...))` como acción Livewire — dispara la
  descarga en el navegador sin necesitar una URL ni ruta propia.

No se tocó `ContactRequestPost.php` (el guardado en disco `local` ya era correcto, coincide con
`ContactMailable.php`) ni `ContactMailable.php` (ya lee del disco correcto).

## Alcance

Es un bug aislado — verificado que no se repite en ningún otro lugar del proyecto (grep de
`storeAs`/`store` sin disco, `asset('storage/`, `Storage::path(` en todo `app/` y
`resources/views/`).

---

## Ver el adjunto en grande: modal perezoso (2026-09-30)

En el detalle de una solicitud (`/admin/contact-requests/{id}`) el adjunto se veía solo como miniatura de 150 px.

- **`hintActions` con dos acciones** sobre `ImageEntry::make('file')`: **Preview** y **Download**. Siguen sin rutas nuevas
  ni Controllers: corren por las rutas Livewire autenticadas del panel, igual que antes.
- **Preview** abre un modal de Filament (`modalWidth('7xl')`, sin botón de enviar, solo **Close**) con la imagen
  ampliada. El cuerpo es `resources/views/filament/infolists/contact-request-attachment-preview.blade.php`.
- **Es perezoso**: `modalContent()` es un closure que solo se evalúa al abrir el modal, así que el archivo grande no se
  lee ni se incrusta hasta que alguien pulsa Preview. La miniatura se conserva como estaba (data: URI).
- Los helpers `attachmentDataUri()` y `attachmentIsImage()` del recurso comparten la lectura del disco `local`.

### Los adjuntos no siempre son imágenes

El formulario público **hoy** solo acepta `jpeg,jpg,png,gif,webp` (`ContactRequestPost`), pero la base tiene registros
antiguos con **PDF** (por ejemplo los ids 12, 100 y 101 en local). Un PDF como `data:` URI dentro de una `<img>` sale
como imagen rota, así que:

- Si el archivo no es una imagen (`mimeType` no empieza por `image/`), la miniatura devuelve `null` y **Preview se
  oculta**. El PDF se baja con **Download**.
- **Download** ahora solo aparece si el archivo existe en `storage/app/files-form`. Antes seguía visible con el archivo
  ausente y fallaba al pulsarlo.

Verificado en local con el registro 146 (un PNG): aparecen Preview y Download, y Preview abre el modal con la imagen.
No se probó un PDF real en disco (en la base local los PDF ya no están en `files-form`).
