Files
construprogress/docs/MOBILE_APP_BRIEF.md
T
javierandClaude Opus 4.7 ca04b5a760 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>
2026-07-07 13:29:27 +02:00

10 KiB

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; el modelo offline está en MOBILE_SYNC_PROTOCOL.md. Este brief los resume y añade ejemplos de payloads reales y el modelo de datos.

Para trabajar en el repo móvil con Claude Code viendo este backend: claude --add-dir C:\xampp\htdocs\construprogress


1. Objetivo

App de seguimiento de obra que funciona sin conexión en campo: descarga los 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)

  • Token Bearer por dispositivo, con ability mobile-sync.
  • POST /login con { email, password, device_name, app_version? }{ token, user }.
  • En el resto de llamadas: cabecera Authorization: Bearer <token>.
  • 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).

curl -X POST https://<host>/api/v1/login \
  -H "Content-Type: application/json" \
  -d '{"email":"user@mai.group","password":"secret","device_name":"Pixel-8"}'
# → { "token": "12|abc...", "user": { "id":1, "name":"...", "roles":[...], "permissions":[...] } }

3. Endpoints (8)

Método Ruta Uso Rate limit
POST /login Token de dispositivo 10/min
GET /me Usuario + permisos
POST /logout Revocar token
GET /projects Proyectos accesibles
GET /projects/{id}/bundle?since= PULL: snapshot o delta + tombstones
GET /templates?since= Plantillas asignadas a proyectos accesibles
POST /sync PUSH: lote de mutaciones offline 60/min
POST /media Subida de ficheros (multipart) 120/min

4. PULL — descarga de datos

4.1 Primera sincronización (snapshot completo)

GET /projects/{id}/bundle devuelve:

{
  "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":    [ ... ],   // checklist por incidencia
  "issue_comments": [ ... ],   // comentarios por incidencia
  "templates":      [ ... ],   // asignadas al proyecto (vía pivot)
  "media":          [ ... ],
  "deleted":        {}         // vacío en snapshot completo
}

4.2 Sincronizaciones siguientes (delta)

GET /projects/{id}/bundle?since=<ISO8601 URL-encoded> → solo lo cambiado tras since y un objeto deleted con los ids borrados (tombstones) por entidad:

"deleted": {
  "phases": [], "layers": [], "features": [], "inspections": [],
  "issues": [], "issue_tasks": [], "issue_comments": []
}

⚠️ URL-encodea el since (el + del offset horario). Guarda server_time de cada respuesta y úsalo como el siguiente since.

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

Envía un lote. Cada operación lleva una uuid generada en el cliente (clave de idempotencia) y client_updated_at:

{
  "operations": [
    {
      "entity": "feature",
      "op": "update",
      "uuid": "0f8e2b6c-....",            // único y estable por operación
      "client_updated_at": "2026-07-07T11:30:00+00:00",
      "data": { "id": 5, "status": "completed", "progress": 100 }
    }
  ]
}

Respuesta — un resultado por operación:

{ "results": [
  { "uuid": "0f8e...", "status": "applied", "server_id": 5 }
] }

statusapplied | duplicate | conflict | error.

  • duplicate: ya se había aplicado esa uuid (reintento seguro).
  • conflict: el servidor es más nuevo → trae "server": {...} con el valor actual (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
progress_update.create { phase_id, progress(0-100), comment?, location? } 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
issue_task.create { issue_id, title, assigned_to?, due_date?, is_done? } edit issues
issue_task.update { id, title?, assigned_to?, due_date?, is_done? } edit issues
issue_comment.create { issue_id, body } view issues

Valores enum:

  • issue.priority: low | medium | high | critical
  • issue.status: open | in_review | resolved | closed
  • issue.type: defect | safety | quality | documentation | other

El servidor SIEMPRE fija user_id/reported_by/project_id y valida permiso + pertenencia al proyecto. El cliente nunca los manda.

6. Media — POST /media (multipart/form-data)

Campos: uuid (idempotencia), parent_entity, parent_id, file, category? (image|document|other), description?.

parent_entityfeature | issue | issue_task | issue_comment | project | phase | layer.

curl -X POST https://<host>/api/v1/media \
  -H "Authorization: Bearer <token>" \
  -F "uuid=4b1f...-uuid" \
  -F "parent_entity=issue" -F "parent_id=12" \
  -F "category=image" -F "file=@/path/defecto.jpg"
# → { "status":"applied", "media": { "id":99, "url":"/storage/...", ... } }

Requiere permiso upload media + pertenencia al proyecto. Idempotente por uuid.

7. Modelo de datos (campos que devuelve el bundle)

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, 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,
                 reported_by, assigned_to, resolved_at, updated_at
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, 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

8. Arquitectura cliente recomendada

Stack: React Native + Expo (alternativa: Flutter). Offline-first:

  1. BD local: SQLite (expo-sqlite / WatermelonDB) — o Drift/Isar en Flutter. Refleja las entidades del bundle.
  2. Sincronización PULL: guarda server_time; en cada arranque/con red llama a bundle?since=<último server_time>, aplica upserts y borra los deleted.
  3. Outbox (cola de salida): cada cambio offline genera una operación con uuid propio y se encola. Con red, envías el lote a /sync y procesas los resultados: applied/duplicate → marcar enviado; conflict → re-mergear; error → revisar.
  4. Media: sube los ficheros pendientes a /media (también con uuid) y referencia 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
GET /projects → elegir proyecto
GET /projects/{id}/bundle (sin since) → poblar BD local
... trabajo offline (encolar operaciones + fotos) ...
con red: POST /sync (lote) → POST /media (ficheros) → GET bundle?since=server_time

9. Checklist de arranque del repo móvil

  • 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 (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 (grupos + fotos), incidencias (checklist, comentarios, fotos), indicador de estado de sincronización.