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

179 lines
9.9 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 |
## 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).
- `project_members.role_in_project`: `'owner'` (dueño, se asigna solo al crear el proyecto, nunca
se toca desde el endpoint de rol), `'admin'` (administrador **de ese proyecto puntual** — un
proyecto puede tener varios, otorgado por quien ya puede escribir el proyecto, sin importar el
rol global de a quién se lo otorgan), `NULL`/otro = miembro sin permisos de escritura (salvo que
además sea `project_lead` global, comportamiento previo sin cambios).
- 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.