¿Qué haces cuando un usuario te escribe "la app se cerró sola" y eso es todo lo que sabes? Sin un stack trace, sin saber en qué pantalla estaba ni qué versión tenía instalada, lo único que queda es adivinar. Desde que empecé con Xamarin en 2017, la respuesta a esa pregunta casi siempre pasó por un servicio de terceros que se quedaba con los datos de mis usuarios.
Hoy quiero contarte sobre Avizoro, un proyecto open source (licencia MIT) en el que vengo trabajando: una plataforma de crash reporting y observabilidad para .NET que corre en tu propio servidor. Se integra en tu app .NET MAUI con una línea y te muestra crashes, ANRs, sesiones y rendimiento en un dashboard en tiempo real.
Vamos a ver qué captura, cómo levantar el servidor con Docker, cómo conectarlo a tu app y, sobre todo, cuándo te conviene y cuándo no.
¿Qué cambió y por qué importa?
Durante años, la respuesta por defecto para una app Xamarin fue Visual Studio App Center. Eso se acabó. Según la página oficial de retiro, App Center se retiró el 31 de marzo de 2025, y lo único que sigue vivo es Analytics & Diagnostics, con fecha de caducidad.

| Fecha | Qué pasó |
|---|---|
| 31 de marzo de 2025 | Se retira App Center (Build, Test, Distribution). Analytics & Diagnostics queda con prórroga. |
| 15 de abril de 2026 | Azure Monitor para móviles entra en Public Preview como camino de migración. |
| 19 de agosto de 2026 | Sale la versión 1.0.0 de Avizoro (todavía con el nombre IntelligenceKit). |
| 12 de octubre de 2026 | Avizoro 2.0.0: nuevo nombre y la session timeline. |
| Fin de marzo de 2027 | Termina el soporte de Analytics & Diagnostics en App Center. |
Para diagnósticos, Microsoft recomienda servicios comerciales (Datadog, Dynatrace, New Relic) o el nuevo Azure Monitor para móviles, que al momento de escribir esto sigue en preview. Todos son servicios en la nube de alguien más.
A mi parecer, faltaba una opción más: algo ligero, pensado para .NET MAUI y que puedas hospedar tú mismo. Esa es la idea detrás de Avizoro:
- Nativo de MAUI: captura de crashes en Android e iOS, contexto del dispositivo y captura de la última pantalla.
- Self-hosted: tus datos, tu servidor, tu base de datos.
- Una línea:
builder.UseAvizoro(dsn)y listo. - Todo .NET: el núcleo no depende de MAUI, así que también hay SDKs para ASP.NET Core, Blazor WebAssembly, WPF, WinForms, Avalonia y apps de consola.
Importante: Avizoro antes se publicaba como IntelligenceKit. Si vienes de la 1.x, la 2.0.0 cambia los IDs de los paquetes, los namespaces y los nombres de los tipos, pero el comportamiento es el mismo: la migración es un buscar y reemplazar. El detalle está en el CHANGELOG.
¿Cómo funciona por dentro?
Avizoro tiene tres piezas: el SDK que vive en tu app, el servidor que recibe y agrupa los eventos, y el dashboard (Blazor WebAssembly) que los muestra en vivo con SignalR.

