diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..245d8b4 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,14 @@ +{ + "permissions": { + "allow": [ + "mcp__github__list_issues", + "Bash(gh issue:*)", + "Bash(bash scripts/propagate-version.sh)", + "Bash(dart format:*)", + "Bash(flutter analyze:*)", + "Bash(flutter test:*)", + "Bash(bash scripts/propagate-version.sh --check)", + "Bash(gh pr:*)" + ] + } +} diff --git a/assets/version.json b/assets/version.json index d5efc68..6f9087e 100644 --- a/assets/version.json +++ b/assets/version.json @@ -1 +1 @@ -{"version": "0.1.16"} +{"version": "0.1.17"} diff --git a/lib/features/apgar/domain/apgar_calculator.dart b/lib/features/apgar/domain/apgar_calculator.dart new file mode 100644 index 0000000..d5c22e9 --- /dev/null +++ b/lib/features/apgar/domain/apgar_calculator.dart @@ -0,0 +1,56 @@ +import 'package:emerkit/shared/domain/entities/severity.dart'; + +class ApgarResult { + final int appearance; + final int pulse; + final int grimace; + final int activity; + final int respiration; + final int total; + final Severity severity; + + const ApgarResult({ + required this.appearance, + required this.pulse, + required this.grimace, + required this.activity, + required this.respiration, + required this.total, + required this.severity, + }); +} + +class ApgarCalculator { + const ApgarCalculator(); + + ApgarResult calculate({ + required int appearance, + required int pulse, + required int grimace, + required int activity, + required int respiration, + }) { + final total = appearance + pulse + grimace + activity + respiration; + return ApgarResult( + appearance: appearance, + pulse: pulse, + grimace: grimace, + activity: activity, + respiration: respiration, + total: total, + severity: _severity(total), + ); + } + + Severity _severity(int total) { + if (total >= 7) { + return const Severity(label: 'Normal', level: SeverityLevel.mild); + } + if (total >= 4) { + return const Severity( + label: 'Depresion moderada', level: SeverityLevel.moderate); + } + return const Severity( + label: 'Depresion severa', level: SeverityLevel.severe); + } +} diff --git a/lib/features/apgar/domain/apgar_data.dart b/lib/features/apgar/domain/apgar_data.dart new file mode 100644 index 0000000..48d5904 --- /dev/null +++ b/lib/features/apgar/domain/apgar_data.dart @@ -0,0 +1,72 @@ +import 'package:emerkit/shared/domain/entities/scored_item.dart'; +import 'package:emerkit/shared/domain/entities/clinical_reference.dart'; + +class ApgarData { + ApgarData._(); + + static const appearanceResponses = [ + ScoredItem(label: 'Completamente rosado', score: 2), + ScoredItem(label: 'Acrocianosis', score: 1), + ScoredItem(label: 'Cianosis/palidez generalizada', score: 0), + ]; + + static const pulseResponses = [ + ScoredItem(label: '\u2265 100 lpm', score: 2), + ScoredItem(label: '< 100 lpm', score: 1), + ScoredItem(label: 'Ausente', score: 0), + ]; + + static const grimaceResponses = [ + ScoredItem(label: 'Llanto vigoroso, tos/estornudo', score: 2), + ScoredItem(label: 'Mueca, llanto debil', score: 1), + ScoredItem(label: 'Sin respuesta', score: 0), + ]; + + static const activityResponses = [ + ScoredItem(label: 'Movimiento activo', score: 2), + ScoredItem(label: 'Flexion debil', score: 1), + ScoredItem(label: 'Flacido', score: 0), + ]; + + static const respirationResponses = [ + ScoredItem(label: 'Llanto fuerte, regular', score: 2), + ScoredItem(label: 'Lenta, irregular', score: 1), + ScoredItem(label: 'Ausente', score: 0), + ]; + + static const infoSections = { + '\u00bfQue es?': + 'El Test de Apgar es una escala de valoracion neonatal desarrollada por Virginia Apgar en 1952. ' + 'Evalua cinco parametros del recien nacido (Apariencia, Pulso, Gesticulacion, Actividad y ' + 'Respiracion) al minuto 1 y 5 de vida. Es la herramienta estandar para la evaluacion rapida ' + 'del estado del neonato tras el nacimiento.', + 'Interpretacion': '7-10: Normal (recien nacido en buenas condiciones)\n' + '4-6: Depresion moderada (puede requerir estimulacion o soporte)\n' + '0-3: Depresion severa (requiere reanimacion inmediata)\n\n' + 'Se evalua al minuto 1 (estado inicial) y al minuto 5 (respuesta a reanimacion).\n' + 'Si Apgar a los 5 minutos < 7, se repite cada 5 minutos hasta los 20 minutos.', + 'Cuando aplicarlo': 'Atencion al parto extrahospitalario\n' + 'Nacimiento en domicilio, via publica o ambulancia\n' + 'Valoracion inicial del recien nacido\n' + 'Seguimiento de la respuesta a reanimacion neonatal', + 'Limitaciones': 'Es una valoracion subjetiva y puede variar entre observadores.\n' + 'No predice mortalidad ni morbilidad neurologica a largo plazo por si solo.\n' + 'Prematuros pueden tener puntuaciones mas bajas sin implicar patologia.\n' + 'No sustituye la monitorizacion continua del recien nacido.', + }; + + static const references = [ + ClinicalReference( + 'Apgar V. A proposal for a new method of evaluation of the newborn infant. Curr Res Anesth Analg. 1953;32(4):260-267.', + ), + ClinicalReference( + 'American Academy of Pediatrics Committee on Fetus and Newborn. The Apgar Score. Pediatrics. 2015;136(4):819-822.', + ), + ClinicalReference( + 'Neonatal Resuscitation Program (NRP). 8th Edition. AAP, 2021.', + ), + ClinicalReference( + 'ERC Guidelines 2025. Resuscitation and support of transition of babies at birth.', + ), + ]; +} diff --git a/lib/features/apgar/presentation/apgar_screen.dart b/lib/features/apgar/presentation/apgar_screen.dart new file mode 100644 index 0000000..fa834aa --- /dev/null +++ b/lib/features/apgar/presentation/apgar_screen.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:emerkit/shared/domain/entities/severity.dart'; +import 'package:emerkit/shared/presentation/theme/app_colors.dart'; +import 'package:emerkit/shared/presentation/widgets/tool_screen_base.dart'; +import 'package:emerkit/shared/presentation/widgets/result_banner.dart'; +import 'package:emerkit/shared/presentation/widgets/scored_item_selector.dart'; +import 'package:emerkit/shared/presentation/widgets/tool_info_panel.dart'; +import '../domain/apgar_calculator.dart'; +import '../domain/apgar_data.dart'; + +class ApgarScreen extends StatefulWidget { + const ApgarScreen({super.key}); + + @override + State createState() => _ApgarScreenState(); +} + +class _ApgarScreenState extends State { + static const _calculator = ApgarCalculator(); + int _appearance = 2, + _pulse = 2, + _grimace = 2, + _activity = 2, + _respiration = 2; + + ApgarResult get _result => _calculator.calculate( + appearance: _appearance, + pulse: _pulse, + grimace: _grimace, + activity: _activity, + respiration: _respiration, + ); + + void _reset() => setState(() { + _appearance = 2; + _pulse = 2; + _grimace = 2; + _activity = 2; + _respiration = 2; + }); + + @override + Widget build(BuildContext context) { + final r = _result; + return ToolScreenBase( + title: 'Test de Apgar', + onReset: _reset, + resultWidget: ResultBanner( + value: '${r.total}', + label: r.severity.label, + subtitle: + 'A: ${r.appearance} P: ${r.pulse} G: ${r.grimace} A: ${r.activity} R: ${r.respiration}', + color: r.severity.level.color, + severityLevel: r.severity.level, + ), + toolBody: ListView( + padding: const EdgeInsets.all(12), + children: [ + ScoredItemSelector( + title: 'Apariencia (A)', + icon: Icons.palette, + items: ApgarData.appearanceResponses, + selectedScore: _appearance, + onChanged: (v) => setState(() => _appearance = v), + ), + ScoredItemSelector( + title: 'Pulso (P)', + icon: Icons.favorite, + items: ApgarData.pulseResponses, + selectedScore: _pulse, + onChanged: (v) => setState(() => _pulse = v), + ), + ScoredItemSelector( + title: 'Gesticulacion (G)', + icon: Icons.sentiment_satisfied, + items: ApgarData.grimaceResponses, + selectedScore: _grimace, + onChanged: (v) => setState(() => _grimace = v), + ), + ScoredItemSelector( + title: 'Actividad (A)', + icon: Icons.fitness_center, + items: ApgarData.activityResponses, + selectedScore: _activity, + onChanged: (v) => setState(() => _activity = v), + ), + ScoredItemSelector( + title: 'Respiracion (R)', + icon: Icons.air, + items: ApgarData.respirationResponses, + selectedScore: _respiration, + onChanged: (v) => setState(() => _respiration = v), + ), + ], + ), + infoBody: const ToolInfoPanel( + sections: ApgarData.infoSections, + references: ApgarData.references, + ), + ); + } +} + +extension _SeverityColor on SeverityLevel { + Color get color { + switch (this) { + case SeverityLevel.mild: + return AppColors.severityMild; + case SeverityLevel.moderate: + return AppColors.severityModerate; + case SeverityLevel.severe: + return AppColors.severitySevere; + } + } +} diff --git a/lib/features/home/presentation/tool_registry.dart b/lib/features/home/presentation/tool_registry.dart index 49d02b0..843c25c 100644 --- a/lib/features/home/presentation/tool_registry.dart +++ b/lib/features/home/presentation/tool_registry.dart @@ -186,6 +186,12 @@ class ToolRegistry { icon: Icons.science_outlined, route: '/diluciones', category: ToolCategory.tecnicas), + ToolEntry( + id: 'apgar', + name: 'Apgar', + icon: Icons.child_friendly, + route: '/apgar', + category: ToolCategory.valoracion), // Protección ToolEntry( id: 'epi', diff --git a/lib/shared/presentation/router/app_router.dart b/lib/shared/presentation/router/app_router.dart index b2262c4..58949b4 100644 --- a/lib/shared/presentation/router/app_router.dart +++ b/lib/shared/presentation/router/app_router.dart @@ -33,6 +33,7 @@ import 'package:emerkit/features/gps_converter/presentation/gps_converter_screen import 'package:emerkit/features/wallace/presentation/wallace_screen.dart'; import 'package:emerkit/features/shock_index/presentation/shock_index_screen.dart'; import 'package:emerkit/features/diluciones/presentation/diluciones_screen.dart'; +import 'package:emerkit/features/apgar/presentation/apgar_screen.dart'; final appRouter = GoRouter( initialLocation: '/', @@ -80,5 +81,6 @@ final appRouter = GoRouter( GoRoute(path: '/wallace', builder: (_, __) => const WallaceScreen()), GoRoute(path: '/shock-index', builder: (_, __) => const ShockIndexScreen()), GoRoute(path: '/diluciones', builder: (_, __) => const DilucionesScreen()), + GoRoute(path: '/apgar', builder: (_, __) => const ApgarScreen()), ], ); diff --git a/pubspec.yaml b/pubspec.yaml index a681e46..532ba3a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: emerkit description: EmerKit - Herramientas clinicas para profesionales de emergencias publish_to: 'none' -version: 0.1.16+116 +version: 0.1.17+117 environment: sdk: ^3.5.0 diff --git a/test/domain/apgar_calculator_test.dart b/test/domain/apgar_calculator_test.dart new file mode 100644 index 0000000..70267f8 --- /dev/null +++ b/test/domain/apgar_calculator_test.dart @@ -0,0 +1,111 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:emerkit/shared/domain/entities/severity.dart'; +import 'package:emerkit/features/apgar/domain/apgar_calculator.dart'; + +void main() { + const calculator = ApgarCalculator(); + + group('Recien nacido sano (puntuacion maxima)', () { + test('Apgar 10 es Normal', () { + final r = calculator.calculate( + appearance: 2, + pulse: 2, + grimace: 2, + activity: 2, + respiration: 2, + ); + expect(r.total, 10); + expect(r.severity.label, 'Normal'); + expect(r.severity.level, SeverityLevel.mild); + }); + }); + + group('Depresion severa (puntuacion minima)', () { + test('Apgar 0 es Depresion severa', () { + final r = calculator.calculate( + appearance: 0, + pulse: 0, + grimace: 0, + activity: 0, + respiration: 0, + ); + expect(r.total, 0); + expect(r.severity.label, 'Depresion severa'); + expect(r.severity.level, SeverityLevel.severe); + }); + }); + + group('Frontera severa/moderada (Apgar 3-4)', () { + test('Apgar 3 es Depresion severa', () { + final r = calculator.calculate( + appearance: 0, + pulse: 1, + grimace: 1, + activity: 0, + respiration: 1, + ); + expect(r.total, 3); + expect(r.severity.label, 'Depresion severa'); + expect(r.severity.level, SeverityLevel.severe); + }); + + test('Apgar 4 es Depresion moderada', () { + final r = calculator.calculate( + appearance: 1, + pulse: 1, + grimace: 1, + activity: 0, + respiration: 1, + ); + expect(r.total, 4); + expect(r.severity.label, 'Depresion moderada'); + expect(r.severity.level, SeverityLevel.moderate); + }); + }); + + group('Frontera moderada/normal (Apgar 6-7)', () { + test('Apgar 6 es Depresion moderada', () { + final r = calculator.calculate( + appearance: 1, + pulse: 2, + grimace: 1, + activity: 1, + respiration: 1, + ); + expect(r.total, 6); + expect(r.severity.label, 'Depresion moderada'); + expect(r.severity.level, SeverityLevel.moderate); + }); + + test('Apgar 7 es Normal', () { + final r = calculator.calculate( + appearance: 1, + pulse: 2, + grimace: 2, + activity: 1, + respiration: 1, + ); + expect(r.total, 7); + expect(r.severity.label, 'Normal'); + expect(r.severity.level, SeverityLevel.mild); + }); + }); + + group('Caso mixto tipico (acrocianosis con buena respuesta)', () { + test('Neonato con acrocianosis y respuesta parcial', () { + final r = calculator.calculate( + appearance: 1, + pulse: 2, + grimace: 1, + activity: 1, + respiration: 1, + ); + expect(r.appearance, 1); + expect(r.pulse, 2); + expect(r.grimace, 1); + expect(r.activity, 1); + expect(r.respiration, 1); + expect(r.total, 6); + }); + }); +} diff --git a/version.json b/version.json index d5efc68..6f9087e 100644 --- a/version.json +++ b/version.json @@ -1 +1 @@ -{"version": "0.1.16"} +{"version": "0.1.17"}