16 escenarios de MAUI y NotasApp. Lee el caso, predice qué ocurrirá y
escribe tu solución antes de consultar la propuesta. Los fragmentos
ilustran el concepto; requieren el proyecto, paquetes, imports y
configuración de plataforma correspondientes.
Cómo resolver cada caso
Describe el síntoma y la causa probable.
Elige un diagnóstico y explica qué evidencia lo apoya.
Propón el cambio mínimo y cómo lo comprobarías.
Contrasta la solución y responde la variación del caso.
16 escenarios
CASO 01 · MVVM
El saludo cambia en memoria, pero no en pantalla
El botón cambia Saludo. El Label sigue mostrando el valor anterior y
no aparece ninguna excepción.
Código que debes analizar
public class SaludoViewModel
{
public string Saludo { get; set; } = "Hola";
public void Saludar() => Saludo = "Hola, Luis";
}
// Label: Text="{Binding Saludo}"
Decide antes de mirar
Ver solución y respuesta modelo
Qué resuelve
OneWay sí permite actualizar el destino cuando la fuente notifica.
Una propiedad automática no avisa por sí sola. El Toolkit genera
la notificación y separa la acción de los controles.
Implementar notificación o usar ObservableProperty.
Enlazar el comando sin manipular el Label.
Ahora cambia el escenario
¿Qué cambiaría si faltara BindingContext en vez de la
notificación?
Ver respuesta de la variación
Sin BindingContext, el enlace no encuentra la fuente. Asignaría
el ViewModel como contexto y comprobaría el nombre de Saludo;
solo después revisaría su notificación.
CASO 02 · MVVM
El formulario no devuelve lo escrito
Un Entry muestra el valor inicial. Al guardar, el modelo todavía
tiene el valor antiguo.
Código que debes analizar
<Entry Text="{Binding Nombre, Mode=OneWay}" />
Decide antes de mirar
Ver solución y respuesta modelo
Qué resuelve
OneWay solo comunica fuente→UI. Para edición necesitas el camino
inverso y una propiedad escribible; notificación también importa
para cambios posteriores desde el modelo.
Una propuesta de corrección
<Entry Text="{Binding Nombre, Mode=TwoWay}" />
// En el ViewModel:
[ObservableProperty] private string nombre = "";
Cómo abordarlo
Identificar la dirección esperada del dato.
Comprobar setter y BindingContext.
Usar TwoWay para entrada editable.
Ahora cambia el escenario
¿Por qué un Label normalmente no necesita TwoWay?
Ver respuesta de la variación
Label normalmente muestra información y no la edita. OneWay es
suficiente para recibir cambios notificados desde la fuente; no
hay un valor escrito por el usuario que devolver.
CASO 03 · Colecciones
La fila no refleja una edición
Agregar y quitar notas funciona. Cambiar el texto de una nota no
actualiza la fila.
Código que debes analizar
public class Nota { public string Texto { get; set; } }
public ObservableCollection<Nota> Notas { get; } = new();
// Más tarde:
Notas[0].Texto = "Texto corregido";
Decide antes de mirar
Ver solución y respuesta modelo
Qué resuelve
CollectionChanged informa altas, bajas y reemplazos. Cambios
internos del elemento requieren PropertyChanged en ese objeto.
Esto es distinto de persistir el cambio.
Una propuesta de corrección
public partial class Nota : ObservableObject
{
[ObservableProperty] private string texto = "";
}
// La colección permanece igual.
Notas[0].Texto = "Texto corregido";
Cómo abordarlo
Distinguir cambio de colección de cambio de elemento.
Hacer observable el modelo editable.
Guardar aparte si también debe persistir.
Ahora cambia el escenario
¿Qué ocurriría al reemplazar el elemento completo en vez de editar
su Texto?
Ver respuesta de la variación
Reemplazar Notas[0] emite un cambio de colección y permite
mostrar el nuevo elemento. Si después se editan propiedades del
elemento, ese objeto todavía necesita notificar esos cambios.
CASO 04 · Async
La pantalla se congela al sincronizar
Al tocar Sincronizar, la UI deja de responder mientras espera la
red.
Result bloquea el hilo llamador. await permite suspender la
espera. No garantiza que un bucle pesado de CPU no bloquee ni
reemplaza el manejo de errores.
Una propuesta de corrección
[RelayCommand]
private async Task SincronizarAsync()
{
try
{
Mensaje = await _http.GetStringAsync("posts");
}
catch (HttpRequestException)
{
Mensaje = "No se pudo contactar el servidor.";
}
}
Cómo abordarlo
Localizar Result, Wait o trabajo síncrono largo.
Propagar await hasta el comando.
Manejar errores y cancelación según el flujo.
Ahora cambia el escenario
Si el problema fuera calcular millones de elementos, ¿bastaría con
poner async?
Ver respuesta de la variación
No. async no mueve automáticamente el cálculo a otro hilo. Para
trabajo costoso de CPU evaluaría Task.Run, cancelación y
límites; mantendría las actualizaciones de UI en su contexto
adecuado.
CASO 05 · Async
El botón queda bloqueado después del error
La API falla y IsBusy queda true. El siguiente toque sale
inmediatamente.
Una excepción evita llegar a la última línea. finally restaura el
estado tanto en éxito como en error. El bloqueo de doble toque y
la política de reintento son responsabilidades complementarias.
¿Cómo usarías IsRunning de un comando asíncrono del Toolkit?
Ver respuesta de la variación
Enlazaría el indicador de actividad a IsRunning del comando
asíncrono. Revisaría su política de ejecuciones concurrentes y
CanExecute; seguiría manejando errores y cancelación en la
operación.
CASO 06 · SQLite
La primera consulta dice que no existe la tabla
El constructor inicia CreateTableAsync. La pantalla consulta
inmediatamente y falla de forma intermitente.
Código que debes analizar
public NotaRepository()
{
_db = new SQLiteAsyncConnection(ruta);
_db.CreateTableAsync<Nota>(); // tarea ignorada
}
public Task<List<Nota>> LeerAsync()
=> _db.Table<Nota>().ToListAsync();
Decide antes de mirar
Ver solución y respuesta modelo
Qué resuelve
Iniciar la creación no significa haberla terminado. Espera una
tarea compartida de inicialización antes de cada operación; así
varias consultas no lanzan inicializaciones independientes.
Manejar un fallo de inicialización sin informar éxito.
Ahora cambia el escenario
¿Por qué Task es útil aquí aunque el constructor no pueda ser
async?
Ver respuesta de la variación
Un constructor no puede usar await directamente. Puede guardar
una Task de inicialización para que los métodos posteriores la
esperen, o se puede usar una fábrica asíncrona que entregue el
objeto ya preparado.
CASO 07 · Persistencia
La nota desaparece al volver
El usuario escribe y la app guarda únicamente al intentar cerrarse.
El proceso termina abruptamente.
Código que debes analizar
string borrador = entrada.Text;
// Se espera a Destroying para guardarlo.
Decide antes de mirar
Ver solución y respuesta modelo
Qué resuelve
Los eventos de ciclo de vida son útiles, pero no una garantía de
completar trabajo antes de cada terminación. Guarda al confirmar
y, si procede, con autosave controlado; reconstruye al iniciar.
Esperar y confirmar el resultado antes de mostrar Guardado.
Ahora cambia el escenario
¿Qué límite tendría guardar solo en Preferences un conjunto grande
de notas?
Ver respuesta de la variación
Preferences sirve para ajustes pequeños de clave/valor, no como
sustituto de una base estructurada grande. Usaría SQLite y un
repositorio para notas, consultas y actualizaciones.
CASO 08 · Shell
El detalle abre la nota equivocada
La página se reutiliza y conserva datos anteriores. Cambiar el id
recibido no vuelve a cargar la nota.
Código que debes analizar
public int Id { get; set; }
// La nota se carga solo una vez en el constructor.
Decide antes de mirar
Ver solución y respuesta modelo
Qué resuelve
La ruta identifica el registro. Debes reaccionar al parámetro,
validar tipo y cargar el dato actualizado. IQueryAttributable
ofrece recepción explícita; evita ocultar errores de carga en
async void.
Una propuesta de corrección
// Fragmento del ViewModel:
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
if (query.TryGetValue("id", out var raw) && raw is int id)
Id = id;
}
// La página espera la carga al aparecer:
protected override async void OnAppearing()
{
base.OnAppearing();
try { await _vm.CargarAsync(); }
catch (Exception) { await DisplayAlertAsync("Error", "No se pudo cargar", "OK"); }
}
// Navegación:
await Shell.Current.GoToAsync("detalle",
new ShellNavigationQueryParameters { ["id"] = nota.Id });
Cómo abordarlo
Registrar la ruta y validar el parámetro.
Asignar el id al destino.
Esperar carga y manejar ausencia o error.
Ahora cambia el escenario
Si navegaras con una cadena de consulta, ¿cómo validarías y
convertirías el id?
Ver respuesta de la variación
Leer el valor recibido, convertirlo con int.TryParse, rechazar
IDs inválidos y cargar el registro correspondiente. Para valores
de texto construidos en una URI, codificaría los componentes;
nunca asumiría que cualquier parámetro es válido.
CASO 09 · Permisos
La cámara no abre después de negar
La app solicita repetidamente permiso y no permite seguir sin foto.
Código que debes analizar
while (await Permissions.RequestAsync<Permissions.Camera>()
!= PermissionStatus.Granted) { }
Decide antes de mirar
Ver solución y respuesta modelo
Qué resuelve
No fuerces un permiso. Consulta estado, solicita desde un contexto
válido y permite alternativa. ShouldShowRationale=false no basta
por sí solo para inferir bloqueo permanente.
Una propuesta de corrección
var status = await Permissions.CheckStatusAsync<Permissions.Camera>();
if (status != PermissionStatus.Granted)
status = await Permissions.RequestAsync<Permissions.Camera>();
if (status != PermissionStatus.Granted)
{
Mensaje = "Puedes guardar la nota sin foto.";
return;
}
// Ofrecer Ajustes solo si procede y el usuario lo elige.
Cómo abordarlo
Declarar uso en cada plataforma.
Solicitar al usar la función, no en bucle.
Ofrecer alternativa y Ajustes cuando proceda.
Ahora cambia el escenario
¿Por qué no se debe solicitar cámara para guardar una nota de
texto?
Ver respuesta de la variación
Guardar texto no necesita cámara. Solicitaría el permiso solo
cuando el usuario elija adjuntar una foto; si lo niega, la nota
seguiría siendo utilizable.
CASO 10 · Capacidades
Cancelar una foto rompe el formulario
El usuario cierra la cámara. El código intenta leer FileName de un
resultado nulo.
Código que debes analizar
var foto = await MediaPicker.Default.CapturePhotoAsync();
var ruta = Path.Combine(FileSystem.AppDataDirectory, foto.FileName);
Decide antes de mirar
Ver solución y respuesta modelo
Qué resuelve
Capturar no garantiza un archivo: cancelación puede devolver null
y hay excepciones de permisos/dispositivo. Copia el contenido al
almacenamiento privado si debe conservarse.
Una propuesta de corrección
if (!MediaPicker.Default.IsCaptureSupported) return;
// Previamente: gestionar permisos según plataforma.
var foto = await MediaPicker.Default.CapturePhotoAsync();
if (foto is null) return;
var ruta = Path.Combine(FileSystem.AppDataDirectory, foto.FileName);
using var origen = await foto.OpenReadAsync();
using var destino = File.Create(ruta);
await origen.CopyToAsync(destino);
Cómo abordarlo
Separar captura disponible de permiso concedido.
Manejar cancelación y fallos en el llamador.
Copiar la foto con política de nombres y limpieza.
Ahora cambia el escenario
¿Qué harías para evitar sobrescribir dos fotos con el mismo
nombre?
Ver respuesta de la variación
Generaría un nombre propio único, por ejemplo un GUID y una
extensión permitida, y guardaría esa ruta en el modelo. También
definiría limpieza de archivos huérfanos y trataría fallos de
copia.
CASO 11 · Conectividad
Hay WiFi, pero la API falla
NetworkAccess indica Internet. El servidor responde500 y la app
elimina la nota pendiente como si se hubiera sincronizado.
Una respuesta HTTP de error no siempre lanza excepción en
Send/Post. Comprueba status y contrato de la API. Si falla,
conserva el pendiente y evita reintentos que creen duplicados.
Una propuesta de corrección
using var respuesta = await _http.PostAsJsonAsync("notas", dto, ct);
respuesta.EnsureSuccessStatusCode();
// Validar confirmación/ID devuelto según contrato del servidor.
nota.Pendiente = false;
await _repo.GuardarAsync(nota);
Cómo abordarlo
Guardar local antes de depender de red.
Comprobar status y confirmación.
Conservar pendiente ante fallo y planificar reintento
idempotente.
Ahora cambia el escenario
¿Qué ocurre si el servidor guardó, pero se perdió su respuesta?
Ver respuesta de la variación
El cliente no sabe si la operación llegó a completarse. Usaría
una identidad estable o clave de idempotencia aceptada por el
servidor y reconciliación antes de repetir, para evitar
registros duplicados.
CASO 12 · API y JSON
El JSON es válido, pero el objeto no coincide
La API usa title y body. El DTO espera otros nombres sin un mapeo
explícito.
Código que debes analizar
public record NotaDto(string Titulo, string Cuerpo);
// Respuesta: { "title":"Compra", "body":"Leche" }
Decide antes de mirar
Ver solución y respuesta modelo
Qué resuelve
Validez sintáctica JSON no garantiza correspondencia semántica.
Define DTO acorde al contrato o atributos JsonPropertyName, valida
requeridos y transforma al modelo local.
Una propuesta de corrección
public record NotaDto(
[property: JsonPropertyName("title")] string Titulo,
[property: JsonPropertyName("body")] string Cuerpo);
// Requiere System.Text.Json.Serialization.
// Validar contenido recibido antes de persistir.
Cómo abordarlo
Inspeccionar nombres y tipos reales del payload.
Definir DTO y mapeo explícito.
Validar y convertir al modelo de la app.
Ahora cambia el escenario
¿Cómo tratarías un body ausente o null?
Ver respuesta de la variación
Declararía el campo como anulable si el contrato permite
ausencia, validaría el DTO después de deserializar y decidiría
si rechazar el dato o aplicar un valor predeterminado
documentado. No guardaría datos incompletos silenciosamente.
CASO 13 · Dependencias
El detalle conserva estado de otra nota
Una página de detalle y su ViewModel se registran Singleton y
guardan datos de la nota anterior.
Singleton comparte instancia y estado por contenedor. Para detalle
independiente puede convenir Transient;no elimina la necesidad de
recibir parámetros y recargar correctamente. Elige vida por
necesidades, no por una regla única.
¿Cuándo sí sería útil compartir un ViewModel Singleton?
Ver respuesta de la variación
Un Singleton puede servir para estado compartido de sesión o una
lista que debe conservarse mientras vive la app. Evitaría usarlo
por defecto para estado exclusivo de una nota y controlaría sus
suscripciones.
CASO 14 · Interfaz
La lista casi no ocupa espacio
La fila de la lista se mide como Auto. La pantalla tiene espacio,
pero no se reparte como esperaba el diseño.
Auto mide contenido;la fila estrella recibe espacio restante
dentro de una restricción de tamaño. Comprueba también el
contenedor padre y evita envolver una lista virtualizada de manera
que pierda sus límites.
Las estrellas reparten el espacio disponible en proporción1:2:
la segunda columna recibe el doble de la primera, después de
considerar otras restricciones y tamaños.
CASO 15 · Recursos
El modo oscuro cambia el fondo, pero el texto desaparece
Se cambia el fondo por tema, pero el texto mantiene un color oscuro
fijo.
El tema no corrige automáticamente cualquier valor fijo. Usa
recursos coherentes para texto/fondo y revisa contraste, estados y
controles completos.
Una propuesta de corrección
<Label Text="Mis notas"
TextColor="{AppThemeBinding Light=#22282D, Dark=#E6EAED}"
BackgroundColor="{AppThemeBinding Light=White, Dark=#12161A}" />
// En la app, reutiliza colores del ResourceDictionary.
Cómo abordarlo
Identificar pares fondo/texto.
Definir recursos y valores por tema.
Probar contraste y estados interactivos.
Ahora cambia el escenario
¿Por qué un ResourceDictionary evita repetir colores por toda la
app?
Ver respuesta de la variación
Un ResourceDictionary centraliza valores con nombre. Cambiar un
color actualiza sus referencias, reduce inconsistencias y
permite mantener pares Light/Dark reutilizables.
CASO 16 · C# moderno
La nota no tiene ubicación
Latitud es double?. Se lee Value aunque la nota fue creada sin GPS.
Código que debes analizar
double? latitud = nota.Latitud;
Mostrar(latitud.Value);
Decide antes de mirar
Ver solución y respuesta modelo
Qué resuelve
Nullable representa ausencia legítima. Cero es una coordenada
válida, no sinónimo universal de sin ubicación. Usa pattern
matching o HasValue y muestra un estado adecuado.
Una propuesta de corrección
if (nota.Latitud is double lat)
Mostrar(lat);
else
Mensaje = "Nota sin ubicación.";
Cómo abordarlo
Entender por qué el dato es opcional.
Validar antes de acceder a Value.
Mostrar ausencia sin inventar coordenadas.
Ahora cambia el escenario
¿Cuál es la diferencia entre string? y double? en el sistema de
tipos?
Ver respuesta de la variación
double? es Nullable<double>, que permite representar
ausencia de un tipo valor. string? es una anotación de
nulabilidad para un tipo referencia que habilita advertencias
del compilador; no crea un nuevo tipo de string en ejecución.