043433bf9b
Una tarea siempre necesita una actividad contenedora, asi que convertir crea DOS cosas: el proyecto nuevo y, dentro, una actividad nueva que recibe las tareas -- y comentarios/adjuntos -- de la actividad original. Se piden ambos nombres (precargados con el nombre actual) antes de confirmar. La actividad original se borra al final, ya vacia de tareas (no choca con el bloqueo de removeActivity). Exige el mismo permiso que crear un proyecto, no solo escribir la actividad. Nuevo POST /api/activities/:id/convert-to-project. Probado de punta a punta en dev: prioridad heredada, tareas y comentario movidos correctamente, actividad original eliminada, navega al proyecto nuevo al terminar. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
174 lines
11 KiB
Markdown
174 lines
11 KiB
Markdown
# API - project.linkedcorp.net
|
|
|
|
## Base URL
|
|
|
|
- Desarrollo: `http://localhost:4019/api`
|
|
- Producción: `https://project.linkedcorp.net/api`
|
|
|
|
## Autenticacion
|
|
|
|
- Tipo: Bearer JWT (`Authorization: Bearer <token>`), emitido por `/api/auth/login`.
|
|
- El backend revalida `is_active` del usuario en cada request (no solo la firma del JWT).
|
|
|
|
## Formato de respuesta
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"data": {},
|
|
"error": null
|
|
}
|
|
```
|
|
|
|
## Endpoints principales
|
|
|
|
### Auth
|
|
- `POST /api/auth/login` — `{ email, password }` → `{ token, user }`. Si el usuario no esta
|
|
`active` (`pending_verification`/`pending_approval`) o `is_active=0`, `403` con mensaje especifico.
|
|
- `POST /api/auth/register` — público. `{ name, email, password }` → si es el primer usuario del
|
|
sistema: `{ bootstrap: true, token, user }` (superadmin, sesion directa); si no:
|
|
`{ bootstrap: false }` y se envia (o loguea, sin SMTP) un correo de verificación.
|
|
- `GET /api/auth/verify-email?token=...` — público. Pasa el usuario a `pending_approval` y notifica
|
|
in-app a los `admin`/`superadmin` activos.
|
|
- `GET /api/auth/invite?token=...` — público. `{ email }` de la invitación (para prellenar el
|
|
formulario), o `400` si es inválida/expiró.
|
|
- `POST /api/auth/accept-invite` — público. `{ token, name, password }` → completa la cuenta
|
|
creada por `POST /api/projects/:id/members` con `{ email }` (ver arriba): elige nombre y clave,
|
|
pasa a `active` directo (sin aprobación — quien invitó ya la vetó) y devuelve `{ token, user }`
|
|
con sesión iniciada, igual que el bootstrap del primer usuario.
|
|
- `POST /api/auth/forgot-password` — público. `{ email }` → respuesta **siempre genérica**.
|
|
- `POST /api/auth/reset-password` — público. `{ token, password }`.
|
|
- `PUT /api/auth/change-password` — autenticado. `{ currentPassword, newPassword }`, exige la
|
|
clave actual (distinto de `reset-password`, que usa un token de correo sin sesión).
|
|
- `POST /api/auth/logout`
|
|
- `GET /api/auth/me` — usuario actual
|
|
|
|
### Usuarios (`/api/users`) — lectura para cualquier autenticado; escritura solo Administrador/Superadministrador
|
|
- `GET /api/users` · `POST /api/users` · `PUT /api/users/:id` · `PATCH /api/users/:id/active`
|
|
- `POST /api/users/:id/approve` — `{ roleId?, departmentId? }`, solo sobre usuarios `pending_approval`
|
|
- `POST /api/users/:id/reject` — borra al usuario `pending_approval` (se audita el email antes)
|
|
|
|
### Departamentos (`/api/departments`)
|
|
- `GET /api/departments` (árbol) · `POST /api/departments` · `PUT /api/departments/:id` · `DELETE /api/departments/:id`
|
|
|
|
### Proyectos (`/api/projects`)
|
|
- `GET /api/projects` (filtros: estado, responsable, prioridad; `trash=true` = papelera, solo
|
|
superadmin) · `POST /api/projects`
|
|
- `GET /api/projects/:id` · `PUT /api/projects/:id`
|
|
- `DELETE /api/projects/:id` — eliminación lógica (`deleted_at`/`deleted_by`), mismo permiso que
|
|
editar el proyecto. `POST /api/projects/:id/restore` y `DELETE /api/projects/:id/permanent`
|
|
(borrado físico, irreversible, arrastra actividades/comentarios/adjuntos) — ambos solo
|
|
superadmin; `permanent` exige que el proyecto ya esté en la papelera (400 si no).
|
|
- `POST /api/projects/:id/members` — `{ userId }` (usuario existente) o `{ email }` (si el correo
|
|
no pertenece a nadie todavía, crea una cuenta `invited` y le manda un correo de invitación para
|
|
que complete su registro; la fila de `project_members` ya queda creada de una vez). `roleInProject`
|
|
opcional: `'admin'` = administrador del proyecto. Responde `{ members, invited }`.
|
|
- `PUT /api/projects/:id/members/:userId/role` (`{ admin: true|false }`, otorga o quita el rol de
|
|
administrador **de ese proyecto** — un proyecto puede tener uno o más administradores, sin
|
|
importar el rol global del usuario; nunca toca al `owner`) ·
|
|
`DELETE /api/projects/:id/members/:userId`
|
|
|
|
### Actividades (`/api/activities`)
|
|
- `GET /api/projects/:projectId/activities` · `POST /api/projects/:projectId/activities`
|
|
- `GET /api/activities/:id` · `PUT /api/activities/:id`
|
|
- `DELETE /api/activities/:id` — bloqueado con `400` si la actividad tiene tareas (hay que
|
|
eliminarlas primero); limpia comentarios/adjuntos de la actividad de paso (no hay FK física
|
|
cruzada). Arrastra `activity_dependencies` por `ON DELETE CASCADE`.
|
|
- `POST /api/activities/:id/dependencies` · `DELETE /api/activities/:id/dependencies/:dependsOnId`
|
|
- `PUT /api/projects/:projectId/activities/reorder` — `{ ids: [...] }` (orden manual, arrastrar
|
|
con el mouse); posiciones 1..N, nunca 0 (0 = "nunca reordenado a mano", ver `sort_order`).
|
|
- `PUT /api/activities/:id/move` — `{ projectId }`. Mueve la actividad a otro proyecto; exige
|
|
permiso de escritura en ambos proyectos. Tareas/comentarios/adjuntos se mueven solos (referencian
|
|
`activity_id`, no `project_id`). Las dependencias existentes (en cualquiera de los dos sentidos)
|
|
se eliminan — solo tienen sentido dentro del mismo proyecto. `sort_order` vuelve a 0 en el destino.
|
|
- `POST /api/activities/:id/convert-to-project` — `{ projectName, taskActivityName }`. Crea un
|
|
proyecto nuevo (`projectName`) y, dentro, una actividad nueva (`taskActivityName`) que recibe
|
|
las tareas/comentarios/adjuntos de la actividad original; la original se borra al final (ya sin
|
|
tareas). Exige el mismo permiso que crear un proyecto (no solo escribir la actividad). Responde
|
|
`{ project, activity }` (201).
|
|
|
|
### Tareas (`/api/activities/:activityId/tasks`)
|
|
- `GET` · `POST` · `PUT /api/tasks/:id` · `PATCH /api/tasks/:id/done` · `DELETE /api/tasks/:id`
|
|
- `PUT /api/activities/:activityId/tasks/reorder` — `{ ids: [...] }` (mismo mecanismo de orden
|
|
manual que las actividades).
|
|
|
|
### Tareas recurrentes (`/api/recurring-tasks`) — menú propio, independiente de Proyectos
|
|
- `GET` · `POST` · `PUT /api/recurring-tasks/:id` · `DELETE /api/recurring-tasks/:id`
|
|
- `POST /api/recurring-tasks/:id/complete` — cierra el ciclo actual y calcula el próximo
|
|
vencimiento sumando la periodicidad (`interval_value` + `interval_unit`).
|
|
- `POST /api/recurring-tasks/from-task/:taskId` — crea una tarea recurrente a partir de una tarea
|
|
puntual de una actividad (la original no se borra); mismo body que `POST /`.
|
|
- Body: `{ name, description?, projectId?, responsibleUserId?, intervalUnit: 'days'|'months'|'years',
|
|
intervalValue, nextDueDate, endDate?, reminderDaysBefore, reminderHour: 0-23 }`. `endDate` vacío
|
|
= se repite sin fin; al completar un ciclo que la supera, `is_active` pasa a `false` solo.
|
|
- Recordatorio por correo: un job en el propio proceso del backend revisa cada 15 minutos
|
|
(`src/jobs/recurringTaskReminders.js`) y manda el correo cuando `next_due_date -
|
|
reminderDaysBefore` cae hoy, a la hora `reminderHour`. Un solo envío por ciclo
|
|
(`last_reminder_sent_at`, se limpia al completar el ciclo).
|
|
|
|
### Comentarios (`/api/comments`)
|
|
- `GET /api/comments?entityType=project&entityId=1` · `POST /api/comments`
|
|
|
|
### Adjuntos (`/api/attachments`)
|
|
- `GET /api/attachments?entityType=activity&entityId=1` · `POST /api/attachments` (multipart)
|
|
- `GET /api/attachments/:id/download` · `DELETE /api/attachments/:id`
|
|
|
|
### Notificaciones (`/api/notifications`)
|
|
- `GET /api/notifications` · `PATCH /api/notifications/:id/read` · `PATCH /api/notifications/read-all`
|
|
|
|
### Búsqueda (`/api/search`)
|
|
- `GET /api/search?q=texto` — global: proyectos, actividades y tareas (de cualquier proyecto) que
|
|
coincidan. `{ projects: [...], activities: [...], tasks: [...] }`, cada item de actividades/tareas
|
|
incluye los datos de su proyecto (y actividad, para tareas) para armar la migaja de pan en el
|
|
cliente. `q` con menos de 2 caracteres devuelve listas vacías. Máx 20 resultados por tipo.
|
|
- `GET /api/search?q=texto&projectId=1` — acotado a un proyecto: solo `{ activities, tasks }` de
|
|
ESE proyecto (sin el dato del proyecto, ya es el contexto).
|
|
|
|
### Actualizaciones (`/api/updater`) — solo Superadministrador
|
|
- `GET /api/updater/status` — compara la `VERSION` local contra `CHANGELOG.md` de `main` en
|
|
Gitea (leído de forma anónima por HTTPS, sin token — requiere repo público y vista anónima
|
|
habilitada en el servidor de Gitea). `{ currentVersion, upToDate, latestVersion,
|
|
overallCriticality?, hasMigrations, pending: [{ version, date, criticality, summary }] }`.
|
|
`criticality` se deriva del dígito de versión que cambió (X.Y.Z: 1ro=alta, 2do=media,
|
|
3ro=baja), nunca se etiqueta a mano. `hasMigrations` compara los archivos de
|
|
`backend/database/migrations/` locales contra los de `main`. Es solo informativo — no aplica
|
|
ningún cambio, la actualización real sigue siendo manual.
|
|
|
|
### Dashboard (`/api/dashboard`)
|
|
- `GET /api/dashboard/summary` — KPIs (activos, atrasados, pendientes/vencidas/hoy, avance promedio)
|
|
- `GET /api/dashboard/calendar?month=2026-09` — actividades por fecha límite
|
|
- `GET /api/projects/:id/gantt` — actividades con fechas/avance para el Gantt
|
|
|
|
### Catálogos (`/api/catalogs`) — lectura general, escritura solo Superadministrador
|
|
- `GET/POST/PUT/DELETE /api/catalogs/project-statuses`
|
|
- `GET/POST/PUT/DELETE /api/catalogs/activity-statuses`
|
|
- `GET/POST/PUT/DELETE /api/catalogs/priorities`
|
|
- `GET/POST/PUT/DELETE /api/catalogs/project-types`
|
|
|
|
### Auditoría (`/api/audit`) — solo Superadministrador / Administrador / Jefe de TI
|
|
- `GET /api/audit?entityType=project&entityId=1`
|
|
|
|
### Configuración SMTP (`/api/settings`) — solo Superadministrador
|
|
- `GET /api/settings/smtp` — `{ host, port, user, from, configured }` (nunca la clave)
|
|
- `PUT /api/settings/smtp` — `{ host, port, user, from, pass? }` (`pass` vacío = no cambiarla)
|
|
- `POST /api/settings/smtp/test` — `{ host, port, user, from, pass?, to }` (`pass` vacío = usa la
|
|
guardada). Envía un correo real con los valores del formulario (aunque no se hayan guardado) y
|
|
responde `{ sent: true }` o `400` con el error real del servidor SMTP. Rate-limited (10/15min).
|
|
|
|
### Marca de la empresa (`/api/company`)
|
|
- `GET /api/company` — público (sin auth, lo usan login/registro/recuperación). `{ name, hasLogo,
|
|
logoVersion }`; `name` por defecto "LinkedCorp" si nunca se configuró.
|
|
- `GET /api/company/logo` — público. Devuelve el archivo de imagen del logo, o `404` si no hay
|
|
ninguno configurado (el cliente cae al logo estático embebido).
|
|
- `PUT /api/company` — solo Superadministrador, multipart: `name?` (texto) y `logo?` (archivo,
|
|
PNG/JPG/WEBP/SVG, máx 3MB). Al subir un logo nuevo se borra el archivo anterior (nunca quedan
|
|
huérfanos). Guardado en `system_settings` (mismo patrón que SMTP), sin tabla nueva.
|
|
|
|
## Errores esperados
|
|
|
|
- `400` validación
|
|
- `401` no autenticado / token inválido o expirado
|
|
- `403` sin permisos para la acción (según rol)
|
|
- `404` no encontrado (incluye cuando el recurso existe pero no pertenece al usuario/alcance)
|
|
- `500` error interno (sanitizado, sin detalle de stack en producción)
|