feat: Controladores con operaciones CRUD completas
This commit is contained in:
114
backend/Controllers/DiscosController.cs
Normal file
114
backend/Controllers/DiscosController.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Inventario.API.Data;
|
||||||
|
using Inventario.API.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Inventario.API.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class DiscosController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly DapperContext _context;
|
||||||
|
|
||||||
|
public DiscosController(DapperContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GET /api/discos ---
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Consultar()
|
||||||
|
{
|
||||||
|
var query = "SELECT Id, Mediatype, Size FROM dbo.discos;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var discos = await connection.QueryAsync<Disco>(query);
|
||||||
|
return Ok(discos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GET /api/discos/{id} ---
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> ConsultarDetalle(int id)
|
||||||
|
{
|
||||||
|
var query = "SELECT Id, Mediatype, Size FROM dbo.discos WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var disco = await connection.QuerySingleOrDefaultAsync<Disco>(query, new { Id = id });
|
||||||
|
if (disco == null)
|
||||||
|
{
|
||||||
|
return NotFound("Disco no encontrado.");
|
||||||
|
}
|
||||||
|
return Ok(disco);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- POST /api/discos ---
|
||||||
|
// Replica la lógica de recibir uno o varios discos y crear solo los que no existen.
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Ingresar([FromBody] List<Disco> discos)
|
||||||
|
{
|
||||||
|
var queryCheck = "SELECT * FROM dbo.discos WHERE Mediatype = @Mediatype AND Size = @Size;";
|
||||||
|
var queryInsert = "INSERT INTO dbo.discos (Mediatype, Size) VALUES (@Mediatype, @Size); SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||||
|
|
||||||
|
var resultados = new List<object>();
|
||||||
|
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
foreach (var disco in discos)
|
||||||
|
{
|
||||||
|
var existente = await connection.QuerySingleOrDefaultAsync<Disco>(queryCheck, new { disco.Mediatype, disco.Size });
|
||||||
|
|
||||||
|
if (existente == null)
|
||||||
|
{
|
||||||
|
var nuevoId = await connection.ExecuteScalarAsync<int>(queryInsert, new { disco.Mediatype, disco.Size });
|
||||||
|
var nuevoDisco = new Disco { Id = nuevoId, Mediatype = disco.Mediatype, Size = disco.Size };
|
||||||
|
resultados.Add(new { action = "created", registro = nuevoDisco });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Opcional: podrías añadirlo si quieres saber cuáles ya existían
|
||||||
|
// resultados.Add(new { action = "exists", registro = existente });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Devolvemos HTTP 200 OK con la lista de los discos que se crearon.
|
||||||
|
return Ok(resultados);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- PUT /api/discos/{id} ---
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> Actualizar(int id, [FromBody] Disco disco)
|
||||||
|
{
|
||||||
|
var query = "UPDATE dbo.discos SET Mediatype = @Mediatype, Size = @Size WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(query, new { disco.Mediatype, disco.Size, Id = id });
|
||||||
|
if (filasAfectadas == 0)
|
||||||
|
{
|
||||||
|
return NotFound("Disco no encontrado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var discoActualizado = new Disco { Id = id, Mediatype = disco.Mediatype, Size = disco.Size };
|
||||||
|
return Ok(discoActualizado);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DELETE /api/discos/{id} ---
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> Borrar(int id)
|
||||||
|
{
|
||||||
|
var query = "DELETE FROM dbo.discos WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(query, new { Id = id });
|
||||||
|
if (filasAfectadas == 0)
|
||||||
|
{
|
||||||
|
return NotFound("Disco no encontrado.");
|
||||||
|
}
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
205
backend/Controllers/EquiposController.cs
Normal file
205
backend/Controllers/EquiposController.cs
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Inventario.API.Data;
|
||||||
|
using Inventario.API.Helpers;
|
||||||
|
using Inventario.API.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Inventario.API.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class EquiposController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly DapperContext _context;
|
||||||
|
|
||||||
|
public EquiposController(DapperContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GET /api/equipos ---
|
||||||
|
// Consulta todos los equipos con sus relaciones
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Consultar()
|
||||||
|
{
|
||||||
|
// Query para traer todo en una sola llamada a la BD
|
||||||
|
var query = @"
|
||||||
|
SELECT
|
||||||
|
e.*,
|
||||||
|
s.Id as SectorId, s.Nombre as SectorNombre,
|
||||||
|
u.Id as UsuarioId, u.Username, u.Password,
|
||||||
|
d.Id as DiscoId, d.Mediatype, d.Size,
|
||||||
|
mr.Id as MemoriaRamId, mr.part_number as PartNumber, mr.Fabricante, mr.Tamano, mr.Velocidad, emr.Slot
|
||||||
|
FROM dbo.equipos e
|
||||||
|
LEFT JOIN dbo.sectores s ON e.sector_id = s.id
|
||||||
|
LEFT JOIN dbo.usuarios_equipos ue ON e.id = ue.equipo_id
|
||||||
|
LEFT JOIN dbo.usuarios u ON ue.usuario_id = u.id
|
||||||
|
LEFT JOIN dbo.equipos_discos ed ON e.id = ed.equipo_id
|
||||||
|
LEFT JOIN dbo.discos d ON ed.disco_id = d.id
|
||||||
|
LEFT JOIN dbo.equipos_memorias_ram emr ON e.id = emr.equipo_id
|
||||||
|
LEFT JOIN dbo.memorias_ram mr ON emr.memoria_ram_id = mr.id;";
|
||||||
|
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var equipoDict = new Dictionary<int, Equipo>();
|
||||||
|
|
||||||
|
await connection.QueryAsync<Equipo, Sector, Usuario, Disco, MemoriaRamDetalle, Equipo>(
|
||||||
|
query, (equipo, sector, usuario, disco, memoria) =>
|
||||||
|
{
|
||||||
|
if (!equipoDict.TryGetValue(equipo.Id, out var equipoActual))
|
||||||
|
{
|
||||||
|
equipoActual = equipo;
|
||||||
|
equipoActual.Sector = sector;
|
||||||
|
equipoDict.Add(equipoActual.Id, equipoActual);
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
equipoActual.Discos.Add(disco);
|
||||||
|
if (memoria != null && !equipoActual.MemoriasRam.Any(m => m.Id == memoria.Id && m.Slot == memoria.Slot))
|
||||||
|
equipoActual.MemoriasRam.Add(memoria);
|
||||||
|
|
||||||
|
return equipoActual;
|
||||||
|
},
|
||||||
|
splitOn: "SectorId,UsuarioId,DiscoId,MemoriaRamId"
|
||||||
|
);
|
||||||
|
return Ok(equipoDict.Values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GET /api/equipos/{hostname} ---
|
||||||
|
[HttpGet("{hostname}")]
|
||||||
|
public async Task<IActionResult> ConsultarDetalle(string hostname)
|
||||||
|
{
|
||||||
|
var query = @"SELECT
|
||||||
|
e.*,
|
||||||
|
s.Id as SectorId, s.Nombre as SectorNombre,
|
||||||
|
u.Id as UsuarioId, u.Username, u.Password
|
||||||
|
FROM dbo.equipos e
|
||||||
|
LEFT JOIN dbo.sectores s ON e.sector_id = s.id
|
||||||
|
LEFT JOIN dbo.usuarios_equipos ue ON e.id = ue.equipo_id
|
||||||
|
LEFT JOIN dbo.usuarios u ON ue.usuario_id = u.id
|
||||||
|
WHERE e.Hostname = @Hostname;";
|
||||||
|
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var equipoDict = new Dictionary<int, Equipo>();
|
||||||
|
var equipo = (await connection.QueryAsync<Equipo, Sector, Usuario, Equipo>(
|
||||||
|
query, (e, sector, usuario) =>
|
||||||
|
{
|
||||||
|
if (!equipoDict.TryGetValue(e.Id, out var equipoActual))
|
||||||
|
{
|
||||||
|
equipoActual = e;
|
||||||
|
equipoActual.Sector = sector;
|
||||||
|
equipoDict.Add(equipoActual.Id, equipoActual);
|
||||||
|
}
|
||||||
|
if (usuario != null && !equipoActual.Usuarios.Any(u => u.Id == usuario.Id))
|
||||||
|
equipoActual.Usuarios.Add(usuario);
|
||||||
|
|
||||||
|
return equipoActual;
|
||||||
|
},
|
||||||
|
new { Hostname = hostname },
|
||||||
|
splitOn: "SectorId,UsuarioId"
|
||||||
|
)).FirstOrDefault();
|
||||||
|
|
||||||
|
if (equipo == null) return NotFound("Equipo no encontrado.");
|
||||||
|
|
||||||
|
return Ok(equipo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- POST /api/equipos/{hostname} ---
|
||||||
|
[HttpPost("{hostname}")]
|
||||||
|
public async Task<IActionResult> Ingresar(string hostname, [FromBody] Equipo equipoData)
|
||||||
|
{
|
||||||
|
var findQuery = "SELECT * FROM dbo.equipos WHERE Hostname = @Hostname;";
|
||||||
|
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var equipoExistente = await connection.QuerySingleOrDefaultAsync<Equipo>(findQuery, new { Hostname = hostname });
|
||||||
|
|
||||||
|
if (equipoExistente == null)
|
||||||
|
{
|
||||||
|
// Crear
|
||||||
|
var insertQuery = @"INSERT INTO dbo.equipos (Hostname, Ip, Mac, Motherboard, Cpu, Ram_installed, Ram_slots, Os, Architecture)
|
||||||
|
VALUES (@Hostname, @Ip, @Mac, @Motherboard, @Cpu, @Ram_installed, @Ram_slots, @Os, @Architecture);
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||||
|
var nuevoId = await connection.ExecuteScalarAsync<int>(insertQuery, equipoData);
|
||||||
|
equipoData.Id = nuevoId;
|
||||||
|
return CreatedAtAction(nameof(ConsultarDetalle), new { hostname = equipoData.Hostname }, equipoData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Actualizar y registrar historial
|
||||||
|
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);
|
||||||
|
if (equipoData.Mac != equipoExistente.Mac) cambios["mac"] = (equipoExistente.Mac ?? "", equipoData.Mac ?? "");
|
||||||
|
|
||||||
|
var updateQuery = @"UPDATE dbo.equipos SET Ip = @Ip, Mac = @Mac, Motherboard = @Motherboard,
|
||||||
|
Cpu = @Cpu, Ram_installed = @Ram_installed, Ram_slots = @Ram_slots, Os = @Os, Architecture = @Architecture
|
||||||
|
WHERE Hostname = @Hostname;";
|
||||||
|
await connection.ExecuteAsync(updateQuery, equipoData);
|
||||||
|
|
||||||
|
if (cambios.Count > 0)
|
||||||
|
{
|
||||||
|
await HistorialHelper.RegistrarCambios(_context, equipoExistente.Id, cambios);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(equipoData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- PUT /api/equipos/{id} ---
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> Actualizar(int id, [FromBody] Equipo equipoData)
|
||||||
|
{
|
||||||
|
var updateQuery = @"UPDATE dbo.equipos SET Hostname = @Hostname, Ip = @Ip, Mac = @Mac, Motherboard = @Motherboard,
|
||||||
|
Cpu = @Cpu, Ram_installed = @Ram_installed, Ram_slots = @Ram_slots, Os = @Os, Architecture = @Architecture
|
||||||
|
WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
// Asignamos el ID del parámetro de la ruta al objeto que recibimos.
|
||||||
|
equipoData.Id = id;
|
||||||
|
// Ahora pasamos el objeto completo a Dapper.
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(updateQuery, equipoData);
|
||||||
|
if (filasAfectadas == 0) return NotFound("Equipo no encontrado.");
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DELETE /api/equipos/{id} ---
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> Borrar(int id)
|
||||||
|
{
|
||||||
|
// La base de datos está configurada con ON DELETE CASCADE, por lo que las relaciones se borrarán automáticamente.
|
||||||
|
var query = "DELETE FROM dbo.equipos WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(query, new { Id = id });
|
||||||
|
if (filasAfectadas == 0) return NotFound("Equipo no encontrado.");
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GET /api/equipos/{hostname}/historial ---
|
||||||
|
[HttpGet("{hostname}/historial")]
|
||||||
|
public async Task<IActionResult> ConsultarHistorial(string hostname)
|
||||||
|
{
|
||||||
|
var query = @"SELECT h.* FROM dbo.historial_equipos h
|
||||||
|
JOIN dbo.equipos e ON h.equipo_id = e.id
|
||||||
|
WHERE e.Hostname = @Hostname
|
||||||
|
ORDER BY h.fecha_cambio DESC;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var equipo = await connection.QueryFirstOrDefaultAsync<Equipo>("SELECT Id FROM dbo.equipos WHERE Hostname = @Hostname", new { Hostname = hostname });
|
||||||
|
if (equipo == null) return NotFound("Equipo no encontrado.");
|
||||||
|
|
||||||
|
var historial = await connection.QueryAsync<HistorialEquipo>(query, new { Hostname = hostname });
|
||||||
|
return Ok(new { equipo = hostname, historial });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
155
backend/Controllers/MemoriasRamController.cs
Normal file
155
backend/Controllers/MemoriasRamController.cs
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Inventario.API.Data;
|
||||||
|
using Inventario.API.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Inventario.API.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class MemoriasRamController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly DapperContext _context;
|
||||||
|
|
||||||
|
public MemoriasRamController(DapperContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GET /api/memoriasram ---
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Consultar()
|
||||||
|
{
|
||||||
|
var query = "SELECT Id, part_number as PartNumber, Fabricante, Tamano, Velocidad FROM dbo.memorias_ram;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var memorias = await connection.QueryAsync<MemoriaRam>(query);
|
||||||
|
return Ok(memorias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GET /api/memoriasram/{id} ---
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> ConsultarDetalle(int id)
|
||||||
|
{
|
||||||
|
var query = "SELECT Id, part_number as PartNumber, Fabricante, Tamano, Velocidad FROM dbo.memorias_ram WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var memoria = await connection.QuerySingleOrDefaultAsync<MemoriaRam>(query, new { Id = id });
|
||||||
|
if (memoria == null)
|
||||||
|
{
|
||||||
|
return NotFound("Módulo de memoria RAM no encontrado.");
|
||||||
|
}
|
||||||
|
return Ok(memoria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- POST /api/memoriasram ---
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Ingresar([FromBody] List<MemoriaRam> memorias)
|
||||||
|
{
|
||||||
|
// Consulta para verificar la existencia. Maneja correctamente los valores nulos.
|
||||||
|
var queryCheck = @"SELECT * FROM dbo.memorias_ram WHERE
|
||||||
|
(part_number = @Part_number OR (part_number IS NULL AND @Part_number IS NULL)) AND
|
||||||
|
(fabricante = @Fabricante OR (fabricante IS NULL AND @Fabricante IS NULL)) AND
|
||||||
|
tamano = @Tamano AND
|
||||||
|
(velocidad = @Velocidad OR (velocidad IS NULL AND @Velocidad IS NULL));";
|
||||||
|
|
||||||
|
var queryInsert = @"INSERT INTO dbo.memorias_ram (part_number, fabricante, tamano, velocidad)
|
||||||
|
VALUES (@Part_number, @Fabricante, @Tamano, @Velocidad);
|
||||||
|
SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||||
|
|
||||||
|
var resultados = new List<object>();
|
||||||
|
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
foreach (var memoria in memorias)
|
||||||
|
{
|
||||||
|
var existente = await connection.QuerySingleOrDefaultAsync<MemoriaRam>(queryCheck, memoria);
|
||||||
|
|
||||||
|
if (existente == null)
|
||||||
|
{
|
||||||
|
var nuevoId = await connection.ExecuteScalarAsync<int>(queryInsert, memoria);
|
||||||
|
var nuevaMemoria = new MemoriaRam
|
||||||
|
{
|
||||||
|
Id = nuevoId,
|
||||||
|
Part_number = memoria.Part_number,
|
||||||
|
Fabricante = memoria.Fabricante,
|
||||||
|
Tamano = memoria.Tamano,
|
||||||
|
Velocidad = memoria.Velocidad
|
||||||
|
};
|
||||||
|
resultados.Add(new { action = "created", registro = nuevaMemoria });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
resultados.Add(new { action = "exists", registro = existente });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(resultados);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- PUT /api/memoriasram/{id} ---
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> Actualizar(int id, [FromBody] MemoriaRam memoria)
|
||||||
|
{
|
||||||
|
var query = @"UPDATE dbo.memorias_ram SET
|
||||||
|
part_number = @Part_number,
|
||||||
|
fabricante = @Fabricante,
|
||||||
|
tamano = @Tamano,
|
||||||
|
velocidad = @Velocidad
|
||||||
|
WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(query, new { memoria.Part_number, memoria.Fabricante, memoria.Tamano, memoria.Velocidad, Id = id });
|
||||||
|
if (filasAfectadas == 0)
|
||||||
|
{
|
||||||
|
return NotFound("Módulo de memoria RAM no encontrado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
memoria.Id = id;
|
||||||
|
return Ok(memoria);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DELETE /api/memoriasram/{id} ---
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> Borrar(int id)
|
||||||
|
{
|
||||||
|
var deleteAssociationsQuery = "DELETE FROM dbo.equipos_memorias_ram WHERE memoria_ram_id = @Id;";
|
||||||
|
var deleteRamQuery = "DELETE FROM dbo.memorias_ram WHERE Id = @Id;";
|
||||||
|
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
using (var transaction = connection.BeginTransaction())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Primero eliminamos las asociaciones en la tabla intermedia
|
||||||
|
await connection.ExecuteAsync(deleteAssociationsQuery, new { Id = id }, transaction: transaction);
|
||||||
|
|
||||||
|
// Luego eliminamos el módulo de RAM
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(deleteRamQuery, new { Id = id }, transaction: transaction);
|
||||||
|
|
||||||
|
if (filasAfectadas == 0)
|
||||||
|
{
|
||||||
|
// Si no se borró nada, hacemos rollback y devolvemos NotFound.
|
||||||
|
transaction.Rollback();
|
||||||
|
return NotFound("Módulo de memoria RAM no encontrado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si todo salió bien, confirmamos la transacción.
|
||||||
|
transaction.Commit();
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
transaction.Rollback();
|
||||||
|
throw; // Relanza la excepción para que sea manejada por el middleware de errores de ASP.NET Core
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,40 +5,118 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
|
|
||||||
namespace Inventario.API.Controllers
|
namespace Inventario.API.Controllers
|
||||||
{
|
{
|
||||||
// [ApiController] habilita comportamientos estándar de API como la validación automática.
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
// [Route("api/[controller]")] define la URL base para este controlador.
|
|
||||||
// "[controller]" se reemplaza por el nombre de la clase sin "Controller", es decir, "api/sectores".
|
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
public class SectoresController : ControllerBase
|
public class SectoresController : ControllerBase
|
||||||
{
|
{
|
||||||
// Campo privado para guardar la referencia a nuestro contexto de Dapper.
|
|
||||||
private readonly DapperContext _context;
|
private readonly DapperContext _context;
|
||||||
|
|
||||||
// El constructor recibe el DapperContext a través de la inyección de dependencias que configuramos en Program.cs.
|
|
||||||
public SectoresController(DapperContext context)
|
public SectoresController(DapperContext context)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// [HttpGet] marca este método para que responda a peticiones GET a la ruta base ("api/sectores").
|
// --- GET /api/sectores ---
|
||||||
|
// Método para obtener todos los sectores.
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> ConsultarSectores()
|
public async Task<IActionResult> ConsultarSectores()
|
||||||
{
|
{
|
||||||
// Definimos nuestra consulta SQL. Es buena práctica listar las columnas explícitamente.
|
|
||||||
var query = "SELECT Id, Nombre FROM dbo.sectores ORDER BY Nombre;";
|
var query = "SELECT Id, Nombre FROM dbo.sectores ORDER BY Nombre;";
|
||||||
|
|
||||||
// Creamos una conexión a la base de datos. El 'using' asegura que la conexión se cierre y se libere correctamente, incluso si hay errores.
|
|
||||||
using (var connection = _context.CreateConnection())
|
using (var connection = _context.CreateConnection())
|
||||||
{
|
{
|
||||||
// Usamos el método QueryAsync de Dapper.
|
|
||||||
// <Sector> le dice a Dapper que mapee cada fila del resultado a un objeto de nuestra clase Sector.
|
|
||||||
// 'await' espera a que la base de datos responda sin bloquear el servidor.
|
|
||||||
var sectores = await connection.QueryAsync<Sector>(query);
|
var sectores = await connection.QueryAsync<Sector>(query);
|
||||||
|
|
||||||
// Ok() crea una respuesta HTTP 200 OK y serializa la lista de sectores a formato JSON.
|
|
||||||
return Ok(sectores);
|
return Ok(sectores);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- GET /api/sectores/{id} ---
|
||||||
|
// Método para obtener un único sector por su ID.
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> ConsultarSectorDetalle(int id)
|
||||||
|
{
|
||||||
|
var query = "SELECT Id, Nombre FROM dbo.sectores WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
// QuerySingleOrDefaultAsync es perfecto para obtener un solo registro.
|
||||||
|
// Devuelve el objeto si lo encuentra, o null si no existe.
|
||||||
|
var sector = await connection.QuerySingleOrDefaultAsync<Sector>(query, new { Id = id });
|
||||||
|
|
||||||
|
if (sector == null)
|
||||||
|
{
|
||||||
|
// Si no se encuentra, devolvemos un error HTTP 404 Not Found.
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
return Ok(sector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- POST /api/sectores ---
|
||||||
|
// Método para crear un nuevo sector.
|
||||||
|
// Nota: Cambiado de /:nombre a un POST estándar que recibe el objeto en el body. Es más RESTful.
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> IngresarSector([FromBody] Sector sector)
|
||||||
|
{
|
||||||
|
var checkQuery = "SELECT COUNT(1) FROM dbo.sectores WHERE Nombre = @Nombre;";
|
||||||
|
var insertQuery = "INSERT INTO dbo.sectores (Nombre) VALUES (@Nombre); SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||||
|
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
// Primero, verificamos si ya existe un sector con ese nombre.
|
||||||
|
var existe = await connection.ExecuteScalarAsync<bool>(checkQuery, new { sector.Nombre });
|
||||||
|
if (existe)
|
||||||
|
{
|
||||||
|
// Si ya existe, devolvemos un error HTTP 409 Conflict.
|
||||||
|
return Conflict($"Ya existe un sector con el nombre '{sector.Nombre}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteScalarAsync ejecuta la consulta y devuelve el primer valor de la primera fila (el nuevo ID).
|
||||||
|
var nuevoId = await connection.ExecuteScalarAsync<int>(insertQuery, new { sector.Nombre });
|
||||||
|
var nuevoSector = new Sector { Id = nuevoId, Nombre = sector.Nombre };
|
||||||
|
|
||||||
|
// Devolvemos una respuesta HTTP 201 Created, con la ubicación del nuevo recurso y el objeto creado.
|
||||||
|
return CreatedAtAction(nameof(ConsultarSectorDetalle), new { id = nuevoId }, nuevoSector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- PUT /api/sectores/{id} ---
|
||||||
|
// Método para actualizar un sector existente.
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> ActualizarSector(int id, [FromBody] Sector sector)
|
||||||
|
{
|
||||||
|
var query = "UPDATE dbo.sectores SET Nombre = @Nombre WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
// ExecuteAsync devuelve el número de filas afectadas.
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(query, new { Nombre = sector.Nombre, Id = id });
|
||||||
|
|
||||||
|
if (filasAfectadas == 0)
|
||||||
|
{
|
||||||
|
// Si no se afectó ninguna fila, significa que el ID no existía.
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devolvemos HTTP 204 No Content para indicar que la actualización fue exitosa pero no hay nada que devolver.
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DELETE /api/sectores/{id} ---
|
||||||
|
// Método para eliminar un sector.
|
||||||
|
[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)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
123
backend/Controllers/UsuariosController.cs
Normal file
123
backend/Controllers/UsuariosController.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Inventario.API.Data;
|
||||||
|
using Inventario.API.Models;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Inventario.API.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class UsuariosController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly DapperContext _context;
|
||||||
|
|
||||||
|
public UsuariosController(DapperContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GET /api/usuarios ---
|
||||||
|
[HttpGet]
|
||||||
|
public async Task<IActionResult> Consultar()
|
||||||
|
{
|
||||||
|
var query = "SELECT Id, Username, Password, Created_at, Updated_at FROM dbo.usuarios ORDER BY Username;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var usuarios = await connection.QueryAsync<Usuario>(query);
|
||||||
|
return Ok(usuarios);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GET /api/usuarios/{id} ---
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> ConsultarDetalle(int id)
|
||||||
|
{
|
||||||
|
var query = "SELECT Id, Username, Password, Created_at, Updated_at FROM dbo.usuarios WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var usuario = await connection.QuerySingleOrDefaultAsync<Usuario>(query, new { Id = id });
|
||||||
|
if (usuario == null)
|
||||||
|
{
|
||||||
|
return NotFound("Usuario no encontrado.");
|
||||||
|
}
|
||||||
|
return Ok(usuario);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- POST /api/usuarios ---
|
||||||
|
// Este endpoint replica la lógica "upsert" del original: si el usuario existe, lo actualiza; si no, lo crea.
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> Ingresar([FromBody] Usuario usuario)
|
||||||
|
{
|
||||||
|
var findQuery = "SELECT * FROM dbo.usuarios WHERE Username = @Username;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var usuarioExistente = await connection.QuerySingleOrDefaultAsync<Usuario>(findQuery, new { usuario.Username });
|
||||||
|
|
||||||
|
if (usuarioExistente != null)
|
||||||
|
{
|
||||||
|
// El usuario ya existe, lo actualizamos (solo la contraseña si viene)
|
||||||
|
var updateQuery = "UPDATE dbo.usuarios SET Password = @Password WHERE Id = @Id;";
|
||||||
|
await connection.ExecuteAsync(updateQuery, new { usuario.Password, Id = usuarioExistente.Id });
|
||||||
|
|
||||||
|
// Devolvemos el usuario actualizado
|
||||||
|
var usuarioActualizado = await connection.QuerySingleOrDefaultAsync<Usuario>(findQuery, new { usuario.Username });
|
||||||
|
return Ok(usuarioActualizado);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// El usuario no existe, lo creamos
|
||||||
|
var insertQuery = "INSERT INTO dbo.usuarios (Username, Password) VALUES (@Username, @Password); SELECT CAST(SCOPE_IDENTITY() as int);";
|
||||||
|
var nuevoId = await connection.ExecuteScalarAsync<int>(insertQuery, new { usuario.Username, usuario.Password });
|
||||||
|
|
||||||
|
var nuevoUsuario = new Usuario
|
||||||
|
{
|
||||||
|
Id = nuevoId,
|
||||||
|
Username = usuario.Username,
|
||||||
|
Password = usuario.Password
|
||||||
|
};
|
||||||
|
return CreatedAtAction(nameof(ConsultarDetalle), new { id = nuevoId }, nuevoUsuario);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- PUT /api/usuarios/{id} ---
|
||||||
|
// Endpoint específico para actualizar la contraseña, como en el original.
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> Actualizar(int id, [FromBody] Usuario data)
|
||||||
|
{
|
||||||
|
var updateQuery = "UPDATE dbo.usuarios SET Password = @Password WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(updateQuery, new { data.Password, Id = id });
|
||||||
|
|
||||||
|
if (filasAfectadas == 0)
|
||||||
|
{
|
||||||
|
return NotFound("Usuario no encontrado.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Para replicar la respuesta del backend original, volvemos a consultar el usuario (sin la contraseña).
|
||||||
|
var selectQuery = "SELECT Id, Username FROM dbo.usuarios WHERE Id = @Id;";
|
||||||
|
var usuarioActualizado = await connection.QuerySingleOrDefaultAsync(selectQuery, new { Id = id });
|
||||||
|
|
||||||
|
return Ok(usuarioActualizado);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DELETE /api/usuarios/{id} ---
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> Borrar(int id)
|
||||||
|
{
|
||||||
|
var query = "DELETE FROM dbo.usuarios WHERE Id = @Id;";
|
||||||
|
using (var connection = _context.CreateConnection())
|
||||||
|
{
|
||||||
|
var filasAfectadas = await connection.ExecuteAsync(query, new { Id = id });
|
||||||
|
if (filasAfectadas == 0)
|
||||||
|
{
|
||||||
|
return NotFound("Usuario no encontrado.");
|
||||||
|
}
|
||||||
|
return NoContent(); // Respuesta HTTP 204 No Content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
backend/Helpers/HistorialHelper.cs
Normal file
29
backend/Helpers/HistorialHelper.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Dapper;
|
||||||
|
using Inventario.API.Data;
|
||||||
|
using Inventario.API.Models;
|
||||||
|
|
||||||
|
namespace Inventario.API.Helpers
|
||||||
|
{
|
||||||
|
public static class HistorialHelper
|
||||||
|
{
|
||||||
|
public static async Task RegistrarCambios(DapperContext context, int equipoId, Dictionary<string, (string anterior, string nuevo)> cambios)
|
||||||
|
{
|
||||||
|
var query = @"INSERT INTO dbo.historial_equipos (equipo_id, campo_modificado, valor_anterior, valor_nuevo)
|
||||||
|
VALUES (@EquipoId, @CampoModificado, @ValorAnterior, @ValorNuevo);";
|
||||||
|
|
||||||
|
using (var connection = context.CreateConnection())
|
||||||
|
{
|
||||||
|
foreach (var cambio in cambios)
|
||||||
|
{
|
||||||
|
await connection.ExecuteAsync(query, new
|
||||||
|
{
|
||||||
|
EquipoId = equipoId,
|
||||||
|
CampoModificado = cambio.Key,
|
||||||
|
ValorAnterior = cambio.Value.anterior,
|
||||||
|
ValorNuevo = cambio.Value.nuevo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Inventario.API")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("Inventario.API")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+80eeac45d8b90ed69a6479e9d3dcadde5e317a90")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+10f2f2ba67eac900e8a7b63ced2f3689c309fa6f")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("Inventario.API")]
|
[assembly: System.Reflection.AssemblyProductAttribute("Inventario.API")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("Inventario.API")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("Inventario.API")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
Reference in New Issue
Block a user