Entrega 2: implementación completa de AEnEA (backend IA, app Expo, despliegue) - #294
Entrega 2: implementación completa de AEnEA (backend IA, app Expo, despliegue)#294jialvarez wants to merge 6 commits into
Conversation
Entrega 1: Documentación Técnica, Arquitectura y Registro de Prompts (AEnEA)
…file - AIOrchestratorService now supports a third routing value (IRRELEVANT) so off-topic voice notes/photos are acknowledged (200, status=ignored) instead of being forced into a fabricated clinical record. - Fix frontend/package-lock.json desync and add .npmrc (legacy-peer-deps) so `npm ci` — what EAS Build actually runs — succeeds; this was causing EAS builds to fail during dependency install. - Wire up real EAS/Render deployment values (projectId, package name, permissions, backend URL) and correct render CLI usage in DEPLOY.md. - Update readme.md API spec to document the IRRELEVANT routing contract. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAEnEA adds a FastAPI backend with PostgreSQL persistence and AI-based clinical extraction, an Expo mobile frontend for onboarding and health timelines, PDF export, deployment configuration, automated tests, and expanded project documentation. ChangesAEnEA medical passport platform
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
render.yaml (1)
22-24: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winReplace wildcard CORS for the medical API.
CORS_ORIGINS: "*"permits every browser origin to call the API. Combined with the documented no-auth patient routes, arbitrary sites can invoke medical endpoints if they obtain apatient_id. Restrict CORS to trusted origins or disable it for native-only clients.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@render.yaml` around lines 22 - 24, Update the CORS_ORIGINS configuration for the medical API to remove the wildcard value. Restrict it to the project’s trusted browser origins, or disable CORS when the API is intended only for native clients.
🟠 Major comments (23)
backend/app/core/config.py-12-12 (1)
12-12: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRemove wildcard CORS defaults for the health-data API. The runtime default and copied environment template both permit every browser origin; require explicit frontend origins for each deployment.
backend/app/core/config.py#L12-L12: replace the wildcard fallback with a safe development-only origin or requireCORS_ORIGINSat startup.backend/.env.example#L6-L6: show explicit local frontend origin(s), not*.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/core/config.py` at line 12, Remove the wildcard CORS default from cors_origins in backend/app/core/config.py:12-12 by requiring CORS_ORIGINS at startup or using an explicit development-only frontend origin. Update backend/.env.example:6-6 to list explicit local frontend origin(s) instead of "*", keeping both configuration sources aligned.backend/app/services/ai_orchestrator.py-64-82 (1)
64-82: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftDo not discard red flags returned with baseline extractions.
Line 64 persists a
BASELINEwhile droppingresult.red_flag, even though the prompt requests red-flag analysis for all new content. Persist alerts independently of event routing (or reject baseline red flags until that storage/UI path exists); otherwise valid safety signals disappear.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/services/ai_orchestrator.py` around lines 64 - 82, Update the BASELINE handling in the orchestrator method around result.routing and add persistence for result.red_flag independently of baseline event creation, or explicitly reject baseline red flags until supported storage/UI exists. Ensure valid red flags are never silently discarded while preserving the existing baseline item response.backend/app/schemas/ai_contracts.py-51-57 (1)
51-57: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winEnforce mutually exclusive routing payloads.
This validator still accepts
BASELINEwith anevent,TIMELINEwith abaseline, andIRRELEVANTwith either payload. The orchestrator persists only the field matching its routing branch, so malformed AI outputs can silently ignore clinical data. Make the schema match the documented contract: each routing path gets exactly the required payload, andIRRELEVANTgets none.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/schemas/ai_contracts.py` around lines 51 - 57, The _check_routing_payload validator must enforce exact payload matching: BASELINE requires baseline and rejects event, TIMELINE requires event and rejects baseline, and IRRELEVANT rejects both payloads. Update the existing routing checks while preserving the current validation errors for missing required payloads.backend/app/main.py-11-17 (1)
11-17: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winReject wildcard origins when credentials are enabled.
cors_origin_listcan return["*"], while this middleware enables credentials and wildcard methods/headers. Require an explicit frontend-origin allowlist and enumerate the required methods/headers before enabling credentials; otherwise an arbitrary origin can make credentialed cross-origin requests.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/main.py` around lines 11 - 17, Update the CORSMiddleware configuration in the application setup to reject or fail fast when settings.cors_origin_list contains the wildcard origin while allow_credentials is enabled. Require explicit frontend origins and replace wildcard methods and headers with the specific methods and headers the API needs before allowing credentialed requests.Source: Linters/SAST tools
backend/app/api/v1/health.py-36-38 (1)
36-38: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy liftEnforce a shared maximum request size before buffering ingestion payloads. Both endpoints accept client-controlled content without a size limit, risking memory exhaustion and excessive downstream AI usage.
backend/app/api/v1/health.py#L36-L38: read audio in bounded chunks and reject uploads exceeding the configured limit.backend/app/api/v1/health.py#L50-L60: reject oversized encoded payloads before decoding and enforce the same decoded-byte limit; configure an ingress/ASGI body limit as well.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/api/v1/health.py` around lines 36 - 38, Enforce one shared maximum request-size limit across both endpoints in backend/app/api/v1/health.py: in the audio upload flow at lines 36-38, read in bounded chunks and reject payloads exceeding the limit before processing; in the encoded-payload flow at lines 50-60, reject oversized encoded input before decoding and reject decoded bytes over the same limit. Also configure the ingress/ASGI body limit to match.backend/docker-compose.yml-6-10 (1)
6-10: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winDo not expose database tools with known fallback credentials. Docker publishes these ports on all host interfaces; a network peer can access Postgres using the committed fallback credentials, and Adminer provides a browser client for it.
backend/docker-compose.yml#L6-L10: requirePOSTGRES_PASSWORDinstead of defaulting toaenea, and bind the development database port to127.0.0.1.backend/docker-compose.yml#L19-L23: bind Adminer to127.0.0.1or place it behind an explicit development-only profile.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/docker-compose.yml` around lines 6 - 10, Update backend/docker-compose.yml at lines 6-10 to require POSTGRES_PASSWORD without the committed fallback and bind the database port to 127.0.0.1; update lines 19-23 to bind Adminer to 127.0.0.1 or place it behind an explicit development-only profile.backend/app/models/medical_event.py-21-21 (1)
21-21: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftPersist a normalized sortable event date.
MedicalEvent.dateaccepts arbitrary display text, while the repository applies a lexicographic SQL sort. Non-ISO or approximate clinical dates will produce an incorrect medical timeline.
backend/app/models/medical_event.py#L21-L21: store a normalizedDatevalue or a separate normalized sort key; retain approximate/original wording in a distinct display field.backend/app/repositories/sqlalchemy_repository.py#L87-L93: order by that normalized date key and add a migration for existing rows.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/models/medical_event.py` at line 21, Update backend/app/models/medical_event.py:21-21 so MedicalEvent stores a normalized Date value or dedicated sortable date key, while preserving approximate/original wording in a separate display field. Update backend/app/repositories/sqlalchemy_repository.py:87-93 to order by the normalized date key, and add a migration to populate or convert existing rows consistently.backend/requirements.txt-1-9 (1)
1-9: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftUpgrade FastAPI and
python-multipartto patched releases.
backend/requirements.txtpinsfastapi==0.115.6with Starlette 0.41.3 andpython-multipart==0.0.19, both affected by form-data parsing/security advisories while this app exposes upload endpoints. Update FastAPI andpython-multipartto release-compatible patched versions, resolve the lockfile, and re-scan.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/requirements.txt` around lines 1 - 9, Update the FastAPI and python-multipart pins in requirements.txt to patched, mutually compatible releases, then regenerate or resolve the project lockfile so transitive Starlette dependencies are updated accordingly. Verify the upload/form-data dependencies no longer resolve to vulnerable versions.Source: Linters/SAST tools
backend/Dockerfile-1-16 (1)
1-16: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRun the application as a non-root user.
There is no
USERdirective, so Alembic and Uvicorn run as root. Add an unprivileged runtime user and ensure/apphas the required ownership. Trivy reports the same issue as DS-0002.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/Dockerfile` around lines 1 - 16, Add an unprivileged runtime user in the Dockerfile, ensure `/app` and its contents are owned by that user, and switch to it with a `USER` directive before the existing `CMD` so Alembic and Uvicorn run without root privileges.Source: Linters/SAST tools
readme.md-297-305 (1)
297-305: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftDo not use
patient_idas the only protection for medical data.The no-login contract makes a UUID in the URL the only caller credential. Anyone who obtains it can access that patient’s passport. Add authentication/authorization before production, or explicitly restrict this deployment to a non-production demo.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@readme.md` around lines 297 - 305, Update the API documentation around the patient endpoints and US-00 contract to explicitly state that the UUID patient_id is not sufficient protection for medical data. Require authentication and authorization before production use, or clearly restrict the deployment to a non-production demo.backend/Dockerfile-1-10 (1)
1-10: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy liftUse a multi-stage runtime image.
The final image retains
gccandlibpq-dev. Move compilation/install tooling into a builder stage and copy only runtime artifacts into the production stage.As per coding guidelines, the Render deployment must use a multi-stage build.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/Dockerfile` around lines 1 - 10, Convert the backend Dockerfile to a multi-stage build: use a builder stage based on python:3.13-slim to install gcc, libpq-dev, and Python dependencies, then create a separate runtime stage that contains only the application, required runtime libraries, and installed dependency artifacts. Ensure the final Render image does not retain compilation tools such as gcc or libpq-dev.Source: Coding guidelines
render.yaml-1-14 (1)
1-14: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftAlign the deployment topology and environment URLs.
The manifest, EAS profiles, and README do not define an isolated staging environment: preview points at production, Render provisions only one database/service, and the README names a different host.
render.yaml#L1-L14: add separate staging and production services/databases and configure the Oregon region.frontend/eas.json#L14-L24: pointpreviewto staging and reserve the production URL forproduction.readme.md#L28-L30: document the actual configured hosts and separate environment URLs.As per coding guidelines, staging and production must be separate Render services with distinct database instances.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@render.yaml` around lines 1 - 14, Update render.yaml to provision distinct Oregon-region staging and production services and databases, preserving separate database instances for each environment. In frontend/eas.json lines 14-24, point the preview profile to the staging host and keep the production host exclusively in the production profile. In readme.md lines 28-30, document the actual configured staging and production hosts with separate environment URLs.Source: Coding guidelines
frontend/package.json-5-22 (1)
5-22: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAdd the dependency required by the development build.
frontend/eas.jsonenablesdevelopmentClient: true, butfrontend/package.jsondoes not declareexpo-dev-client. Add the Expo-SDK-compatible version withnpx expo install expo-dev-client, or setdevelopmentClient: falseif this profile is intended to run in Expo Go.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/package.json` around lines 5 - 22, Update the frontend dependencies to include the Expo-SDK-compatible expo-dev-client package, matching the developmentClient: true setting in eas.json; use npx expo install expo-dev-client so the package version aligns with the installed Expo SDK.frontend/components/categoryColors.ts-17-25 (1)
17-25: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRender red flags as danger states, not only alert text.
redFlagcurrently appends “/ ALERTA” but retains the category color, so a flagged consultation can still appear gray or blue.
frontend/components/categoryColors.ts#L17-L25: acceptredFlagand return a dedicated danger palette including#EF4444.frontend/components/CategoryBadge.tsx#L10-L16: passredFlaginto the color lookup so badge styling matches its alert label.As per coding guidelines, “Error/Danger state must use
#EF4444(Red 500) for error messages, invalid states, and danger actions.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/components/categoryColors.ts` around lines 17 - 25, Update frontend/components/categoryColors.ts lines 17-25 so getCategoryColor accepts redFlag and returns a dedicated danger palette containing `#EF4444` when flagged, while preserving the existing category lookup otherwise. Update frontend/components/CategoryBadge.tsx lines 10-16 to pass redFlag into getCategoryColor so flagged badges use the danger styling.Source: Coding guidelines
frontend/app/onboarding.tsx-84-120 (1)
84-120: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAdd visible focus states to every
Pressable.The sex options, date selector, and submit button have no keyboard-focus treatment. Provide an equivalent visible 2px Indigo-500 focus ring/border for focused controls.
As per coding guidelines, “All buttons must implement focus state with
focus:ring-2 focus:ring-indigo-500for keyboard accessibility.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/app/onboarding.tsx` around lines 84 - 120, The Pressable controls in the sex options, date selector, and submit button need visible keyboard-focus styling. Update the corresponding Pressable style definitions in the onboarding screen to apply an equivalent 2px Indigo-500 focus ring or border when focused, while preserving their existing selected, disabled, and base styles.Source: Coding guidelines
frontend/app/onboarding.tsx-63-122 (1)
63-122: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winMake the form reachable with the keyboard or date picker open.
KeyboardAvoidingViewdoes not scroll; on smaller screens, especially Android wherebehavioris undefined, the keyboard/date picker can cover required fields or the submit action. Wrap the form in a scrollable container with keyboard-aware tap handling.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/app/onboarding.tsx` around lines 63 - 122, Wrap the onboarding form inside a scrollable container nested with KeyboardAvoidingView, enabling keyboard-aware tap handling so inputs and the submit action remain reachable when the keyboard or date picker is open. Preserve the existing form fields, date-picker behavior, and submission controls around the current component structure.frontend/app/onboarding.tsx-49-57 (1)
49-57: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPrevent duplicate patient records after local-storage failure.
If
createPatient()succeeds butsetStoredPatientId()rejects, this catch reports creation failure. Retrying submits another patient POST and leaves the first medical profile orphaned. Retain the created ID and retry persistence/navigation separately, or make creation idempotent.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/app/onboarding.tsx` around lines 49 - 57, Update the submission flow around createPatient and setStoredPatientId so a successful patient creation is not repeated when local-storage persistence fails. Retain the returned patient.id and handle persistence/navigation retry separately, or make createPatient idempotent, while preserving the existing error alert for genuine creation failures and routing successful persistence to /timeline.frontend/services/pdfExportService.ts-22-25 (1)
22-25: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftUse a stable, unique passport identifier.
Patients sharing the same first four letters receive the same printed ID, and one patient’s ID changes each calendar year. Use a backend-issued opaque display identifier instead of deriving one from the name and current date.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/services/pdfExportService.ts` around lines 22 - 25, Replace buildPassportId’s name-and-year derivation with the backend-issued opaque patient display identifier. Use the existing identifier field from Patient, preserving its value unchanged so IDs remain stable and unique across patients and calendar years.frontend/components/RedFlagBanner.tsx-10-16 (1)
10-16: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winShow every red-flag event in emergency summaries.
find()renders only the first flagged event. Additional urgent events are omitted from both the in-app alert and the PDF’s critical-alert section.
frontend/components/RedFlagBanner.tsx#L10-L16: filter all flagged events and render each justification.frontend/services/pdfExportService.ts#L28-L40: generate a critical-alert block for every flagged event.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/components/RedFlagBanner.tsx` around lines 10 - 16, Update frontend/components/RedFlagBanner.tsx lines 10-16 to collect all red-flag events and render each alertJustification in the in-app emergency banner, preserving the empty state when none are flagged. Update frontend/services/pdfExportService.ts lines 28-40 to generate a critical-alert block for every flagged event rather than only the first.frontend/app/timeline.tsx-33-49 (1)
33-49: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winRecover from initial-load failures instead of showing a permanent overlay.
Line 34 can reject outside the
try, and failures in Lines 41-44 leavepassportnull. Lines 99-101 then render an always-visible loading modal with no retry path. Catch the entire load flow and render an error/retry state.Also applies to: 99-101
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/app/timeline.tsx` around lines 33 - 49, Update loadPassport so the patient-ID lookup and onboarding redirect are covered by the same error handling as the patient/passport requests, and track initial-load failures instead of leaving passport null. In the timeline render path around the loading modal, replace the permanent loading state with an error/retry state when loading fails, using loadPassport as the retry action while preserving the loading indicator during active requests.frontend/services/audioRecorder.ts-16-27 (1)
16-27: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winGate recording on confirmed microphone permission.
useVoiceNoteRecorder()starts permission setup after render andstart()does not check whether permission was granted.FloatingMicButtononly catchesprepareToRecordAsync()rejection, so a denied permission can leave the recording flow without a recoverable UI result. Retain the permission state fromuseEffect, wait on the permission before exposing/start recording, and surface a clear denial message.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/services/audioRecorder.ts` around lines 16 - 27, Update useVoiceNoteRecorder to retain microphone permission state from the useEffect request, prevent start from proceeding until permission setup has completed, and return a clear denial error when permission is not granted. Ensure FloatingMicButton can receive that rejection so denied access produces a recoverable UI result instead of attempting recorder.prepareToRecordAsync.frontend/components/LoadingOverlay.tsx-10-15 (1)
10-15: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAnnounce dynamic status changes to assistive technology.
Both components render status/alert text but never announce the change to screen readers, so users won’t be notified when loading starts or when a red-flag warning appears.
- Apply an alert role/live region for the
LoadingOverlay.message.- Apply an alert role/live region for the urgent urgent clinical alert in
RedFlagBanner.container.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/components/LoadingOverlay.tsx` around lines 10 - 15, Apply an alert accessibility role/live-region behavior to the element rendering LoadingOverlay.message in frontend/components/LoadingOverlay.tsx:10-15 and to RedFlagBanner.container in frontend/components/RedFlagBanner.tsx:13-17, ensuring dynamic loading and urgent clinical alert changes are announced to assistive technology.Source: Coding guidelines
frontend/app/timeline.tsx-107-109 (1)
107-109: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winAdd button semantics and visible focus states to these Pressables.
These interactive elements do not expose button semantics or a visible focus indicator, and icon-only controls still need accessible names.
frontend/app/timeline.tsx#L107: addaccessibilityRole="button", keyboard/focus styling, anddisabledsupport if the export action can be unavailable.frontend/components/EventDetailModal.tsx#L44: add the same button semantics/focus styling for the close control.frontend/components/FloatingMicButton.tsx#L38-L44: addaccessibilityLabel/accessibilityState={{ busy, disabled, pressed }}, button semantics, and focus styling for stop/start recording.frontend/components/TimelineCard.tsx#L15-L28: add button semantics, focus styling, and mark the location text/helper as decorative when present.frontend/components/UploadDocumentButton.tsx#L42-L44: addaccessibilityLabel="Subir documento", button semantics, disabled state, and focus styling.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/app/timeline.tsx` around lines 107 - 109, The Pressable controls need consistent button semantics, keyboard-visible focus styling, and appropriate accessibility state. Update frontend/app/timeline.tsx lines 107-109 around handleExportPdf, adding button semantics, focus styling, and disabled support if export can be unavailable; update frontend/components/EventDetailModal.tsx lines 44-46 around the close control with the same semantics and focus styling; update frontend/components/FloatingMicButton.tsx lines 38-44 with an accessible label, button role, busy/disabled/pressed state, and focus styling; update frontend/components/TimelineCard.tsx lines 15-28 with button semantics, focus styling, and decorative treatment for location text/helper content; update frontend/components/UploadDocumentButton.tsx lines 42-44 with the “Subir documento” label, button role, disabled state, and focus styling.Source: Coding guidelines
🟡 Minor comments (12)
backend/app/repositories/interfaces.py-27-34 (1)
27-34: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winRename parameters that shadow Python’s
typebuiltin. Ruff A002 is raised in both port methods and their implementation; this will fail linting.
backend/app/repositories/interfaces.py#L27-L34: renametypetobaseline_type.backend/app/repositories/interfaces.py#L37-L51: renametypetoevent_type.backend/app/repositories/sqlalchemy_repository.py#L29-L47: mirrorbaseline_typeand assign it toClinicalBaseline.type.backend/app/repositories/sqlalchemy_repository.py#L49-L81: mirrorevent_typeand assign it toMedicalEvent.type.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/repositories/interfaces.py` around lines 27 - 34, Rename the shadowing type parameters across all affected methods: use baseline_type in backend/app/repositories/interfaces.py lines 27-34 and backend/app/repositories/sqlalchemy_repository.py lines 29-47, assigning it to ClinicalBaseline.type; use event_type in backend/app/repositories/interfaces.py lines 37-51 and backend/app/repositories/sqlalchemy_repository.py lines 49-81, assigning it to MedicalEvent.type.Source: Linters/SAST tools
backend/app/models/base.py-29-36 (1)
29-36: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winReturn normalized UUID values for PostgreSQL UUID binds.
PG_UUID(as_uuid=True)still uses this TypeDecorator’sprocess_bind_param, but the PostgreSQL branch currently returnsstr(value)before theuuid.UUIDcheck. Normalize non-UUID input first, then let PostgreSQL acceptuuid.UUID; keepCHAR(36)serialization for SQLite.Proposed fix
def process_bind_param(self, value, dialect): if value is None: return value + if not isinstance(value, uuid.UUID): + value = uuid.UUID(value) if dialect.name == "postgresql": - return str(value) - if not isinstance(value, uuid.UUID): - return str(uuid.UUID(value)) + return value return str(value)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/app/models/base.py` around lines 29 - 36, Update process_bind_param to normalize non-UUID values with uuid.UUID before the PostgreSQL branch, then return the UUID object for PostgreSQL while preserving string serialization for CHAR(36)/SQLite and the existing None behavior.readme.md-564-564 (1)
564-564: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the API field name
red_flag.This acceptance criterion uses
bandera_roja, while the documented model and API usered_flag. The mismatch can make frontend/backend tests assert a field that never exists.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@readme.md` at line 564, Update the acceptance criterion to use the documented API field name red_flag instead of bandera_roja, while preserving the required true value and the existing mobile and PDF warning behavior.DEPLOY.md-52-57 (1)
52-57: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMake local physical-device networking consistent.
Both the deployment instructions and development profile use
localhostwhile describing phone-based testing.
DEPLOY.md#L52-L57: document a LAN/tunnel API URL and bind Uvicorn to a reachable interface.frontend/eas.json#L7-L12: avoid hardcodinglocalhostfor device builds, or clearly limit the profile to emulators.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@DEPLOY.md` around lines 52 - 57, Update DEPLOY.md lines 52-57 to document using a LAN or tunnel API URL for physical-device testing and instruct binding Uvicorn to a reachable interface; update frontend/eas.json lines 7-12 to avoid hardcoding localhost for device builds, or explicitly restrict that profile to emulator use.readme.md-577-583 (1)
577-583: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUpdate the stale Ticket 1 route.
It still specifies
/api/v1/health/process-voice; the current contract is/api/v1/patients/{patient_id}/process-voice. Keep the ticket acceptance criteria aligned with the implemented API.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@readme.md` around lines 577 - 583, Update Ticket 1’s endpoint description to use the current route `/api/v1/patients/{patient_id}/process-voice` instead of `/api/v1/health/process-voice`, and align its acceptance criteria with the implemented patient-scoped API contract while preserving the existing response and testing requirements.DEPLOY.md-45-46 (1)
45-46: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMake the EAS URL handoff step match the actual config.
frontend/eas.jsonalready contains concrete URLs; it does not containREPLACE_WITH_YOUR_RENDER_URL. This step is therefore a no-op and can leave deployers believing the backend URL was configured.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@DEPLOY.md` around lines 45 - 46, Update the EAS URL handoff step in DEPLOY.md to match the actual frontend/eas.json configuration: remove the instruction to replace REPLACE_WITH_YOUR_RENDER_URL and describe the concrete URL fields or values deployers must verify or update for their Render backend.readme.md-159-159 (1)
159-159: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the current audio package name.
The documentation says
expo-av, but the frontend declares and configuresexpo-audioinfrontend/package.jsonandfrontend/app.json. Following this section would lead developers to the wrong package/API.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@readme.md` at line 159, Update the “Frontend Mobile (React Native + Expo)” documentation to reference the currently configured expo-audio package instead of expo-av, including the corresponding audio API wording while preserving the existing description of native stream recording.readme.md-153-153 (1)
153-153: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUpdate the documented database provider.
This line says production PostgreSQL runs on AWS, while the deployment manifest provisions Render PostgreSQL. Use Render or provider-agnostic wording to avoid misleading operators.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@readme.md` at line 153, Update the “Justificación de Arquitectura” documentation to replace the AWS PostgreSQL provider reference with Render PostgreSQL or provider-agnostic wording, while preserving the existing SQLite test-environment distinction.readme.md-193-193 (1)
193-193: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winSeparate Expo internal builds from Expo Go scanning.
The EAS
preview/internal distribution build has its built artifact link and QR for installer access; Expo Go is for the localnpx expo startworkflow. Rewordreadme.md:193so these are listed as separate flows.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@readme.md` at line 193, Reword the Frontend description to distinguish EAS preview/internal distribution builds, which provide installable artifacts via link or QR, from the local Expo Go workflow launched with npx expo start. Remove the implication that EAS builds are scanned directly in Expo Go, while preserving the web static preview distribution mention.frontend/app/onboarding.tsx-131-153 (1)
131-153: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winFix insufficient input contrast.
#94A3B8on white is about 2.6:1, below the required 4.5:1 for normal placeholder text.#CBD5E1on white is about 1.5:1, below the 3:1 UI-component threshold. Use darker placeholder and border colors.As per coding guidelines, “Ensure text contrast ratios meet minimum requirements: 4.5:1 for normal text … and 3:1 for UI components.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/app/onboarding.tsx` around lines 131 - 153, Update the onboarding styles `datePlaceholder`, `input`, and `sexOption` to use darker placeholder text and border colors that meet the required 4.5:1 text contrast and 3:1 UI-component contrast thresholds, while preserving the existing layout and selected-state styling.Source: Coding guidelines
frontend/app/onboarding.tsx-126-163 (1)
126-163: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAlign these styles with the required design tokens.
frontend/app/onboarding.tsx#L126-L163: apply Inter, replace non-4px spacing such as6,10,14, and36, and use#4F46E5for the primary selection/continue actions.frontend/components/BaselineChip.tsx#L21-L34: apply Inter, replacepaddingVertical: 6, and use Veterinary Green#10B981for the medication health state.frontend/components/CategoryBadge.tsx#L21-L29: apply Inter and replacepaddingVertical: 3with the 4px spacing scale.As per coding guidelines, “Use 'Inter' font family”; “Use spacing scale based on 4px increments”; “Primary Blue (
#4F46E5/ Indigo 600) should be used for main actions”; and “Use Veterinary Green (#10B981/ Emerald 500) for … health-related elements.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/app/onboarding.tsx` around lines 126 - 163, Update the styles in frontend/app/onboarding.tsx lines 126-163 to use the Inter font, convert non-4px spacing values including 6, 10, 14, and 36 to the 4px spacing scale, and replace primary selection and continue-action colors with `#4F46E5`. Update frontend/components/BaselineChip.tsx lines 21-34 to use Inter, replace paddingVertical: 6 with a 4px-scale value, and use `#10B981` for its medication health state. Update frontend/components/CategoryBadge.tsx lines 21-29 to use Inter and replace paddingVertical: 3 with the 4px spacing value.Source: Coding guidelines
frontend/components/BaselineHeader.tsx-42-42 (1)
42-42: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReplace low-contrast muted text.
#94A3B8is roughly 2.5:1 against white/light backgrounds, below the required 4.5:1 for normal text.
frontend/components/BaselineHeader.tsx#L42-L42: use a compliant muted-text color.frontend/app/timeline.tsx#L153-L158: use a compliant empty-state color.frontend/services/pdfExportService.ts#L129-L130: use a compliant empty-note/footer color.As per coding guidelines, normal text must meet a minimum 4.5:1 contrast ratio.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/components/BaselineHeader.tsx` at line 42, Replace the low-contrast muted color with a color meeting the 4.5:1 contrast requirement at frontend/components/BaselineHeader.tsx lines 42-42, frontend/app/timeline.tsx lines 153-158, and frontend/services/pdfExportService.ts lines 129-130, preserving each existing empty-state or footer style’s other properties.Source: Coding guidelines
🧹 Nitpick comments (4)
frontend/app.json (1)
26-31: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winVerify the foreground-service permission set.
The documented flow records voice, but the manifest requests
FOREGROUND_SERVICE_MEDIA_PLAYBACK, which Android defines for media playback; microphone foreground services useFOREGROUND_SERVICE_MICROPHONE. Remove unused foreground-service permissions, or configure and test the microphone-specific background-recording path. (developer.android.com)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/app.json` around lines 26 - 31, Update the permissions array in frontend/app.json to match the documented voice-recording flow: remove the media-playback foreground-service permission and add the microphone-specific foreground-service permission if background recording is supported. Remove any other unused foreground-service permissions, then verify the resulting manifest configuration with the recording path.readme.md (1)
577-577: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winFix the Markdown heading hierarchy.
These headings jump from
##directly to####. Use###or add the missing level to satisfy the reported MD001 warnings.Also applies to: 643-643
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@readme.md` at line 577, Fix the Markdown heading hierarchy for the Ticket 1 heading and the corresponding heading at the other reported location by changing each heading from level four to level three, or by adding the missing intermediate level, so headings do not skip from ## to #### and MD001 warnings are resolved.Source: Linters/SAST tools
frontend/services/pdfExportService.ts (1)
107-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the required Inter font stack in the PDF.
The export explicitly starts with Helvetica instead of Inter.
Proposed fix
- body { font-family: Helvetica, Arial, sans-serif; color: `#1e293b`; padding: 32px; } + body { font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; color: `#1e293b`; padding: 32px; }As per coding guidelines, use “Inter” with
-apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto, and sans-serif fallbacks.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/services/pdfExportService.ts` at line 107, Update the body font-family declaration in the PDF export HTML template to use Inter first, followed by -apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto, and sans-serif fallbacks, replacing Helvetica and Arial.Source: Coding guidelines
frontend/app/timeline.tsx (1)
145-151: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftCentralize and apply the mandated semantic color tokens.
Main actions currently use
#0F172A/#1D4ED8, while danger states use#DC2626; these diverge from the required primary#4F46E5and danger#EF4444values.
frontend/app/timeline.tsx#L145-L151: use the primary action token for export.frontend/components/EventDetailModal.tsx#L69-L76: use the primary action token for close.frontend/components/FloatingMicButton.tsx#L49-L66: use primary and required danger tokens.frontend/components/LoadingOverlay.tsx#L8-L14: use the primary token for the activity indicator.frontend/components/RedFlagBanner.tsx#L21-L32: use the required danger token for the urgent state.frontend/components/TimelineCard.tsx#L32-L58: use the required danger token and replace off-scale 14px/6px spacing with 16px/8px.frontend/components/UploadDocumentButton.tsx#L48-L65: use the primary action token.frontend/services/pdfExportService.ts#L107-L130: use the same semantic tokens in the exported document.As per coding guidelines, Primary Blue (
#4F46E5) is required for main actions and Error/Danger states must use#EF4444.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/app/timeline.tsx` around lines 145 - 151, Centralize the semantic colors and apply them consistently: update frontend/app/timeline.tsx (145-151), EventDetailModal.tsx (69-76), FloatingMicButton.tsx (49-66), LoadingOverlay.tsx (8-14), UploadDocumentButton.tsx (48-65), and the main-action portions of pdfExportService.ts (107-130) to use primary `#4F46E5`; update FloatingMicButton.tsx, RedFlagBanner.tsx (21-32), TimelineCard.tsx (32-58), and the danger portions of pdfExportService.ts to use `#EF4444`. In TimelineCard.tsx, also replace the 14px/6px spacing values with 16px/8px.Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 171dacfc-4fe9-458e-81f7-3fabf98b6db1
⛔ Files ignored due to path filters (9)
backend/tests/fixtures/sample_audio.wavis excluded by!**/*.wavfrontend/assets/android-icon-background.pngis excluded by!**/*.pngfrontend/assets/android-icon-foreground.pngis excluded by!**/*.pngfrontend/assets/android-icon-monochrome.pngis excluded by!**/*.pngfrontend/assets/favicon.pngis excluded by!**/*.pngfrontend/assets/icon.pngis excluded by!**/*.pngfrontend/assets/splash-icon.pngis excluded by!**/*.pngfrontend/package-lock.jsonis excluded by!**/package-lock.jsonpasaporte_medico_inteligente.pdfis excluded by!**/*.pdf
📒 Files selected for processing (87)
.serena/.gitignore.serena/project.ymlDEPLOY.mdbackend/.env.examplebackend/.gitignorebackend/Dockerfilebackend/alembic.inibackend/app/__init__.pybackend/app/api/__init__.pybackend/app/api/v1/__init__.pybackend/app/api/v1/health.pybackend/app/api/v1/patients.pybackend/app/api/v1/router.pybackend/app/core/__init__.pybackend/app/core/config.pybackend/app/core/database.pybackend/app/core/deps.pybackend/app/exceptions.pybackend/app/main.pybackend/app/models/__init__.pybackend/app/models/base.pybackend/app/models/clinical_baseline.pybackend/app/models/medical_event.pybackend/app/models/patient.pybackend/app/repositories/__init__.pybackend/app/repositories/interfaces.pybackend/app/repositories/sqlalchemy_repository.pybackend/app/schemas/__init__.pybackend/app/schemas/ai_contracts.pybackend/app/schemas/clinical_baseline.pybackend/app/schemas/medical_event.pybackend/app/schemas/passport.pybackend/app/schemas/patient.pybackend/app/services/__init__.pybackend/app/services/ai_orchestrator.pybackend/app/services/openai_client.pybackend/app/services/prompt_templates.pybackend/docker-compose.ymlbackend/migrations/env.pybackend/migrations/script.py.makobackend/migrations/versions/0001_initial_schema.pybackend/pytest.inibackend/requirements-dev.txtbackend/requirements.txtbackend/tests/__init__.pybackend/tests/conftest.pybackend/tests/e2e/__init__.pybackend/tests/e2e/test_process_voice_e2e.pybackend/tests/fixtures/README.mdbackend/tests/integration/__init__.pybackend/tests/integration/test_api_endpoints.pybackend/tests/integration/test_health_repository.pybackend/tests/integration/test_patient_repository.pybackend/tests/unit/__init__.pybackend/tests/unit/test_ai_orchestrator_parsing.pyfrontend/.env.examplefrontend/.gitignorefrontend/.npmrcfrontend/app.jsonfrontend/app/_layout.tsxfrontend/app/index.tsxfrontend/app/onboarding.tsxfrontend/app/timeline.tsxfrontend/components/BaselineChip.tsxfrontend/components/BaselineHeader.tsxfrontend/components/CategoryBadge.tsxfrontend/components/EventDetailModal.tsxfrontend/components/FloatingMicButton.tsxfrontend/components/LoadingOverlay.tsxfrontend/components/RedFlagBanner.tsxfrontend/components/TimelineCard.tsxfrontend/components/UploadDocumentButton.tsxfrontend/components/categoryColors.tsfrontend/eas.jsonfrontend/package.jsonfrontend/services/audioRecorder.tsfrontend/services/healthRepository.tsfrontend/services/httpClient.tsfrontend/services/patientStore.tsfrontend/services/pdfExportService.tsfrontend/tsconfig.jsonfrontend/types/clinicalBaseline.tsfrontend/types/medicalEvent.tsfrontend/types/passport.tsfrontend/types/patient.tsreadme.mdrender.yaml
Resumen
Implementa de extremo a extremo la plataforma AEnEA documentada en la Entrega 1 (que era solo
diseño/documentación) más la funcionalidad de onboarding (US-00) detectada como hueco tras la
revisión inicial:
GUIDagnóstico dePostgres/SQLite,
AIOrchestratorServicecon cliente OpenAI inyectable (Whisper STT + GPT-4o-minipara clasificación estructurada BASELINE/TIMELINE/IRRELEVANT y detección de banderas rojas
cruzando el historial longitudinal), endpoints
POST/GET /api/v1/patientsy/patients/{id}/process-voice|process-document|passport.baseline, banner de bandera roja, grabación de voz (
expo-audio), captura de documentos(
expo-image-picker) y exportación a PDF (expo-print/expo-sharing) fiel al diseño depasaporte_medico_inteligente.pdf.render.yaml+Dockerfile(backend en Render, Postgres gestionado) yeas.json(build de Android vía EAS) — ya desplegado y verificado en producción.readme.mdactualizado (modelo de datos, API, US-00, tickets/PRs deEntrega 2) con convención de nombres en inglés para el código y español para el contenido.
Test plan
pytestbackend: 23 tests en verde (unitarios + integración; E2E real opcional conOPENAI_API_KEY)npx tsc --noEmitsin errores en el frontendnpx expo exportbundlea sin fallos (1156 módulos)/healthy/docsverificados en producciónSummary by CodeRabbit
New Features
Documentation
Tests