From 053c55ca96f59baa6a0def1f0bbac38bab2b16df Mon Sep 17 00:00:00 2001 From: Pablinux Date: Thu, 5 Jun 2025 00:33:52 -0500 Subject: [PATCH] Creacion de aoi miembros en ruta clientes. --- src/controladores/controlador_Miembros.js | 519 ++++++++++++++++++++++ src/rutas/rt_clientes.js | 36 +- 2 files changed, 554 insertions(+), 1 deletion(-) create mode 100644 src/controladores/controlador_Miembros.js diff --git a/src/controladores/controlador_Miembros.js b/src/controladores/controlador_Miembros.js new file mode 100644 index 0000000..8f59b7c --- /dev/null +++ b/src/controladores/controlador_Miembros.js @@ -0,0 +1,519 @@ +// src/controladores/controlador_Miembros.js + +const controladorMiembros = { + + /** + * @function listarMiembros + * @description Lista todos los registros de miembros, incluyendo información de su cliente y tipo de membresía. + * @param {Object} req - Objeto de solicitud de Express. + * @param {Object} res - Objeto de respuesta de Express. + */ + listarMiembros: (req, res) => { + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para listar miembros:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + const query = ` + SELECT + cm.id_miembro, + cm.matricula, + cm.client_id, + cli.client_nombre, + cli.client_rucCed, + cm.id_tipo_membresia, + ctm.nombre_tipo AS nombre_tipo_membresia, + ctm.categoria_membresia, + cm.estado, + cm.fecha_registro, + cm.fecha_inicio, + cm.fecha_fin, + cm.fecha_ultimo_acceso, + cm.visitas_realizadas_hoy, + cm.observaciones, + cm.creado_por, + cm.fecha_creacion, + cm.fecha_actualizacion + FROM + clientes_miembros cm + JOIN + clientes cli ON cm.client_id = cli.client_id + JOIN + clientes_membresias ctm ON cm.id_tipo_membresia = ctm.id_tipo_membresia + ORDER BY cm.fecha_registro DESC + `; + + connection.query(query, (err, rows) => { + if (err) { + console.error('Error al listar miembros:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al listar miembros', error: err.message }); + } + res.json(rows); + }); + }); + }, + + /** + * @function obtenerMiembroPorId + * @description Obtiene un miembro específico por su ID. + * @param {Object} req - Objeto de solicitud de Express (req.params.id_miembro). + * @param {Object} res - Objeto de respuesta de Express. + */ + obtenerMiembroPorId: (req, res) => { + const { id_miembro } = req.params; + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para miembro por ID:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + const query = ` + SELECT + cm.id_miembro, + cm.matricula, + cm.client_id, + cli.client_nombre, + cli.client_rucCed, + cm.id_tipo_membresia, + ctm.nombre_tipo AS nombre_tipo_membresia, + ctm.categoria_membresia, + cm.estado, + cm.fecha_registro, + cm.fecha_inicio, + cm.fecha_fin, + cm.fecha_ultimo_acceso, + cm.visitas_realizadas_hoy, + cm.observaciones, + cm.creado_por, + cm.fecha_creacion, + cm.fecha_actualizacion + FROM + clientes_miembros cm + JOIN + clientes cli ON cm.client_id = cli.client_id + JOIN + clientes_membresias ctm ON cm.id_tipo_membresia = ctm.id_tipo_membresia + WHERE cm.id_miembro = ? + `; + + connection.query(query, [id_miembro], (err, rows) => { + if (err) { + console.error('Error al obtener miembro por ID:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener miembro', error: err.message }); + } + if (rows.length === 0) { + return res.status(404).json({ mensaje: 'Miembro no encontrado' }); + } + res.json(rows[0]); + }); + }); + }, + + /** + * @function crearMiembro + * @description Crea un nuevo registro de miembro. + * @param {Object} req - Objeto de solicitud de Express (req.body contiene los datos del miembro). + * @param {Object} res - Objeto de respuesta de Express. + */ + crearMiembro: (req, res) => { + const { + matricula, client_id, id_tipo_membresia, fecha_inicio, fecha_fin, + observaciones, creado_por // Añadir creado_por si lo envías desde el frontend + } = req.body; + + // Validar campos obligatorios + if (!matricula || !client_id || !id_tipo_membresia || !fecha_inicio || !fecha_fin) { + return res.status(400).json({ mensaje: 'Faltan campos obligatorios para crear el miembro (matricula, client_id, id_tipo_membresia, fecha_inicio, fecha_fin).' }); + } + + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para crear miembro:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + const query = ` + INSERT INTO clientes_miembros ( + matricula, client_id, id_tipo_membresia, estado, fecha_registro, + fecha_inicio, fecha_fin, observaciones, creado_por + ) VALUES (?, ?, ?, ?, NOW(), ?, ?, ?, ?) + `; + // 'ACTIVO' como estado por defecto en el INSERT + const values = [ + matricula, client_id, id_tipo_membresia, 'ACTIVO', + fecha_inicio, fecha_fin, observaciones, creado_por + ]; + + connection.query(query, values, (err, result) => { + if (err) { + console.error('Error al crear miembro:', err); + if (err.code === 'ER_DUP_ENTRY') { + return res.status(409).json({ mensaje: 'Ya existe un miembro con esa matrícula.', error: err.message }); + } + // Posibles errores de clave foránea (client_id o id_tipo_membresia no existen) + if (err.code === 'ER_NO_REFERENCED_ROW_2' || err.code === 'ER_NO_REFERENCED_ROW') { + return res.status(400).json({ mensaje: 'El cliente o el tipo de membresía especificado no existe.', error: err.message }); + } + return res.status(500).json({ mensaje: 'Error interno del servidor al crear miembro', error: err.message }); + } + res.status(201).json({ mensaje: 'Miembro creado exitosamente', id_miembro: result.insertId }); + }); + }); + }, + + /** + * @function actualizarMiembro + * @description Actualiza un registro de miembro existente por su ID. + * @param {Object} req - Objeto de solicitud de Express (req.params.id_miembro, req.body). + * @param {Object} res - Objeto de respuesta de Express. + */ + actualizarMiembro: (req, res) => { + const { id_miembro } = req.params; + const { + matricula, client_id, id_tipo_membresia, estado, fecha_inicio, fecha_fin, + fecha_ultimo_acceso, visitas_realizadas_hoy, observaciones // No incluir 'creado_por' aquí, ya que es en la creación + } = req.body; + + // Validar campos obligatorios para la actualización (aquí podrías hacer que algunos sean opcionales) + if (!matricula || !client_id || !id_tipo_membresia || !estado || !fecha_inicio || !fecha_fin) { + return res.status(400).json({ mensaje: 'Faltan campos obligatorios para actualizar el miembro (matricula, client_id, id_tipo_membresia, estado, fecha_inicio, fecha_fin).' }); + } + + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para actualizar miembro:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + const query = ` + UPDATE clientes_miembros SET + matricula = ?, client_id = ?, id_tipo_membresia = ?, estado = ?, + fecha_inicio = ?, fecha_fin = ?, fecha_ultimo_acceso = ?, + visitas_realizadas_hoy = ?, observaciones = ?, fecha_actualizacion = NOW() + WHERE id_miembro = ? + `; + const values = [ + matricula, client_id, id_tipo_membresia, estado, + fecha_inicio, fecha_fin, fecha_ultimo_acceso, + visitas_realizadas_hoy, observaciones, id_miembro + ]; + + connection.query(query, values, (err, result) => { + if (err) { + console.error('Error al actualizar miembro:', err); + if (err.code === 'ER_DUP_ENTRY') { + return res.status(409).json({ mensaje: 'Ya existe otro miembro con esa matrícula.', error: err.message }); + } + if (err.code === 'ER_NO_REFERENCED_ROW_2' || err.code === 'ER_NO_REFERENCED_ROW') { + return res.status(400).json({ mensaje: 'El cliente o el tipo de membresía especificado no existe.', error: err.message }); + } + return res.status(500).json({ mensaje: 'Error interno del servidor al actualizar miembro', error: err.message }); + } + + if (result.affectedRows === 0) { + return res.status(404).json({ mensaje: 'Miembro no encontrado para actualizar' }); + } + res.json({ mensaje: 'Miembro actualizado exitosamente' }); + }); + }); + }, + + /** + * @function actualizarEstadoMiembro + * @description Actualiza el estado de un miembro por su ID (ej. a 'SUSPENDIDO' o 'CANCELADO'). + * Esta función es para cambios de estado específicos sin actualizar todos los campos. + * @param {Object} req - Objeto de solicitud de Express (req.params.id_miembro, req.body.estado). + * @param {Object} res - Objeto de respuesta de Express. + */ + actualizarEstadoMiembro: (req, res) => { + const { id_miembro } = req.params; + const { estado, observaciones } = req.body; // Puedes añadir motivo, usuario, etc. + + if (!estado) { + return res.status(400).json({ mensaje: 'El campo estado es obligatorio para actualizar el estado del miembro.' }); + } + + // Validar que el estado sea uno de los permitidos por el ENUM + const estadosPermitidos = ['ACTIVO', 'VENCIDO', 'SUSPENDIDO', 'CANCELADO']; + if (!estadosPermitidos.includes(estado)) { + return res.status(400).json({ mensaje: `El estado proporcionado '${estado}' no es válido. Los estados permitidos son: ${estadosPermitidos.join(', ')}.` }); + } + + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para actualizar estado de miembro:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + // Opcional: Obtener el estado anterior para el historial antes de actualizar + connection.query('SELECT estado, fecha_inicio, fecha_fin, id_tipo_membresia FROM clientes_miembros WHERE id_miembro = ?', [id_miembro], (err, currentMemberRows) => { + if (err) { + console.error('Error al obtener estado actual del miembro:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener estado actual', error: err.message }); + } + if (currentMemberRows.length === 0) { + return res.status(404).json({ mensaje: 'Miembro no encontrado para actualizar estado' }); + } + + const estadoAnterior = currentMemberRows[0].estado; + const fechaInicioAnterior = currentMemberRows[0].fecha_inicio; + const fechaFinAnterior = currentMemberRows[0].fecha_fin; + const idTipoMembresiaAnterior = currentMemberRows[0].id_tipo_membresia; + + + connection.query( + `UPDATE clientes_miembros SET estado = ?, observaciones = ?, fecha_actualizacion = NOW() WHERE id_miembro = ?`, + [estado, observaciones, id_miembro], + (err, result) => { + if (err) { + console.error('Error al actualizar estado del miembro:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al actualizar estado del miembro', error: err.message }); + } + + if (result.affectedRows === 0) { + return res.status(404).json({ mensaje: 'Miembro no encontrado para actualizar estado' }); + } + + // Registrar el cambio en historial_estados_miembro + connection.query( + `INSERT INTO historial_estados_miembro ( + id_miembro, estado_anterior, estado_nuevo, fecha_cambio, motivo_cambio, + fecha_inicio_anterior, fecha_fin_anterior, fecha_inicio_nueva, fecha_fin_nueva, + id_tipo_membresia_anterior, id_tipo_membresia_nueva, usuario_responsable, observaciones + ) VALUES (?, ?, ?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + id_miembro, + estadoAnterior, + estado, // Nuevo estado + `Cambio de estado a ${estado}`, // Motivo por defecto, puedes personalizarlo + fechaInicioAnterior, fechaFinAnterior, // Fechas anteriores + currentMemberRows[0].fecha_inicio, currentMemberRows[0].fecha_fin, // Fechas actuales (no cambiadas por esta función, pero para el registro) + idTipoMembresiaAnterior, idTipoMembresiaAnterior, // Tipos de membresía (no cambiados por esta función) + req.body.usuario_responsable || 'Sistema/API', // O del token/sesión + observaciones + ], + (historialErr) => { + if (historialErr) { + console.error('Error al registrar historial de estado:', historialErr); + // No se devuelve un 500 aquí para no afectar la respuesta principal, + // pero es importante loguear este error. + } + res.json({ mensaje: 'Estado del miembro actualizado exitosamente', nuevo_estado: estado }); + } + ); + } + ); + }); + }); + }, + + /** + * @function eliminarMiembro + * @description Elimina lógicamente un miembro por su ID (cambia su estado a 'CANCELADO'). + * Se recomienda esta operación en lugar de la eliminación física para mantener el historial. + * @param {Object} req - Objeto de solicitud de Express (req.params.id_miembro para el ID). + * @param {Object} res - Objeto de respuesta de Express. + */ + eliminarMiembro: (req, res) => { + const { id_miembro } = req.params; + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para eliminar miembro:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + // Obtener estado actual antes de actualizar para el historial + connection.query('SELECT estado, fecha_inicio, fecha_fin, id_tipo_membresia FROM clientes_miembros WHERE id_miembro = ?', [id_miembro], (err, currentMemberRows) => { + if (err) { + console.error('Error al obtener estado actual del miembro para eliminación:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener estado para eliminación', error: err.message }); + } + if (currentMemberRows.length === 0) { + return res.status(404).json({ mensaje: 'Miembro no encontrado para eliminar' }); + } + + const estadoAnterior = currentMemberRows[0].estado; + const fechaInicioAnterior = currentMemberRows[0].fecha_inicio; + const fechaFinAnterior = currentMemberRows[0].fecha_fin; + const idTipoMembresiaAnterior = currentMemberRows[0].id_tipo_membresia; + + connection.query('UPDATE clientes_miembros SET estado = "CANCELADO", fecha_actualizacion = NOW() WHERE id_miembro = ?', [id_miembro], (err, result) => { + if (err) { + console.error('Error al eliminar miembro (lógicamente):', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al eliminar miembro', error: err.message }); + } + + if (result.affectedRows === 0) { + return res.status(404).json({ mensaje: 'Miembro no encontrado para eliminar' }); + } + + // Registrar el cambio en historial_estados_miembro + connection.query( + `INSERT INTO historial_estados_miembro ( + id_miembro, estado_anterior, estado_nuevo, fecha_cambio, motivo_cambio, + fecha_inicio_anterior, fecha_fin_anterior, fecha_inicio_nueva, fecha_fin_nueva, + id_tipo_membresia_anterior, id_tipo_membresia_nueva, usuario_responsable, observaciones + ) VALUES (?, ?, ?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + id_miembro, + estadoAnterior, + 'CANCELADO', // Nuevo estado + 'Eliminación lógica del miembro (estado CANCELADO)', + fechaInicioAnterior, fechaFinAnterior, // Fechas anteriores + currentMemberRows[0].fecha_inicio, currentMemberRows[0].fecha_fin, // Fechas actuales + idTipoMembresiaAnterior, idTipoMembresiaAnterior, // Tipos de membresía + req.body.usuario_responsable || 'Sistema/API', // O del token/sesión + `Miembro con matrícula ${currentMemberRows[0].matricula} ha sido cancelado.` // Observaciones + ], + (historialErr) => { + if (historialErr) { + console.error('Error al registrar historial de eliminación (lógica):', historialErr); + // No se devuelve un 500 aquí. + } + res.json({ mensaje: 'Miembro eliminado (lógicamente) exitosamente' }); + } + ); + }); + }); + }); + }, + + /** + * @function listarMiembrosPorCliente + * @description Lista todos los miembros asociados a un cliente específico por su client_id. + * @param {Object} req - Objeto de solicitud de Express (req.params.client_id). + * @param {Object} res - Objeto de respuesta de Express. + */ + listarMiembrosPorCliente: (req, res) => { + const { client_id } = req.params; + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para listar miembros por cliente:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + const query = ` + SELECT + cm.id_miembro, + cm.matricula, + cm.client_id, + cli.client_nombre, + cli.client_rucCed, + cm.id_tipo_membresia, + ctm.nombre_tipo AS nombre_tipo_membresia, + ctm.categoria_membresia, + cm.estado, + cm.fecha_inicio, + cm.fecha_fin + FROM + clientes_miembros cm + JOIN + clientes cli ON cm.client_id = cli.client_id + JOIN + clientes_membresias ctm ON cm.id_tipo_membresia = ctm.id_tipo_membresia + WHERE cm.client_id = ? + ORDER BY cm.fecha_registro DESC + `; + + connection.query(query, [client_id], (err, rows) => { + if (err) { + console.error('Error al listar miembros por cliente:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al listar miembros por cliente', error: err.message }); + } + res.json(rows); + }); + }); + }, + + /** + * @function listarMiembrosPorEstado + * @description Lista todos los miembros con un estado específico. + * @param {Object} req - Objeto de solicitud de Express (req.params.estado). + * @param {Object} res - Objeto de respuesta de Express. + */ + listarMiembrosPorEstado: (req, res) => { + const { estado } = req.params; + // Validar que el estado sea uno de los permitidos por el ENUM + const estadosPermitidos = ['ACTIVO', 'VENCIDO', 'SUSPENDIDO', 'CANCELADO']; + if (!estadosPermitidos.includes(estado)) { + return res.status(400).json({ mensaje: `El estado proporcionado '${estado}' no es válido. Los estados permitidos son: ${estadosPermitidos.join(', ')}.` }); + } + + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para listar miembros por estado:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + const query = ` + SELECT + cm.id_miembro, + cm.matricula, + cli.client_nombre, + ctm.nombre_tipo AS nombre_tipo_membresia, + cm.estado, + cm.fecha_inicio, + cm.fecha_fin + FROM + clientes_miembros cm + JOIN + clientes cli ON cm.client_id = cli.client_id + JOIN + clientes_membresias ctm ON cm.id_tipo_membresia = ctm.id_tipo_membresia + WHERE cm.estado = ? + ORDER BY cm.fecha_registro DESC + `; + + connection.query(query, [estado], (err, rows) => { + if (err) { + console.error('Error al listar miembros por estado:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al listar miembros por estado', error: err.message }); + } + res.json(rows); + }); + }); + }, + + /** + * @function listarMiembrosPorTipoMembresia + * @description Lista todos los miembros asociados a un tipo de membresía específico por su id_tipo_membresia. + * @param {Object} req - Objeto de solicitud de Express (req.params.id_tipo_membresia). + * @param {Object} res - Objeto de respuesta de Express. + */ + listarMiembrosPorTipoMembresia: (req, res) => { + const { id_tipo_membresia } = req.params; + req.getConnection((err, connection) => { + if (err) { + console.error('Error al obtener conexión para listar miembros por tipo de membresía:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al obtener conexión', error: err.message }); + } + + const query = ` + SELECT + cm.id_miembro, + cm.matricula, + cli.client_nombre, + cm.estado, + cm.fecha_inicio, + cm.fecha_fin + FROM + clientes_miembros cm + JOIN + clientes cli ON cm.client_id = cli.client_id + WHERE cm.id_tipo_membresia = ? + ORDER BY cm.fecha_registro DESC + `; + + connection.query(query, [id_tipo_membresia], (err, rows) => { + if (err) { + console.error('Error al listar miembros por tipo de membresía:', err); + return res.status(500).json({ mensaje: 'Error interno del servidor al listar miembros por tipo de membresía', error: err.message }); + } + res.json(rows); + }); + }); + } + }; + + module.exports = controladorMiembros; \ No newline at end of file diff --git a/src/rutas/rt_clientes.js b/src/rutas/rt_clientes.js index 55dab54..85dff12 100644 --- a/src/rutas/rt_clientes.js +++ b/src/rutas/rt_clientes.js @@ -4,6 +4,7 @@ const rutas = express.Router(); const controladorClientes = require('../controladores/controlador_Clientes'); // Importa el NUEVO controlador de Membresías const controladorMembresias = require('../controladores/controlador_Membresias'); +const controladorMiembros = require('../controladores/controlador_Miembros'); //indice inical rutas.get('/clientes', controladorClientes.ver);//ver lista de clientes @@ -31,9 +32,42 @@ rutas.post('/api/tipos-membresia', controladorMembresias.crearTipoMembresia); rutas.put('/api/tipos-membresia/:id', controladorMembresias.actualizarTipoMembresia); rutas.delete('/api/tipos-membresia/:id', controladorMembresias.eliminarTipoMembresia); +// --- 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); + + // Ruta para obtener ciudades rutas.get('/api/ciudades', controladorClientes.obtenerCiudades); - //APP_SIGMA consultas rutas.get('/consultaClientesJson', controladorClientes.app_pedidos_clientes);//consulta clientes: /consultaClientesJson?consulta=dato rutas.get('/busquedaSRI/', controladorClientes.buscarCli_sri);//API consulta clientes