From 8afb1d09cb4a0e1f497d7adf98603f064b4bb4cb Mon Sep 17 00:00:00 2001 From: Pablinux Date: Sun, 8 Jun 2025 02:29:21 -0500 Subject: [PATCH] Cambios de unificacion en ingresos de visitas. --- .../controlador_Clientes_MiembrosVisitas.js | 481 +++++++++++++----- src/rutas/rt_clientes.js | 46 +- 2 files changed, 379 insertions(+), 148 deletions(-) diff --git a/src/controladores/controlador_Clientes_MiembrosVisitas.js b/src/controladores/controlador_Clientes_MiembrosVisitas.js index 84c32fd..8588c29 100644 --- a/src/controladores/controlador_Clientes_MiembrosVisitas.js +++ b/src/controladores/controlador_Clientes_MiembrosVisitas.js @@ -1,69 +1,70 @@ // src/controladores/controlador_Clientes_MiembrosVisitas.js -// NO NECESITARÁS 'moment-timezone' NI CÁLCULOS MANUALES COMPLEJOS -// const moment = require('moment-timezone'); - -// ¡IMPORTANTE: Cambia el nombre de esta constante para que coincida con la importación en rt_clientes.js! const controlador_Clientes_MiembrosVisitas = { /** - * @function registrarVisita - * @description Registra una nueva visita de miembro en la tabla clientes_miembros_visitas. - * @param {Object} req - Objeto de solicitud de Express. + * @function crearVisita + * @description Registra una nueva visita en la tabla `clientes_miembros_visitas`. + * Maneja tanto visitas de miembros como de clientes ocasionales. + * @param {Object} req - Objeto de solicitud de Express (req.body). * @param {Object} res - Objeto de respuesta de Express. */ - registrarVisita: (req, res) => { + crearVisita: (req, res) => { req.getConnection((err, connection) => { if (err) { - console.error('Error al obtener conexión para registrar visita:', err); + console.error('Error al obtener conexión para crear visita:', err); return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); } const { - id_miembro, - matricula_registrada, + client_id, // Obligatorio, siempre debe estar + es_miembro, // Booleano: true (1) o false (0) + id_miembro, // Opcional, solo si es_miembro es true nombre_area_acceso, - estado_acceso, // 'CONCEDIDO' o 'DENEGADO' + estado_acceso, motivo_denegacion, registrado_por, observaciones } = req.body; - // Validación básica de los campos requeridos - if (!id_miembro || !matricula_registrada || !nombre_area_acceso || !estado_acceso) { - return res.status(400).json({ mensaje: 'Faltan campos obligatorios para registrar la visita (id_miembro, matricula_registrada, nombre_area_acceso, estado_acceso).' }); + // Validaciones básicas de campos obligatorios + if (client_id === undefined || client_id === null || !nombre_area_acceso || !estado_acceso || es_miembro === undefined || es_miembro === null) { + return res.status(400).json({ mensaje: 'Faltan campos obligatorios (client_id, nombre_area_acceso, estado_acceso, es_miembro).' }); } - - // Asegúrate de que estado_acceso sea uno de los valores ENUM permitidos if (!['CONCEDIDO', 'DENEGADO'].includes(estado_acceso.toUpperCase())) { return res.status(400).json({ mensaje: 'El estado de acceso debe ser "CONCEDIDO" o "DENEGADO".' }); } - // --- SIMPLIFICACIÓN: Usar la fecha nativa de JavaScript --- - const now = new Date(); // Esto obtendrá la fecha y hora local del servidor (Quito) + // Validación de coherencia entre `es_miembro` y `id_miembro` (gestionada por código) + if (es_miembro && (id_miembro === undefined || id_miembro === null)) { + return res.status(400).json({ mensaje: 'Si es_miembro es TRUE, id_miembro es obligatorio.' }); + } + if (!es_miembro && (id_miembro !== undefined && id_miembro !== null)) { + return res.status(400).json({ mensaje: 'Si es_miembro es FALSE, id_miembro no debe ser proporcionado.' }); + } - // Formatear la fecha y hora a 'YYYY-MM-DD HH:mm:ss' para MySQL + // Usar la fecha nativa de JavaScript, asumiendo que el servidor está en UTC-5 (Quito) + const now = new Date(); const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, '0'); // getMonth() es 0-indexado + const month = String(now.getMonth() + 1).padStart(2, '0'); const day = String(now.getDate()).padStart(2, '0'); const hours = String(now.getHours()).padStart(2, '0'); const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); - const fecha_hora_visita = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; - // --------------------------------------------------- const query = ` INSERT INTO clientes_miembros_visitas - (id_miembro, matricula_registrada, fecha_hora_visita, nombre_area_acceso, estado_acceso, motivo_denegacion, registrado_por, observaciones) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + (client_id, es_miembro, id_miembro, fecha_hora_visita, nombre_area_acceso, estado_acceso, motivo_denegacion, registrado_por, observaciones) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `; const values = [ - id_miembro, - matricula_registrada, - fecha_hora_visita, // Usa la fecha formateada + client_id, + es_miembro, // Se insertará como 1 (TRUE) o 0 (FALSE) en MySQL + es_miembro ? id_miembro : null, // Inserta id_miembro si es miembro, de lo contrario NULL + fecha_hora_visita, nombre_area_acceso, - estado_acceso.toUpperCase(), // Asegura que se guarde en mayúsculas + estado_acceso.toUpperCase(), motivo_denegacion || null, registrado_por || null, observaciones || null @@ -71,11 +72,14 @@ const controlador_Clientes_MiembrosVisitas = { connection.query(query, values, (err, result) => { if (err) { - console.error('Error al registrar visita:', err); + console.error('Error al crear visita:', err); if (err.code === 'ER_NO_REFERENCED_ROW_2' || err.code === 'ER_NO_REFERENCED_ROW') { - return res.status(400).json({ mensaje: 'El ID de miembro especificado no existe. Asegúrate de que el miembro esté registrado en el sistema.', error: err.message }); + let msg = 'El ID de cliente o ID de miembro especificado no existe.'; + if (es_miembro) msg += ' Asegúrese de que el miembro y su cliente asociado existan.'; + else msg += ' Asegúrese de que el cliente exista.'; + return res.status(400).json({ mensaje: msg, error: err.message }); } - return res.status(500).json({ mensaje: 'Error interno del servidor al registrar la visita', error: err.message }); + return res.status(500).json({ mensaje: 'Error interno del servidor al crear la visita', error: err.message }); } if (result.affectedRows === 1) { @@ -83,15 +87,16 @@ const controlador_Clientes_MiembrosVisitas = { mensaje: 'Visita registrada exitosamente.', id_visita: result.insertId, datos_visita: { - id_miembro, - matricula_registrada, + client_id, + es_miembro, + id_miembro: es_miembro ? id_miembro : null, nombre_area_acceso, estado_acceso, - fecha_hora_visita // Devuelve la fecha formateada + fecha_hora_visita } }); } else { - res.status(500).json({ mensaje: 'Error al registrar la visita: no se afectó ninguna fila.' }); + res.status(500).json({ mensaje: 'Error al crear la visita: no se afectó ninguna fila.' }); } }); }); @@ -99,8 +104,9 @@ const controlador_Clientes_MiembrosVisitas = { /** * @function listarVisitas - * @description Lista todos los registros de visitas. - * @param {Object} req - Objeto de solicitud de Express. + * @description Lista todas las visitas, con información detallada de cliente y miembro si aplica. + * Soporta filtros opcionales por client_id, id_miembro, estado_acceso, nombre_area_acceso, y rango de fechas. + * @param {Object} req - Objeto de solicitud de Express (req.query para filtros). * @param {Object} res - Objeto de respuesta de Express. */ listarVisitas: (req, res) => { @@ -110,30 +116,81 @@ const controlador_Clientes_MiembrosVisitas = { return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); } - const query = ` + let query = ` SELECT - cv.id_visita, - cv.id_miembro, + cmv.id_visita, + cmv.fecha_hora_visita, + cmv.nombre_area_acceso, + cmv.estado_acceso, + cmv.motivo_denegacion, + cmv.registrado_por, + cmv.observaciones, + cmv.client_id, + cli.client_nombre, + cli.client_rucCed, + cmv.es_miembro, + cmv.id_miembro, cm.matricula AS matricula_miembro, - cli.client_nombre AS nombre_cliente, - cv.matricula_registrada, - cv.fecha_hora_visita, - cv.nombre_area_acceso, - cv.estado_acceso, - cv.motivo_denegacion, - cv.registrado_por, - cv.observaciones, - cv.fecha_creacion + ctm.nombre_tipo AS nombre_tipo_membresia, + cmv.fecha_creacion, + cmv.fecha_actualizacion FROM - clientes_miembros_visitas cv + clientes_miembros_visitas cmv JOIN - clientes_miembros cm ON cv.id_miembro = cm.id_miembro - JOIN - clientes cli ON cm.client_id = cli.client_id - ORDER BY cv.fecha_hora_visita DESC + clientes cli ON cmv.client_id = cli.client_id + LEFT JOIN + clientes_miembros cm ON cmv.id_miembro = cm.id_miembro + LEFT JOIN + clientes_membresias ctm ON cm.id_tipo_membresia = ctm.id_tipo_membresia + WHERE 1=1 `; + const queryParams = []; - connection.query(query, (err, rows) => { + // Aplicar filtros desde req.query + if (req.query.client_id) { + query += ` AND cmv.client_id = ?`; + queryParams.push(req.query.client_id); + } + if (req.query.id_miembro) { + query += ` AND cmv.id_miembro = ?`; + queryParams.push(req.query.id_miembro); + } + if (req.query.estado_acceso) { + const estado = req.query.estado_acceso.toUpperCase(); + if (['CONCEDIDO', 'DENEGADO'].includes(estado)) { + query += ` AND cmv.estado_acceso = ?`; + queryParams.push(estado); + } else { + return res.status(400).json({ mensaje: 'Estado de acceso inválido para el filtro.' }); + } + } + if (req.query.nombre_area) { + query += ` AND cmv.nombre_area_acceso LIKE ?`; + queryParams.push(`%${req.query.nombre_area}%`); + } + if (req.query.fecha_desde) { + query += ` AND cmv.fecha_hora_visita >= ?`; + queryParams.push(req.query.fecha_desde + ' 00:00:00'); // Desde el inicio del día + } + if (req.query.fecha_hasta) { + query += ` AND cmv.fecha_hora_visita <= ?`; + queryParams.push(req.query.fecha_hasta + ' 23:59:59'); // Hasta el final del día + } + // Filtro para `es_miembro` (true/false) + if (req.query.es_miembro !== undefined && req.query.es_miembro !== null) { + const esMiembroBool = req.query.es_miembro === 'true' || req.query.es_miembro === '1'; + query += ` AND cmv.es_miembro = ?`; + queryParams.push(esMiembroBool); + } + + query += ` ORDER BY cmv.fecha_hora_visita DESC`; + // Implementar paginación si se necesita + // if (req.query.limit && req.query.offset) { + // query += ` LIMIT ? OFFSET ?`; + // queryParams.push(parseInt(req.query.limit), parseInt(req.query.offset)); + // } + + connection.query(query, queryParams, (err, rows) => { if (err) { console.error('Error al listar visitas:', err); return res.status(500).json({ mensaje: 'Error interno del servidor al listar visitas', error: err.message }); @@ -159,25 +216,31 @@ const controlador_Clientes_MiembrosVisitas = { const query = ` SELECT - cv.id_visita, - cv.id_miembro, + cmv.id_visita, + cmv.fecha_hora_visita, + cmv.nombre_area_acceso, + cmv.estado_acceso, + cmv.motivo_denegacion, + cmv.registrado_por, + cmv.observaciones, + cmv.client_id, + cli.client_nombre, + cli.client_rucCed, + cmv.es_miembro, + cmv.id_miembro, cm.matricula AS matricula_miembro, - cli.client_nombre AS nombre_cliente, - cv.matricula_registrada, - cv.fecha_hora_visita, - cv.nombre_area_acceso, - cv.estado_acceso, - cv.motivo_denegacion, - cv.registrado_por, - cv.observaciones, - cv.fecha_creacion + ctm.nombre_tipo AS nombre_tipo_membresia, + cmv.fecha_creacion, + cmv.fecha_actualizacion FROM - clientes_miembros_visitas cv + clientes_miembros_visitas cmv JOIN - clientes_miembros cm ON cv.id_miembro = cm.id_miembro - JOIN - clientes cli ON cm.client_id = cli.client_id - WHERE cv.id_visita = ? + clientes cli ON cmv.client_id = cli.client_id + LEFT JOIN + clientes_miembros cm ON cmv.id_miembro = cm.id_miembro + LEFT JOIN + clientes_membresias ctm ON cm.id_tipo_membresia = ctm.id_tipo_membresia + WHERE cmv.id_visita = ? `; connection.query(query, [id_visita], (err, rows) => { @@ -193,9 +256,164 @@ const controlador_Clientes_MiembrosVisitas = { }); }, + /** + * @function actualizarVisita + * @description Actualiza un registro de visita existente por su ID en `clientes_miembros_visitas`. + * Solo permite actualizar campos como área, estado, motivo, registrado_por, observaciones. + * NO se permite actualizar `client_id`, `es_miembro`, `id_miembro` o `fecha_hora_visita`. + * @param {Object} req - Objeto de solicitud de Express (req.params.id_visita, req.body). + * @param {Object} res - Objeto de respuesta de Express. + */ + actualizarVisita: (req, res) => { + const { id_visita } = req.params; + const { + nombre_area_acceso, + estado_acceso, + motivo_denegacion, + registrado_por, + observaciones + } = req.body; + + if (!nombre_area_acceso || !estado_acceso) { + return res.status(400).json({ mensaje: 'Faltan campos obligatorios para actualizar la visita (nombre_area_acceso, estado_acceso).' }); + } + if (!['CONCEDIDO', 'DENEGADO'].includes(estado_acceso.toUpperCase())) { + return res.status(400).json({ mensaje: 'El estado de acceso debe ser "CONCEDIDO" o "DENEGADO".' }); + } + + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para actualizar visita:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + const query = ` + UPDATE clientes_miembros_visitas SET + nombre_area_acceso = ?, + estado_acceso = ?, + motivo_denegacion = ?, + registrado_por = ?, + observaciones = ?, + fecha_actualizacion = CURRENT_TIMESTAMP() + WHERE id_visita = ? + `; + const values = [ + nombre_area_acceso, + estado_acceso.toUpperCase(), + motivo_denegacion || null, + registrado_por || null, + observaciones || null, + id_visita + ]; + + connection.query(query, values, (err, result) => { + if (err) { + console.error('Error al actualizar visita:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al actualizar visita', error: err.message }); + } + + if (result.affectedRows === 0) { + return res.status(404).json({ mensaje: 'Visita no encontrada para actualizar' }); + } + res.json({ mensaje: 'Visita actualizada exitosamente' }); + }); + }); + }, + + /** + * @function eliminarVisita + * @description Elimina físicamente una visita de la tabla `clientes_miembros_visitas`. + * NOTA: Para propósitos de auditoría, a menudo se prefiere una "eliminación lógica" + * (por ejemplo, actualizando un campo `estado_visita` a 'ANULADA'). + * @param {Object} req - Objeto de solicitud de Express (req.params.id_visita). + * @param {Object} res - Objeto de respuesta de Express. + */ + eliminarVisita: (req, res) => { + const { id_visita } = req.params; + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para eliminar visita:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + const query = `DELETE FROM clientes_miembros_visitas WHERE id_visita = ?`; + + connection.query(query, [id_visita], (err, result) => { + if (err) { + console.error('Error al eliminar visita:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al eliminar visita', error: err.message }); + } + + if (result.affectedRows === 0) { + return res.status(404).json({ mensaje: 'Visita no encontrada para eliminar' }); + } + res.json({ mensaje: 'Visita eliminada exitosamente.' }); + }); + }); + }, + + /** + * @function listarVisitasPorCliente + * @description Lista visitas filtradas por un `client_id` específico. + * Incluye tanto visitas de miembros como de clientes ocasionales asociados a ese cliente. + * @param {Object} req - Objeto de solicitud de Express (req.params.client_id). + * @param {Object} res - Objeto de respuesta de Express. + */ + listarVisitasPorCliente: (req, res) => { + const { client_id } = req.params; + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para listar visitas por cliente:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + const query = ` + SELECT + cmv.id_visita, + cmv.fecha_hora_visita, + cmv.nombre_area_acceso, + cmv.estado_acceso, + cmv.motivo_denegacion, + cmv.registrado_por, + cmv.observaciones, + cmv.client_id, + cli.client_nombre, + cli.client_rucCed, + cmv.es_miembro, + cmv.id_miembro, + cm.matricula AS matricula_miembro, + ctm.nombre_tipo AS nombre_tipo_membresia, + cmv.fecha_creacion, + cmv.fecha_actualizacion + FROM + clientes_miembros_visitas cmv + JOIN + clientes cli ON cmv.client_id = cli.client_id + LEFT JOIN + clientes_miembros cm ON cmv.id_miembro = cm.id_miembro + LEFT JOIN + clientes_membresias ctm ON cm.id_tipo_membresia = ctm.id_tipo_membresia + WHERE cmv.client_id = ? + ORDER BY cmv.fecha_hora_visita DESC + `; + + connection.query(query, [client_id], (err, rows) => { + if (err) { + console.error('Error al listar visitas por cliente:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al listar visitas por cliente', error: err.message }); + } + if (rows.length === 0) { + return res.status(404).json({ mensaje: 'No se encontraron visitas para el cliente proporcionado.' }); + } + res.json(rows); + }); + }); + }, + /** * @function listarVisitasPorMiembro - * @description Lista el historial de visitas para un miembro específico por su id_miembro. + * @description Lista visitas filtradas por un `id_miembro` específico. + * Solo devolverá visitas donde `es_miembro` sea TRUE. * @param {Object} req - Objeto de solicitud de Express (req.params.id_miembro). * @param {Object} res - Objeto de respuesta de Express. */ @@ -209,18 +427,32 @@ const controlador_Clientes_MiembrosVisitas = { const query = ` SELECT - cv.id_visita, - cv.matricula_registrada, - cv.fecha_hora_visita, - cv.nombre_area_acceso, - cv.estado_acceso, - cv.motivo_denegacion, - cv.registrado_por, - cv.observaciones + cmv.id_visita, + cmv.fecha_hora_visita, + cmv.nombre_area_acceso, + cmv.estado_acceso, + cmv.motivo_denegacion, + cmv.registrado_por, + cmv.observaciones, + cmv.client_id, + cli.client_nombre, + cli.client_rucCed, + cmv.es_miembro, + cmv.id_miembro, + cm.matricula AS matricula_miembro, + ctm.nombre_tipo AS nombre_tipo_membresia, + cmv.fecha_creacion, + cmv.fecha_actualizacion FROM - clientes_miembros_visitas cv - WHERE cv.id_miembro = ? - ORDER BY cv.fecha_hora_visita DESC + clientes_miembros_visitas cmv + JOIN + clientes cli ON cmv.client_id = cli.client_id + JOIN + clientes_miembros cm ON cmv.id_miembro = cm.id_miembro AND cmv.es_miembro = TRUE + LEFT JOIN + clientes_membresias ctm ON cm.id_tipo_membresia = ctm.id_tipo_membresia + WHERE cmv.id_miembro = ? + ORDER BY cmv.fecha_hora_visita DESC `; connection.query(query, [id_miembro], (err, rows) => { @@ -229,7 +461,7 @@ const controlador_Clientes_MiembrosVisitas = { return res.status(500).json({ mensaje: 'Error interno del servidor al listar visitas por miembro', error: err.message }); } if (rows.length === 0) { - return res.status(404).json({ mensaje: 'No se encontraron visitas para el miembro con el ID proporcionado.' }); + return res.status(404).json({ mensaje: 'No se encontraron visitas para el miembro proporcionado.' }); } res.json(rows); }); @@ -238,13 +470,12 @@ const controlador_Clientes_MiembrosVisitas = { /** * @function listarVisitasPorEstadoAcceso - * @description Lista las visitas filtradas por un estado de acceso específico (CONCEDIDO/DENEGADO). + * @description Lista visitas filtradas por un estado de acceso específico (CONCEDIDO/DENEGADO). * @param {Object} req - Objeto de solicitud de Express (req.params.estado_acceso). * @param {Object} res - Objeto de respuesta de Express. */ listarVisitasPorEstadoAcceso: (req, res) => { const { estado_acceso } = req.params; - // Validar que el estado sea uno de los permitidos por el ENUM const estadosPermitidos = ['CONCEDIDO', 'DENEGADO']; if (!estadosPermitidos.includes(estado_acceso.toUpperCase())) { return res.status(400).json({ mensaje: `El estado de acceso proporcionado '${estado_acceso}' no es válido. Los estados permitidos son: ${estadosPermitidos.join(', ')}.` }); @@ -258,23 +489,30 @@ const controlador_Clientes_MiembrosVisitas = { const query = ` SELECT - cv.id_visita, - cv.id_miembro, + cmv.id_visita, + cmv.fecha_hora_visita, + cmv.nombre_area_acceso, + cmv.estado_acceso, + cmv.motivo_denegacion, + cmv.registrado_por, + cmv.observaciones, + cmv.client_id, + cli.client_nombre, + cli.client_rucCed, + cmv.es_miembro, + cmv.id_miembro, cm.matricula AS matricula_miembro, - cli.client_nombre AS nombre_cliente, - cv.matricula_registrada, - cv.fecha_hora_visita, - cv.nombre_area_acceso, - cv.estado_acceso, - cv.motivo_denegacion + ctm.nombre_tipo AS nombre_tipo_membresia FROM - clientes_miembros_visitas cv + clientes_miembros_visitas cmv JOIN - clientes_miembros cm ON cv.id_miembro = cm.id_miembro - JOIN - clientes cli ON cm.client_id = cli.client_id - WHERE cv.estado_acceso = ? - ORDER BY cv.fecha_hora_visita DESC + clientes cli ON cmv.client_id = cli.client_id + LEFT JOIN + clientes_miembros cm ON cmv.id_miembro = cm.id_miembro + LEFT JOIN + clientes_membresias ctm ON cm.id_tipo_membresia = ctm.id_tipo_membresia + WHERE cmv.estado_acceso = ? + ORDER BY cmv.fecha_hora_visita DESC `; connection.query(query, [estado_acceso.toUpperCase()], (err, rows) => { @@ -289,13 +527,12 @@ const controlador_Clientes_MiembrosVisitas = { /** * @function listarVisitasPorArea - * @description Lista las visitas filtradas por el nombre del área de acceso. + * @description Lista visitas filtradas por el nombre del área de acceso. * @param {Object} req - Objeto de solicitud de Express (req.params.nombre_area). * @param {Object} res - Objeto de respuesta de Express. */ listarVisitasPorArea: (req, res) => { const { nombre_area } = req.params; - if (!nombre_area) { return res.status(400).json({ mensaje: 'El nombre del área es obligatorio.' }); } @@ -308,26 +545,33 @@ const controlador_Clientes_MiembrosVisitas = { const query = ` SELECT - cv.id_visita, - cv.id_miembro, + cmv.id_visita, + cmv.fecha_hora_visita, + cmv.nombre_area_acceso, + cmv.estado_acceso, + cmv.motivo_denegacion, + cmv.registrado_por, + cmv.observaciones, + cmv.client_id, + cli.client_nombre, + cli.client_rucCed, + cmv.es_miembro, + cmv.id_miembro, cm.matricula AS matricula_miembro, - cli.client_nombre AS nombre_cliente, - cv.matricula_registrada, - cv.fecha_hora_visita, - cv.nombre_area_acceso, - cv.estado_acceso, - cv.motivo_denegacion + ctm.nombre_tipo AS nombre_tipo_membresia FROM - clientes_miembros_visitas cv + clientes_miembros_visitas cmv JOIN - clientes_miembros cm ON cv.id_miembro = cm.id_miembro - JOIN - clientes cli ON cm.client_id = cli.client_id - WHERE cv.nombre_area_acceso LIKE ? - ORDER BY cv.fecha_hora_visita DESC + clientes cli ON cmv.client_id = cli.client_id + LEFT JOIN + clientes_miembros cm ON cmv.id_miembro = cm.id_miembro + LEFT JOIN + clientes_membresias ctm ON cm.id_tipo_membresia = ctm.id_tipo_membresia + WHERE cmv.nombre_area_acceso LIKE ? + ORDER BY cmv.fecha_hora_visita DESC `; - connection.query(query, [`%${nombre_area}%`], (err, rows) => { // Uso de LIKE para búsqueda parcial + connection.query(query, [`%${nombre_area}%`], (err, rows) => { if (err) { console.error('Error al listar visitas por área:', err); return res.status(500).json({ mensaje: 'Error interno del servidor al listar visitas por área', error: err.message }); @@ -336,7 +580,6 @@ const controlador_Clientes_MiembrosVisitas = { }); }); } - }; module.exports = controlador_Clientes_MiembrosVisitas; \ No newline at end of file diff --git a/src/rutas/rt_clientes.js b/src/rutas/rt_clientes.js index 6a25068..3115b1d 100644 --- a/src/rutas/rt_clientes.js +++ b/src/rutas/rt_clientes.js @@ -5,7 +5,7 @@ const controladorClientes = require('../controladores/controlador_Clientes'); const controladorMembresias = require('../controladores/controlador_Clientes_Membresias'); const controladorMiembros = require('../controladores/controlador_Clientes_Miembros'); const controladorAreas = require('../controladores/controlador_Areas'); // Controlador para áreas de configuración -const controladorVisitasMiembros = require('../controladores/controlador_Clientes_MiembrosVisitas'); // Controlador para visitas de miembros +const controladorVisitas = require('../controladores/controlador_Clientes_MiembrosVisitas'); // Controlador para visitas de miembros //indice inical rutas.get('/clientes', controladorClientes.ver);//ver lista de clientes @@ -44,57 +44,45 @@ rutas.delete('/api/tipos-membresia/:id', controladorMembresias.eliminarTipoMembr // --- Rutas RESTful para MIEMBROS (tabla 'clientes_miembros') --- // Estas rutas apuntarán al nuevo controlador_Miembros // Obtener todos los miembros -// GET /api/miembros rutas.get('/api/miembros', controladorMiembros.listarMiembros); // Obtener un miembro por su ID -// GET /api/miembros/:id_miembro rutas.get('/api/miembros/:id_miembro', controladorMiembros.obtenerMiembroPorId); // Crear un nuevo miembro -// POST /api/miembros // Datos esperados en el cuerpo de la solicitud: matricula, client_id, id_tipo_membresia, fecha_inicio, fecha_fin, etc. rutas.post('/api/miembros', controladorMiembros.crearMiembro); // Actualizar un miembro existente por su ID -// PUT /api/miembros/:id_miembro // Datos esperados en el cuerpo de la solicitud para actualizar: estado, fechas, etc. rutas.put('/api/miembros/:id_miembro', controladorMiembros.actualizarMiembro); // Eliminar (lógicamente/cambiar estado a CANCELADO/SUSPENDIDO) un miembro por su ID -// DELETE /api/miembros/:id_miembro // NOTA: Dada la importancia del historial, es más común cambiar el estado a 'CANCELADO' o 'SUSPENDIDO' // en lugar de una eliminación física. El controlador debería manejar esto. rutas.delete('/api/miembros/:id_miembro', controladorMiembros.eliminarMiembro); // --- Rutas Adicionales Comunes para Miembros (Opcional) --- // Obtener miembros por client_id (útil para ver todas las membresías de un cliente) -// GET /api/clientes/:client_id/miembros rutas.get('/api/clientes/:client_id/miembros', controladorMiembros.listarMiembrosPorCliente); // Obtener miembros por estado (ej. 'ACTIVO', 'VENCIDO') -// GET /api/miembros/estado/:estado rutas.get('/api/miembros/estado/:estado', controladorMiembros.listarMiembrosPorEstado); // Obtener miembros por tipo de membresía -// GET /api/tipos-membresia/:id_tipo_membresia/miembros rutas.get('/api/tipos-membresia/:id_tipo_membresia/miembros', controladorMiembros.listarMiembrosPorTipoMembresia); -// --- Rutas RESTful para VISITAS de Miembros (tabla 'clientes_miembros_visitas') --- -// Estas rutas apuntarán al nuevo controlador_VisitasMiembros -// Registrar una nueva visita de miembro -// POST /api/visitas-miembros -// Datos esperados en el cuerpo: id_miembro, matricula_registrada, nombre_area_acceso, estado_acceso, motivo_denegacion (opcional), registrado_por (opcional), observaciones (opcional) -rutas.post('/api/visitas-miembros', controladorVisitasMiembros.registrarVisita); -// Obtener todas las visitas (puede ser útil para reportes o auditorías, pero cuidado con el volumen) -// GET /api/visitas-miembros -rutas.get('/api/visitas-miembros', controladorVisitasMiembros.listarVisitas); -// Obtener visitas por ID de miembro (para ver el historial de un miembro específico) -// GET /api/miembros/:id_miembro/visitas -rutas.get('/api/miembros/:id_miembro/visitas', controladorVisitasMiembros.listarVisitasPorMiembro); +/// --- Rutas RESTful para VISITAS UNIFICADAS (tabla 'clientes_visitas') --- +// Estas rutas apuntarán al nuevo controlador_ClientesVisitas +// Registrar una nueva visita (para miembro o ocasional) +rutas.post('/api/visitas', controladorVisitas.crearVisita); // Renombrado a 'crearVisita' +// Obtener todas las visitas (con posibilidad de filtros, cuidado con volumen) +rutas.get('/api/visitas', controladorVisitas.listarVisitas); // Obtener una visita específica por su ID -// GET /api/visitas-miembros/:id_visita -rutas.get('/api/visitas-miembros/:id_visita', controladorVisitasMiembros.obtenerVisitaPorId); -// (Opcional) Obtener visitas por estado de acceso (CONCEDIDO/DENEGADO) -// GET /api/visitas-miembros/estado/:estado_acceso -rutas.get('/api/visitas-miembros/estado/:estado_acceso', controladorVisitasMiembros.listarVisitasPorEstadoAcceso); -// (Opcional) Obtener visitas por área de acceso -// GET /api/visitas-miembros/area/:nombre_area -rutas.get('/api/visitas-miembros/area/:nombre_area', controladorVisitasMiembros.listarVisitasPorArea); +rutas.get('/api/visitas/:id_visita', controladorVisitas.obtenerVisitaPorId); +// Actualizar una visita existente (ej. corregir observaciones, cambiar estado si se permite) +rutas.put('/api/visitas/:id_visita', controladorVisitas.actualizarVisita); +// Eliminar lógicamente una visita (ej. marcar como 'ANULADA' si tuvieras ese estado) +rutas.delete('/api/visitas/:id_visita', controladorVisitas.eliminarVisita); // Implementación para una eliminación lógica +// Rutas de consulta específicas para visitas +rutas.get('/api/visitas/por-cliente/:client_id', controladorVisitas.listarVisitasPorCliente); +rutas.get('/api/visitas/por-miembro/:id_miembro', controladorVisitas.listarVisitasPorMiembro); +rutas.get('/api/visitas/estado/:estado_acceso', controladorVisitas.listarVisitasPorEstadoAcceso); +rutas.get('/api/visitas/area/:nombre_area', controladorVisitas.listarVisitasPorArea); // Rutas para la administración de Áreas de Acceso rutas.get('/api/areas', controladorAreas.listarAreas);