docs(mobile): actualizar API v1.1 (templates globales+pivot, feature_types, issues enriquecidas)

- openapi.yaml: bumped a 1.1.0, añadido changelog con los cambios acumulados
  respecto al brief inicial; /templates y bundle.templates documentan que ahora
  usan pivot (asignación por proyecto); enums de issue documentados.
- MOBILE_APP_BRIEF.md: reescrito para reflejar v1.1 — bundle con feature_types /
  issue_tasks / issue_comments; feature con feature_type_id/is_active; issue.type;
  nuevas ops (issue.update, issue_task.*, issue_comment.create); parent_entity
  admite issue_task/issue_comment; deleted incluye tasks/comments; y bloque
  detallado sobre templates ahora globales con fields[].group/question/help.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 13:29:27 +02:00
co-authored by Claude Opus 4.7
parent e05958e89f
commit ca04b5a760
2 changed files with 105 additions and 31 deletions
+46 -24
View File
@@ -1,5 +1,7 @@
# ConstruProgress — Brief para la App Móvil
**Contrato de la API móvil (v1.1)** — actualizado 2026-07-07.
Documento único de traspaso para construir la app móvil que consume la API de
ConstruProgress. La **fuente de verdad** del contrato es [`openapi.yaml`](openapi.yaml);
el modelo offline está en [`MOBILE_SYNC_PROTOCOL.md`](MOBILE_SYNC_PROTOCOL.md). Este
@@ -13,9 +15,9 @@ brief los resume y añade ejemplos de payloads reales y el modelo de datos.
## 1. Objetivo
App de **seguimiento de obra** que funciona **sin conexión** en campo: descarga los
datos de un proyecto (estructura + plantillas), permite trabajar offline (actualizar
progreso, registrar inspecciones, gestionar incidencias con tareas/comentarios/fotos)
y **sincroniza cuando hay red**.
datos de un proyecto (estructura + plantillas asignadas), permite trabajar offline
(actualizar progreso, registrar inspecciones con fotos, gestionar incidencias con
tareas/comentarios/fotos) y **sincroniza cuando hay red**.
## 2. Autenticación (Laravel Sanctum)
@@ -25,8 +27,7 @@ y **sincroniza cuando hay red**.
- `POST /logout` revoca el token del dispositivo actual.
- Guarda el token en almacenamiento seguro (Expo SecureStore / flutter_secure_storage).
**Base URL:** `https://<host>/api/v1` (confirmar host de despliegue; en local XAMPP
suele ser `http://localhost/construprogress/public/api/v1`).
**Base URL:** `https://<host>/api/v1` (confirmar host de despliegue).
```bash
curl -X POST https://<host>/api/v1/login \
@@ -44,9 +45,9 @@ curl -X POST https://<host>/api/v1/login \
| POST | `/logout` | Revocar token | — |
| GET | `/projects` | Proyectos accesibles | — |
| GET | `/projects/{id}/bundle?since=` | PULL: snapshot o delta + tombstones | — |
| GET | `/templates?since=` | Plantillas de inspección (version+hash) | — |
| GET | `/templates?since=` | Plantillas **asignadas** a proyectos accesibles | — |
| POST | `/sync` | PUSH: lote de mutaciones offline | 60/min |
| POST | `/media` | Subir fichero (multipart) | 120/min |
| POST | `/media` | Subida de ficheros (multipart) | 120/min |
## 4. PULL — descarga de datos
@@ -55,18 +56,19 @@ curl -X POST https://<host>/api/v1/login \
```jsonc
{
"server_time": "2026-06-18T12:00:00+00:00", // úsalo como próximo `since`
"server_time": "2026-07-07T12:00:00+00:00", // úsalo como próximo `since`
"project": { ... },
"phases": [ ... ],
"layers": [ ... ],
"features": [ ... ],
"feature_types": [ ... ], // catálogo global (id, name, color)
"inspections": [ ... ],
"issues": [ ... ],
"issue_tasks": [ ... ],
"issue_comments": [ ... ],
"templates": [ ... ],
"issue_tasks": [ ... ], // checklist por incidencia
"issue_comments": [ ... ], // comentarios por incidencia
"templates": [ ... ], // asignadas al proyecto (vía pivot)
"media": [ ... ],
"deleted": {} // vacío en snapshot completo
"deleted": {} // vacío en snapshot completo
}
```
@@ -84,9 +86,15 @@ y un objeto `deleted` con los **ids borrados** (tombstones) por entidad:
> ⚠️ **URL-encodea el `since`** (el `+` del offset horario). Guarda `server_time` de
> cada respuesta y úsalo como el siguiente `since`.
### 4.3 Plantillas
`GET /templates?since=` devuelve las plantillas de inspección de los proyectos
accesibles, cada una con `version` (timestamp) y `hash` (para detectar cambios).
### 4.3 Plantillas (globales, con asignación por proyecto)
`GET /templates?since=` devuelve las plantillas del **catálogo global** que están
**asignadas** a alguno de los proyectos accesibles del usuario (vía pivot
`inspection_template_project`). Cada plantilla trae `version` (timestamp) y `hash`.
> **Cambio de v1.0 → v1.1:** las plantillas ya **no** viven por proyecto. Son un
> catálogo global, y los proyectos consumen las que se les han asignado.
> `template.phase_id` se **eliminó**. `template.project_id` sigue en el JSON por
> compatibilidad pero **no** determina visibilidad — trátalo como informativo.
## 5. PUSH — `POST /sync`
@@ -100,7 +108,7 @@ idempotencia) y `client_updated_at`:
"entity": "feature",
"op": "update",
"uuid": "0f8e2b6c-....", // único y estable por operación
"client_updated_at": "2026-06-18T11:30:00+00:00",
"client_updated_at": "2026-07-07T11:30:00+00:00",
"data": { "id": 5, "status": "completed", "progress": 100 }
}
]
@@ -119,12 +127,12 @@ Respuesta — **un resultado por operación**:
(last-write-wins por `client_updated_at`). Resuélvelo en el cliente y reintenta.
- `error`: trae `"error": "..."` (validación o permisos).
### 5.1 Operaciones soportadas (entity.op → data → permiso requerido)
### 5.1 Operaciones soportadas
| entity.op | `data` | Permiso |
|---|---|---|
| `progress_update.create` | `{ phase_id, progress(0-100), comment?, location? }` | `update progress` |
| `feature.update` | `{ id, status?, progress?(0-100), responsible? }` | `update progress` |
| `feature.update` | `{ id, status?, progress?(0-100), responsible?, is_active?, feature_type_id? }` | `update progress` |
| `inspection.create` | `{ feature_id, template_id?, data?, status?, result?, notes? }` | `create inspections` |
| `issue.create` | `{ project_id, feature_id?, title, description?, priority?, status?, type? }` | `create issues` |
| `issue.update` | `{ id, title?, description?, priority?, status?, type?, assigned_to?, resolution_notes? }` | `edit issues` |
@@ -164,7 +172,8 @@ project : id, reference, name, address, lat, lng, status, updated_at
phase : id, name, order, color, progress_percent, updated_at
layer : id, phase_id, name, color, updated_at
feature : id, layer_id, name, geometry(GeoJSON), status, progress,
responsible, template_id, updated_at
responsible, template_id, feature_type_id, is_active, updated_at
feature_type : id, name, color # catálogo global
inspection : id, feature_id, layer_id, template_id, user_id, data(obj),
status, result, notes, created_at, updated_at
issue : id, feature_id, title, description, status, priority, type,
@@ -172,8 +181,13 @@ issue : id, feature_id, title, description, status, priority, type,
issue_task : id, issue_id, title, is_done, done_at, done_by, assigned_to,
due_date, order, updated_at
issue_comment : id, issue_id, user_id, body, created_at, updated_at
template : id, project_id, phase_id, name, description, fields(array),
version, hash, updated_at
template : id, name, description, fields(array), version, hash,
project_id(informativo, puede ser null), updated_at
# fields[]: cada campo puede incluir:
# group (string, sección para agrupar)
# question (string, prompt corto en la inspección)
# help (string, ayuda/comentarios largos)
# name, label, type, required, options, min, max, step
media : id, uuid, parent_entity, parent_id, url, name, file_type,
category, updated_at
```
@@ -193,6 +207,13 @@ media : id, uuid, parent_entity, parent_id, url, name, file_type,
la `url` devuelta.
5. **Token**: en almacenamiento seguro; si 401 → re-login.
### Renderizar plantillas de inspección
Cada plantilla trae `fields[]` con posibles metadatos: `group` (agrupar campos por
sección), `question` (mostrar como prompt), `help` (bajo el campo). El resto de
campos (`name`, `label`, `type`, `required`, `options`, `min`, `max`, `step`) sigue
igual. Al enviar la inspección (`inspection.create`), `data` es un objeto plano
`{ nombre_del_campo: valor }` — los grupos son solo de presentación.
### Flujo típico de sesión
```
login → guardar token
@@ -206,7 +227,8 @@ con red: POST /sync (lote) → POST /media (ficheros) → GET bundle?since=serve
- [ ] Elegir stack (RN+Expo / Flutter) y crear el proyecto.
- [ ] `claude --add-dir C:\xampp\htdocs\construprogress` para tener el contrato a mano.
- [ ] Capa de API (login/me/logout, projects, bundle, templates, sync, media).
- [ ] BD local + repositorios por entidad.
- [ ] BD local + repositorios por entidad (incluyendo feature_types, issue_tasks,
issue_comments, y templates con `fields[].group/question/help`).
- [ ] Motor de sync (PULL delta + outbox PUSH + media) con manejo de conflictos.
- [ ] UI: lista de proyectos, mapa/fases, inspecciones, incidencias (checklist,
comentarios, fotos), indicador de estado de sincronización.
- [ ] UI: lista de proyectos, mapa/fases, inspecciones (grupos + fotos), incidencias
(checklist, comentarios, fotos), indicador de estado de sincronización.
+59 -7
View File
@@ -1,11 +1,37 @@
openapi: 3.0.3
info:
title: ConstruProgress Mobile API
version: "1.0.0"
version: "1.1.0"
description: >
Offline-first sync API for the mobile app. Auth via Laravel Sanctum bearer
tokens (ability `mobile-sync`). All protected endpoints require
`Authorization: Bearer <token>`. See docs/MOBILE_SYNC_PROTOCOL.md.
## Changelog (from v1.0)
- Inspection templates are now **global** and assigned to projects via
pivot. `bundle.templates` and `/templates` return templates *assigned* to
the accessible projects (no longer filtered by `project_id`).
`template.phase_id` was **removed**. `project_id` remains in the schema
for compatibility but no longer determines visibility.
- Template `fields[]` items may include `group` (section), `question` (short
prompt shown at inspection time) and `help` (long help text).
- New entities in the bundle: `feature_types` (global catalogue),
`issue_tasks`, `issue_comments`.
- `feature` gains `feature_type_id`, `is_active`; `issue` gains `type`.
- New `/sync` ops: `issue.update`, `issue_task.create`, `issue_task.update`,
`issue_comment.create`. `feature.update` accepts `is_active` and
`feature_type_id`; `issue.create`/`update` accept `type`.
- `POST /media` `parent_entity` also accepts `issue_task` and `issue_comment`.
- `deleted` tombstones include `issue_tasks` and `issue_comments`.
servers:
- url: /api/v1
security:
@@ -85,7 +111,12 @@ paths:
"403": { description: Not a member of the project }
/templates:
get:
summary: Inspection templates for accessible projects (with version/hash)
summary: Global inspection templates ASSIGNED to any accessible project (via pivot)
description: >
Templates are a global catalogue. This endpoint returns the templates
that have been *assigned* (via `inspection_template_project` pivot) to
any of the user's accessible projects. Each item includes `version`
(updated_at timestamp) and `hash` for change detection.
parameters:
- name: since
in: query
@@ -183,12 +214,33 @@ components:
phases: { type: array, items: { type: object } }
layers: { type: array, items: { type: object } }
features: { type: array, items: { type: object } }
feature_types: { type: array, items: { type: object } }
feature_types:
type: array
description: Global catalogue of feature types (id, name, color)
items: { type: object }
inspections: { type: array, items: { type: object } }
issues: { type: array, items: { type: object } }
issue_tasks: { type: array, items: { type: object } }
issue_comments: { type: array, items: { type: object } }
templates: { type: array, items: { type: object } }
issues:
type: array
description: >
Includes `type` (defect|safety|quality|documentation|other),
`status` (open|in_review|resolved|closed),
`priority` (low|medium|high|critical).
items: { type: object }
issue_tasks:
type: array
description: Checklist tasks per issue.
items: { type: object }
issue_comments:
type: array
description: Comment thread per issue.
items: { type: object }
templates:
type: array
description: >
Templates assigned to this project via pivot (no longer filtered by
project_id). Each item exposes version/hash for change detection and
`fields[]` where each field may include `group`, `question`, `help`.
items: { type: object }
media: { type: array, items: { type: object } }
deleted:
type: object