Commit Graph

49 Commits

Author SHA1 Message Date
Milton Gonzalez 4583e3c456 feat: version 2.17.5 -- Panel: medidor y detalle en "Proyectos atrasados"
La tarjeta muestra un medidor semicircular (atrasados/activos) con el valor al
centro; al hacer clic, el bloque inferior lista los proyectos atrasados en vez
de mis actividades. Por ahora solo esta tarjeta.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 17:02:45 -05:00
Milton Gonzalez 9231bd2c7a fix: version 2.17.4 -- el avance del proyecto ignoraba que habia actividades eliminadas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 15:47:17 -05:00
Milton Gonzalez 30bb546b84 feat: version 2.17.0 -- Equipos: pestaña Usuarios (sesion, usuarios y grupos locales)
Migracion 027 (device_accounts).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 11:50:44 -05:00
Milton Gonzalez 4a305df73e feat: version 2.16.0 -- contador de cierre en Contraseñas, cierre por inactividad y ajustes de Equipos
Contraseñas: el cierre automatico ahora es por inactividad (no saca al usuario a medio escribir),
token de desbloqueo renovable y contador visible junto al titulo. Equipos: GUID como componente.
Sube la version para que el panel de Actualizaciones ofrezca los cambios 2.15.0 -> 2.16.0
(migraciones 025 y 026, instalador del agente y pestaña Compartir).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 08:52:46 -05:00
Milton Gonzalez d60b4b1d77 feat: version 2.15.0 -- pestaña Compartir en Equipos (acceso de solo lectura)
Admin/superadmin dan acceso de solo lectura (Resumen, Equipos, Software) a otros usuarios;
el backend valida el acceso en cada endpoint y el menu aparece solo a quien lo tenga.
Migracion 026 (device_viewers).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 08:32:53 -05:00
Milton Gonzalez d5f09dc91b feat: version 2.15.0 -- instalador del agente GLPI, filtro de componentes y token recuperable
Instalador .bat generado en el navegador, casilla para ocultar actualizaciones/drivers/componentes
en Software, buscador por pestaña en el detalle, GUIDs como componentes y tokens de ingesta
cifrados (migraciones 024 y 025).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 08:17:53 -05:00
Milton Gonzalez e25c2c3b35 feat: version 2.15.0 -- menu Equipos (inventario de TI con agente GLPI)
Ingesta POST /api/inventory (JSON/XML, token de instalacion), upsert por deviceid,
historial de cambios, pestañas Resumen/Equipos/Software/Agentes, detalle con buscador
por pestaña y casilla para ocultar actualizaciones/drivers/componentes del software.
Migraciones 023 y 024. Diseño en docs/ARQUITECTURA-INVENTARIO.md.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-19 19:05:00 -05:00
Milton Gonzalez 7139f1604e feat: version 2.14.0 -- compartir grupo completo y destruir con cascada
Contraseñas: "Compartir grupo" aplica el mismo share de solo lectura a todas
las credenciales de un grupo de una vez (el grupo sigue sin ser una unidad
de permiso -- es una accion de conveniencia). El grupo y cada credencial se
marcan con el icono de compartido. "Destruir grupo" ahora tiene dos
opciones: solo desagrupar (como antes) o destruir tambien las N credenciales,
con advertencia explicita del conteo.

Fix de paso en ComboBox (afecta a toda la app): el panel se cerraba solo al
abrirse si el trigger estaba cerca del borde de un contenedor con scroll,
por un micro-scroll automatico del navegador al enfocar el boton.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-14 17:42:39 -05:00
Milton Gonzalez 3da652c136 feat: version 2.13.0 -- grupos y compartir en Contraseñas
Grupos de un solo nivel (tarjetas, clic entra a ver sus credenciales) y compartir
una credencial individual de solo lectura con otro usuario registrado (puede
revelar/copiar la contraseña real, nunca editar/borrar/compartir/mover de grupo).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-14 16:14:36 -05:00
Milton Gonzalez 3257c4f065 feat: version 2.12.0 -- editar y borrar comentarios
Hasta ahora un comentario (de proyecto o actividad) solo se podia crear, nunca tocar. Se
agrega PUT/DELETE /comments/:id, restringido a quien lo escribio o a superadmin/admin (igual
criterio que useria para notas/tareas: nunca otro colaborador aunque tenga permiso de
escritura en el proyecto). Migracion 021 agrega updated_at a comments para mostrar "Editado"
igual que ya hace Notas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-14 12:22:27 -05:00
Milton Gonzalez 4520abe96f feat: version 2.11.0 -- contadores de dependencias/comentarios/documentos/miembros en listados
Actividades (dentro de un proyecto): nuevas columnas con la cantidad de dependencias,
comentarios y documentos de cada fila -- subconsultas correlacionadas agregadas al
LIST_SELECT existente (mismo patron que share_count de notas/proyectos), siempre numericas
(0 si no hay, nunca oculto).

