b2c25c4fe5
La IA consulta solo el nombre del procesador y devuelve el año (el mas antiguo si hay dos). Migracion 039. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
340 lines
24 KiB
Markdown
340 lines
24 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).
|
||
- El JWT incluye `sv` y `sid`. Cada petición valida la cuenta y la fila activa de `user_sessions`.
|
||
Cambiar la clave conserva la sesión actual y cierra las demás; restablecerla cierra todas.
|
||
Una sesión revocada responde `401` con `code: "SESSION_REVOKED"`; los JWT antiguos sin `sid`
|
||
requieren volver a iniciar sesión tras aplicar la migración `033`.
|
||
|
||
## Formato de respuesta
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
## Edición concurrente
|
||
|
||
Los `PUT` de proyectos, comentarios, actividades, tareas, departamentos, usuarios, los cuatro
|
||
catálogos, notas, tareas recurrentes, equipos y SMTP exigen `editVersion` (entero positivo)
|
||
obtenido de la lectura correspondiente como `edit_version`. El `PUT /api/company` exige el mismo
|
||
campo en multipart. La API responde `428` si falta o es inválido y `409` si otro guardado cambió
|
||
el registro. En empresa y SMTP la respuesta `GET` incluye `edit_version`.
|
||
Las reuniones siguen el mismo contrato (`PUT /api/meetings/:id`).
|
||
|
||
## Conexiones externas
|
||
|
||
`/api/integrations` (JWT). Superadmin: `GET/POST /connections`, `PUT /connections/:id`,
|
||
`POST /connections/:id/test`, `GET /connections/:id/browse?source=&table=` (fuentes, tablas,
|
||
estructura + 20 filas), `PUT /sources/:purpose` (`patients`/`employees`, valida el mapeo contra la
|
||
estructura real), `POST /sources/patients/sync {full}`. Admin y superadmin: `GET /sources` (el admin
|
||
solo ve estado), `GET /employees[?available=1]`, `POST /employees/sync`, `GET /providers?search=` (busca en la fuente
|
||
Proveedores, marca `employee_id`), `POST /providers/:codigo/import {lastNames, firstNames, position}`. El token nunca se devuelve.
|
||
En la Agenda (recepción): `GET /api/clinic/patients/source`, `GET /api/clinic/patients/external?search=`
|
||
(modo en vivo) y `POST /api/clinic/patients/external/:codigo/import`. `POST /api/users` acepta
|
||
`employeeId` (Desde nómina).
|
||
|
||
## Agenda médica (v4.7.0)
|
||
|
||
Todas las rutas bajo `/api/clinic` exigen JWT, módulo `clinic` y fila activa en `clinic_staff`.
|
||
Sin la fila se responde 403. `scope=own` solo accede a su médico; `scope=all` gestiona todos.
|
||
Configuración exige además rol admin/superadmin. Fechas `YYYY-MM-DD HH:mm:ss` en hora civil
|
||
`America/Guayaquil`; rangos de listado de máximo 93 días.
|
||
|
||
| Método y ruta | Uso |
|
||
|---|---|
|
||
| `GET /staff/me` | alcance y condición de médico |
|
||
| `GET /doctors` | médicos visibles con horario y tipos activos |
|
||
| `GET /availability?doctorId=&date=&typeId=` | huecos de 15 minutos con duración del tipo |
|
||
| `GET /appointments?from=&to=[&doctorId=][&patientId=]` | citas del rango, incluidas canceladas y eliminadas (`deleted_at`) para trazabilidad; `patientId` solo recepción |
|
||
| `POST /appointments`, `PUT /appointments/:id` | crear o reprogramar/editar; `PUT` exige `editVersion` |
|
||
| `POST /appointments/:id/delete` | `{reason,editVersion}` borrado lógico (solo recepción): libera el horario, conserva la fila con quién/cuándo/motivo |
|
||
| `POST /appointments/:id/status` | `{status,editVersion}`; médico propio solo `attended`/`no_show` |
|
||
| `POST /appointments/:id/cancel` | `{reason,editVersion}`, solo recepción |
|
||
| `GET /blocks?from=&to=[&doctorId=]` | ocurrencias expandidas del rango |
|
||
| `POST /blocks`, `PUT /blocks/:id` | bloqueo; `{confirmAffected:true}` para guardar si afecta citas |
|
||
| `DELETE /blocks/:id?editVersion=` | eliminar bloqueo con versión |
|
||
| `GET /patients?search=&page=&pageSize=` | búsqueda paginada, solo recepción |
|
||
| `GET /patients/:id/appointments` | últimas 100 citas, solo recepción |
|
||
| `POST /patients`, `PUT /patients/:id` | alta y edición, solo recepción; `PUT` exige `editVersion` |
|
||
| `GET /config/staff`, `PUT /config/staff/:id` | personal y permiso interno |
|
||
| `GET/PUT /config/schedules/:id` | tramos; `PUT` recibe `{segments:[{weekday,startsTime,endsTime}]}` |
|
||
| `GET/POST /config/types`, `PUT /config/types/:id` | tipos de cita |
|
||
|
||
Una cita fuera del horario o en choque devuelve 409 con motivo concreto. Un bloqueo afectado
|
||
responde `{requiresConfirmation:true,affected:[...]}` y no se guarda sin confirmación. Las
|
||
ediciones con versión obsoleta devuelven 409. Las altas, ediciones, cambios de estado y
|
||
cancelaciones se auditan sin copiar datos de pacientes al detalle de auditoría.
|
||
|
||
## Calendario de reuniones (v4.4.0)
|
||
|
||
Todas las rutas requieren JWT y módulo `calendar`. Las fechas son `YYYY-MM-DD HH:mm:ss` en hora
|
||
civil `America/Guayaquil` (UTC-5). `GET /api/meetings?from=&to=&page=&pageSize=[&projectId=]` (sin `projectId`: solo reuniones que el
|
||
usuario organiza o a las que fue invitado, cualquier rol; con `projectId`: todas las del proyecto si
|
||
puede leerlo, si no 404) devuelve
|
||
`{items,total,page,pageSize}` con ocurrencias expandidas (`occurrence_start`, `occurrence_end`);
|
||
rango máximo 93 días y hasta 1500 ocurrencias por página (predeterminado: 1500).
|
||
`GET /api/meetings/activities?from=&to=` devuelve hasta 1000 vencimientos de actividades donde el
|
||
usuario es responsable (con `is_closed` y `overdue`).
|
||
`GET /api/meetings/tasks?from=&to=`: tareas donde el usuario es responsable (`activity_id`, `project_id`,
|
||
`is_closed`, `overdue`). `GET /api/meetings/projects?from=&to=`: proyectos donde es dueño o miembro,
|
||
una fila por fecha en rango (`milestone`: `start` | `end`). Ambas requieren módulo `projects`.
|
||
`GET /api/meetings/recurring?from=&to=` (requiere también el módulo `recurring_tasks`) devuelve los
|
||
próximos vencimientos de las tareas recurrentes activas que el usuario creó o de las que es responsable, calculados al vuelo
|
||
desde `next_due_date` con la misma regla que `advanceCycle` y cortados en `end_date`. Cada fila trae
|
||
`due_date`, `is_next` (el ciclo pendiente real), `overdue` y `can_open` (si puede abrirla en Tareas recurrentes).
|
||
|
||
Todas las filas de calendario traen `shared` (lo creó/organiza otra persona y me incluye).
|
||
|
||
- `GET /api/meetings/:id`: detalle, participantes con RSVP y excepciones.
|
||
- `POST /api/meetings`: `{title,description,startsAt,endsAt,allDay,location,videoUrl,projectId,reminderMinutes,participantUserIds,externalEmails,recurrence}`.
|
||
- `PUT /api/meetings/:id`: mismo cuerpo con `editVersion` (aplica a toda la serie).
|
||
- `DELETE /api/meetings/:id`: **cancelación lógica** de toda la serie, con `METHOD:CANCEL`.
|
||
- `POST /api/meetings/:id/occurrences/:date/cancel`: excluye una ocurrencia; `date` URL encoded.
|
||
- `PUT /api/meetings/:id/rsvp`: `{status:"accepted"|"declined"|"tentative"}`; solo participante propio.
|
||
|
||
`recurrence` es `null` o `{frequency:"daily"|"weekly"|"monthly",interval,weekdays,until,count}`;
|
||
`weekdays` usa 0=lunes a 6=domingo. Se exige exactamente uno de `until` o `count` y máximo
|
||
365 ocurrencias. Cuando hay choque de participantes, POST/PUT devuelve
|
||
`{requiresConfirmation:true,warnings:[nombres]}` sin guardar; reenviar con
|
||
`confirmConflicts:true` confirma. Correo y avisos se despachan en segundo plano; sin SMTP el correo
|
||
se registra en consola de desarrollo. Antes de usar estas rutas se debe aplicar la migración 034.
|
||
|
||
## 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` — revoca la sesión actual.
|
||
- `GET /api/auth/me` — usuario actual
|
||
- `GET /api/auth/sessions` — sesiones activas propias (`id`, navegador, SO, IP, fechas, `current`).
|
||
- `DELETE /api/auth/sessions/:id` — cierra una sesión propia; la actual equivale a logout.
|
||
- `POST /api/auth/sessions/revoke-others` — cierra las demás sesiones propias.
|
||
|
||
### 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`
|
||
- `GET /api/users/:id/sessions` · `DELETE /api/users/:id/sessions/:sessionId` ·
|
||
`POST /api/users/:id/sessions/revoke-all` — solo admin/superadmin; un admin no puede administrar
|
||
sesiones de un superadmin. Desactivar un usuario también revoca sus sesiones.
|
||
- `POST /api/users/:id/approve` — `{ roleId?, departmentId?, editVersion }`, solo sobre usuarios `pending_approval`; también rechaza cambios concurrentes con `409`.
|
||
- `POST /api/users/:id/reject` — borra al usuario `pending_approval` (se audita el email antes)
|
||
- `GET /api/users/:id/modules` · `PUT /api/users/:id/modules` `{ modules: [...] }` — solo Superadministrador.
|
||
Módulos: `projects`, `calendar`, `recurring_tasks`, `notes`, `vault`, `devices`, `organization`. No aplica a
|
||
un superadmin (`400`). `GET /api/auth/me` y el login devuelven `modules` (efectivos del usuario).
|
||
Las rutas de un módulo deshabilitado responden `403`; la búsqueda global devuelve vacío.
|
||
|
||
### 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: `statusId`, `priorityId`, `ownerUserId`, `dueSoon=true`,
|
||
`progress=hide|only`, `q`, `trash=true` solo superadmin; `page` desde 1 y `pageSize` 1–100,
|
||
predeterminado 20) → `{ items, total, page, pageSize }` · `POST /api/projects`.
|
||
- `GET /api/projects/options` → `{ id, code, name }[]` accesibles al usuario para selectores.
|
||
- `GET /api/projects/:id` · `PUT /api/projects/:id` — para editar, enviar `editVersion` obtenido
|
||
de `GET`; `428` sin versión, `409` si otra edición la cambió.
|
||
- `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`)
|
||
|
||
- `PUT /api/comments/:id` — `{ message, editVersion }`; `428` sin versión y `409` si cambió.
|
||
- `GET /api/comments?entityType=project&entityId=1` · `POST /api/comments`
|
||
|
||
### Adjuntos (`/api/attachments`)
|
||
- `GET /api/attachments?entityType=activity&entityId=1` · `POST /api/attachments?entityType=activity&entityId=1`
|
||
(multipart; entidad y permiso se validan antes de recibir el archivo)
|
||
- `GET /api/attachments/:id/download` · `DELETE /api/attachments/:id`
|
||
|
||
### Notas (`/api/notes`)
|
||
- `GET /api/notes` — solo las del usuario autenticado, ordenadas por `updated_at` descendente.
|
||
- `POST /api/notes` — `{ content }`. `PUT /api/notes/:id` — `{ content, editVersion }`. `DELETE /api/notes/:id`.
|
||
- Siempre privadas: una nota de otro usuario responde `404` (no `403`, para no revelar que
|
||
existe). Sin restricción de rol — cualquier autenticado tiene las suyas.
|
||
|
||
### Equipos (`/api/devices`) — superadmin / admin / it_manager (tokens: solo superadmin / admin)
|
||
- `GET /api/devices?q=&kind=&status=&stale=1&diskFull=1` · `GET /api/devices/:id` (con hardware,
|
||
red, software e historial) · `PUT /api/devices/:id` — `{ status, assignedUserId, departmentId, notes, editVersion }`.
|
||
- `GET /api/devices/dashboard` — KPIs y top (SO, tipo, software). `GET /api/devices/software` ·
|
||
`GET /api/devices/software/devices?name=` — software transversal.
|
||
- `GET /api/devices/access` — `{canView, canEdit, canManage}` del usuario (lo usa el menú). Lectura (`GET` de
|
||
dashboard/listado/detalle/software) también para usuarios con acceso compartido.
|
||
- `GET/POST /api/devices/viewers` · `DELETE /api/devices/viewers/:userId` — compartir Equipos (admin/superadmin).
|
||
- `POST /api/devices/:id/cpu-year` → equipo con `cpu_year`: la IA consulta solo el nombre del procesador y devuelve el año de lanzamiento (el más antiguo si hay dos). Roles que editan equipos; `400` sin procesador o IA sin configurar, `422` si no se pudo determinar; 30/hora.
|
||
- `GET /api/devices/:id/raw` — último inventario crudo del agente.
|
||
- `GET /api/devices/tokens/:id/secret` — token en claro (descifrado) para armar el instalador; audita `reveal`.
|
||
- `GET/POST /api/devices/tokens` · `PATCH/DELETE /api/devices/tokens/:id` — tokens de ingesta
|
||
(el token en claro solo se devuelve en el `POST`).
|
||
|
||
### Ingesta del agente GLPI (sin JWT; token de ingesta)
|
||
- `POST /api/inventory` (alias `POST /front/inventory.php`) — cuerpo JSON/XML, opcionalmente
|
||
comprimido (gzip/zlib). Auth: password Basic / `Bearer` / `X-Ingest-Token` / `?token=`.
|
||
`401` sin token válido, `400` formato inválido. Responde `{"status":"ok"}` (o `<REPLY/>` en XML).
|
||
|
||
### 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.
|
||
|
||
### IA (`/api/ai`) — requiere clave configurada por el superadmin
|
||
- `GET /api/ai/status` → `{ enabled }` (cualquier autenticado).
|
||
- `POST /api/ai/projects/:projectId/plan` `{ description?, lang? }` → `{ activities: [{ name, description, durationDays, tasks: [] }] }`. Requiere módulo Proyectos y escritura en el proyecto; 30/hora.
|
||
- `POST /api/ai/projects/:projectId/plan/apply` `{ activities }` → `{ created }` (crea actividades y tareas).
|
||
- `POST /api/ai/new-project/propose` `{ objective, startDate, lang? }` → `{ name, objective, description, projectTypeId, projectTypeName, estimatedEndDate, activities }`; `400` si el objetivo es corto o la fecha inválida (sin llamar a la IA), `422` si no es un proyecto. 30/hora.
|
||
- `POST /api/ai/new-project` `{ objective, startDate, name, description?, projectTypeId?, requestingDepartmentId, activities }` → `{ id, code }`. Roles que crean proyectos.
|
||
- `POST /api/ai/projects/:projectId/report` `{ lang? }` → `{ project, generatedAt, summary, health, achievements, delays: [{ item, reason }], risks, nextSteps }`. Requiere lectura del proyecto; 30/hora.
|
||
- `GET /api/settings/ai/usage?period=month|all` → `{ period, users: [{ userId, userName, calls, inputTokens, outputTokens, costUsd, lastUsed }], total }` — solo superadmin.
|
||
- `GET/PUT /api/settings/ai` `{ apiKey?, model, editVersion }` y `POST /api/settings/ai/test` — solo superadmin; la clave nunca se devuelve.
|
||
|
||
### Log de errores (`/api/error-logs`)
|
||
- `POST /api/error-logs` — público (sin auth, rate-limited a 30/15min), lo usa el frontend para
|
||
reportar errores propios (incluso sin sesión). `{ message, stack?, url?, severity? }`
|
||
(`severity`: `alta`/`media`/`baja`; si falta o es inválida, cae en `media`). Si llega un token
|
||
válido en `Authorization`, se guarda el usuario asociado, sin exigirlo.
|
||
- `GET /api/error-logs?source=backend|frontend&severity=alta|media|baja&limit=` — solo
|
||
Superadministrador. Lista los errores más recientes (máx 1000 guardados, se recorta solo).
|
||
Backend guarda `stack`/`url`/`method`/usuario automáticamente en cada excepción 500 no manejada
|
||
(ver `errorHandler.js`) con `severity=alta` siempre — nunca se etiqueta a mano.
|
||
- `DELETE /api/error-logs` — solo Superadministrador. Borra todo el historial.
|
||
|
||
### 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, edit_version }` (nunca la clave)
|
||
- `PUT /api/settings/smtp` — `{ host, port, user, from, pass?, editVersion }` (`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), `editVersion` y `logo?` (archivo,
|
||
PNG/JPG/WEBP, máx 3MB; se comprueba la firma real del archivo). 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)
|
||
|
||
## Cambio de correo (v4.6.0)
|
||
|
||
- `GET /api/account/email-change`: última solicitud propia (requiere JWT).
|
||
- `POST /api/account/email-change`: `{ newEmail, currentPassword }`; crea una solicitud tras validar la clave, con límite de 20 solicitudes por 15 minutos.
|
||
- `DELETE /api/account/email-change`: cancela la solicitud propia pendiente o aprobada.
|
||
- `GET /api/users/email-change-requests`: pendientes que el admin/superadmin puede decidir.
|
||
- `POST /api/users/email-change-requests/:id/approve`: aprueba y envía el enlace de confirmación al correo nuevo.
|
||
- `POST /api/users/email-change-requests/:id/reject`: `{ reason }` obligatorio; avisa al correo actual.
|
||
- `POST /api/auth/email-change/self-approve`: `{ token }`; enlace público de aprobación exclusiva del superadmin, un solo uso.
|
||
- `POST /api/auth/email-change/confirm`: `{ token }`; enlace público de confirmación del correo nuevo. Revoca todas las sesiones al aplicar.
|
||
|
||
Los endpoints públicos tienen límite de 20 solicitudes por 15 minutos. Los enlaces duran 24 horas; las solicitudes abiertas vencen a los 7 días. Los tokens se almacenan solo como SHA-256.
|