diff --git a/lib/pantallas/pantalla_vacaciones.dart b/lib/pantallas/pantalla_vacaciones.dart index 3fb2d97..79328ad 100644 --- a/lib/pantallas/pantalla_vacaciones.dart +++ b/lib/pantallas/pantalla_vacaciones.dart @@ -842,10 +842,38 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> { ], ), const SizedBox(height: 16), - FilledButton.icon( - onPressed: _guardar, - icon: const Icon(Icons.check_rounded), - label: Text(l10n.saveRangeAction), + Row( + children: [ + Expanded( + child: FilledButton.icon( + onPressed: _guardar, + icon: const Icon(Icons.check_rounded), + label: Text(l10n.saveRangeAction), + ), + ), + // Fix `vacaciones-delete`: only when EDITING an existing + // range (never when creating one -- there is nothing to + // delete yet). Reuses the exact same confirmation dialog + // (`_confirmarEliminarRango`) and deletion method + // (`eliminarRangoVacaciones`) the swipe-to-delete gesture + // already uses on both `_HeroRangoActivo` and + // `_TarjetaRangoVacaciones` -- no new deletion path. + if (widget.rango != null) ...[ + const SizedBox(width: 10), + OutlinedButton.icon( + key: const ValueKey('vacation-delete-button'), + style: OutlinedButton.styleFrom( + foregroundColor: Theme.of(context).colorScheme.error, + side: BorderSide( + color: Theme.of(context).colorScheme.error, + ), + ), + onPressed: _eliminar, + icon: const Icon(Icons.delete_outline_rounded), + label: Text(l10n.deleteAction), + ), + ], + ], ), ], ), @@ -903,6 +931,21 @@ class _EditorVacacionesSheetState extends State<_EditorVacacionesSheet> { } if (mounted) Navigator.pop(context); } + + /// Fix `vacaciones-delete`: mirrors `_guardar`'s pop-on-success shape, + /// but confirms first (via the same `_confirmarEliminarRange` dialog the + /// swipe gesture uses) and calls `eliminarRangoVacaciones` instead of + /// saving. Only reachable when [widget.rango] is non-null (the delete + /// button itself is hidden otherwise). + Future _eliminar() async { + final rango = widget.rango; + if (rango == null) return; + final l10n = AppLocalizations.of(context); + final confirmado = await _confirmarEliminarRango(context, l10n); + if (!confirmado || !mounted) return; + await context.read().eliminarRangoVacaciones(rango.id); + if (mounted) Navigator.pop(context); + } } class _PickerButton extends StatelessWidget { diff --git a/test/pantallas/pantalla_vacaciones_test.dart b/test/pantallas/pantalla_vacaciones_test.dart index f0e15bd..6cc9dca 100644 --- a/test/pantallas/pantalla_vacaciones_test.dart +++ b/test/pantallas/pantalla_vacaciones_test.dart @@ -614,4 +614,144 @@ void main() { }, ); }); + + group('fix vacaciones-delete: el editor ofrece una accion de eliminar solo ' + 'al editar un rango existente, reusando _confirmarEliminarRango y ' + 'eliminarRangoVacaciones exactamente como el swipe', () { + testWidgets( + 'creando un rango NUEVO (CTA "Anadir rango"), el editor NO muestra ' + 'una accion de eliminar -- no hay nada que borrar todavia', + (tester) async { + final estado = await _crearEstado(); + addTearDown(estado.dispose); + + await tester.pumpWidget(_buildScreen(estado)); + await _pumpEstable(tester); + final l10n = AppLocalizations.of( + tester.element(find.byType(PantallaVacaciones)), + ); + + await tester.tap(find.text(l10n.addVacationRangeCta)); + await _pumpEstable(tester); + + expect(find.text(l10n.newVacationRangeTitle), findsOneWidget); + expect( + find.byKey(const ValueKey('vacation-delete-button')), + findsNothing, + ); + }, + ); + + testWidgets('editando un rango EXISTENTE (tap en su tarjeta), el editor SI ' + 'muestra una accion de eliminar junto al boton de guardar', ( + tester, + ) async { + final estado = await _crearEstado( + vacaciones: [ + RangoVacaciones( + id: 'f2', + nombre: 'Verano', + inicio: _hoyDia.add(const Duration(days: 20)), + fin: _hoyDia.add(const Duration(days: 25)), + ), + ], + ); + addTearDown(estado.dispose); + + await tester.pumpWidget(_buildScreen(estado)); + await _pumpEstable(tester); + + await tester.tap(find.byKey(const ValueKey('vacaciones-tarjeta-f2'))); + await _pumpEstable(tester); + + final l10n = AppLocalizations.of( + tester.element(find.byType(PantallaVacaciones)), + ); + expect(find.text(l10n.editVacationRangeTitle), findsOneWidget); + expect( + find.byKey(const ValueKey('vacation-delete-button')), + findsOneWidget, + ); + }); + + testWidgets( + 'tocar eliminar en el editor pide confirmacion (misma que el swipe); ' + 'cancelar conserva el rango y el editor sigue abierto', + (tester) async { + final estado = await _crearEstado( + vacaciones: [ + RangoVacaciones( + id: 'f2', + nombre: 'Verano', + inicio: _hoyDia.add(const Duration(days: 20)), + fin: _hoyDia.add(const Duration(days: 25)), + ), + ], + ); + addTearDown(estado.dispose); + + await tester.pumpWidget(_buildScreen(estado)); + await _pumpEstable(tester); + final l10n = AppLocalizations.of( + tester.element(find.byType(PantallaVacaciones)), + ); + + await tester.tap(find.byKey(const ValueKey('vacaciones-tarjeta-f2'))); + await _pumpEstable(tester); + + await tester.tap(find.byKey(const ValueKey('vacation-delete-button'))); + await _pumpEstable(tester); + expect(find.text(l10n.vacationDeleteConfirmTitle), findsOneWidget); + + await tester.tap(find.text(l10n.cancelAction)); + await _pumpEstable(tester); + + expect(estado.vacaciones, hasLength(1)); + expect(find.text(l10n.editVacationRangeTitle), findsOneWidget); + }, + ); + + testWidgets('tocar eliminar en el editor y confirmar llama a ' + 'eliminarRangoVacaciones y cierra el editor (mismo efecto que el ' + 'swipe, sin pasar por _guardar)', (tester) async { + final estado = await _crearEstado( + vacaciones: [ + RangoVacaciones( + id: 'f2', + nombre: 'Verano', + inicio: _hoyDia.add(const Duration(days: 20)), + fin: _hoyDia.add(const Duration(days: 25)), + ), + ], + ); + addTearDown(estado.dispose); + + await tester.pumpWidget(_buildScreen(estado)); + await _pumpEstable(tester); + final l10n = AppLocalizations.of( + tester.element(find.byType(PantallaVacaciones)), + ); + + await tester.tap(find.byKey(const ValueKey('vacaciones-tarjeta-f2'))); + await _pumpEstable(tester); + + await tester.tap(find.byKey(const ValueKey('vacation-delete-button'))); + await _pumpEstable(tester); + expect(find.text(l10n.vacationDeleteConfirmTitle), findsOneWidget); + + // `find.widgetWithText(FilledButton, ...)`, not a bare + // `find.text(...)`: the sheet's own OutlinedButton delete action + // (same "Eliminar" label) is still in the tree behind the dialog, + // so a bare text finder would ambiguously match both. + await tester.tap(find.widgetWithText(FilledButton, l10n.deleteAction)); + await _pumpEstable(tester); + + expect(estado.vacaciones, isEmpty); + expect( + find.text(l10n.editVacationRangeTitle), + findsNothing, + reason: 'the sheet must pop, exactly like a successful _guardar', + ); + }); + }); }