Proyectos: nuevas columnas con la cantidad de miembros, actividades completas al 100% sobre
el total (ej. "1/4", mismo patron pendientes/total que ya usa la pestaña de tareas de una
actividad), comentarios y documentos.

Nuevo componente CountBadge (icono + numero) reutilizado en ambos listados -- a diferencia de
SharedWithIcon/Tabs que ocultan el badge en 0, este siempre lo muestra.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-14 11:40:07 -05:00
Milton Gonzalez 0964ffb8a8 feat: version 2.10.4 -- tarjetas de Notas arrastrables para reordenar
Nueva columna sort_order en notes (migracion 020, mismo patron que 012_sort_order.sql de
activities/tasks). Se reusa el hook useDragReorder ya existente (drag & drop nativo, sin
libreria nueva) -- el icono de agarre va solo en una esquina de la tarjeta, nunca en la
tarjeta completa, para no pisar el click que abre la edicion (mismo truco que ya usa
ListView.jsx: stopPropagation en el handle).

Solo las notas propias son arrastrables/reordenables; las compartidas conmigo quedan
siempre despues, ordenadas por fecha (el orden manual de otro usuario no tiene sentido para
mi vista). Se desactiva mientras hay un filtro de busqueda activo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 16:17:51 -05:00
Milton Gonzalez 2d1bfc0bfd feat: version 2.10.3 -- reenviar correo de invitacion/verificacion desde Usuarios
Nueva opcion "Reenviar correo" en el menu de 3 puntos de Organizacion -> Usuarios, para
usuarios en estado 'invited' o 'pending_verification'. Reusa las mismas columnas
(verification_token/verification_token_expires) y plantillas que el envio original
(inviteUser / auth/service.js#register), solo genera un token nuevo -- el anterior pudo
vencer o perderse en spam.

De paso se corrige que el estado "invited" no tenia rama propia en el menu de acciones (caia
en el branch generico de editar/activar-desactivar, que no aplica a una cuenta placeholder sin
clave real todavia) ni traduccion de su badge (user_status_invited faltaba en i18n).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 16:02:24 -05:00
Milton Gonzalez 98d9196791 feat: version 2.10.2 -- icono de compartida + buscador en tarjetas de Notas
Tarjetas de Notas: icono junto a la fecha cuando la nota esta compartida (propia compartida
con alguien, via share_count nuevo en la consulta; o recibida de otro usuario). Buscador
junto al titulo que filtra las tarjetas en vivo por contenido (client-side, sin ida y vuelta
al backend ya que el contenido no esta cifrado como en Contraseñas).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 15:37:31 -05:00
Milton Gonzalez da4d83e7cc fix: version 2.10.1 -- "Probar correo" tiraba Error interno si la clave SMTP no descifraba
El decrypt() de la clave SMTP guardada estaba fuera del try/catch tanto en testSmtpConfig
(el boton "Probar") como en getSmtpConfigInternal (usado por el mailer real para forgot-
password, registro, invitaciones). Si ENCRYPTION_KEY no coincide con la que se uso para
guardar -- el caso documentado de exilaser, que recibio un dump de LinkedCorp con su propia
clave -- el fallo de decrypt() se propagaba sin control: "Error interno" generico en el boton
de prueba, y un crash silencioso en el envio real en vez de la degradacion a modo dev que el
propio comentario del codigo promete. Ambos casos ahora devuelven un mensaje claro / degradan
correctamente.

De paso se agrega `app.set('trust proxy', 1)`: la app corre detras de Nginx Proxy Manager pero
Express no lo sabia, asi que express-rate-limit usaba la IP del proxy para todos los clientes
(un cupo compartido en vez de uno por usuario) y tiraba un aviso de validacion en cada request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 12:26:00 -05:00
Milton Gonzalez 926215297e feat: version 2.10.0 -- Notas en tarjetas, crear/editar en popup, compartir de solo lectura
Rediseno completo del modulo Notas (era lista inline, sin popup, sin compartir):
- Tarjetas con resumen del contenido en vez de lista vertical; la primera tarjeta siempre es
  un "+" para crear.
- Crear y editar se hacen en un DetailPopup (antes: textarea fijo arriba + edicion inline por
  tarjeta).
- Nueva tabla note_shares (migracion 019): compartir una nota con otro usuario ya registrado,
  siempre de solo lectura -- el dueño sigue siendo el unico que edita/borra/comparte (mismo
  patron que project_members pero sin rol ni invitacion por correo, ya que acá no hace falta).
  Quien recibe la nota la ve con "Compartida por {nombre}", sin ningun control de edicion.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-12 12:09:04 -05:00
Milton Gonzalez 3d220bcc20 feat: version 2.9.0 -- ver documentos .md/.txt directo, sin descargar
Agrega un boton "Ver" en Documentos (adjuntos de proyectos/actividades) para archivos de
texto plano y Markdown: se leen via blob (mismo mecanismo ya usado para descargar, que ya
maneja el Bearer token) y se muestran en un popup -- el .md se renderiza con formato (marked)
y se sanitiza con DOMPurify antes de insertarse en el DOM, para no abrir una via de XSS con
HTML/JS embebido en un documento subido por otro usuario.

De paso corrige que subir un .md fallaba con "tipo de archivo no permitido": el navegador no
manda un mimetype confiable para esa extension (varia segun SO), asi que ahora se normaliza
por extension en el fileFilter de multer en vez de depender solo del mimetype del navegador.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-07 17:17:03 -05:00
Milton Gonzalez 8214df62ae feat: version 2.8.1 -- catalogo de logos de marca en Contraseñas (simple-icons, CC0)
Amplia el selector de icono de una credencial con ~70 logos reales de marcas conocidas
(Google, GitHub, Cloudflare, cPanel, MikroTik, WordPress, etc.), empaquetados localmente
desde el paquete simple-icons (dominio publico) y tree-shakeados por Vite. Se mantiene el
criterio de privacidad ya establecido: ninguna consulta externa de favicon por entrada.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 14:16:31 -05:00
Milton Gonzalez 551ed9b790 feat: version 2.8.0 -- buscador en Contraseñas (nombre/usuario/enlace/notas)
Filtra las tarjetas mientras se escribe. Nombre/usuario/enlace se
comparan en texto plano; las notas (cifradas) se descifran solo en
el servidor para la comparacion -- el contenido nunca viaja de vuelta
al cliente salvo al revelar esa entrada puntual.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 12:57:10 -05:00
Milton Gonzalez 127ca2ba7e feat: version 2.7.2 -- tarjetas con icono en Contraseñas + fix borrado de password al editar
Listado de Contraseñas pasa de tabla a tarjetas: icono elegible (
catalogo fijo, sin servicios externos de favicon por privacidad) o
inicial del nombre como fallback, junto con nombre y enlace.

Fix real: editar una credencial sin revelar la contraseña antes la
dejaba vacia al guardar -- ahora se conserva la que ya estaba si no
se escribe una nueva.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 12:41:22 -05:00
Milton Gonzalez 0be0712ff0 feat: version 2.7.0 -- menu Contraseñas (repositorio de credenciales privado)
Nuevo modulo backend/src/modules/vault/: contraseña y notas cifradas
en el servidor (AES-256-GCM, mismo mecanismo ya usado para la clave
SMTP). Acceso al menu protegido con un PIN de 4 digitos independiente
del login (token corto propio, header X-Vault-Token, nunca el JWT de
sesion). El PIN no participa del cifrado -- cambiarlo o restablecerlo
por correo nunca afecta las contraseñas ya guardadas. Privado por
usuario, sin excepcion. Cada accion sensible (crear/editar/eliminar/
revelar/desbloquear) queda en audit_log. Compartir con otro usuario
queda para una proxima version.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 12:12:54 -05:00
Milton Gonzalez 82308faeb1 feat: version 2.6.0 -- barra de progreso rediseñada + icono de compartido
Porcentaje de avance dentro de la barra (antes aparte), fondo gris
visible en vez de casi invisible sobre el tema oscuro. Nuevo icono en
el listado de Proyectos que muestra con quien esta compartido cada
proyecto y su permiso, solo si aplica.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-30 08:04:50 -05:00
Milton Gonzalez fda83e9dff fix: version 2.5.0 -- compartir proyectos con permisos reales (Lectura/Escritura)
Bug de seguridad real corregido: cualquier usuario autenticado veia
TODOS los proyectos (listado, detalle, actividades, tareas,
comentarios, adjuntos, KPIs del panel), sin importar si era miembro.
Ahora nadie ve un proyecto ajeno sin que su dueno se lo comparta --
los roles de administracion (superadmin/admin/it_manager) mantienen
visibilidad total para soporte.

Nuevo modelo de permisos por proyecto, simple y no granular:
- Solo el dueno original puede compartir un proyecto (agregar/quitar
  miembros, cambiar su nivel) -- ni un admin del proyecto ni un rol
  global pueden hacerlo por el.
- Dos niveles: Lectura (solo ver) y Escritura (crear/editar/eliminar
  actividades y tareas -- nunca el proyecto en si: no lo borra, no
  edita sus datos, no crea proyectos nuevos).

Actividades y tareas pasan a borrado logico (antes se borraban
fisico sin poder recuperar) -- nueva papelera por proyecto con
restaurar.

Incluye tambien el filtro "Usuario" en Proyectos (probado en dev en
un paso anterior), que va de la mano con este cambio de visibilidad.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-25 18:19:41 -05:00
Milton Gonzalez fa4ddbe164 feat: version 2.2.0 -- boton real de actualizacion (auto-update)
POST /api/updater/apply dispara scripts/apply-update.sh (backup +
git pull + migraciones + build + restart) en un proceso separado.
Apagado por defecto: solo se activa si el servidor define
UPDATE_APP_DIR/UPDATE_BACKEND_SERVICE/UPDATE_FRONTEND_SERVICE en su
backend/.env. Sin esa config, el panel se comporta igual que antes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 12:18:12 -05:00
Milton Gonzalez ceda779b75 feat: menu Notas -- apuntes rapidos personales
Nuevo menu "Notas" en el sidebar: crear, editar y eliminar apuntes rapidos, con fecha y hora de
creacion (y de ultima edicion si aplica). Siempre privadas de cada usuario -- una nota de otro
usuario responde 404, no 403, para no revelar que existe. Sin restriccion de rol.

Nuevo notes (tabla) + GET/POST/PUT/DELETE /api/notes. Probado de punta a punta en dev: crear,
editar, borrar y el formato de fecha/hora funcionan correctamente.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 15:54:05 -05:00
Milton Gonzalez feadd2e715 feat: filtro de severidad (Alta/Media/Baja) en el log de errores
Se asigna sola, sin etiquetar nada a mano: backend y errores de render de React (rompen toda la
pantalla) = alta; errores JS sueltos = media; promesas rechazadas sin manejar = baja (suelen ser
fallos de red puntuales, no criticos). Nuevo filtro en el panel ademas del de origen ya existente.

Migracion 014_error_logs_severity.sql (ALTER TABLE ADD COLUMN IF NOT EXISTS). Probado en dev: las
tres severidades se asignan correctamente segun el origen, un valor invalido del frontend cae en
media, y el filtro combinado con origen funciona.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 14:50:36 -05:00
Milton Gonzalez dec8891506 feat: log de errores para el admin (backend + frontend)
Panel nuevo en Configuracion ("Errores del sistema", solo superadmin): errores de backend
(excepciones 500 no manejadas, con stack/ruta/metodo/usuario) y de frontend (errores JS y
promesas sin catch en el navegador del usuario, antes invisibles para cualquiera mas alla de esa
persona). Filtro por origen, boton para limpiar el historial.

Error Boundary de React: si algo rompe el render, ya no queda pantalla en blanco -- aviso simple
con boton de recargar, y el error queda reportado igual.

Nuevo error_logs (tabla, se recorta sola a 1000 filas) + GET/DELETE /api/error-logs (superadmin)
+ POST /api/error-logs (publico, rate-limited, para reportar incluso sin sesion).

Probado de punta a punta en dev: error real de backend (500) capturado con stack completo y
usuario correcto, error JS global y promesa rechazada capturados desde el navegador, panel
muestra ambos y el boton de limpiar funciona. Se encontro y corrigio un bug real en el camino: la
identificacion de usuario opcional leia jwt.payload.id en vez de .sub (como realmente lo firma
auth/service.js), asi que nunca identificaba a nadie.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 14:31:40 -05:00
Milton Gonzalez 043433bf9b feat: convertir una actividad en proyecto (con sus tareas)
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>
2026-09-17 08:59:16 -05:00
Milton Gonzalez 59aadc6a35 feat: panel de Actualizaciones en Configuracion (solo informativo)
Compara la version instalada contra el CHANGELOG.md de main en Gitea (lectura anonima por HTTPS,
sin token) y muestra cada version pendiente con su criticidad -- derivada del digito que cambio
(1ro=Alta, 2do=Media, 3ro=Baja, sin etiquetar nada a mano, regla ya documentada en
VERSIONADO.md), una descripcion breve, y si trae migraciones de base de datos nuevas. Banner en
rojo avisando que la actualizacion requiere reiniciar el sistema.

No instala nada automaticamente -- solo informa. Aplicar la actualizacion (git pull, build,
migrar, reiniciar servicios) sigue siendo manual, a criterio del administrador de cada
instalacion, tal cual DEPLOY_WEB.md ya documenta.

Nuevo GET /api/updater/status (solo superadmin). Requiere que el repo en Gitea sea publico con
vista anonima habilitada -- documentado en DEPLOY_WEB.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 17:11:58 -05:00
Milton Gonzalez 67cb58ed0b feat: el buscador de Proyectos y Actividades tambien busca en tareas
Buscador de Proyectos: ahora busca tambien en actividades y tareas de cualquier proyecto (antes
solo nombre/codigo del proyecto). Resultados en un panel flotante con la ruta completa
"Proyecto - Actividad - Tarea"; clic navega al proyecto y abre la actividad encontrada
(pestaña Tareas si el resultado era una tarea).

Buscador de Actividades (dentro de un proyecto): busca tambien en las tareas de esa actividad,
mismo panel con "Actividad - Tarea".

Nuevo GET /api/search?q=...&projectId= (global o acotado a un proyecto). Nuevos componentes/hooks
reutilizables: SearchResultsDropdown, useDebouncedValue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 15:04:53 -05:00
Milton Gonzalez 53570bd4f8 feat: mover una actividad (con sus tareas) a otro proyecto
Menu de tres puntos en el listado de Actividades -> elegir proyecto destino -> confirmar
("Se movera la actividad... (junto con sus tareas)... Aceptar/Cancelar) antes de ejecutar.

Tareas, comentarios y adjuntos se mueven solos (referencian la actividad, no el proyecto). Las
dependencias existentes se eliminan al mover -- solo tienen sentido dentro del mismo proyecto
(addDependency ya lo exigia). Nuevo PUT /api/activities/:id/move, exige permiso de escritura en
ambos proyectos (origen y destino).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 14:47:57 -05:00
Milton Gonzalez 2a1bdf1ba9 feat: nombre y logo de empresa configurables (solo dev, no desplegado a prod)
Nueva marca configurable desde Configuracion -> Empresa (solo superadmin): nombre y logo,
guardados en system_settings (mismo patron que SMTP, sin tabla nueva). Nuevo endpoint publico
GET /api/company (+ /logo) para que login/registro/recuperacion de clave puedan mostrar la marca
antes de iniciar sesion. Sin configurar, se ve "LinkedCorp" y el logo estatico de siempre.

Probado en dev (build + Playwright: panel de Configuracion, sidebar expandido/compacto, login
publico), sin desplegar a produccion por pedido explicito del usuario -- este commit sirve de
punto de control en git antes de decidir el despliegue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 14:42:15 -05:00
Milton Gonzalez 80981e9063 feat: arrastrar con el mouse para reordenar actividades y tareas
Hook reutilizable useDragReorder (drag & drop nativo HTML5, sin
libreria nueva) usado en dos lugares: ListView (opt-in via onReorder,
deshabilitado mientras haya un ordenamiento por columna activo) para
el listado de actividades de un proyecto, y el listado de tareas de
una actividad. Orden persistido en sort_order (activities y tasks,
migracion 012, posiciones siempre 1..N).

Fix real encontrado durante las pruebas: sincronizar el estado interno
del hook por la REFERENCIA del arreglo de entrada entraba en loop
infinito de renders cuando el llamador pasa "algo || []" (un arreglo
nuevo en cada render mientras los datos cargan). Corregido para
sincronizar por el contenido (ids), no por referencia.

Probado en dev con curl (persistencia del orden) y Playwright
(arrastrar filas en el navegador, orden visible y guardado
correctamente, sin errores de consola tras el fix).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 12:41:19 -05:00
Milton Gonzalez 5e0bc0809e feat: eliminar actividad, bloqueado si tiene tareas pendientes
No existia forma de borrar una actividad. Agregado DELETE
/api/activities/:id con boton "Eliminar" en el listado (popup de
confirmacion). Si tiene tareas, se bloquea con 400 y mensaje claro en
vez de arrastrar todo en cascada sin avisar.

De paso: la logica de borrar adjunto (fila + archivo en disco) se
extrajo a un attachments/service.js reutilizable, usado tanto por el
borrado individual como por el borrado en cascada de una actividad
(evita archivos huerfanos). Comentarios de la actividad tambien se
limpian (sin FK fisica cruzada, entity_type/entity_id es polimorfico).

Probado en dev con curl (bloquea con tarea, permite sin tareas, 404
tras borrar) y Playwright (mensaje de advertencia visible).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 12:30:53 -05:00
Milton Gonzalez 13a69aadc3 feat: campo "hasta" opcional en Tareas recurrentes
Vacio = se repite sin fin (comportamiento previo). Al completar un
ciclo cuyo proximo vencimiento supera "hasta", la tarea se desactiva
sola. Probado en dev con curl (3 ciclos seguidos, se desactiva justo
al superar la fecha) y validacion de que "hasta" no puede ser anterior
al proximo vencimiento.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 10:29:09 -05:00
Milton Gonzalez 72b616bd02 feat: Tareas recurrentes con recordatorio por correo
Menu propio en la barra lateral, independiente de Proyectos:
- Periodicidad configurable (cada N dias/meses/años), proximo
  vencimiento, responsable, proyecto de origen opcional.
- "Completar ciclo" avanza el proximo vencimiento sumando la
  periodicidad y limpia el recordatorio ya enviado.
- Recordatorio por correo automatico N dias antes del vencimiento
  (default 7), a una hora del dia configurable. Un job en el propio
  proceso del backend (setInterval cada 15 min) revisa y envia, sin
  infraestructura nueva (cron del SO, cola, etc).
- Desde la pestaña Tareas de una actividad, cada tarea tiene un boton
  "Convertir en recurrente" que la promueve (la tarea original no se
  borra).

Probado en dev con curl (crear, completar ciclo avanza next_due_date,
promover desde una tarea real, disparo real del job de recordatorios
verificado forzando fecha/hora, segunda corrida no reenvia) y Playwright
(listado, popup de creacion, popup de promocion anidado sobre el detalle
de actividad) sin errores de consola.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 10:17:02 -05:00
Milton Gonzalez ccf5cbeb13 feat: ordenar proyectos por prioridad real + separa paginas web de dominios
canWriteProject/ListView ya soportaba ordenar por cualquier columna con
clic en el encabezado; solo Prioridad ordenaba alfabetico por el texto
en vez de por severidad real. Se agrega pr.level a la consulta y
sortValue en la columna.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-11 09:41:07 -05:00
Milton Gonzalez f8b80d9bb4 feat: permite rechazar (eliminar) usuarios en pending_verification
Antes solo se podia rechazar un usuario en pending_approval. Una cuenta
autorregistrada que nunca confirmo su correo quedaba en
pending_verification para siempre, sin ninguna accion disponible en
Usuarios para quitarla.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 16:00:00 -05:00
Milton Gonzalez bb6bd6865c refactor: renombra Subtareas a Tareas en todo el sistema
Actividad -> Tarea, sin nivel "sub" intermedio (no hay una entidad "Tarea"
de la que estas fueran sub-, asi que el prefijo sobraba). Tabla subtasks
-> tasks (RENAME TABLE idempotente en la migracion 005; 001_initial.sql
tambien actualizado para que una instalacion nueva cree la tabla ya con
el nombre final, sin pasar por el nombre viejo). Modulo backend, rutas,
servicio frontend, pestaña del detalle de actividad, i18n y documentacion
actualizados. Sin cambios de comportamiento, solo nombres.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 08:55:58 -05:00
Milton Gonzalez d7478d0f21 feat: invitar a un proyecto por correo a alguien que aun no es usuario
Al agregar un miembro, el popup ahora permite elegir un usuario existente
o escribir un correo nuevo. Si ese correo no pertenece a nadie, se crea
una cuenta en estado 'invited' (nombre/clave placeholder) y se le manda
un correo con un enlace de un solo uso para que complete su registro
(elige su nombre y clave). La fila de project_members se crea de una vez
al invitar, asi que en cuanto acepta ya es miembro sin pasos adicionales.
A diferencia del autorregistro publico, no pasa por aprobacion de un
admin -- quien lo agrego a su proyecto ya lo vetó.

Nuevos endpoints publicos GET /api/auth/invite y POST /api/auth/accept-invite.
Nueva pagina AcceptInvitePage (/aceptar-invitacion). Migracion 004 agrega
el status 'invited' al enum de users.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 23:55:02 -05:00
Milton Gonzalez f96391d360 feat: administradores por proyecto y Configuracion solo para superadmin
- Un proyecto puede tener uno o mas administradores (role_in_project=
  'admin' en project_members), independiente del rol global del usuario.
  Nuevo PUT /api/projects/:id/members/:userId/role; canWriteProject lo
  reconoce ademas del comportamiento previo (admin/superadmin/it_manager
  global, o project_lead miembro). UI: casilla al agregar miembro + boton
  de escudo para promover/degradar (nunca toca al Dueño).
- Menu "Configuracion" (apariencia, catalogos, SMTP) visible y accesible
  solo para el superadmin: oculto en el nav y con guarda en la ruta
  /configuracion para quien la teclee directo. Escritura de catalogos
  restringida de [superadmin, admin] a solo superadmin para que el
  backend sea consistente con la UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 09:41:06 -05:00
Milton Gonzalez 5a25ed066d feat: eliminar proyecto (logico), papelera para superadmin y orden por creacion
- DELETE /api/projects/🆔 eliminacion logica (deleted_at/deleted_by), mismo
  permiso que editar el proyecto. Popup de confirmacion antes de aplicarla.
- Papelera (GET /api/projects?trash=true, POST /:id/restore, DELETE
  /:id/permanent) restringida al superadmin; el borrado fisico exige que el
  proyecto ya este en la papelera y arrastra actividades/comentarios/adjuntos
  por ON DELETE CASCADE.
- Dashboard (KPIs, actividades pendientes/calendario, proyectos por estado)
  excluye proyectos eliminados logicamente.
- Columna "Creado" en el listado de proyectos, ordenado por defecto de mas
  nuevo a mas antiguo (ListView admite initialSort ademas del orden por clic
  en cabecera que ya tenia).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 09:26:35 -05:00
Milton Gonzalez 6a0b7e8769 fix: quita cualquier espacio (no solo bordes) de host/usuario/remitente/destino SMTP
trim() no corregia un espacio colado a mitad del host (ej. "mail.linkedcorp. net"),
que es exactamente el caso real que seguia fallando en produccion tras el fix
anterior. Un dominio o correo nunca lleva espacios: ahora se remueven todos.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 14:28:51 -05:00
Milton Gonzalez dbd6ec5201 fix: recorta espacios en host/usuario/remitente/destino de SMTP
Un espacio de mas en el host (tecleado por error, p.ej. "mail.linkedcorp. net")
rompia la resolucion DNS sin ningun indicio visual del problema. Ahora se
recorta con trim() antes de guardar o de probar, tanto en updateSmtpConfig
como en testSmtpConfig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 14:21:23 -05:00
Milton Gonzalez 6fab4be39f feat: boton de prueba real en Configuracion > Correo (SMTP)
Agrega POST /api/settings/smtp/test (superadmin, rate-limited) que envia un
correo real usando los valores actuales del formulario y propaga el error
real del servidor SMTP, a diferencia de sendMail() que nunca lanza. El panel
de SMTP ahora tiene un campo de destinatario y boton "Enviar prueba" con el
resultado real (exito o error), en vez de solo "Guardar" sin forma de
verificar que la configuracion funcione.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:55:34 -05:00
Milton Gonzalez 8c06d20720 feat: cambiar clave desde la app (usuario logueado)
Nuevo PUT /api/auth/change-password: exige la clave actual (a diferencia de
reset-password, que usa un token de correo sin sesion). Opcion "Cambiar clave"
agregada al menu de usuario (popup), disponible para cualquier rol.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 12:35:25 -05:00
Milton Gonzalez 7557dffc52 feat: autorregistro con verificacion de correo + aprobacion, superadmin y SMTP
- POST /api/auth/register: primer usuario del sistema -> superadmin con sesion
  directa (bootstrap, sin correo ni aprobacion); los siguientes quedan
  pending_verification y reciben un correo con enlace de un solo uso (24h).
- GET /api/auth/verify-email: pasa a pending_approval y notifica in-app a los
  admin/superadmin activos.
- admin/superadmin aprueban (POST /api/users/:id/approve, eligen rol y
  departamento) o rechazan (POST /api/users/:id/reject, borra y audita el
  email) desde Organizacion -> Usuarios (nuevo badge de estado + acciones).
- login() da mensaje especifico segun el status de la cuenta en vez del
  generico "credenciales invalidas".
- Recuperacion de clave (forgot/reset-password): respuesta siempre generica,
  token de 1 hora de un solo uso.
- Nuevo modulo settings: SMTP configurable SOLO por superadmin desde
  Configuracion -> Correo (SMTP); clave cifrada AES-256-GCM (ENCRYPTION_KEY)
  en la tabla system_settings, nunca expuesta por la API. sendMail() degrada
  a modo dev (loguea en vez de enviar) si no hay SMTP configurado, y nunca
  deja que un fallo de envio tumbe al que lo llama.
- Migracion 002_registration_and_smtp.sql: rol superadmin, columnas de ciclo
  de vida en users (status, tokens), tabla system_settings, y promueve al
  usuario mas antiguo del sistema a superadmin.
- Fix: React.StrictMode disparaba el efecto de VerifyEmailPage dos veces; la
  segunda llamada (token ya consumido) mostraba "enlace invalido" pese a que
  la verificacion ya habia funcionado. Se agrega una guarda por token.
- Fix (deuda tecnica v1.0.0): se reordena el montaje de rutas en app.js —
  subtasksRoutes (mount generico en /api) va al final, para no interceptar
  con su requireAuth interno una request sin token dirigida a un router mas
  especifico registrado despues.
- Documentacion actualizada: AGENTS.md, SPEC.md, API.md, BASE_DE_DATOS.md,
  CONFIGURACION.md, ESTADO.md, CHANGELOG.md. Version 1.1.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 23:28:46 -05:00
Milton Gonzalez c7b270e6ea chore: prepara el proyecto para el primer deploy a produccion
- seed-db.js: los datos de ejemplo (departamentos TI/Finanzas/RRHH/Comercial +
  usuario admin con clave=usuario) solo se cargan si NODE_ENV != production.
  En produccion el seed solo carga catalogos (roles, prioridades, estados,
  tipos de proyecto) — la organizacion real la crea el admin desde la UI.
- Nuevo backend/src/scripts/create-admin.js: crea el usuario Administrador
  inicial de produccion con una clave fuerte generada al azar (nunca
  clave=usuario, esa convencion es solo para dev).
- frontend: agrega "serve" como dependencia de produccion (arquetipo B: el
  frontend corre como proceso separado sirviendo el build con
  `serve -s dist`).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 22:38:01 -05:00
Milton Gonzalez 1d22adc42e chore: estructura inicial del MVP
Backend (Express + MariaDB) y frontend (React + Vite, identidad Fluent/Windows) con el
nucleo funcional: seguridad, organizacion, proyectos, actividades, subtareas, dependencias,
dashboard, calendario, gantt, comentarios, adjuntos, notificaciones in-app y catalogos.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 08:37:58 -05:00