# Vehicle documents

How a car's private documents are filed from the template's uploads, listed by type with their status, opened through a five-minute signed link, and how a due date becomes a reminder.

## What it does [#what-it-does]

A vehicle document (RF15, US18) is a file in the Organization's private storage filed among one car's documents with a kind (`registration`, `title`, `insurance`, `tax`, `purchase_invoice`, `loan_agreement`, `warranty`, `inspection`, `receipt`, `other`), a title and an optional due date. The bytes travel by the template's direct upload ([Documents and uploads](/docs/features/documents)): `uploads.prepare`, one signed PUT or S3 multipart above 16 MiB, `uploads.complete`. There is no second storage path.

- Filing checks that the car and the uploaded file belong to the Active Organization. The client generates the id, so a replay answers the first filing (RNF04).
- A due date is a `reminder` (basis `date`, titled like the document, `reminder.document_id`), written in the same transaction. Changing the date moves it and reopens it if it was done; clearing the date or deleting the document deletes it. These reminders are alerts, not services: `reminder.list` (M-Manutencao, the M-Veiculo next service) leaves them out.
- A document is mutable (kind, title, due date) and deletable, unless it is the receipt of an expense or a service (`receiptOf`): that record keeps its proof (`CONFLICT`). Deleting a document leaves the uploaded file in Files.
- Opening one asks `vehicleDocument.url`, a presigned GET of its key that expires after 300 seconds (RNF05).

| Procedure                                                                       | Failures                                                           |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `vehicleDocument.list` `{ vehicleId, limit ≤ 100 }` (newest first)              | `INPUT_VALIDATION`, `NOT_FOUND` (car)                              |
| `vehicleDocument.create` `{ id, vehicleId, fileId, kind, title, dueOn? }`       | `INPUT_VALIDATION`, `NOT_FOUND` (car, file), `CONFLICT` (id taken) |
| `vehicleDocument.update` `{ id, kind?, title?, dueOn? }` (`dueOn: null` clears) | `INPUT_VALIDATION`, `NOT_FOUND`                                    |
| `vehicleDocument.delete` `{ id }`                                               | `NOT_FOUND`, `CONFLICT` (a receipt)                                |
| `vehicleDocument.url` `{ id }`                                                  | `NOT_FOUND`                                                        |

All are for Members of the Organization; a non-Member gets `FORBIDDEN`.

## In the app [#in-the-app]

M-Documentos (`features/vehicle-document/vehicle-document.screen.tsx`, route `/documents?vehicleId=`) opens from M-Veiculo's Documents pill and segment. The segmented All | Garage | Insurance | Taxes filters the two-column grid (Garage is every kind but insurance and tax; one column at the largest text sizes). The status line and the chip's tint come from `vehicle-document.model.ts`: expired or due within 30 days is `warning`, a registration or inspection in force is "valid" in `positive`, another due date "expires …", and without one a title is "on file", a tax "paid", the rest "private" (`text2`). A card opens a sheet: a photo shows from its signed link, a PDF opens in the in-app browser, then edit or delete (confirmed). "Add document" and ⋯ open the form (`vehicle-document.form.screen.tsx`): a photo from the library or a PDF from Files (`expo-document-picker`), the type, the name (the type's by default) and the due date. The file uploads once per pick, so a retry after a failed filing reuses it. `autofolio://dev/M-Documentos` opens the seeded M3's six documents.

## Data and isolation [#data-and-isolation]

`vehicle_document` and `reminder` have row-level security by Active Organization, and composite foreign keys keep a document on its car and its file in its Organization; `uploaded_file` keys are CHECKed under `orgs/<organizationId>/`. The tests prove another Organization gets `NOT_FOUND` on every procedure and that every signed key is under the caller's prefix (RNF06). No `audit_log` event is written; titles and file names never go to logs.

## Key files [#key-files]

- [`packages/api/src/features/vehicle-document/vehicle-document.service.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/api/src/features/vehicle-document/vehicle-document.service.ts)
- [`packages/api/src/features/reminder/reminder.service.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/packages/api/src/features/reminder/reminder.service.ts)
- [`apps/mobile/src/features/vehicle-document/vehicle-document.hooks.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/apps/mobile/src/features/vehicle-document/vehicle-document.hooks.ts)
- [`apps/mobile/src/platform/uploads/upload-file.ts`](https://github.com/CarlosZiegler/autofolio/blob/main/apps/mobile/src/platform/uploads/upload-file.ts)