Lo que más me gusta del diseño es la cola offline. Cuando tu app se cae, el proceso está a punto de morir y no es momento de hacer una llamada de red. Por eso el crash se escribe primero en una base SQLite local (avizoro.db3, dentro del AppDataDirectory) y se envía en el siguiente arranque. Lo mismo pasa si el usuario está sin conexión, o si el servidor responde 429 por el límite de peticiones: el evento se reintenta después y no se pierde.
Del lado del servidor, los eventos con el mismo tipo de excepción y el mismo primer frame de tu código se agrupan en un solo issue. Los frames de System.* y Microsoft.* se saltan, así que un NullReferenceException dentro de LINQ se agrupa por el método tuyo que llamó a LINQ, no por LINQ.
Paso 1: prueba el demo en dos minutos
Antes de tocar tu app, mira cómo se ve. El repo trae un stack de demo con dos semanas de datos de ejemplo (versiones, una regresión, ANRs, endpoints lentos y feedback de usuarios) en modo solo lectura.
Necesitas Docker. Si estás en Windows y todavía no lo tienes, te conté cómo instalarlo en Cómo instalar Docker en Windows 11 (WSL) sin Docker Desktop.
git clone https://github.com/avizoro/avizoro
cd avizoro
AVIZORO_READ_TOKEN=demo docker compose -f docker-compose.yml -f docker-compose.demo.yml up -d
En PowerShell, la variable de entorno se define aparte:
$env:AVIZORO_READ_TOKEN = "demo"
docker compose -f docker-compose.yml -f docker-compose.demo.yml up -d
Abre http://localhost:8080 y entra con el token demo.
Paso 2: levanta tu propio servidor
Cuando quieras usarlo de verdad, el mismo docker-compose.yml levanta PostgreSQL, el servidor y el dashboard:
cp .env.example .env # edita AVIZORO_READ_TOKEN con un valor largo y aleatorio
docker compose up -d
- Dashboard: http://localhost:8080
- API: http://localhost:7099
El servidor crea y migra el esquema solo al arrancar. Y no estás amarrado a PostgreSQL:
| Base de datos | ¿Cuándo usarla? |
|---|---|
| SQLite (por defecto) | Pruebas locales o equipos pequeños. Cero configuración. |
| PostgreSQL | Lo que usa el stack de Docker Compose. Mi opción para producción. |
| SQL Server | Si tu equipo ya lo opera. |
Ojo: en Docker el servidor corre en modoProduction, y ahí la lectura es fail-closed: si no definesAVIZORO_READ_TOKEN, el dashboard y la API de consulta quedan bloqueados en vez de servir datos sin protección. Y en producción, ponlo siempre detrás de HTTPS.
Si no quieres administrar una VM, el repo incluye plantillas para Render, Azure App Service y Railway en deploy/README.md.
Paso 3: crea un proyecto y copia el DSN
Entra al dashboard con tu token de administrador y ve a Projects → New project. La página te muestra el DSN del proyecto, que es una sola cadena con todo lo que el SDK necesita:
http://{projectKey}@{host}:{port}/{projectId}
También puedes crearlo desde un script:
curl -X POST http://localhost:7099/admin/projects \
-H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{ "projectId": "mi-app", "name": "Mi App" }'
Importante: la projectKey no es un secreto. Es un identificador público que viaja dentro de tu app y solo sirve para enrutar eventos. El servidor acepta eventos únicamente de proyectos registrados y aplica un límite de peticiones por IP (300 cada 60 segundos por defecto). Lo que sí es secreto es el token de administrador y la read key de cada proyecto.Paso 4: agrega el SDK a tu app .NET MAUI
Instala el paquete (trae Avizoro.Core como dependencia):
dotnet add package Avizoro.Maui
Y en MauiProgram.cs:
using Avizoro.Maui.Extensions;
public static class MauiProgram
{
#if ANDROID
// 10.0.2.2 es el alias del emulador de Android para tu máquina.
private const string Dsn = "http://projectKey@10.0.2.2:7099/mi-app";
#else
// El simulador de iOS, Mac Catalyst y Windows llegan directo a localhost.
private const string Dsn = "http://projectKey@localhost:7099/mi-app";
#endif
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseAvizoro(Dsn);
return builder.Build();
}
}
Eso es todo. Esa llamada registra la captura de crashes, la cola offline, las sesiones, la detección de ANR, los breadcrumbs de navegación, los tiempos de carga de cada página y la integración con ILogger. El nombre y la versión de la app se detectan solos.
Ojo: el emulador de Android no llega a tu máquina conlocalhost; usa10.0.2.2. Además, como en local el DSN usahttp, Android necesita permiso para tráfico sin cifrar. El sample del repo lo resuelve conandroid:usesCleartextTraffic="true"en elAndroidManifest.xml. Úsalo solo para desarrollo: en producción tu DSN debe serhttps.

