- ¿Qué es este proyecto?
- ¿Cómo funciona ASP.NET MVC? (explicado simple)
- Estructura del proyecto
- Archivos clave — qué hace cada uno
- Base de datos
- Flujo de una petición de principio a fin
- Componentes visuales reutilizables (Shared/)
- Cómo agregar una nueva funcionalidad
- Flujo de trabajo Git (Git Flow)
- Configuración inicial
SGESWeb es una aplicación web para el SENA que permite gestionar eventos institucionales.
| ¿Quién la usa? | ¿Qué puede hacer? |
|---|---|
| 👤 Administrador | Crear y gestionar eventos |
| 🎓 Aprendiz | Ver el listado de eventos disponibles |
Cuando un usuario inicia sesión, la aplicación detecta su rol automáticamente y lo redirige a la pantalla correcta.
Si nunca has trabajado con este framework, piensa en él así:
El usuario hace clic o escribe una URL
↓
CONTROLADOR recibe la petición
(es como el "gerente" que decide qué hacer)
↓
Habla con el MODELO
(que tiene la lógica y habla con la base de datos)
↓
El controlador manda los datos a la VISTA
(que es el HTML que ve el usuario)
El patrón MVC significa:
| Letra | Nombre | Rol en SGESWeb |
|---|---|---|
| M | Model | Clases C# que representan datos y hacen consultas SQL (EventoModel, EventoDAO, AuthDAO…) |
| V | View | Archivos .cshtml con el HTML + lógica de presentación (CrearEvento.cshtml, Login.cshtml…) |
| C | Controller | Clases C# que reciben URLs, llaman al modelo y retornan una vista (EventoController, AuthController…) |
La URL http://localhost/Evento/CrearEvento se traduce así:
/Evento → busca EventoController.cs
/CrearEvento → ejecuta el método CrearEvento() dentro de ese controlador
Esto está definido en App_Start/RouteConfig.cs:
url: "{controller}/{action}/{id}"
// ↑ ↑ ↑
// EventoController CrearEvento (opcional)La ruta por defecto (página de inicio) es Auth/Login.
SGESWeb/
│
├── 📄 README.md ← Este archivo
├── 📄 .gitignore
├── 📄 LICENSE
│
└── SGES.Web/
└── SGES.Web/ ← Proyecto principal ASP.NET MVC
│
├── 🗂 App_Start/ ← Configuración que se carga al arrancar
│ ├── BundleConfig.cs ← Agrupa y minifica CSS/JS
│ ├── FilterConfig.cs ← Filtros globales (ej: manejo de errores)
│ └── RouteConfig.cs ← Reglas de URL
│
├── 🗂 Controllers/ ← Controladores (reciben peticiones HTTP)
│ ├── AuthController.cs ← Login / Logout
│ ├── EventoController.cs ← Crear evento / Listado
│ └── HomeController.cs ← Páginas de prueba (About, Contact)
│
├── 🗂 Models/ ← Datos y acceso a BD
│ ├── Conexión.cs ← Abre la conexión SQL Server
│ ├── LoginModel.cs ← Datos del formulario de login
│ ├── UsuarioSesion.cs ← Datos del usuario en sesión
│ ├── EventoModel.cs ← Estructura de un evento
│ ├── EventoDAO.cs ← Consultas SQL de eventos
│ └── AuthDAO.cs ← Consultas SQL de autenticación
│
├── 🗂 Views/ ← Plantillas HTML (.cshtml)
│ ├── _ViewStart.cshtml ← Indica que todas las vistas usan _Layout
│ │
│ ├── 🗂 Auth/
│ │ └── Login.cshtml ← Pantalla de inicio de sesión
│ │
│ ├── 🗂 Evento/
│ │ ├── CrearEvento.cshtml ← Formulario para crear un evento
│ │ └── Listado.cshtml ← Tabla de eventos disponibles
│ │
│ ├── 🗂 Home/ ← Vistas de prueba (no se usan en producción)
│ │
│ └── 🗂 Shared/ ← Componentes reutilizables y layout
│ ├── _Layout.cshtml ← Plantilla base (navbar + footer)
│ ├── _TextBox.cshtml ← Campo de texto
│ ├── _TextArea.cshtml ← Campo de texto multilínea
│ ├── _ComboBox.cshtml ← Lista desplegable
│ ├── _BotonGuardar.cshtml
│ ├── _BotonCancelar.cshtml
│ ├── _BotonEliminar.cshtml
│ ├── _Card.cshtml ← Contenedor tarjeta Bootstrap
│ ├── _TablaBasica.cshtml ← Tabla de datos reutilizable
│ ├── COMPONENTES.md ← 📖 Guía detallada de cada componente
│ └── site.css ← Estilos personalizados del proyecto
│
├── 🗂 Database/ ← Scripts SQL
│ ├── Shema.sql ← Crea todas las tablas
│ └── Data.sql ← Datos de prueba
│
├── 🗂 Content/ ← Archivos CSS (Bootstrap + Site.css)
├── 🗂 Scripts/ ← Archivos JS (jQuery, Bootstrap, Validate)
│
├── Global.asax ← Punto de arranque de la aplicación
└── Web.config ← Configuración: cadena de conexión BD, etc.
Es el archivo de configuración principal. Lo más importante que contiene es la cadena de conexión a la base de datos:
<add name="SGESConnection"
connectionString="Data Source=.;Initial Catalog=SGES;Integrated Security=True;"
providerName="System.Data.SqlClient" />¿Qué significa?
Data Source=.significa "SQL Server local".Initial Catalog=SGESes el nombre de la base de datos. Si tu SQL Server tiene un nombre distinto, cámbialo aquí.
Se encarga de abrir la conexión con SQL Server usando la cadena del Web.config. Todos los DAOs la usan.
public SqlConnection ObtenerConexion()
{
return new SqlConnection(cadenaConexion);
}Guarda los datos del usuario que está logueado:
public class UsuarioSesion
{
public int Id { get; set; }
public string Nombre { get; set; }
public string Tipo { get; set; } // "Administrador" o "Aprendiz"
}Se almacena en Session["Usuario"] al iniciar sesión y se usa en toda la aplicación para saber quién está conectado y qué puede ver.
Hace las consultas SQL para autenticar. Busca primero en la tabla Usuario (administradores) y si no encuentra, en Aprendiz. Retorna un UsuarioSesion con nombre y tipo, o null si las credenciales son incorrectas.
Representa un evento de la base de datos:
| Propiedad | Tipo | Columna en BD |
|---|---|---|
IdEvento |
int |
idEvento |
NombreEvento |
string |
nombreEvento |
TipoEvento |
string |
tipoEvento |
FechaHoraInicio |
DateTime |
fechaHoraInicio |
FechaHoraFin |
DateTime |
fechaHoraFin |
IdUser |
int |
idUser (FK → Usuario) |
Contiene las operaciones de base de datos para eventos:
ObtenerEventos()→SELECT * FROM Eventosy retorna una lista deEventoModelInsertarEvento(evento)→INSERT INTO Eventoscon los datos del modelo
| Método | HTTP | Descripción |
|---|---|---|
Login() |
GET | Muestra el formulario. Si ya hay sesión, redirige según rol |
Login(model) |
POST | Valida credenciales. Si son correctas, guarda sesión y redirige |
Logout() |
GET | Limpia la sesión y redirige al login |
La redirección según rol funciona así:
if (usuario.Tipo == "Administrador")
return RedirectToAction("CrearEvento", "Evento");
return RedirectToAction("Listado", "Evento");| Método | HTTP | URL | Acceso |
|---|---|---|---|
CrearEvento() |
GET | /Evento/CrearEvento |
Solo si hay sesión |
CrearEvento(evento) |
POST | /Evento/CrearEvento |
Solo si hay sesión |
Listado() |
GET | /Evento/Listado |
Solo si hay sesión |
Validaciones del servidor en CrearEvento POST:
- Fecha de inicio no puede ser en el pasado
- Fecha de fin debe ser posterior a la de inicio
- Si el modelo tiene errores (
ModelState.IsValid == false), regresa la vista con los errores
Es la plantilla base de toda la aplicación. Todas las vistas se insertan dentro de ella en el lugar donde dice @RenderBody(). Contiene:
- El
<head>con los CSS - La barra de navegación (
<nav>) que muestra opciones según el rol del usuario - El contenedor principal con el contenido
- Los scripts de jQuery y Bootstrap al final
Usa los componentes de Shared/ para construir el formulario. Incluye un bloque @section scripts con validación JavaScript en tiempo real que marca los campos como inválidos (clase is-invalid) al salir de ellos.
Formulario con 4 campos: Nombre, Tipo (combo), Fecha inicio, Fecha fin. La validación JavaScript valida cada campo al perder foco y bloquea el envío si alguno falla. El servidor hace una segunda validación independiente.
Muestra una tabla simple con todos los eventos cuya fecha de inicio sea igual o posterior a DateTime.Now, ordenados cronológicamente.
Agrupa los archivos CSS y JS en "bundles" para que el navegador los cargue eficientemente:
// Todos los CSS
"~/Content/css" → bootstrap.css + site.css
// Scripts
"~/bundles/jquery" → jquery-3.7.0.js
"~/bundles/bootstrap" → bootstrap.bundle.jsLos scripts están en Database/.
- Abre SQL Server Management Studio (SSMS)
- Ejecuta
Database/Shema.sql— crea la BD y todas las tablas - Ejecuta
Database/Data.sql— inserta datos de prueba
Usuarios de prueba creados:
| ID | Nombre | Contraseña | Tipo |
|---|---|---|---|
| 1 | Carlos Ruiz | Admin123 | Administrador |
| 2 | Ana Martinez | Admin456 | Administrador |
| 1 (idApr) | Carlos Ramirez | pass123 | Aprendiz |
| 2 (idApr) | Laura Gomez | pass456 | Aprendiz |
⚠️ ElidUserdeUsuarioy elidAprdeAprendizson independientes. El login busca primero enUsuarioy luego enAprendiz.
Ejemplo completo: un Administrador crea un evento.
1. Usuario escribe /Evento/CrearEvento en el navegador
↓
2. RouteConfig.cs lo enruta a EventoController → CrearEvento() [GET]
↓
3. El controlador verifica Session["Usuario"] != null
└─ Si no hay sesión → redirige a /Auth/Login
↓
4. Pone los tipos de evento en ViewBag.TiposEvento
↓
5. return View(new EventoModel()) → carga CrearEvento.cshtml
↓
6. _Layout.cshtml envuelve la vista con el navbar y el footer
↓
7. El usuario llena el formulario y hace clic en "Guardar"
↓
8. El navegador ejecuta la validación JS (blur/change/submit)
└─ Si algún campo falla → muestra el error, bloquea el envío
↓
9. Si todo OK → POST a /Evento/CrearEvento
↓
10. EventoController → CrearEvento(EventoModel evento) [POST]
├─ Asigna evento.IdUser desde la sesión
├─ Valida fechas en el servidor (ModelState.AddModelError si fallan)
├─ if (!ModelState.IsValid) → return View(evento) con errores
└─ _dao.InsertarEvento(evento) → INSERT en SQL Server
↓
11. TempData["Success"] = "Evento creado correctamente."
→ RedirectToAction("CrearEvento") [GET limpio]
Para la documentación detallada de cada componente, ve a
Views/Shared/COMPONENTES.md.
El proyecto tiene una librería de componentes en Views/Shared/. En lugar de escribir HTML repetido en cada vista, se usan Partial Views con parámetros vía ViewData.
@Html.Partial("_NombreComponente", null, new ViewDataDictionary(ViewData) {
{ "parametro1", valor1 },
{ "parametro2", valor2 }
})
⚠️ Siempre usanew ViewDataDictionary(ViewData)(nonew ViewDataDictionary()). Esto preserva los errores de validación del modelo actual.
| Componente | Archivo | Para qué sirve |
|---|---|---|
_TextBox |
_TextBox.cshtml |
Campo de texto (text, number, password, date, datetime-local…) |
_TextArea |
_TextArea.cshtml |
Campo de texto multilínea |
_ComboBox |
_ComboBox.cshtml |
Lista desplegable <select> |
_BotonGuardar |
_BotonGuardar.cshtml |
Botón type="submit" azul |
_BotonCancelar |
_BotonCancelar.cshtml |
Enlace de navegación con apariencia de botón |
_BotonEliminar |
_BotonEliminar.cshtml |
Botón que hace POST con confirmación |
_Card |
_Card.cshtml |
Contenedor tipo tarjeta Bootstrap con encabezado |
_TablaBasica |
_TablaBasica.cshtml |
Tabla de datos con soporte para acciones por fila |
Seguir estos pasos garantiza que la funcionalidad quede integrada correctamente con el resto del proyecto.
¿Cuándo? Si la funcionalidad necesita representar una entidad nueva (ej: Inscripcion, Programa).
Crea dos archivos en Models/:
Models/InscripcionModel.cs — la clase que representa los datos:
namespace SGES.Web.Models
{
public class InscripcionModel
{
public int IdInscrip { get; set; }
public DateTime FechaInscrip { get; set; }
public string Modalidad { get; set; }
public int IdApr { get; set; }
public int IdEvento { get; set; }
}
}Models/InscripcionDAO.cs — las consultas SQL:
namespace SGES.Web.Models
{
public class InscripcionDAO
{
private readonly Conexion cn = new Conexion();
public void Inscribir(InscripcionModel inscripcion)
{
using (SqlConnection con = cn.ObtenerConexion())
{
string sql = @"INSERT INTO Inscripciones
(fechaInscrip, modalidadInscrip, idApr, idEvento)
VALUES (@fecha, @modalidad, @idApr, @idEvento)";
SqlCommand cmd = new SqlCommand(sql, con);
cmd.Parameters.AddWithValue("@fecha", inscripcion.FechaInscrip);
cmd.Parameters.AddWithValue("@modalidad", inscripcion.Modalidad);
cmd.Parameters.AddWithValue("@idApr", inscripcion.IdApr);
cmd.Parameters.AddWithValue("@idEvento", inscripcion.IdEvento);
con.Open();
cmd.ExecuteNonQuery();
}
}
}
}Si la funcionalidad pertenece a un módulo nuevo, crea un archivo en Controllers/.
Controllers/InscripcionController.cs:
using SGES.Web.Models;
using System.Web.Mvc;
namespace SGES.Web.Controllers
{
public class InscripcionController : Controller
{
private readonly InscripcionDAO _dao;
public InscripcionController() : this(new InscripcionDAO()) { }
public InscripcionController(InscripcionDAO dao)
{
_dao = dao;
}
// Acción privada para obtener usuario de sesión
private UsuarioSesion UsuarioActual
{
get { return Session["Usuario"] as UsuarioSesion; }
}
[HttpGet]
public ActionResult Inscribirse(int id)
{
if (UsuarioActual == null)
return RedirectToAction("Login", "Auth");
// Aquí podrías cargar datos del evento para mostrarlos
ViewBag.IdEvento = id;
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Inscribirse(InscripcionModel inscripcion)
{
if (UsuarioActual == null)
return RedirectToAction("Login", "Auth");
inscripcion.IdApr = UsuarioActual.Id;
inscripcion.FechaInscrip = DateTime.Today;
if (!ModelState.IsValid)
return View(inscripcion);
_dao.Inscribir(inscripcion);
TempData["Success"] = "¡Inscripción realizada con éxito!";
return RedirectToAction("Listado", "Evento");
}
}
}
⚠️ Siempre verificaSession["Usuario"] != nullal inicio de cada acción que requiera autenticación.
Crea una carpeta con el nombre del controlador (sin "Controller") dentro de Views/ y agrega el archivo .cshtml.
Views/Inscripcion/Inscribirse.cshtml:
@model SGES.Web.Models.InscripcionModel
@{
ViewBag.Title = "Inscribirse al Evento";
}
@* Encabezado con el componente _Card *@
@Html.Partial("_Card", null, new ViewDataDictionary(ViewData) {
{ "titulo", "Inscripción a Evento" },
{ "icono", "bi-person-plus" },
{ "subtitulo", "Complete los datos para inscribirse" }
})
@using (Html.BeginForm("Inscribirse", "Inscripcion", FormMethod.Post))
{
@Html.AntiForgeryToken()
@* Muestra todos los errores del modelo en un bloque *@
@Html.ValidationSummary(false, "", new { @class = "alert alert-danger" })
@Html.Partial("_ComboBox", null, new ViewDataDictionary(ViewData) {
{ "id", "Modalidad" },
{ "label", "Modalidad" },
{ "items", new SelectList(new[] { "Presencial", "Virtual" }) },
{ "selectedValue", Model != null ? Model.Modalidad : "" },
{ "defaultOption", "-- Seleccione --" },
{ "required", true }
})
@Html.ValidationMessage("Modalidad", "", new { @class = "text-danger small" })
@* Campo oculto para el ID del evento *@
<input type="hidden" name="IdEvento" value="@ViewBag.IdEvento" />
<div class="mt-3">
@Html.Partial("_BotonGuardar", null, new ViewDataDictionary(ViewData) {
{ "texto", "Confirmar inscripción" },
{ "icono", "bi-check-circle" }
})
@Html.Partial("_BotonCancelar", null, new ViewDataDictionary(ViewData) {
{ "url", Url.Action("Listado", "Evento") }
})
</div>
}
@* Validación JS en tiempo real (opcional pero recomendado) *@
@section scripts {
<script>
(function () {
var modalidad = document.getElementById("Modalidad");
function getSpan(fieldId) {
return document.querySelector('[data-valmsg-for="' + fieldId + '"]');
}
function validarModalidad() {
if (!modalidad.value) {
modalidad.classList.add("is-invalid");
var span = getSpan("Modalidad");
if (span) { span.textContent = "Debe seleccionar una modalidad."; }
return false;
}
modalidad.classList.remove("is-invalid");
modalidad.classList.add("is-valid");
return true;
}
modalidad.addEventListener("change", validarModalidad);
document.querySelector("form").addEventListener("submit", function (e) {
if (!validarModalidad()) e.preventDefault();
});
}());
</script>
}Si la nueva página debe aparecer en la barra de navegación, edita Views/Shared/_Layout.cshtml:
@* Dentro del bloque if (u.Tipo == "Administrador") o fuera según el rol *@
<li>
@Html.ActionLink("Inscripciones", "Index", "Inscripcion",
new { area = "" }, new { @class = "nav-link" })
</li>✅ Models/NuevaEntidadModel.cs → Propiedades (qué datos tiene)
✅ Models/NuevaEntidadDAO.cs → Consultas SQL (cómo se accede a BD)
✅ Controllers/NuevaController.cs → Lógica de negocio y validaciones
✅ Views/Nueva/
├── Index.cshtml → Listado (si aplica)
├── Crear.cshtml → Formulario de creación (si aplica)
└── Detalle.cshtml → Vista detallada (si aplica)
⚙️ Views/Shared/_Layout.cshtml → Agrega enlace en navbar (si aplica)
El proyecto usa Git Flow con tres niveles de ramas:
main ← código en producción (estable)
└── develop ← rama de integración (aquí está este código)
└── HU-{ID_JIRA}-{nombre-funcionalidad} ← ramas de trabajo
Las ramas de funcionalidad deben empezar con el ID de Jira porque hay una automatización que vincula los commits con las historias de usuario:
# ✅ Correcto
git checkout -b HU-14-inscripcion-aprendiz
git checkout -b HU-15-editar-evento
git checkout -b HU-16-eliminar-evento
# ❌ Incorrecto — la automatización no lo detectará
git checkout -b inscripcion-aprendiz
git checkout -b feature/nueva-funcionalidad# 1. Asegúrate de estar en develop y actualizado
git checkout develop
git pull origin develop
# 2. Crea tu rama (con el ID de Jira)
git checkout -b HU-14-inscripcion-aprendiz
# 3. Desarrolla la funcionalidad...
# (crea los archivos descritos en la sección anterior)
# 4. Haz commits descriptivos
git add .
git commit -m "HU-14: Agrega modelo y DAO de inscripción"
git commit -m "HU-14: Agrega InscripcionController con validaciones"
git commit -m "HU-14: Agrega vista Inscribirse con componentes Shared"
# 5. Sube tu rama al remoto
git push origin HU-14-inscripcion-aprendiz
# 6. Abre un Pull Request hacia develop en el repositorio remoto
# (NO mergees directamente a develop sin revisión)| Rama | Propósito | ¿Se puede hacer push directo? |
|---|---|---|
main |
Producción | ❌ Solo via PR desde develop |
develop |
Integración | ❌ Solo via PR desde rama de HU |
HU-{id}-* |
Tu trabajo | ✅ Sí |
- Visual Studio 2022 (o posterior)
- .NET Framework 4.8
- SQL Server (Express funciona)
- SQL Server Management Studio (SSMS)
# 1. Clona el repositorio
git clone <url-del-repositorio>
cd SGESWeb
# 2. Cambia a la rama develop
git checkout develop-
Abre
SGES.Web/SGES.Web.slnxen Visual Studio -
Configura la base de datos:
- Abre SSMS
- Ejecuta
Database/Shema.sql - Ejecuta
Database/Data.sql
-
Verifica la cadena de conexión en
Web.config. Si tu SQL Server local tiene un nombre específico (ej:DESKTOP-ABC\SQLEXPRESS), cambiaData Source=.por ese nombre:connectionString="Data Source=DESKTOP-ABC\SQLEXPRESS;Initial Catalog=SGES;Integrated Security=True;"
-
Presiona F5 en Visual Studio para correr el proyecto
-
El navegador abrirá
/Auth/Loginautomáticamente
develop — SGESWeb · SENA
