Mejoras integrales en UI, lógica de negocio y auditoría
Este commit introduce una serie de mejoras significativas en toda la aplicación, abordando la experiencia de usuario, la consistencia de los datos, la robustez del backend y la implementación de un historial de cambios completo. ✨ **Funcionalidades y Mejoras (Features & Enhancements)** * **Historial de Auditoría Completo:** * Se implementa el registro en el historial para todas las acciones CRUD manuales: creación de equipos, adición y eliminación de discos, RAM y usuarios. * Los cambios de campos simples (IP, Hostname, etc.) ahora también se registran detalladamente. * **Consistencia de Datos Mejorada:** * **RAM:** La selección de RAM en el modal de "Añadir RAM" y la vista de "Administración" ahora agrupan los módulos por especificaciones (Fabricante, Tamaño, Velocidad), eliminando las entradas duplicadas causadas por diferentes `part_number`. * **Arquitectura:** El campo de edición para la arquitectura del equipo se ha cambiado de un input de texto a un selector con las opciones fijas "32 bits" y "64 bits". * **Experiencia de Usuario (UX) Optimizada:** * El botón de "Wake On Lan" (WOL) ahora se deshabilita visualmente si el equipo no tiene una dirección MAC registrada. * Se corrige el apilamiento de modales: los sub-modales (Añadir Disco/RAM/Usuario) ahora siempre aparecen por encima del modal principal de detalles y bloquean su cierre. * El historial de cambios se actualiza en tiempo real en la interfaz después de añadir o eliminar un componente, sin necesidad de cerrar y reabrir el modal. 🐛 **Correcciones (Bug Fixes)** * **Actualización de Estado en Vivo:** Al añadir/eliminar un módulo de RAM, los campos "RAM Instalada" y "Última Actualización" ahora se recalculan en el backend y se actualizan instantáneamente en el frontend. * **Historial de Sectores Legible:** Se corrige el registro del historial para que al cambiar un sector se guarde el *nombre* del sector (ej. "Técnica") en lugar de su ID numérico. * **Formulario de Edición:** El dropdown de "Sector" en el modo de edición ahora preselecciona correctamente el sector asignado actualmente al equipo. * **Error Crítico al Añadir RAM:** Se soluciona un error del servidor (`Sequence contains more than one element`) que ocurría al añadir manualmente un tipo de RAM que ya existía con múltiples `part_number`. Se reemplazó `QuerySingleOrDefaultAsync` por `QueryFirstOrDefaultAsync` para mayor robustez. * **Eliminación Segura:** Se impide la eliminación de un sector si este tiene equipos asignados, protegiendo la integridad de los datos. ♻️ **Refactorización (Refactoring)** * **Servicio de API Centralizado:** Toda la lógica de llamadas `fetch` del frontend ha sido extraída de los componentes y centralizada en un único servicio (`apiService.ts`), mejorando drásticamente la mantenibilidad y organización del código. * **Optimización de Renders:** Se ha optimizado el rendimiento de los modales mediante el uso del hook `useCallback` para memorizar funciones que se pasan como props. * **Nulabilidad en C#:** Se han resuelto múltiples advertencias de compilación (`CS8620`) en el backend al especificar explícitamente los tipos de referencia anulables (`string?`), mejorando la seguridad de tipos del código.
This commit is contained in:
@@ -16,12 +16,33 @@ namespace Inventario.API.Controllers
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// DTO para devolver los valores y su conteo
|
||||
// --- DTOs para los componentes ---
|
||||
public class ComponenteValorDto
|
||||
{
|
||||
public string Valor { get; set; } = "";
|
||||
public int Conteo { get; set; }
|
||||
}
|
||||
public class UnificarComponenteDto
|
||||
{
|
||||
public required string ValorNuevo { get; set; }
|
||||
public required string ValorAntiguo { get; set; }
|
||||
}
|
||||
|
||||
public class RamAgrupadaDto
|
||||
{
|
||||
public string? Fabricante { get; set; }
|
||||
public int Tamano { get; set; }
|
||||
public int? Velocidad { get; set; }
|
||||
public int Conteo { get; set; }
|
||||
}
|
||||
|
||||
public class BorrarRamAgrupadaDto
|
||||
{
|
||||
public string? Fabricante { get; set; }
|
||||
public int Tamano { get; set; }
|
||||
public int? Velocidad { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("componentes/{tipo}")]
|
||||
public async Task<IActionResult> GetComponenteValores(string tipo)
|
||||
@@ -53,13 +74,6 @@ namespace Inventario.API.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
// DTO para la petición de unificación
|
||||
public class UnificarComponenteDto
|
||||
{
|
||||
public required string ValorNuevo { get; set; }
|
||||
public required string ValorAntiguo { get; set; }
|
||||
}
|
||||
|
||||
[HttpPut("componentes/{tipo}/unificar")]
|
||||
public async Task<IActionResult> UnificarComponenteValores(string tipo, [FromBody] UnificarComponenteDto dto)
|
||||
{
|
||||
@@ -93,63 +107,64 @@ namespace Inventario.API.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
// DTO para devolver los valores de RAM y su conteo
|
||||
public class RamMaestraDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Part_number { get; set; }
|
||||
public string? Fabricante { get; set; }
|
||||
public int Tamano { get; set; }
|
||||
public int? Velocidad { get; set; }
|
||||
public int Conteo { get; set; }
|
||||
}
|
||||
|
||||
// --- Devuelve la RAM agrupada ---
|
||||
[HttpGet("componentes/ram")]
|
||||
public async Task<IActionResult> GetComponentesRam()
|
||||
{
|
||||
var query = @"
|
||||
SELECT
|
||||
mr.Id, mr.part_number as PartNumber, mr.Fabricante, mr.Tamano, mr.Velocidad,
|
||||
mr.Fabricante,
|
||||
mr.Tamano,
|
||||
mr.Velocidad,
|
||||
COUNT(emr.memoria_ram_id) as Conteo
|
||||
FROM
|
||||
dbo.memorias_ram mr
|
||||
LEFT JOIN
|
||||
dbo.equipos_memorias_ram emr ON mr.id = emr.memoria_ram_id
|
||||
GROUP BY
|
||||
mr.Id, mr.part_number, mr.Fabricante, mr.Tamano, mr.Velocidad
|
||||
mr.Fabricante,
|
||||
mr.Tamano,
|
||||
mr.Velocidad
|
||||
ORDER BY
|
||||
Conteo DESC, mr.Fabricante, mr.Tamano;";
|
||||
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var valores = await connection.QueryAsync<RamMaestraDto>(query);
|
||||
var valores = await connection.QueryAsync<RamAgrupadaDto>(query);
|
||||
return Ok(valores);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("componentes/ram/{id}")]
|
||||
public async Task<IActionResult> BorrarComponenteRam(int id)
|
||||
// --- Elimina un grupo completo ---
|
||||
[HttpDelete("componentes/ram")]
|
||||
public async Task<IActionResult> BorrarComponenteRam([FromBody] BorrarRamAgrupadaDto dto)
|
||||
{
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
// 1. Verificación de seguridad: Asegurarse de que el módulo no esté en uso.
|
||||
var usageQuery = "SELECT COUNT(*) FROM dbo.equipos_memorias_ram WHERE memoria_ram_id = @Id;";
|
||||
var usageCount = await connection.ExecuteScalarAsync<int>(usageQuery, new { Id = id });
|
||||
// Verificación de seguridad: Asegurarse de que el grupo no esté en uso.
|
||||
var usageQuery = @"
|
||||
SELECT COUNT(emr.id)
|
||||
FROM dbo.memorias_ram mr
|
||||
LEFT JOIN dbo.equipos_memorias_ram emr ON mr.id = emr.memoria_ram_id
|
||||
WHERE (mr.Fabricante = @Fabricante OR (mr.Fabricante IS NULL AND @Fabricante IS NULL))
|
||||
AND mr.Tamano = @Tamano
|
||||
AND (mr.Velocidad = @Velocidad OR (mr.Velocidad IS NULL AND @Velocidad IS NULL));";
|
||||
|
||||
var usageCount = await connection.ExecuteScalarAsync<int>(usageQuery, dto);
|
||||
|
||||
if (usageCount > 0)
|
||||
{
|
||||
return Conflict($"Este módulo de RAM está en uso por {usageCount} equipo(s) y no puede ser eliminado.");
|
||||
return Conflict(new { message = $"Este grupo de RAM está en uso por {usageCount} equipo(s) y no puede ser eliminado." });
|
||||
}
|
||||
|
||||
// 2. Si no está en uso, proceder con la eliminación.
|
||||
var deleteQuery = "DELETE FROM dbo.memorias_ram WHERE Id = @Id;";
|
||||
var filasAfectadas = await connection.ExecuteAsync(deleteQuery, new { Id = id });
|
||||
|
||||
if (filasAfectadas == 0)
|
||||
{
|
||||
return NotFound("Módulo de RAM no encontrado.");
|
||||
}
|
||||
// Si no está en uso, proceder con la eliminación de todos los registros maestros que coincidan.
|
||||
var deleteQuery = @"
|
||||
DELETE FROM dbo.memorias_ram
|
||||
WHERE (Fabricante = @Fabricante OR (Fabricante IS NULL AND @Fabricante IS NULL))
|
||||
AND Tamano = @Tamano
|
||||
AND (Velocidad = @Velocidad OR (Velocidad IS NULL AND @Velocidad IS NULL));";
|
||||
|
||||
await connection.ExecuteAsync(deleteQuery, dto);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -172,19 +187,14 @@ namespace Inventario.API.Controllers
|
||||
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
// 1. Verificación de seguridad: Asegurarse de que el valor no esté en uso.
|
||||
var usageQuery = $"SELECT COUNT(*) FROM dbo.equipos WHERE {columnName} = @Valor;";
|
||||
var usageCount = await connection.ExecuteScalarAsync<int>(usageQuery, new { Valor = valor });
|
||||
|
||||
if (usageCount > 0)
|
||||
{
|
||||
return Conflict($"Este valor está en uso por {usageCount} equipo(s) y no puede ser eliminado. Intente unificarlo en su lugar.");
|
||||
return Conflict(new { message = $"Este valor está en uso por {usageCount} equipo(s) y no puede ser eliminado. Intente unificarlo en su lugar." });
|
||||
}
|
||||
|
||||
// Esta parte es más conceptual. Un componente de texto no existe en una tabla maestra,
|
||||
// por lo que no hay nada que "eliminar". El hecho de que el conteo sea 0 significa
|
||||
// que ya no existe en la práctica. Devolvemos éxito para confirmar esto.
|
||||
// Si tuviéramos tablas maestras (ej: dbo.sistemas_operativos), aquí iría la consulta DELETE.
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Inventario.API.Controllers
|
||||
{
|
||||
var query = @"
|
||||
SELECT
|
||||
e.Id, e.Hostname, e.Ip, e.Mac, e.Motherboard, e.Cpu, e.Ram_installed, e.Ram_slots, e.Os, e.Architecture, e.created_at, e.updated_at, e.Origen,
|
||||
e.Id, e.Hostname, e.Ip, e.Mac, e.Motherboard, e.Cpu, e.Ram_installed, e.Ram_slots, e.Os, e.Architecture, e.created_at, e.updated_at, e.Origen, e.sector_id,
|
||||
s.Id as Id, s.Nombre,
|
||||
u.Id as Id, u.Username, u.Password, ue.Origen as Origen,
|
||||
d.Id as Id, d.Mediatype, d.Size, ed.Origen as Origen, ed.Id as EquipoDiscoId,
|
||||
@@ -51,7 +51,6 @@ namespace Inventario.API.Controllers
|
||||
{
|
||||
var equipoDict = new Dictionary<int, Equipo>();
|
||||
|
||||
// CAMBIO: Se actualizan los tipos en la función de mapeo de Dapper
|
||||
await connection.QueryAsync<Equipo, Sector, UsuarioEquipoDetalle, DiscoDetalle, MemoriaRamEquipoDetalle, Equipo>(
|
||||
query, (equipo, sector, usuario, disco, memoria) =>
|
||||
{
|
||||
@@ -61,12 +60,11 @@ namespace Inventario.API.Controllers
|
||||
equipoActual.Sector = sector;
|
||||
equipoDict.Add(equipoActual.Id, equipoActual);
|
||||
}
|
||||
// CAMBIO: Se ajusta la lógica para evitar duplicados en los nuevos tipos detallados
|
||||
if (usuario != null && !equipoActual.Usuarios.Any(u => u.Id == usuario.Id))
|
||||
equipoActual.Usuarios.Add(usuario);
|
||||
if (disco != null && !equipoActual.Discos.Any(d => d.Id == disco.Id))
|
||||
if (disco != null && !equipoActual.Discos.Any(d => d.EquipoDiscoId == disco.EquipoDiscoId))
|
||||
equipoActual.Discos.Add(disco);
|
||||
if (memoria != null && !equipoActual.MemoriasRam.Any(m => m.Id == memoria.Id && m.Slot == memoria.Slot))
|
||||
if (memoria != null && !equipoActual.MemoriasRam.Any(m => m.EquipoMemoriaRamId == memoria.EquipoMemoriaRamId))
|
||||
equipoActual.MemoriasRam.Add(memoria);
|
||||
|
||||
return equipoActual;
|
||||
@@ -148,7 +146,7 @@ namespace Inventario.API.Controllers
|
||||
else
|
||||
{
|
||||
// Actualizar y registrar historial
|
||||
var cambios = new Dictionary<string, (string anterior, string nuevo)>();
|
||||
var cambios = new Dictionary<string, (string? anterior, string? nuevo)>();
|
||||
|
||||
// Comparamos campos para registrar en historial
|
||||
if (equipoData.Ip != equipoExistente.Ip) cambios["ip"] = (equipoExistente.Ip, equipoData.Ip);
|
||||
@@ -279,7 +277,6 @@ namespace Inventario.API.Controllers
|
||||
[HttpPost("{hostname}/asociardiscos")]
|
||||
public async Task<IActionResult> AsociarDiscos(string hostname, [FromBody] List<Disco> discosDesdeCliente)
|
||||
{
|
||||
// 1. OBTENER EL EQUIPO
|
||||
var equipoQuery = "SELECT * FROM dbo.equipos WHERE Hostname = @Hostname;";
|
||||
using var connection = _context.CreateConnection();
|
||||
connection.Open();
|
||||
@@ -290,21 +287,16 @@ namespace Inventario.API.Controllers
|
||||
return NotFound("Equipo no encontrado.");
|
||||
}
|
||||
|
||||
// Iniciar una transacción para asegurar que todas las operaciones se completen o ninguna lo haga.
|
||||
using var transaction = connection.BeginTransaction();
|
||||
try
|
||||
{
|
||||
// 2. OBTENER ASOCIACIONES Y DISCOS ACTUALES DE LA BD
|
||||
var discosActualesQuery = @"
|
||||
SELECT d.Id, d.Mediatype, d.Size, ed.Id as EquipoDiscoId
|
||||
FROM dbo.equipos_discos ed
|
||||
JOIN dbo.discos d ON ed.disco_id = d.id
|
||||
WHERE ed.equipo_id = @EquipoId;";
|
||||
|
||||
SELECT d.Id, d.Mediatype, d.Size, ed.Id as EquipoDiscoId
|
||||
FROM dbo.equipos_discos ed
|
||||
JOIN dbo.discos d ON ed.disco_id = d.id
|
||||
WHERE ed.equipo_id = @EquipoId;";
|
||||
var discosEnDb = (await connection.QueryAsync<DiscoAsociado>(discosActualesQuery, new { EquipoId = equipo.Id }, transaction)).ToList();
|
||||
|
||||
// 3. AGRUPAR Y CONTAR DISCOS (del cliente y de la BD)
|
||||
// Crea un diccionario estilo: {"SSD_256": 2, "HDD_1024": 1}
|
||||
var discosClienteContados = discosDesdeCliente
|
||||
.GroupBy(d => $"{d.Mediatype}_{d.Size}")
|
||||
.ToDictionary(g => g.Key, g => g.Count());
|
||||
@@ -313,28 +305,23 @@ namespace Inventario.API.Controllers
|
||||
.GroupBy(d => $"{d.Mediatype}_{d.Size}")
|
||||
.ToDictionary(g => g.Key, g => g.Count());
|
||||
|
||||
var cambios = new Dictionary<string, (string anterior, string nuevo)>();
|
||||
var cambios = new Dictionary<string, (string? anterior, string? nuevo)>();
|
||||
|
||||
// 4. CALCULAR Y EJECUTAR ELIMINACIONES
|
||||
var discosAEliminar = new List<int>();
|
||||
foreach (var discoDb in discosEnDb)
|
||||
{
|
||||
var key = $"{discoDb.Mediatype}_{discoDb.Size}";
|
||||
if (discosClienteContados.TryGetValue(key, out int count) && count > 0)
|
||||
{
|
||||
// Este disco todavía existe en el cliente, decrementamos el contador y lo saltamos.
|
||||
discosClienteContados[key]--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Este disco ya no está en el cliente, marcamos su asociación para eliminar.
|
||||
discosAEliminar.Add(discoDb.EquipoDiscoId);
|
||||
|
||||
// Registrar para el historial
|
||||
var nombreDisco = $"Disco {discoDb.Mediatype} {discoDb.Size}GB";
|
||||
var anterior = discosDbContados.GetValueOrDefault(key, 0);
|
||||
if (!cambios.ContainsKey(nombreDisco)) cambios[nombreDisco] = (anterior.ToString(), (anterior - 1).ToString());
|
||||
else cambios[nombreDisco] = (anterior.ToString(), (int.Parse(cambios[nombreDisco].nuevo) - 1).ToString());
|
||||
else cambios[nombreDisco] = (anterior.ToString(), (int.Parse(cambios[nombreDisco].nuevo!) - 1).ToString());
|
||||
}
|
||||
}
|
||||
if (discosAEliminar.Any())
|
||||
@@ -342,39 +329,33 @@ namespace Inventario.API.Controllers
|
||||
await connection.ExecuteAsync("DELETE FROM dbo.equipos_discos WHERE Id IN @Ids;", new { Ids = discosAEliminar }, transaction);
|
||||
}
|
||||
|
||||
// 5. CALCULAR Y EJECUTAR INSERCIONES
|
||||
foreach (var discoCliente in discosDesdeCliente)
|
||||
{
|
||||
var key = $"{discoCliente.Mediatype}_{discoCliente.Size}";
|
||||
if (discosDbContados.TryGetValue(key, out int count) && count > 0)
|
||||
{
|
||||
// Este disco ya existía, decrementamos para no volver a añadirlo.
|
||||
discosDbContados[key]--;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Este es un disco nuevo que hay que asociar.
|
||||
var disco = await connection.QuerySingleOrDefaultAsync<Disco>("SELECT * FROM dbo.discos WHERE Mediatype = @Mediatype AND Size = @Size;", discoCliente, transaction);
|
||||
var disco = await connection.QueryFirstOrDefaultAsync<Disco>("SELECT * FROM dbo.discos WHERE Mediatype = @Mediatype AND Size = @Size;", discoCliente, transaction);
|
||||
if (disco == null) continue;
|
||||
|
||||
await connection.ExecuteAsync("INSERT INTO dbo.equipos_discos (equipo_id, disco_id, origen) VALUES (@EquipoId, @DiscoId, 'automatica');", new { EquipoId = equipo.Id, DiscoId = disco.Id }, transaction);
|
||||
|
||||
// Registrar para el historial
|
||||
var nombreDisco = $"Disco {disco.Mediatype} {disco.Size}GB";
|
||||
var anterior = discosDbContados.GetValueOrDefault(key, 0);
|
||||
if (!cambios.ContainsKey(nombreDisco)) cambios[nombreDisco] = (anterior.ToString(), (anterior + 1).ToString());
|
||||
else cambios[nombreDisco] = (anterior.ToString(), (int.Parse(cambios[nombreDisco].nuevo) + 1).ToString());
|
||||
else cambios[nombreDisco] = (anterior.ToString(), (int.Parse(cambios[nombreDisco].nuevo!) + 1).ToString());
|
||||
}
|
||||
}
|
||||
|
||||
// 6. REGISTRAR CAMBIOS Y CONFIRMAR TRANSACCIÓN
|
||||
if (cambios.Count > 0)
|
||||
{
|
||||
// Formateamos los valores para el historial
|
||||
var cambiosFormateados = cambios.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => ($"{kvp.Value.anterior} Instalados", $"{kvp.Value.nuevo} Instalados")
|
||||
kvp => ((string?)$"{kvp.Value.anterior} Instalados", (string?)$"{kvp.Value.nuevo} Instalados")
|
||||
);
|
||||
|
||||
await HistorialHelper.RegistrarCambios(_context, equipo.Id, cambiosFormateados);
|
||||
}
|
||||
|
||||
@@ -385,7 +366,6 @@ namespace Inventario.API.Controllers
|
||||
catch (Exception ex)
|
||||
{
|
||||
transaction.Rollback();
|
||||
// Loggear el error en el servidor
|
||||
Console.WriteLine($"Error al asociar discos para {hostname}: {ex.Message}");
|
||||
return StatusCode(500, "Ocurrió un error interno al procesar la solicitud.");
|
||||
}
|
||||
@@ -413,7 +393,8 @@ namespace Inventario.API.Controllers
|
||||
var huellasCliente = new HashSet<string>(memoriasDesdeCliente.Select(crearHuella));
|
||||
var huellasDb = new HashSet<string>(ramEnDb.Select(crearHuella));
|
||||
|
||||
var cambios = new Dictionary<string, (string anterior, string nuevo)>();
|
||||
var cambios = new Dictionary<string, (string? anterior, string? nuevo)>();
|
||||
|
||||
Func<dynamic, string> formatRamDetails = ram =>
|
||||
{
|
||||
var parts = new List<string?> { ram.Fabricante, $"{ram.Tamano}GB", ram.PartNumber, ram.Velocidad?.ToString() + "MHz" };
|
||||
@@ -585,13 +566,13 @@ namespace Inventario.API.Controllers
|
||||
{
|
||||
var findQuery = "SELECT Id FROM dbo.equipos WHERE Hostname = @Hostname;";
|
||||
var insertQuery = @"
|
||||
INSERT INTO dbo.equipos (Hostname, Ip, Motherboard, Cpu, Os, Sector_id, Origen, Ram_installed, Architecture)
|
||||
VALUES (@Hostname, @Ip, @Motherboard, @Cpu, @Os, @Sector_id, 'manual', 0, '');
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
INSERT INTO dbo.equipos (Hostname, Ip, Motherboard, Cpu, Os, Sector_id, Origen, Ram_installed, Architecture)
|
||||
VALUES (@Hostname, @Ip, @Motherboard, @Cpu, @Os, @Sector_id, 'manual', 0, '');
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var existente = await connection.QuerySingleOrDefaultAsync<int?>(findQuery, new { equipoDto.Hostname });
|
||||
var existente = await connection.QueryFirstOrDefaultAsync<int?>(findQuery, new { equipoDto.Hostname });
|
||||
if (existente.HasValue)
|
||||
{
|
||||
return Conflict($"El hostname '{equipoDto.Hostname}' ya existe.");
|
||||
@@ -599,14 +580,13 @@ namespace Inventario.API.Controllers
|
||||
|
||||
var nuevoId = await connection.ExecuteScalarAsync<int>(insertQuery, equipoDto);
|
||||
|
||||
// Devolvemos el objeto completo para que el frontend pueda actualizar su estado
|
||||
var nuevoEquipo = await connection.QuerySingleOrDefaultAsync<Equipo>("SELECT * FROM dbo.equipos WHERE Id = @Id", new { Id = nuevoId });
|
||||
await HistorialHelper.RegistrarCambioUnico(_context, nuevoId, "Equipo", null, "Equipo creado manualmente");
|
||||
|
||||
var nuevoEquipo = await connection.QuerySingleOrDefaultAsync<Equipo>("SELECT * FROM dbo.equipos WHERE Id = @Id", new { Id = nuevoId });
|
||||
if (nuevoEquipo == null)
|
||||
{
|
||||
return StatusCode(500, "No se pudo recuperar el equipo después de crearlo.");
|
||||
}
|
||||
|
||||
return CreatedAtAction(nameof(ConsultarDetalle), new { hostname = nuevoEquipo.Hostname }, nuevoEquipo);
|
||||
}
|
||||
}
|
||||
@@ -616,14 +596,28 @@ namespace Inventario.API.Controllers
|
||||
[HttpDelete("asociacion/disco/{equipoDiscoId}")]
|
||||
public async Task<IActionResult> BorrarAsociacionDisco(int equipoDiscoId)
|
||||
{
|
||||
var query = "DELETE FROM dbo.equipos_discos WHERE Id = @EquipoDiscoId AND Origen = 'manual';";
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var filasAfectadas = await connection.ExecuteAsync(query, new { EquipoDiscoId = equipoDiscoId });
|
||||
if (filasAfectadas == 0)
|
||||
var infoQuery = @"
|
||||
SELECT ed.equipo_id, d.Mediatype, d.Size
|
||||
FROM dbo.equipos_discos ed
|
||||
JOIN dbo.discos d ON ed.disco_id = d.id
|
||||
WHERE ed.Id = @EquipoDiscoId AND ed.Origen = 'manual'";
|
||||
var info = await connection.QueryFirstOrDefaultAsync<(int equipo_id, string Mediatype, int Size)>(infoQuery, new { EquipoDiscoId = equipoDiscoId });
|
||||
|
||||
if (info == default)
|
||||
{
|
||||
return NotFound("Asociación de disco no encontrada o no se puede eliminar porque es automática.");
|
||||
return NotFound("Asociación de disco no encontrada o no es manual.");
|
||||
}
|
||||
|
||||
var deleteQuery = "DELETE FROM dbo.equipos_discos WHERE Id = @EquipoDiscoId;";
|
||||
await connection.ExecuteAsync(deleteQuery, new { EquipoDiscoId = equipoDiscoId });
|
||||
|
||||
var descripcion = $"Disco {info.Mediatype} {info.Size}GB";
|
||||
await HistorialHelper.RegistrarCambioUnico(_context, info.equipo_id, "Componente", descripcion, "Eliminado");
|
||||
|
||||
await connection.ExecuteAsync("UPDATE dbo.equipos SET updated_at = GETDATE() WHERE Id = @Id", new { Id = info.equipo_id });
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -631,14 +625,39 @@ namespace Inventario.API.Controllers
|
||||
[HttpDelete("asociacion/ram/{equipoMemoriaRamId}")]
|
||||
public async Task<IActionResult> BorrarAsociacionRam(int equipoMemoriaRamId)
|
||||
{
|
||||
var query = "DELETE FROM dbo.equipos_memorias_ram WHERE Id = @EquipoMemoriaRamId AND Origen = 'manual';";
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var filasAfectadas = await connection.ExecuteAsync(query, new { EquipoMemoriaRamId = equipoMemoriaRamId });
|
||||
if (filasAfectadas == 0)
|
||||
var infoQuery = @"
|
||||
SELECT emr.equipo_id, emr.Slot, mr.Fabricante, mr.Tamano, mr.Velocidad
|
||||
FROM dbo.equipos_memorias_ram emr
|
||||
JOIN dbo.memorias_ram mr ON emr.memoria_ram_id = mr.id
|
||||
WHERE emr.Id = @Id AND emr.Origen = 'manual'";
|
||||
var info = await connection.QueryFirstOrDefaultAsync<(int equipo_id, string Slot, string? Fabricante, int Tamano, int? Velocidad)>(infoQuery, new { Id = equipoMemoriaRamId });
|
||||
|
||||
if (info == default)
|
||||
{
|
||||
return NotFound("Asociación de RAM no encontrada o no se puede eliminar porque es automática.");
|
||||
return NotFound("Asociación de RAM no encontrada o no es manual.");
|
||||
}
|
||||
|
||||
var deleteQuery = "DELETE FROM dbo.equipos_memorias_ram WHERE Id = @EquipoMemoriaRamId;";
|
||||
await connection.ExecuteAsync(deleteQuery, new { EquipoMemoriaRamId = equipoMemoriaRamId });
|
||||
|
||||
var descripcion = $"Módulo RAM: Slot {info.Slot} - {info.Fabricante ?? ""} {info.Tamano}GB {info.Velocidad?.ToString() ?? ""}MHz";
|
||||
await HistorialHelper.RegistrarCambioUnico(_context, info.equipo_id, "Componente", descripcion, "Eliminado");
|
||||
|
||||
var updateQuery = @"
|
||||
UPDATE e
|
||||
SET
|
||||
e.ram_installed = ISNULL((SELECT SUM(mr.Tamano)
|
||||
FROM dbo.equipos_memorias_ram emr
|
||||
JOIN dbo.memorias_ram mr ON emr.memoria_ram_id = mr.id
|
||||
WHERE emr.equipo_id = @Id), 0),
|
||||
e.updated_at = GETDATE()
|
||||
FROM dbo.equipos e
|
||||
WHERE e.Id = @Id;";
|
||||
|
||||
await connection.ExecuteAsync(updateQuery, new { Id = info.equipo_id });
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -646,14 +665,27 @@ namespace Inventario.API.Controllers
|
||||
[HttpDelete("asociacion/usuario/{equipoId}/{usuarioId}")]
|
||||
public async Task<IActionResult> BorrarAsociacionUsuario(int equipoId, int usuarioId)
|
||||
{
|
||||
var query = "DELETE FROM dbo.usuarios_equipos WHERE equipo_id = @EquipoId AND usuario_id = @UsuarioId AND Origen = 'manual';";
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var filasAfectadas = await connection.ExecuteAsync(query, new { EquipoId = equipoId, UsuarioId = usuarioId });
|
||||
var username = await connection.QuerySingleOrDefaultAsync<string>("SELECT Username FROM dbo.usuarios WHERE Id = @UsuarioId", new { UsuarioId = usuarioId });
|
||||
if (username == null)
|
||||
{
|
||||
return NotFound("Usuario no encontrado.");
|
||||
}
|
||||
|
||||
var deleteQuery = "DELETE FROM dbo.usuarios_equipos WHERE equipo_id = @EquipoId AND usuario_id = @UsuarioId AND Origen = 'manual';";
|
||||
var filasAfectadas = await connection.ExecuteAsync(deleteQuery, new { EquipoId = equipoId, UsuarioId = usuarioId });
|
||||
|
||||
if (filasAfectadas == 0)
|
||||
{
|
||||
return NotFound("Asociación de usuario no encontrada o no se puede eliminar porque es automática.");
|
||||
return NotFound("Asociación de usuario no encontrada o no es manual.");
|
||||
}
|
||||
|
||||
var descripcion = $"Usuario {username}";
|
||||
await HistorialHelper.RegistrarCambioUnico(_context, equipoId, "Componente", descripcion, "Eliminado");
|
||||
|
||||
await connection.ExecuteAsync("UPDATE dbo.equipos SET updated_at = GETDATE() WHERE Id = @Id", new { Id = equipoId });
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
@@ -663,7 +695,6 @@ namespace Inventario.API.Controllers
|
||||
{
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
// 1. Verificar que el equipo existe y es manual
|
||||
var equipoActual = await connection.QuerySingleOrDefaultAsync<Equipo>("SELECT * FROM dbo.equipos WHERE Id = @Id", new { Id = id });
|
||||
if (equipoActual == null)
|
||||
{
|
||||
@@ -674,7 +705,6 @@ namespace Inventario.API.Controllers
|
||||
return Forbid("No se puede modificar un equipo generado automáticamente.");
|
||||
}
|
||||
|
||||
// 2. (Opcional pero recomendado) Verificar que el nuevo hostname no exista ya en otro equipo
|
||||
if (equipoActual.Hostname != equipoDto.Hostname)
|
||||
{
|
||||
var hostExistente = await connection.QuerySingleOrDefaultAsync<int?>("SELECT Id FROM dbo.equipos WHERE Hostname = @Hostname AND Id != @Id", new { equipoDto.Hostname, Id = id });
|
||||
@@ -684,19 +714,46 @@ namespace Inventario.API.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Construir y ejecutar la consulta de actualización
|
||||
var updateQuery = @"UPDATE dbo.equipos SET
|
||||
Hostname = @Hostname,
|
||||
Ip = @Ip,
|
||||
Mac = @Mac,
|
||||
Motherboard = @Motherboard,
|
||||
Cpu = @Cpu,
|
||||
Os = @Os,
|
||||
Sector_id = @Sector_id,
|
||||
updated_at = GETDATE()
|
||||
WHERE Id = @Id AND Origen = 'manual';";
|
||||
var allSectores = await connection.QueryAsync<Sector>("SELECT Id, Nombre FROM dbo.sectores;");
|
||||
var sectorMap = allSectores.ToDictionary(s => s.Id, s => s.Nombre);
|
||||
|
||||
var filasAfectadas = await connection.ExecuteAsync(updateQuery, new
|
||||
var cambios = new Dictionary<string, (string? anterior, string? nuevo)>();
|
||||
|
||||
if (equipoActual.Hostname != equipoDto.Hostname) cambios["Hostname"] = (equipoActual.Hostname, equipoDto.Hostname);
|
||||
if (equipoActual.Ip != equipoDto.Ip) cambios["IP"] = (equipoActual.Ip, equipoDto.Ip);
|
||||
if (equipoActual.Mac != equipoDto.Mac) cambios["MAC Address"] = (equipoActual.Mac ?? "N/A", equipoDto.Mac ?? "N/A");
|
||||
if (equipoActual.Motherboard != equipoDto.Motherboard) cambios["Motherboard"] = (equipoActual.Motherboard ?? "N/A", equipoDto.Motherboard ?? "N/A");
|
||||
if (equipoActual.Cpu != equipoDto.Cpu) cambios["CPU"] = (equipoActual.Cpu ?? "N/A", equipoDto.Cpu ?? "N/A");
|
||||
if (equipoActual.Os != equipoDto.Os) cambios["Sistema Operativo"] = (equipoActual.Os ?? "N/A", equipoDto.Os ?? "N/A");
|
||||
if (equipoActual.Architecture != equipoDto.Architecture) cambios["Arquitectura"] = (equipoActual.Architecture ?? "N/A", equipoDto.Architecture ?? "N/A");
|
||||
if (equipoActual.Ram_slots != equipoDto.Ram_slots) cambios["Slots RAM"] = (equipoActual.Ram_slots?.ToString() ?? "N/A", equipoDto.Ram_slots?.ToString() ?? "N/A");
|
||||
|
||||
if (equipoActual.Sector_id != equipoDto.Sector_id)
|
||||
{
|
||||
string nombreAnterior = equipoActual.Sector_id.HasValue && sectorMap.TryGetValue(equipoActual.Sector_id.Value, out var oldName)
|
||||
? oldName
|
||||
: "Ninguno";
|
||||
string nombreNuevo = equipoDto.Sector_id.HasValue && sectorMap.TryGetValue(equipoDto.Sector_id.Value, out var newName)
|
||||
? newName
|
||||
: "Ninguno";
|
||||
cambios["Sector"] = (nombreAnterior, nombreNuevo);
|
||||
}
|
||||
|
||||
var updateQuery = @"UPDATE dbo.equipos SET
|
||||
Hostname = @Hostname,
|
||||
Ip = @Ip,
|
||||
Mac = @Mac,
|
||||
Motherboard = @Motherboard,
|
||||
Cpu = @Cpu,
|
||||
Os = @Os,
|
||||
Sector_id = @Sector_id,
|
||||
Ram_slots = @Ram_slots,
|
||||
Architecture = @Architecture, -- Campo añadido a la actualización
|
||||
updated_at = GETDATE()
|
||||
OUTPUT INSERTED.*
|
||||
WHERE Id = @Id AND Origen = 'manual';";
|
||||
|
||||
var equipoActualizado = await connection.QuerySingleOrDefaultAsync<Equipo>(updateQuery, new
|
||||
{
|
||||
equipoDto.Hostname,
|
||||
equipoDto.Ip,
|
||||
@@ -705,16 +762,24 @@ namespace Inventario.API.Controllers
|
||||
equipoDto.Cpu,
|
||||
equipoDto.Os,
|
||||
equipoDto.Sector_id,
|
||||
equipoDto.Ram_slots,
|
||||
equipoDto.Architecture,
|
||||
Id = id
|
||||
});
|
||||
|
||||
if (filasAfectadas == 0)
|
||||
if (equipoActualizado == null)
|
||||
{
|
||||
// Esto no debería pasar si las primeras verificaciones pasaron, pero es una salvaguarda
|
||||
return StatusCode(500, "No se pudo actualizar el equipo.");
|
||||
}
|
||||
|
||||
return NoContent(); // Éxito en la actualización
|
||||
if (cambios.Count > 0)
|
||||
{
|
||||
await HistorialHelper.RegistrarCambios(_context, id, cambios);
|
||||
}
|
||||
|
||||
var equipoCompleto = await ConsultarDetalle(equipoActualizado.Hostname);
|
||||
|
||||
return equipoCompleto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,11 +788,10 @@ namespace Inventario.API.Controllers
|
||||
{
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var equipo = await connection.QuerySingleOrDefaultAsync<Equipo>("SELECT Origen FROM dbo.equipos WHERE Id = @Id", new { Id = equipoId });
|
||||
var equipo = await connection.QueryFirstOrDefaultAsync<Equipo>("SELECT Origen FROM dbo.equipos WHERE Id = @Id", new { Id = equipoId });
|
||||
if (equipo == null || equipo.Origen != "manual") return Forbid("Solo se pueden añadir componentes a equipos manuales.");
|
||||
|
||||
// Buscar o crear el disco maestro
|
||||
var discoMaestro = await connection.QuerySingleOrDefaultAsync<Disco>("SELECT * FROM dbo.discos WHERE Mediatype = @Mediatype AND Size = @Size", dto);
|
||||
var discoMaestro = await connection.QueryFirstOrDefaultAsync<Disco>("SELECT * FROM dbo.discos WHERE Mediatype = @Mediatype AND Size = @Size", dto);
|
||||
int discoId;
|
||||
if (discoMaestro == null)
|
||||
{
|
||||
@@ -738,10 +802,14 @@ namespace Inventario.API.Controllers
|
||||
discoId = discoMaestro.Id;
|
||||
}
|
||||
|
||||
// Crear la asociación manual
|
||||
var asociacionQuery = "INSERT INTO dbo.equipos_discos (equipo_id, disco_id, origen) VALUES (@EquipoId, @DiscoId, 'manual'); SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
var nuevaAsociacionId = await connection.ExecuteScalarAsync<int>(asociacionQuery, new { EquipoId = equipoId, DiscoId = discoId });
|
||||
|
||||
var descripcion = $"Disco {dto.Mediatype} {dto.Size}GB";
|
||||
await HistorialHelper.RegistrarCambioUnico(_context, equipoId, "Componente", null, $"Añadido: {descripcion}");
|
||||
|
||||
await connection.ExecuteAsync("UPDATE dbo.equipos SET updated_at = GETDATE() WHERE Id = @Id", new { Id = equipoId });
|
||||
|
||||
return Ok(new { message = "Disco asociado manualmente.", equipoDiscoId = nuevaAsociacionId });
|
||||
}
|
||||
}
|
||||
@@ -751,25 +819,41 @@ namespace Inventario.API.Controllers
|
||||
{
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var equipo = await connection.QuerySingleOrDefaultAsync<Equipo>("SELECT Origen FROM dbo.equipos WHERE Id = @Id", new { Id = equipoId });
|
||||
var equipo = await connection.QueryFirstOrDefaultAsync<Equipo>("SELECT Origen FROM dbo.equipos WHERE Id = @Id", new { Id = equipoId });
|
||||
if (equipo == null || equipo.Origen != "manual") return Forbid("Solo se pueden añadir componentes a equipos manuales.");
|
||||
|
||||
// Lógica similar a la de discos para buscar/crear el módulo maestro
|
||||
int ramId;
|
||||
var ramMaestra = await connection.QuerySingleOrDefaultAsync<MemoriaRam>("SELECT * FROM dbo.memorias_ram WHERE Tamano = @Tamano AND Fabricante = @Fabricante AND Velocidad = @Velocidad", dto);
|
||||
var ramMaestra = await connection.QueryFirstOrDefaultAsync<MemoriaRam>(
|
||||
"SELECT * FROM dbo.memorias_ram WHERE (Fabricante = @Fabricante OR (Fabricante IS NULL AND @Fabricante IS NULL)) AND Tamano = @Tamano AND (Velocidad = @Velocidad OR (Velocidad IS NULL AND @Velocidad IS NULL))", dto);
|
||||
|
||||
if (ramMaestra == null)
|
||||
{
|
||||
ramId = await connection.ExecuteScalarAsync<int>("INSERT INTO dbo.memorias_ram (Tamano, Fabricante, Velocidad) VALUES (@Tamano, @Fabricante, @Velocidad); SELECT CAST(SCOPE_IDENTITY() as int);", dto);
|
||||
var insertQuery = @"INSERT INTO dbo.memorias_ram (part_number, fabricante, tamano, velocidad)
|
||||
VALUES (@PartNumber, @Fabricante, @Tamano, @Velocidad);
|
||||
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
ramId = await connection.ExecuteScalarAsync<int>(insertQuery, dto);
|
||||
}
|
||||
else
|
||||
{
|
||||
ramId = ramMaestra.Id;
|
||||
}
|
||||
|
||||
// Crear la asociación manual
|
||||
var asociacionQuery = "INSERT INTO dbo.equipos_memorias_ram (equipo_id, memoria_ram_id, slot, origen) VALUES (@EquipoId, @RamId, @Slot, 'manual'); SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||
var nuevaAsociacionId = await connection.ExecuteScalarAsync<int>(asociacionQuery, new { EquipoId = equipoId, RamId = ramId, dto.Slot });
|
||||
|
||||
var descripcion = $"Módulo RAM: Slot {dto.Slot} - {dto.Fabricante ?? ""} {dto.Tamano}GB {dto.Velocidad?.ToString() ?? ""}MHz";
|
||||
await HistorialHelper.RegistrarCambioUnico(_context, equipoId, "Componente", null, $"Añadido: {descripcion}");
|
||||
|
||||
var updateQuery = @"
|
||||
UPDATE e SET
|
||||
e.ram_installed = ISNULL((SELECT SUM(mr.Tamano)
|
||||
FROM dbo.equipos_memorias_ram emr
|
||||
JOIN dbo.memorias_ram mr ON emr.memoria_ram_id = mr.id
|
||||
WHERE emr.equipo_id = @Id), 0),
|
||||
e.updated_at = GETDATE()
|
||||
FROM dbo.equipos e
|
||||
WHERE e.Id = @Id;";
|
||||
await connection.ExecuteAsync(updateQuery, new { Id = equipoId });
|
||||
|
||||
return Ok(new { message = "RAM asociada manualmente.", equipoMemoriaRamId = nuevaAsociacionId });
|
||||
}
|
||||
}
|
||||
@@ -779,12 +863,11 @@ namespace Inventario.API.Controllers
|
||||
{
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var equipo = await connection.QuerySingleOrDefaultAsync<Equipo>("SELECT Origen FROM dbo.equipos WHERE Id = @Id", new { Id = equipoId });
|
||||
var equipo = await connection.QueryFirstOrDefaultAsync<Equipo>("SELECT Origen FROM dbo.equipos WHERE Id = @Id", new { Id = equipoId });
|
||||
if (equipo == null || equipo.Origen != "manual") return Forbid("Solo se pueden añadir componentes a equipos manuales.");
|
||||
|
||||
// Buscar o crear el usuario maestro
|
||||
int usuarioId;
|
||||
var usuario = await connection.QuerySingleOrDefaultAsync<Usuario>("SELECT * FROM dbo.usuarios WHERE Username = @Username", dto);
|
||||
var usuario = await connection.QueryFirstOrDefaultAsync<Usuario>("SELECT * FROM dbo.usuarios WHERE Username = @Username", dto);
|
||||
if (usuario == null)
|
||||
{
|
||||
usuarioId = await connection.ExecuteScalarAsync<int>("INSERT INTO dbo.usuarios (Username) VALUES (@Username); SELECT CAST(SCOPE_IDENTITY() as int);", dto);
|
||||
@@ -794,7 +877,6 @@ namespace Inventario.API.Controllers
|
||||
usuarioId = usuario.Id;
|
||||
}
|
||||
|
||||
// Crear la asociación manual
|
||||
try
|
||||
{
|
||||
var asociacionQuery = "INSERT INTO dbo.usuarios_equipos (equipo_id, usuario_id, origen) VALUES (@EquipoId, @UsuarioId, 'manual');";
|
||||
@@ -805,6 +887,11 @@ namespace Inventario.API.Controllers
|
||||
return Conflict("El usuario ya está asociado a este equipo.");
|
||||
}
|
||||
|
||||
var descripcion = $"Usuario {dto.Username}";
|
||||
await HistorialHelper.RegistrarCambioUnico(_context, equipoId, "Componente", null, $"Añadido: {descripcion}");
|
||||
|
||||
await connection.ExecuteAsync("UPDATE dbo.equipos SET updated_at = GETDATE() WHERE Id = @Id", new { Id = equipoId });
|
||||
|
||||
return Ok(new { message = "Usuario asociado manualmente." });
|
||||
}
|
||||
}
|
||||
@@ -866,6 +953,8 @@ namespace Inventario.API.Controllers
|
||||
public string? Cpu { get; set; }
|
||||
public string? Os { get; set; }
|
||||
public int? Sector_id { get; set; }
|
||||
public int? Ram_slots { get; set; }
|
||||
public string? Architecture { get; set; }
|
||||
}
|
||||
|
||||
public class AsociarDiscoManualDto
|
||||
@@ -880,6 +969,7 @@ namespace Inventario.API.Controllers
|
||||
public int Tamano { get; set; }
|
||||
public string? Fabricante { get; set; }
|
||||
public int? Velocidad { get; set; }
|
||||
public string? PartNumber { get; set; }
|
||||
}
|
||||
|
||||
public class AsociarUsuarioManualDto
|
||||
|
||||
@@ -20,7 +20,24 @@ namespace Inventario.API.Controllers
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Consultar()
|
||||
{
|
||||
var query = "SELECT Id, part_number as PartNumber, Fabricante, Tamano, Velocidad FROM dbo.memorias_ram;";
|
||||
var query = @"
|
||||
SELECT
|
||||
MIN(Id) as Id,
|
||||
MIN(part_number) as PartNumber, -- Tomamos un part_number como ejemplo para el modelo
|
||||
Fabricante,
|
||||
Tamano,
|
||||
Velocidad
|
||||
FROM
|
||||
dbo.memorias_ram
|
||||
WHERE
|
||||
Fabricante IS NOT NULL AND Fabricante != ''
|
||||
GROUP BY
|
||||
Fabricante,
|
||||
Tamano,
|
||||
Velocidad
|
||||
ORDER BY
|
||||
Fabricante, Tamano, Velocidad;";
|
||||
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var memorias = await connection.QueryAsync<MemoriaRam>(query);
|
||||
@@ -151,5 +168,21 @@ namespace Inventario.API.Controllers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- GET /api/memoriasram/buscar/{termino} ---
|
||||
[HttpGet("buscar/{termino}")]
|
||||
public async Task<IActionResult> BuscarMemoriasRam(string termino)
|
||||
{
|
||||
var query = @"SELECT Id, part_number as PartNumber, Fabricante, Tamano, Velocidad
|
||||
FROM dbo.memorias_ram
|
||||
WHERE Fabricante LIKE @SearchTerm OR part_number LIKE @SearchTerm
|
||||
ORDER BY Fabricante, Tamano;";
|
||||
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var memorias = await connection.QueryAsync<MemoriaRam>(query, new { SearchTerm = $"%{termino}%" });
|
||||
return Ok(memorias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// backend/Controllers/SectoresController.cs
|
||||
|
||||
using Dapper;
|
||||
using Inventario.API.Data;
|
||||
using Inventario.API.Models;
|
||||
@@ -105,18 +107,29 @@ namespace Inventario.API.Controllers
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> BorrarSector(int id)
|
||||
{
|
||||
var query = "DELETE FROM dbo.sectores WHERE Id = @Id;";
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
var filasAfectadas = await connection.ExecuteAsync(query, new { Id = id });
|
||||
|
||||
if (filasAfectadas == 0)
|
||||
using (var connection = _context.CreateConnection())
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
// 1. VERIFICAR SI EL SECTOR ESTÁ EN USO
|
||||
var usageQuery = "SELECT COUNT(1) FROM dbo.equipos WHERE sector_id = @Id;";
|
||||
var usageCount = await connection.ExecuteScalarAsync<int>(usageQuery, new { Id = id });
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
if (usageCount > 0)
|
||||
{
|
||||
// 2. DEVOLVER HTTP 409 CONFLICT SI ESTÁ EN USO
|
||||
return Conflict(new { message = $"No se puede eliminar. Hay {usageCount} equipo(s) asignados a este sector." });
|
||||
}
|
||||
|
||||
// 3. SI NO ESTÁ EN USO, PROCEDER CON LA ELIMINACIÓN
|
||||
var deleteQuery = "DELETE FROM dbo.sectores WHERE Id = @Id;";
|
||||
var filasAfectadas = await connection.ExecuteAsync(deleteQuery, new { Id = id });
|
||||
|
||||
if (filasAfectadas == 0)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user