Files
project.linkedcorp.net/BASE_DE_DATOS.md
T
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

189 lines
11 KiB
Markdown

# Base de Datos - project.linkedcorp.net
## Motor
- Motor: MariaDB
- Base principal: `project_linkedcorp`
- Host desarrollo: `192.168.222.20`
- Host producción: `192.168.222.32`
- Puerto: `3306`
- Charset: `utf8mb4`
- Collation: `utf8mb4_unicode_ci`
## Fuente de verdad
- La base ya existe: sí (creada en dev el 2026-09-03, usuario `project_linkedcorp`).
- Archivo SQL base: `backend/database/migrations/` (SQL idempotente, numerado, `CREATE TABLE IF NOT EXISTS`).
- Seeds: `backend/database/seeds/` (roles, catálogos, usuario admin de dev).
## Tablas principales (MVP)
| Tabla | Contenido |
|---|---|
| `roles` | catálogo fijo de 6 roles (superadmin, admin, it_manager, project_lead, collaborator, viewer) |
| `users` | usuarios, con rol, departamento y ciclo de vida de registro (`status`, tokens) |
| `system_settings` | clave/valor de configuración del sistema (hoy: SMTP, clave cifrada) |
| `departments` | departamentos/áreas, jerarquía simple vía `parent_id` |
| `project_types` | catálogo de tipos de proyecto |
| `priorities` | catálogo de prioridades (baja/media/alta/crítica) |
| `project_statuses` | catálogo de estados de proyecto |
| `activity_statuses` | catálogo de estados de actividad |
| `projects` | proyectos |
| `project_members` | usuarios asignados a un proyecto (reemplaza "equipos" como módulo aparte) |
| `activities` | actividades de un proyecto |
| `activity_dependencies` | dependencias simples entre actividades ("depende de") |
| `tasks` | checklist liviano dentro de una actividad ("Tareas" en la UI; renombrada desde `subtasks` en v1.8.0) |
| `recurring_tasks` | recordatorios periódicos (ej. renovación de licencias), menú propio "Tareas recurrentes" |
| `comments` | comentarios sobre proyecto o actividad (`entity_type` + `entity_id`) |
| `attachments` | archivos adjuntos a proyecto o actividad |
| `notifications` | notificaciones in-app por usuario |
| `audit_log` | auditoría central (quién, qué, sobre qué, cuándo) |
| `error_logs` | errores de backend (excepciones 500) y frontend (JS/promesas sin catch), panel "Errores del sistema" en Configuración |
| `notes` | apuntes rápidos personales, siempre privados de cada usuario, menú propio "Notas" |
## Relaciones clave
- `users.role_id -> roles.id`, `users.department_id -> departments.id`
- `departments.parent_id -> departments.id` (jerarquía opcional, ej. TI → Desarrollo)
- `projects.status_id -> project_statuses.id`, `projects.priority_id -> priorities.id`,
`projects.project_type_id -> project_types.id`, `projects.owner_user_id -> users.id`,
`projects.requesting_department_id / responsible_department_id -> departments.id`
- `project_members.project_id -> projects.id`, `project_members.user_id -> users.id`
- `activities.project_id -> projects.id`, `activities.status_id -> activity_statuses.id`,
`activities.priority_id -> priorities.id`, `activities.responsible_user_id -> users.id`
- `activity_dependencies.activity_id / depends_on_activity_id -> activities.id`
- `tasks.activity_id -> activities.id`, `tasks.responsible_user_id -> users.id` (nullable)
- `comments.entity_id` / `attachments.entity_id` apuntan a `projects.id` o `activities.id` según
`entity_type` (sin FK física cruzada — se valida en el servicio)
## Reglas de datos
- `projects.progress_pct` y `activities.progress_pct` son **calculados por el servicio**, no
editables directo por el cliente: actividad con tareas = % de tareas hechas; actividad sin
tareas = manual (0-100); proyecto = promedio del avance de sus actividades.
- `users.password_hash` con bcrypt, nunca texto plano ni reversible.
- `users.status` (`pending_verification`/`pending_approval`/`active`/`invited`) es el ciclo de vida
de la cuenta, independiente de `is_active` (que sigue siendo el interruptor manual del admin
para desactivar a alguien ya activo). Un usuario `pending_approval` rechazado se **borra**
físicamente (no hay estado "rechazado" — se audita el email en `audit_log` antes de borrar).
- `invited`: cuenta placeholder creada al agregar por correo a alguien que aún no es usuario desde
Miembros de un proyecto (`name`=`email`, `password_hash` aleatorio inservible, reutiliza
`verification_token`/`verification_token_expires` — 7 días en vez de 24h). Al aceptar la
invitación (`POST /api/auth/accept-invite`) pasa directo a `active` **sin pasar por
`pending_approval`** — a diferencia del autorregistro público, aquí quien lo invitó ya lo vetó al
agregarlo a su proyecto. La fila de `project_members` se crea en el momento de invitar, no al
aceptar, así que en cuanto acepta ya aparece como miembro sin pasos adicionales.
- `recurring_tasks`: `project_id`/`responsible_user_id` son **opcionales** (NULL) — se puede crear
directo desde "Tareas recurrentes" o promoviendo una tarea puntual de una actividad
(`project_id` queda como referencia del proyecto de origen, la tarea puntual original NO se
borra). `last_reminder_sent_at` se limpia a NULL cada vez que se completa un ciclo
(`POST /:id/complete`, que suma `interval_value`/`interval_unit` a `next_due_date`) — es lo que
evita reenviar el correo dos veces en el mismo ciclo. El envío real lo hace un job en el propio
proceso del backend (`setInterval` cada 15 min, sin cron del SO ni cola nueva).
- `system_settings.value` para `smtp_pass_enc` va **cifrado** (AES-256-GCM, `ENCRYPTION_KEY` del
`.env`) — nunca en texto plano, nunca se devuelve por la API (solo `configured: true/false`).
- `system_settings`: `company_name`/`company_logo_filename`/`company_logo_mime` — marca configurable
(nombre y logo de la empresa, `GET /api/company` público, `PUT` solo superadmin), mismo patrón
clave/valor que SMTP, sin tabla nueva. El archivo del logo vive en `STORAGE_ROOT/branding/`, un
solo archivo a la vez (se borra el anterior al subir uno nuevo). Sin valor guardado = "LinkedCorp"
y logo estático embebido (comportamiento por defecto, no rompe instalaciones existentes).
- `activities.sort_order`/`tasks.sort_order`: orden manual (arrastrar con el mouse en la UI). `0` =
nunca reordenado a mano (la migración `012` lo inicializa con el `id` para preservar el orden
previo); el endpoint de reordenar siempre escribe posiciones `1..N`, nunca `0`, para que esa
migración siga siendo idempotente en deploys futuros sin pisar un orden real ya guardado.
- Borrado lógico donde aplique (`users.is_active`); nunca DELETE físico de usuarios **activos** con
historial (los `pending_approval` rechazados sí se borran, ver arriba).
- `projects.deleted_at`/`deleted_by`: eliminación lógica (quien puede editar el proyecto puede
"eliminarlo" — se oculta de listados/dashboard, sigue en BD). Borrado físico (`DELETE`, arrastra
actividades/comentarios/adjuntos por `ON DELETE CASCADE`) solo vía `permanentlyDeleteProject`,
solo superadmin, y solo sobre un proyecto que ya tiene `deleted_at` (dos pasos, nunca directo).
- `activities.deleted_at`/`deleted_by` y `tasks.deleted_at`/`deleted_by` (desde v2.5.0): antes se
borraban físico sin poder recuperar; ahora es lógica igual que `projects` — quien tiene permiso
de Escritura en el proyecto puede eliminarlas y restaurarlas (`POST .../restore`), sin borrado
físico ni endpoint de purga definitiva (no hizo falta, a diferencia de `projects`).
- `project_members.role_in_project`: `'owner'` (dueño, se asigna solo al crear el proyecto, nunca
se toca desde el endpoint de rol), `'write'` (permiso de Escritura: crear/editar/eliminar
(lógico) actividades y tareas del proyecto — nunca el proyecto en sí), `NULL` = Lectura (solo
ver). **Compartir un proyecto (agregar/quitar miembros, cambiar su nivel) es exclusivo del
dueño** (`projects/service.js#canManageMembers`) — ni un rol global de administración puede
hacerlo por él. `'admin'` es un valor legado (de un tercer nivel "administrador de proyecto"
que ya no existe) que se sigue respetando como Escritura en filas viejas, pero no se asigna
desde la UI. Los roles superadmin/admin/it_manager ven, editan y pueden borrar (lógico)
cualquier proyecto igual que antes (soporte/supervisión) — la única acción que queda exclusiva
del dueño, sin excepción ni para ellos, es compartir/dejar de compartir el proyecto.
- Empresa asumida única (no hay tabla `companies` en el MVP).
- Producción siempre usa `192.168.222.32`. Desarrollo siempre usa `192.168.222.20`. No mezclar.
- `error_logs`: sin límite de tiempo, pero se recorta solo a los 1000 registros más recientes en
cada inserción (evita crecimiento sin control si algo entra en loop de errores). Se puede vaciar
del todo desde el panel ("Limpiar registro"). `severity` (alta/media/baja) nunca se etiqueta a
mano: backend y errores de render de React = alta, errores JS sueltos = media, promesas
rechazadas sin manejar = baja.
## Riesgos o decisiones
- Sin triggers de BD para recalcular avances: toda la lógica vive en la capa `service/` del backend
(más simple de depurar y de extender en fase 2).
- `risk_level` es un campo simple en `projects` (bajo/medio/alto) — el módulo completo de Riesgos
(probabilidad/impacto/mitigación) queda para fase 2, sin necesitar migrar este campo.
## Archivos relacionados
- `backend/.env.example`
- `backend/database/migrations/*.sql`
- `backend/database/seeds/*.sql`
---
## Crear la base de datos en DESARROLLO (.20)
Ya ejecutado para este proyecto (2026-09-03):
```bash
bash /home/mgonzalez/desarrollo/project-md-templates/crear-db-dev.sh project_linkedcorp project_linkedcorp
```
BD `project_linkedcorp` y usuario `project_linkedcorp`@`192.168.222.40` creados en `192.168.222.20`.
Las credenciales están en `backend/.env` (no versionado).
Verificar:
```bash
mysql --defaults-extra-file=~/.config/mysql/dev20-admin.cnf -e "SHOW DATABASES LIKE 'project_linkedcorp';"
```
Aplicar migraciones/seeds (una vez creado `backend/src/scripts/migrate-db.js`):
```bash
cd backend
set -a && source .env && set +a
node src/scripts/migrate-db.js
node src/scripts/seed-db.js
```
---
## Setup de base de datos en producción — ejecutado el 2026-09-06
BD `project_linkedcorp` creada en `.32` con `crear-db-prod.sh` (grant a `192.168.222.3`). Detalle
del proceso y estado real en `PRODUCCION.md`.
Migración en el servidor de aplicaciones (`.3`):
```bash
ssh deploy@192.168.222.3 -p 2203
cd /opt/apps/project.linkedcorp.net/source/backend
set -a && source /opt/apps/project.linkedcorp.net/backend/.env && set +a
node src/scripts/migrate-db.js
node src/scripts/seed-db.js # solo catálogos/roles; sin datos de ejemplo en produccion
node src/scripts/create-admin.js "Nombre" correo@dominio # solo la primera vez, bootstrap manual
```
---
## Actualizaciones de esquema en producción
Método: archivos SQL idempotentes en `backend/database/migrations/00X-nombre.sql`
(`CREATE TABLE IF NOT EXISTS` / `information_schema` guards), aplicados con
`node src/scripts/migrate-db.js`. Siempre backup (`mysqldump --single-transaction`) antes de migrar
en producción.