Lo que es opt-in
Algunas funciones vienen apagadas porque envían más datos o pueden tocar información personal. Las activas tú:
builder
.UseMauiApp<App>()
.UseAvizoro(Dsn, o =>
{
o.Environment = "staging";
// Captura reducida de la última pantalla, adjunta al crash.
o.EnableScreenCapture = true;
o.ScreenCaptureExcludedPages.Add("LoginPage");
// En el siguiente arranque le pregunta al usuario qué estaba haciendo.
o.EnableCrashFeedbackPrompt = true;
o.CrashFeedbackTitle = "La app se cerró inesperadamente";
o.CrashFeedbackMessage = "¿Qué estabas haciendo? (opcional)";
o.CrashFeedbackAccept = "Enviar";
o.CrashFeedbackCancel = "Omitir";
// Graba toda la sesión como una línea de tiempo.
o.EnableSessionTimeline = true;
});
// Breadcrumbs y tiempos de cada llamada HTTP de este cliente.
builder.Services.AddHttpClient("api")
.AddAvizoroHandler();
La session timeline es la novedad de la 2.0.0. Normalmente los breadcrumbs viajan con un error, así que una sesión sin errores no deja rastro. Con esta opción, el SDK graba la sesión completa (pantallas, acciones, llamadas HTTP, logs y capturas) y el dashboard la muestra como una línea de tiempo que puedes ver en vivo mientras la app corre.
Paso 5: reporta tus propios eventos
Los crashes se capturan solos. Para lo demás, inyecta IAvizoro:
using Avizoro.Core.Services;
public class CheckoutViewModel(IAvizoro avizoro)
{
public async Task PayAsync()
{
avizoro.SetUser("user-42"); // opcional: un id anónimo
avizoro.SetTag("plan", "pro");
avizoro.AddBreadcrumb("Tocó Pagar"); // viaja con el siguiente evento
try
{
// ... tu lógica de pago
}
catch (Exception ex)
{
await avizoro.TrackExceptionAsync(ex);
}
}
}
El evento aparece de inmediato en Events y, agrupado, en Issues.
¿Qué captura exactamente?
| Qué | Cómo | ¿Activo por defecto? |
|---|---|---|
| Crashes fatales | Excepciones administradas en todas las plataformas y excepciones Java en Android | ✅ |
| ANR | Un watchdog detecta el hilo de UI bloqueado más de 5 s (AnrThreshold) |
✅ |
| Sesiones | Una por cada periodo en primer plano; de ahí salen crash-free sessions y crash-free users | ✅ |
| Rendimiento | Arranque en frío, carga de cada página y HTTP (con AddAvizoroHandler) |
✅ |
| Breadcrumbs | Navegación, ILogger, HTTP y ciclo de vida |
✅ |
| Limpieza de PII | Enmascara correos, tokens, JWT y números de tarjeta | ✅ |
| Última pantalla | Captura reducida adjunta al crash | ❌ opt-in |
| Feedback tras un crash | Pregunta en el siguiente arranque | ❌ opt-in |
| Session timeline | La sesión completa, en vivo | ❌ opt-in |
Y del lado del dashboard tienes ciclo de vida de los issues (resolver, ignorar, asignar), detección de regresiones (un issue resuelto que vuelve a aparecer), salud por versión, y alertas por webhook, Slack, Teams, Discord o correo.
Stack traces legibles en Release
En Release pierdes archivo y número de línea, y en Android R8 ofusca los frames de Java. Avizoro trae un target de MSBuild que sube los símbolos después de compilar:
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<AvizoroUploadSymbols>true</AvizoroUploadSymbols>
<AvizoroServerUrl>https://avizoro.example.com</AvizoroServerUrl>
<AvizoroProjectId>mi-app</AvizoroProjectId>
</PropertyGroup>
El token de administrador va en la variable de entorno AVIZORO_ADMIN_TOKEN, nunca en el repositorio. Si la subida falla, verás un warning, pero el build no se rompe.
¿Cuándo usar Avizoro y cuándo no?
No todo es color de rosa, y prefiero decírtelo yo 😅.

✅ Úsalo si:
- Necesitas que los datos de tus usuarios se queden en tu infraestructura.
- Tu app es .NET MAUI y quieres algo pensado para ella, sin configurar un SDK por plataforma.
- Tienes backend en ASP.NET Core y quieres ver la app y la API en el mismo lugar.
- Vienes de App Center y buscas dónde poner tus diagnósticos antes de marzo de 2027.
❌ Evítalo (por ahora) si:
- Necesitas crashes nativos en iOS. Hoy la captura en iOS y Mac Catalyst cubre excepciones administradas; los
NSExceptiony las señales nativas están en el roadmap. - Necesitas cuentas por usuario y roles en el dashboard. El acceso es por token de administrador o por read key de proyecto.
- No quieres operar un servidor. Self-hosted significa que los respaldos, el TLS y las actualizaciones son tu responsabilidad.
- Tu app todavía es Xamarin o está en .NET 9 o anterior: el SDK apunta a
net10.0.
Checklist
- ✅ Probé el demo con
docker composey el tokendemo. - ✅ Levanté mi servidor con un
AVIZORO_READ_TOKENlargo y aleatorio. - ✅ Creé el proyecto en Projects y copié el DSN.
- ✅ Instalé
Avizoro.Mauiy agreguéUseAvizoro(dsn)enMauiProgram.cs. - ✅ En el emulador de Android usé
10.0.2.2en vez delocalhost. - ✅ Decidí qué opciones opt-in activar (última pantalla, feedback, session timeline).
- ✅ Excluí las pantallas sensibles con
ScreenCaptureExcludedPages. - ✅ Configuré
AvizoroUploadSymbolspara los builds de Release. - ✅ Puse el servidor detrás de HTTPS antes de salir a producción.
- ✅ Creé una alerta para issues nuevos y regresiones.
Conclusión
Ya viste:
- Por qué el retiro de App Center deja un hueco en los diagnósticos de apps .NET.
- Qué es Avizoro y cómo están organizadas sus tres piezas.
- Cómo probar el demo y levantar tu propio servidor con Docker.
- Cómo integrarlo en .NET MAUI con una línea y qué funciones son opt-in.
- Cómo reportar excepciones, tags y breadcrumbs desde tu código.
- Cuándo te conviene y cuándo todavía no.
Mi recomendación personal: empieza por el demo. En dos minutos sabes si lo que muestra el dashboard es lo que necesitas, sin tocar tu app. Si te convence, conéctalo primero a tu build de staging y deja la session timeline para cuando ya tengas claro qué datos quieres guardar.
El proyecto es open source y está recién salido del horno, así que el feedback vale oro. ¿Lo probaste? ¿Te falta algo para poder usarlo en tu app? Escríbeme en X (Twitter) o abre un issue en el repositorio.
Nos leemos pronto 😊
Referencias:
