diff --git a/API.md b/API.md index f17844a..6da93e3 100644 --- a/API.md +++ b/API.md @@ -136,11 +136,13 @@ ### 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? }`. Si llega un token + 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&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`). +- `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`) diff --git a/BASE_DE_DATOS.md b/BASE_DE_DATOS.md index d00d698..302f9f8 100644 --- a/BASE_DE_DATOS.md +++ b/BASE_DE_DATOS.md @@ -105,7 +105,9 @@ - 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"). + 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index ee3164c..3516429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog - project.linkedcorp.net +## [1.21.1] - 2026-09-17 +### Added +- Filtro por severidad (Alta/Media/Baja) en el panel "Errores del sistema", además del filtro por + origen ya existente. La severidad se asigna sola, sin etiquetar nada a mano: errores de 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 críticos). + ## [1.21.0] - 2026-09-17 ### Added - Log de errores para el administrador, panel nuevo en Configuración ("Errores del sistema", diff --git a/VERSION b/VERSION index 3500250..2844977 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.21.0 +1.21.1 diff --git a/backend/database/migrations/014_error_logs_severity.sql b/backend/database/migrations/014_error_logs_severity.sql new file mode 100644 index 0000000..cef6f7e --- /dev/null +++ b/backend/database/migrations/014_error_logs_severity.sql @@ -0,0 +1,4 @@ +-- Severidad automatica (nunca etiquetada a mano): backend y errores de render de React (rompen +-- toda la pantalla) = alta; errores JS sueltos = media; promesas rechazadas sin catch = baja. +-- Filas ya existentes (si las hay) quedan en 'media' por defecto, criterio neutral razonable. +ALTER TABLE error_logs ADD COLUMN IF NOT EXISTS severity ENUM('alta', 'media', 'baja') NOT NULL DEFAULT 'media' AFTER source; diff --git a/backend/package.json b/backend/package.json index 6454e17..8eeb209 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "project-linkedcorp-backend", - "version": "1.21.0", + "version": "1.21.1", "private": true, "type": "module", "main": "src/server.js", diff --git a/backend/src/modules/errorLogs/controller.js b/backend/src/modules/errorLogs/controller.js index b9b3257..3dad80a 100644 --- a/backend/src/modules/errorLogs/controller.js +++ b/backend/src/modules/errorLogs/controller.js @@ -2,7 +2,7 @@ import { ok, asyncHandler } from '../../utils/http.js'; import * as service from './service.js'; export const getErrorLogs = asyncHandler(async (req, res) => { - ok(res, await service.listErrors({ source: req.query.source, limit: req.query.limit })); + ok(res, await service.listErrors({ source: req.query.source, severity: req.query.severity, limit: req.query.limit })); }); export const postErrorLog = asyncHandler(async (req, res) => { diff --git a/backend/src/modules/errorLogs/repository.js b/backend/src/modules/errorLogs/repository.js index 03fc2af..52b9f13 100644 --- a/backend/src/modules/errorLogs/repository.js +++ b/backend/src/modules/errorLogs/repository.js @@ -4,11 +4,11 @@ import { pool } from '../../config/db.js'; // errores): al insertar, se recorta a los MAX_ROWS mas recientes. const MAX_ROWS = 1000; -export async function create({ source, message, stack, url, method, userId, userAgent }) { +export async function create({ source, severity, message, stack, url, method, userId, userAgent }) { await pool.query( - `INSERT INTO error_logs (source, message, stack, url, method, user_id, user_agent) - VALUES (?, ?, ?, ?, ?, ?, ?)`, - [source, message, stack || null, url || null, method || null, userId || null, userAgent || null] + `INSERT INTO error_logs (source, severity, message, stack, url, method, user_id, user_agent) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [source, severity, message, stack || null, url || null, method || null, userId || null, userAgent || null] ); await pool.query( `DELETE FROM error_logs WHERE id NOT IN (SELECT id FROM (SELECT id FROM error_logs ORDER BY created_at DESC LIMIT ?) x)`, @@ -16,13 +16,14 @@ export async function create({ source, message, stack, url, method, userId, user ); } -export async function findAll({ source, limit = 200 } = {}) { +export async function findAll({ source, severity, limit = 200 } = {}) { const clauses = []; const params = []; if (source) { clauses.push('el.source = ?'); params.push(source); } + if (severity) { clauses.push('el.severity = ?'); params.push(severity); } const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : ''; const [rows] = await pool.query( - `SELECT el.id, el.source, el.message, el.stack, el.url, el.method, + `SELECT el.id, el.source, el.severity, el.message, el.stack, el.url, el.method, el.user_id, u.name AS user_name, el.user_agent, el.created_at FROM error_logs el LEFT JOIN users u ON u.id = el.user_id ${where} ORDER BY el.created_at DESC LIMIT ?`, diff --git a/backend/src/modules/errorLogs/service.js b/backend/src/modules/errorLogs/service.js index f2b7020..4c4772c 100644 --- a/backend/src/modules/errorLogs/service.js +++ b/backend/src/modules/errorLogs/service.js @@ -3,12 +3,23 @@ import * as repo from './repository.js'; import { env } from '../../config/env.js'; import { HttpError } from '../../utils/http.js'; +// Severidad SIEMPRE automatica, nunca etiquetada a mano (mismo criterio que la criticidad del +// panel de Actualizaciones): backend y errores de render de React "alta" (rompen toda la +// pantalla), errores JS sueltos "media", promesas rechazadas sin catch "baja" (suelen ser fallos +// de red no criticos). El frontend indica cual de los tres le paso; si manda algo invalido u +// omite el dato, cae en "media" como default neutral. +const VALID_SEVERITIES = new Set(['alta', 'media', 'baja']); +function normalizeSeverity(value) { + return VALID_SEVERITIES.has(value) ? value : 'media'; +} + // Nunca debe tirar: si falla el logueo de un error, no puede convertirse en OTRO error sin // manejar que rompa la respuesta original. Se traga cualquier fallo propio (solo avisa por consola). export async function logBackendError(err, req) { try { await repo.create({ source: 'backend', + severity: 'alta', message: (err?.message || String(err)).slice(0, 2000), stack: (err?.stack || '').slice(0, 5000) || null, url: req?.originalUrl?.slice(0, 500) || null, @@ -39,6 +50,7 @@ export async function logFrontendError(payload, req) { if (!message) throw new HttpError(400, 'message es obligatorio'); await repo.create({ source: 'frontend', + severity: normalizeSeverity(payload.severity), message, stack: (payload.stack || '').slice(0, 5000) || null, url: (payload.url || '').slice(0, 500) || null, diff --git a/frontend/package.json b/frontend/package.json index ef5d399..1a619ff 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "project-linkedcorp-frontend", "private": true, - "version": "1.21.0", + "version": "1.21.1", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/components/ErrorBoundary.jsx b/frontend/src/components/ErrorBoundary.jsx index 7e98741..4a262f8 100644 --- a/frontend/src/components/ErrorBoundary.jsx +++ b/frontend/src/components/ErrorBoundary.jsx @@ -19,6 +19,7 @@ export class ErrorBoundary extends Component { message: error?.message || 'Error de render', stack: `${error?.stack || ''}\n${info?.componentStack || ''}`.slice(0, 5000), url: window.location.href, + severity: 'alta', // rompe toda la pantalla, no solo una parte }); } diff --git a/frontend/src/i18n/messages.js b/frontend/src/i18n/messages.js index 79f6b59..6d67111 100644 --- a/frontend/src/i18n/messages.js +++ b/frontend/src/i18n/messages.js @@ -87,6 +87,11 @@ export const messages = { error_logs_clear: 'Limpiar registro', error_logs_clear_confirm_msg: '¿Seguro que deseas borrar todo el historial de errores registrados? No se puede deshacer.', error_logs_empty: 'No hay errores registrados.', + error_logs_source: 'Origen', + error_logs_severity: 'Severidad', + error_logs_severity_alta: 'Alta', + error_logs_severity_media: 'Media', + error_logs_severity_baja: 'Baja', password_show: 'Mostrar clave', password_hide: 'Ocultar clave', common_new: 'Nuevo', @@ -358,6 +363,11 @@ export const messages = { error_logs_clear: 'Clear log', error_logs_clear_confirm_msg: 'Are you sure you want to delete the entire error log history? This cannot be undone.', error_logs_empty: 'No errors logged.', + error_logs_source: 'Source', + error_logs_severity: 'Severity', + error_logs_severity_alta: 'High', + error_logs_severity_media: 'Medium', + error_logs_severity_baja: 'Low', password_show: 'Show password', password_hide: 'Hide password', common_new: 'New', diff --git a/frontend/src/pages/Configuracion/ErrorLogsPanel.jsx b/frontend/src/pages/Configuracion/ErrorLogsPanel.jsx index fc05cce..e710777 100644 --- a/frontend/src/pages/Configuracion/ErrorLogsPanel.jsx +++ b/frontend/src/pages/Configuracion/ErrorLogsPanel.jsx @@ -7,11 +7,14 @@ import { Button } from '../../components/Button.jsx'; import { Badge } from '../../components/Badge.jsx'; import { ComboBox } from '../../components/ComboBox.jsx'; +const SEVERITY_COLOR = { alta: 'danger', media: 'warning', baja: 'success' }; + export default function ErrorLogsPanel() { const { t, pushToast, openDialog } = useUi(); const runAction = useAction(); const [source, setSource] = useState(''); - const { data: logs, loading, reload } = useAsync(() => listErrorLogs({ source }), [source]); + const [severity, setSeverity] = useState(''); + const { data: logs, loading, reload } = useAsync(() => listErrorLogs({ source, severity }), [source, severity]); function confirmClear() { openDialog({ @@ -34,7 +37,7 @@ export default function ErrorLogsPanel() { return (
-
+
+
+
+