Cuando un usuario reporta un fallo, lo caro no es abrir el ticket: es la semana de correos hasta conseguir reproducirlo. Montar un gestor de incidencias completo ordena los tickets, pero no arregla eso. Aquí verás qué contexto capturar automáticamente, cómo grabar la pantalla sin librerías externas y por qué el vídeo tiene que poder grabarse con el formulario cerrado. Los ejemplos van en Laravel, que es uno de los stacks con los que más trabajamos, pero la parte importante es JavaScript de navegador y sirve con cualquier backend.
El coste de una incidencia no está en el ticket, está en reproducirla
Piensa en el ciclo completo de un fallo reportado por un usuario. Escribe "no me deja guardar". Alguien le pregunta en qué pantalla. Contesta al día siguiente. Le piden una captura. Manda una foto del monitor hecha con el móvil. Le preguntan qué navegador usa. No lo sabe.
Ahí se van tres días y cuatro correos, y el ticket todavía no dice nada útil. Montar un gestor de incidencias completo no arregla eso: ordena tickets, no los hace reproducibles.
Lo que arregla el problema es capturar el contexto en el mismo instante en que el usuario está viendo el fallo, sin pedirle nada que no sepa contestar.
El contexto técnico se captura solo, y es la mitad del trabajo
De todo lo que un desarrollador necesita para reproducir algo, el usuario solo puede aportar una parte: qué intentaba hacer y qué pasó. El resto lo sabe el navegador.
Un widget de reporte bien hecho adjunta sin preguntar:
La URL exacta donde estaba, que resuelve el "en qué pantalla" de un plumazo. El navegador y su versión, sin que nadie tenga que buscarlo en un menú. El tamaño de la ventana, que es donde se esconden la mitad de los fallos de maquetación. El idioma de la aplicación, crítico si es multiidioma. Quién lo reporta, si hay sesión iniciada. Y de dónde venía, que muchas veces es la pista.
El usuario solo escribe el mensaje. Todo lo demás va solo, y sale gratis.
Grabar la pantalla ya no necesita una librería
Grabar la pantalla suena a funcionalidad que exige una dependencia pesada, y por eso casi nadie la ofrece en un formulario de incidencias. La captura de pantalla lleva años siendo parte del navegador, y son cuatro líneas:
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false });
const mime = ['video/webm;codecs=vp9', 'video/webm', 'video/mp4']
.find(t => MediaRecorder.isTypeSupported(t)) || '';
const recorder = new MediaRecorder(stream, { mimeType: mime, videoBitsPerSecond: 2000000 });
Con eso el usuario puede reproducir el fallo delante de ti en vez de describirlo. Y un vídeo de noventa segundos con el fallo ocurriendo vale más que cualquier descripción escrita por alguien que no es técnico.
Hay tres cosas que hay que acotar y no son opcionales. Un tope de duración, porque nadie va a ver siete minutos. Un límite de bitrate, porque el vídeo se acumula en memoria del navegador antes de subirse y dos megabits por segundo ya sobran para leer una interfaz. Y detectar si el navegador puede: la captura de pantalla exige contexto seguro y no existe en móvil, así que el botón solo debe aparecer donde funciona.
El formulario tiene que apartarse para que el usuario reproduzca el fallo
Una funcionalidad puede estar entera y perfectamente probada y aun así no usarla nadie, porque estorba justo en el momento en que se necesita. Grabar la pantalla es el ejemplo más claro que conozco.
Si el usuario pulsa "grabar" y el formulario de reporte se queda encima de la pantalla, no puede reproducir nada: el fallo está debajo del diálogo. Así que al empezar a grabar el formulario se cierra y en su lugar queda una barra pequeña con el cronómetro y un botón de parar. El usuario hace lo que estaba haciendo, para, y el formulario vuelve a abrirse con el vídeo ya adjunto.
Y hay que contemplar que el usuario corte desde la barra nativa del navegador, que aparece siempre que se comparte pantalla:
stream.getVideoTracks()[0].addEventListener('ended', () => this.stopRecording());
Sin esa línea, quien pulse "Dejar de compartir" en el aviso del navegador se queda con una grabación colgada y sin forma de recuperarla.
La grabación vive en el navegador hasta que se envía, y por eso no consume nada de tu servidor mientras el usuario reproduce el fallo. Para que aguante la navegación de una aplicación tipo SPA, monta el widget dentro de un @persist: así el usuario puede ir hasta la pantalla del problema con la grabación en marcha y enviarla desde allí.
No aceptar SVG como captura es una decisión de seguridad
Detalle pequeño con motivo real. En la validación del adjunto, la lista de formatos aceptados incluye JPG, PNG, GIF y WebP, y deja fuera SVG a propósito.
Un SVG es un documento XML que puede contener scripts. Si algún día sirves ese adjunto desde una URL pública, has aceptado subir contenido ejecutable a tu dominio. Es exactamente el tipo de cosa que no se piensa al escribir la lista de formatos y se paga después.
Los adjuntos van a disco privado, y eso obliga a servirlos tú
Por defecto las capturas y los vídeos van a almacenamiento privado, sin URL pública. Es lo correcto, porque una captura de pantalla de un usuario puede contener datos de sus clientes, y una URL adivinable en un bucket público es una filtración esperando a ocurrir.
La contrapartida es que hay que servirlos a mano, con una ruta protegida:
Route::get('/bugs/{report}/screenshot', [AdminController::class, 'bugScreenshot'])
->middleware(['web', 'auth', 'is_admin']);
Son diez líneas y son las que separan un sistema de reportes de una fuga de datos. Si alguien configura el disco como público "para probar", eso ya no se deshace: las URLs quedan expuestas.
Guardar no es avisar
Una incidencia guardada en una tabla que nadie mira no sirve de mucho más que no haberla recogido. Es la parte que se pospone siempre, porque el formulario ya funciona y la confirmación aparece.
Un widget de reporte guarda en tu base de datos y devuelve una confirmación, que es exactamente su trabajo. El aviso y el flujo de tickets son decisiones tuyas, y conviene resolverlas el mismo día que lo instalas: un usuario que se molesta en grabar un vídeo espera notar que alguien lo ha visto.
Lo que hay que añadir son dos cosas y son pequeñas. Una bandeja con estados, aunque sean tres: nuevo, visto, resuelto. Y un aviso al canal donde de verdad miráis, que puede ser un correo o un mensaje a un bot. En Crowd Legal la bandeja marca automáticamente como visto al abrir un reporte y borra los adjuntos del disco al eliminarlo, que es el detalle que evita acumular vídeos de fallos resueltos hace un año.
Qué hay en el mercado y en qué se diferencia
Lo que se vende como herramientas de reporte de errores incluye cosas que no compiten entre sí: unas escuchan a la aplicación, otras al usuario, y otras solo ordenan lo que llega. Confundirlas lleva a comprar la que no era.
Sentry y similares capturan los errores solos, con traza de pila y todo el contexto de la excepción. Es una categoría diferente y complementaria: solo ve lo que revienta. Un botón que no hace nada, un total mal calculado o un texto en el idioma equivocado no lanzan ninguna excepción, así que nunca llegan.
Marker.io, BugHerd y Usersnap están pensadas para ciclos de revisión con cliente: anotar encima de la página, marcar el elemento, y empujar el reporte directamente a Jira o Trello. Para una agencia con clientes revisando entregas eso es valor real. El compromiso: suscripción por usuario, un script de un tercero cargado en tus páginas de producción y los reportes viviendo en un sistema que no es el tuyo.
Jira con su widget te da el flujo de trabajo completo, y ese es su punto fuerte y su coste: es una herramienta de gestión de proyectos entera para recoger cuatro incidencias.
La diferencia de resolverlo dentro de la aplicación es que el reporte es una fila de tu base de datos, con una relación polimórfica al usuario que lo mandó, así que puedes cruzarlo con lo que ya sabes de él. No hay script externo, no hay coste por usuario, y el widget tiene el aspecto de tu producto. A cambio, la bandeja y los avisos los escribes tú.
Y si no trabajas con Laravel
Los criterios son los mismos y sirven para valorar cualquier solución:
- Que capture el contexto sin preguntar. URL, navegador, tamaño de ventana e identidad. Si se los pides al usuario, no te los va a dar bien.
- Que permita grabar la pantalla y que el formulario se aparte mientras graba.
- Que los adjuntos vayan a almacenamiento privado por defecto.
- Que no acepte formatos ejecutables como imagen.
- Y que haya alguien mirando la bandeja. Es la parte que no da ninguna herramienta.
Dónde está el código
El paquete está en Packagist, el código en GitHub y la documentación del paquete recoge todas las opciones. Es MIT, sin dependencias de npm y sin peticiones a terceros.
En Crowd Legal está solo detrás de inicio de sesión, como pestaña en el lateral, con la bandeja de admin y las rutas protegidas que describe este artículo.
Si tienes un producto en producción y las incidencias te llegan por WhatsApp, esto se monta en una tarde y cambia la relación con tus usuarios. Lo trabajamos en aplicaciones web y SaaS a medida, y si nos cuentas cómo os llegan hoy te decimos qué montar.