Capa API tipada de los 8 endpoints, BD local espejo del bundle + outbox (operaciones y media) + cursor de sync, motor runSync (PUSH /sync -> PUSH /media -> PULL bundle?since) con idempotencia por uuid y last-write-wins, mutaciones de alto nivel (write local + encolar), sesion con token en SecureStore, conectividad NetInfo y UI minima (Login -> Proyectos -> Detalle). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
164 lines
4.1 KiB
TypeScript
164 lines
4.1 KiB
TypeScript
/**
|
|
* Mutaciones de alto nivel. Cada una:
|
|
* 1. (updates) escribe optimistamente en la BD local para reflejar el cambio ya,
|
|
* 2. encola la operación en el outbox para enviarla cuando haya red.
|
|
*
|
|
* El servidor SIEMPRE fija user_id/reported_by/project_id y valida permisos:
|
|
* el cliente nunca los envía (ver brief §5.1).
|
|
*/
|
|
import { getDb } from '../db/database';
|
|
import { enqueueOperation } from '../db/outbox';
|
|
import {
|
|
IssuePriority,
|
|
IssueStatus,
|
|
IssueType,
|
|
} from '../api/types';
|
|
|
|
// ----- progress (append-only en servidor) -----
|
|
|
|
export function recordProgressUpdate(input: {
|
|
phase_id: number;
|
|
progress: number;
|
|
comment?: string;
|
|
location?: { lat: number; lng: number };
|
|
}): Promise<string> {
|
|
return enqueueOperation('progress_update', 'create', { ...input });
|
|
}
|
|
|
|
// ----- feature.update (editable, last-write-wins) -----
|
|
|
|
export async function updateFeature(input: {
|
|
id: number;
|
|
status?: string;
|
|
progress?: number;
|
|
responsible?: string;
|
|
}): Promise<string> {
|
|
const db = await getDb();
|
|
const sets: string[] = [];
|
|
const params: unknown[] = [];
|
|
if (input.status !== undefined) {
|
|
sets.push('status = ?');
|
|
params.push(input.status);
|
|
}
|
|
if (input.progress !== undefined) {
|
|
sets.push('progress = ?');
|
|
params.push(input.progress);
|
|
}
|
|
if (input.responsible !== undefined) {
|
|
sets.push('responsible = ?');
|
|
params.push(input.responsible);
|
|
}
|
|
if (sets.length) {
|
|
params.push(input.id);
|
|
await db.runAsync(
|
|
`UPDATE features SET ${sets.join(', ')}, dirty = 1 WHERE id = ?`,
|
|
...(params as never[]),
|
|
);
|
|
}
|
|
return enqueueOperation('feature', 'update', { ...input });
|
|
}
|
|
|
|
// ----- inspection.create (append-only) -----
|
|
|
|
export function createInspection(input: {
|
|
feature_id: number;
|
|
template_id?: number;
|
|
data?: Record<string, unknown>;
|
|
status?: string;
|
|
result?: string;
|
|
notes?: string;
|
|
}): Promise<string> {
|
|
return enqueueOperation('inspection', 'create', { ...input });
|
|
}
|
|
|
|
// ----- issues -----
|
|
|
|
export function createIssue(input: {
|
|
project_id: number;
|
|
feature_id?: number;
|
|
title: string;
|
|
description?: string;
|
|
priority?: IssuePriority;
|
|
status?: IssueStatus;
|
|
type?: IssueType;
|
|
}): Promise<string> {
|
|
return enqueueOperation('issue', 'create', { ...input });
|
|
}
|
|
|
|
export async function updateIssue(input: {
|
|
id: number;
|
|
title?: string;
|
|
description?: string;
|
|
priority?: IssuePriority;
|
|
status?: IssueStatus;
|
|
type?: IssueType;
|
|
assigned_to?: number;
|
|
resolution_notes?: string;
|
|
}): Promise<string> {
|
|
const db = await getDb();
|
|
const cols: Record<string, unknown> = {
|
|
title: input.title,
|
|
description: input.description,
|
|
priority: input.priority,
|
|
status: input.status,
|
|
type: input.type,
|
|
assigned_to: input.assigned_to,
|
|
resolution_notes: input.resolution_notes,
|
|
};
|
|
const sets: string[] = [];
|
|
const params: unknown[] = [];
|
|
for (const [k, v] of Object.entries(cols)) {
|
|
if (v !== undefined) {
|
|
sets.push(`${k} = ?`);
|
|
params.push(v);
|
|
}
|
|
}
|
|
if (sets.length) {
|
|
params.push(input.id);
|
|
await db.runAsync(
|
|
`UPDATE issues SET ${sets.join(', ')}, dirty = 1 WHERE id = ?`,
|
|
...(params as never[]),
|
|
);
|
|
}
|
|
return enqueueOperation('issue', 'update', { ...input });
|
|
}
|
|
|
|
// ----- issue tasks -----
|
|
|
|
export function createIssueTask(input: {
|
|
issue_id: number;
|
|
title: string;
|
|
assigned_to?: number;
|
|
due_date?: string;
|
|
is_done?: boolean;
|
|
}): Promise<string> {
|
|
return enqueueOperation('issue_task', 'create', { ...input });
|
|
}
|
|
|
|
export async function updateIssueTask(input: {
|
|
id: number;
|
|
title?: string;
|
|
assigned_to?: number;
|
|
due_date?: string;
|
|
is_done?: boolean;
|
|
}): Promise<string> {
|
|
const db = await getDb();
|
|
if (input.is_done !== undefined) {
|
|
await db.runAsync(
|
|
'UPDATE issue_tasks SET is_done = ?, dirty = 1 WHERE id = ?',
|
|
input.is_done ? 1 : 0,
|
|
input.id,
|
|
);
|
|
}
|
|
return enqueueOperation('issue_task', 'update', { ...input });
|
|
}
|
|
|
|
// ----- issue comments (append-only) -----
|
|
|
|
export function createIssueComment(input: {
|
|
issue_id: number;
|
|
body: string;
|
|
}): Promise<string> {
|
|
return enqueueOperation('issue_comment', 'create', { ...input });
|
|
}
|