Proyecto ChatBot Con Gemini
This commit is contained in:
30
ChatbotApi/ChatbotApi.csproj
Normal file
30
ChatbotApi/ChatbotApi.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNetEnv" Version="3.1.1" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
||||
<PackageReference Include="LLamaSharp" Version="0.25.0" />
|
||||
<PackageReference Include="LLamaSharp.Backend.Cpu" Version="0.25.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.0" />
|
||||
<PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders" Version="1.2.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Microsoft.EntityFrameworkCore" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
ChatbotApi/ChatbotApi.http
Normal file
6
ChatbotApi/ChatbotApi.http
Normal file
@@ -0,0 +1,6 @@
|
||||
@ChatbotApi_HostAddress = http://localhost:5126
|
||||
|
||||
GET {{ChatbotApi_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
90
ChatbotApi/Constrollers/AdminController.cs
Normal file
90
ChatbotApi/Constrollers/AdminController.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
// Controllers/AdminController.cs
|
||||
using ChatbotApi.Data.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace ChatbotApi.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class AdminController : ControllerBase
|
||||
{
|
||||
private readonly AppContexto _context;
|
||||
|
||||
public AdminController(AppContexto context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
// GET: api/admin/contexto
|
||||
[HttpGet("contexto")]
|
||||
public async Task<IActionResult> GetAllContextoItems()
|
||||
{
|
||||
var items = await _context.ContextoItems.OrderBy(i => i.Clave).ToListAsync();
|
||||
return Ok(items);
|
||||
}
|
||||
|
||||
// POST: api/admin/contexto
|
||||
[HttpPost("contexto")]
|
||||
public async Task<IActionResult> CreateContextoItem([FromBody] ContextoItem item)
|
||||
{
|
||||
if (await _context.ContextoItems.AnyAsync(i => i.Clave == item.Clave))
|
||||
{
|
||||
return BadRequest("La clave ya existe.");
|
||||
}
|
||||
item.FechaActualizacion = DateTime.UtcNow;
|
||||
_context.ContextoItems.Add(item);
|
||||
await _context.SaveChangesAsync();
|
||||
return CreatedAtAction(nameof(GetAllContextoItems), new { id = item.Id }, item);
|
||||
}
|
||||
|
||||
// PUT: api/admin/contexto/5
|
||||
[HttpPut("contexto/{id}")]
|
||||
public async Task<IActionResult> UpdateContextoItem(int id, [FromBody] ContextoItem item)
|
||||
{
|
||||
if (id != item.Id)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
var existingItem = await _context.ContextoItems.FindAsync(id);
|
||||
if (existingItem == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
existingItem.Valor = item.Valor;
|
||||
existingItem.Descripcion = item.Descripcion;
|
||||
existingItem.FechaActualizacion = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// DELETE: api/admin/contexto/5
|
||||
[HttpDelete("contexto/{id}")]
|
||||
public async Task<IActionResult> DeleteContextoItem(int id)
|
||||
{
|
||||
var item = await _context.ContextoItems.FindAsync(id);
|
||||
if (item == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
_context.ContextoItems.Remove(item);
|
||||
await _context.SaveChangesAsync();
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// GET: api/admin/logs
|
||||
[HttpGet("logs")]
|
||||
public async Task<IActionResult> GetConversationLogs()
|
||||
{
|
||||
// Obtenemos los últimos 200 logs, ordenados del más reciente al más antiguo.
|
||||
var logs = await _context.ConversacionLogs
|
||||
.OrderByDescending(log => log.Fecha)
|
||||
.Take(200)
|
||||
.ToListAsync();
|
||||
return Ok(logs);
|
||||
}
|
||||
}
|
||||
}
|
||||
96
ChatbotApi/Constrollers/AuthController.cs
Normal file
96
ChatbotApi/Constrollers/AuthController.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
// /Controllers/AuthController.cs
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
public class LoginRequest
|
||||
{
|
||||
[Required]
|
||||
[MaxLength(100)]
|
||||
public required string Username { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(100)]
|
||||
public required string Password { get; set; }
|
||||
}
|
||||
public class LoginResponse { public required string Token { get; set; } }
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly UserManager<IdentityUser> _userManager;
|
||||
|
||||
// Inyectamos el UserManager que gestiona los usuarios
|
||||
public AuthController(IConfiguration configuration, UserManager<IdentityUser> userManager)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequest loginRequest)
|
||||
{
|
||||
// Buscamos al usuario por su nombre
|
||||
var user = await _userManager.FindByNameAsync(loginRequest.Username);
|
||||
|
||||
// Verificamos si el usuario existe y si la contraseña es correcta
|
||||
if (user != null && await _userManager.CheckPasswordAsync(user, loginRequest.Password))
|
||||
{
|
||||
var token = GenerateJwtToken(user);
|
||||
return Ok(new LoginResponse { Token = token });
|
||||
}
|
||||
|
||||
return Unauthorized("Credenciales inválidas.");
|
||||
}
|
||||
|
||||
// Método para crear el primer usuario administrador (solo para configuración inicial)
|
||||
[HttpPost("setup-admin")]
|
||||
public async Task<IActionResult> SetupAdminUser()
|
||||
{
|
||||
var adminUser = await _userManager.FindByNameAsync("admin");
|
||||
if (adminUser == null)
|
||||
{
|
||||
adminUser = new IdentityUser
|
||||
{
|
||||
UserName = "admin",
|
||||
Email = "tecnica@eldia.com",
|
||||
};
|
||||
var result = await _userManager.CreateAsync(adminUser, "Diagonal423");
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
return Ok("Usuario administrador creado exitosamente.");
|
||||
}
|
||||
return BadRequest(result.Errors);
|
||||
}
|
||||
return Ok("El usuario administrador ya existe.");
|
||||
}
|
||||
|
||||
private string GenerateJwtToken(IdentityUser user)
|
||||
{
|
||||
var jwtKey = _configuration["Jwt:Key"] ?? throw new InvalidOperationException("La clave JWT no está configurada.");
|
||||
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey));
|
||||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, user.UserName!),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||
};
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: _configuration["Jwt:Issuer"],
|
||||
audience: _configuration["Jwt:Audience"],
|
||||
claims: claims,
|
||||
expires: DateTime.UtcNow.AddHours(8),
|
||||
signingCredentials: credentials);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
}
|
||||
436
ChatbotApi/Constrollers/ChatController.cs
Normal file
436
ChatbotApi/Constrollers/ChatController.cs
Normal file
@@ -0,0 +1,436 @@
|
||||
// ChatbotApi/Controllers/ChatController.cs
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using ChatbotApi.Models;
|
||||
using ChatbotApi.Data.Models;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using HtmlAgilityPack;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
|
||||
// Clases de Request/Response
|
||||
public class GeminiRequest { [JsonPropertyName("contents")] public Content[] Contents { get; set; } = default!; }
|
||||
public class Content { [JsonPropertyName("parts")] public Part[] Parts { get; set; } = default!; }
|
||||
public class Part { [JsonPropertyName("text")] public string Text { get; set; } = default!; }
|
||||
public class GeminiResponse { [JsonPropertyName("candidates")] public Candidate[] Candidates { get; set; } = default!; }
|
||||
public class Candidate { [JsonPropertyName("content")] public Content Content { get; set; } = default!; }
|
||||
public class GeminiStreamingResponse { [JsonPropertyName("candidates")] public StreamingCandidate[] Candidates { get; set; } = default!; }
|
||||
public class StreamingCandidate { [JsonPropertyName("content")] public Content Content { get; set; } = default!; }
|
||||
|
||||
namespace ChatbotApi.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class ChatController : ControllerBase
|
||||
{
|
||||
private readonly string _apiUrl;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly IServiceProvider _serviceProvider; // Para crear un scope de DB
|
||||
private readonly ILogger<ChatController> _logger;
|
||||
private static readonly HttpClient _httpClient = new HttpClient();
|
||||
private static readonly string _knowledgeCacheKey = "KnowledgeBase"; // Clave única para nuestra caché
|
||||
|
||||
private static readonly string _siteUrl = "https://www.eldia.com/";
|
||||
private static readonly string[] PrefijosAQuitar = { "VIDEO.- ", "VIDEO. ", "FOTOS.- ", "FOTOS. " };
|
||||
|
||||
public ChatController(IConfiguration configuration, IMemoryCache memoryCache, IServiceProvider serviceProvider, ILogger<ChatController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_cache = memoryCache;
|
||||
_serviceProvider = serviceProvider;
|
||||
var apiKey = configuration["Gemini:GeminiApiKey"]
|
||||
?? throw new InvalidOperationException("La API Key de Gemini no está configurada en .env");
|
||||
|
||||
var baseUrl = configuration["Gemini:GeminiApiUrl"];
|
||||
_apiUrl = $"{baseUrl}{apiKey}";
|
||||
}
|
||||
|
||||
[HttpPost("stream-message")]
|
||||
[EnableRateLimiting("fixed")]
|
||||
public async IAsyncEnumerable<string> StreamMessage(
|
||||
[FromBody] ChatRequest request,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
// --- FASE 1: Validación y Preparación ---
|
||||
if (string.IsNullOrWhiteSpace(request?.Message))
|
||||
{
|
||||
yield return "Error: No he recibido ningún mensaje.";
|
||||
yield break;
|
||||
}
|
||||
|
||||
string userMessage = request.Message;
|
||||
string lowerUserMessage = userMessage.ToLowerInvariant();
|
||||
string context;
|
||||
string? errorMessage = null; // Variable para almacenar el mensaje de error
|
||||
|
||||
try
|
||||
{
|
||||
var knowledgeBase = await GetKnowledgeAsync();
|
||||
string? dbContext = GetContextFromDb(lowerUserMessage, knowledgeBase);
|
||||
context = dbContext ?? await GetWebsiteNewsAsync(_siteUrl, 15);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error al obtener el contexto para el streaming.");
|
||||
errorMessage = "Error: No se pudo obtener la información de contexto.";
|
||||
context = string.Empty; // Aseguramos que el contexto no sea nulo
|
||||
}
|
||||
|
||||
// Si hubo un error en la fase anterior, lo devolvemos
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
yield return errorMessage;
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(context))
|
||||
{
|
||||
yield return "Error: No pude obtener información para responder a tu pregunta.";
|
||||
yield break;
|
||||
}
|
||||
|
||||
// --- FASE 2: Configuración de la Conexión ---
|
||||
Stream? responseStream = null;
|
||||
try
|
||||
{
|
||||
var promptBuilder = new StringBuilder();
|
||||
promptBuilder.AppendLine("INSTRUCCIONES:");
|
||||
promptBuilder.AppendLine("Eres DiaBot, el asistente virtual del periódico El Día. Tu personalidad es profesional, servicial y concisa.");
|
||||
promptBuilder.AppendLine(GetContextFromDb(lowerUserMessage, await GetKnowledgeAsync()) != null ? "Tu tarea es responder la 'PREGUNTA DEL USUARIO' basándote ESTRICTA Y ÚNICAMENTE en el 'CONTEXTO' que contiene información específica (contacto, suscripciones, etc.). No hables de noticias." : "Tu tarea es responder la 'PREGUNTA DEL USUARIO' basándote ESTRICTA Y ÚNICAMENTE en el 'CONTEXTO DEL SITIO WEB' que contiene una lista de noticias. Si el usuario pide la URL de una noticia, DEBES proporcionarla en formato Markdown: '[texto del enlace](URL)'.");
|
||||
promptBuilder.AppendLine("NUNCA INVENTES información. Si la respuesta no está en el contexto, indica amablemente que no encontraste la información.");
|
||||
promptBuilder.AppendLine("\nCONTEXTO:\n---");
|
||||
promptBuilder.AppendLine(context);
|
||||
promptBuilder.AppendLine("---\n\nPREGUNTA DEL USUARIO:\n---");
|
||||
promptBuilder.AppendLine(userMessage);
|
||||
promptBuilder.AppendLine("---\n\nRESPUESTA:");
|
||||
string finalPrompt = promptBuilder.ToString();
|
||||
|
||||
var streamingApiUrl = _apiUrl;
|
||||
var requestData = new GeminiRequest { Contents = new[] { new Content { Parts = new[] { new Part { Text = finalPrompt } } } } };
|
||||
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, streamingApiUrl);
|
||||
httpRequestMessage.Content = JsonContent.Create(requestData);
|
||||
|
||||
var response = await _httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogWarning("La API de Gemini (Streaming) devolvió un error. Status: {StatusCode}, Content: {ErrorContent}", response.StatusCode, errorContent);
|
||||
throw new HttpRequestException("La API de Gemini devolvió un error.");
|
||||
}
|
||||
|
||||
responseStream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
_logger.LogInformation("La operación fue cancelada por el cliente durante la configuración del stream.");
|
||||
yield break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error inesperado durante la configuración del stream.");
|
||||
errorMessage = "Error: Lo siento, estoy teniendo un problema técnico.";
|
||||
}
|
||||
|
||||
// Devolvemos el error de la fase de conexión si ocurrió
|
||||
if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
yield return errorMessage;
|
||||
yield break;
|
||||
}
|
||||
|
||||
// --- FASE 3: Lectura y Devolución del Stream ---
|
||||
var fullBotReply = new StringBuilder();
|
||||
if (responseStream != null)
|
||||
{
|
||||
await using (responseStream)
|
||||
using (var reader = new StreamReader(responseStream))
|
||||
{
|
||||
string? line;
|
||||
while ((line = await reader.ReadLineAsync(cancellationToken)) != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line) || !line.StartsWith("data: ")) continue;
|
||||
|
||||
var jsonString = line.Substring(6);
|
||||
string? chunk = null; // 1. Declaramos la variable 'chunk' fuera del try.
|
||||
|
||||
try
|
||||
{
|
||||
// 2. El bloque try solo se encarga de la deserialización.
|
||||
var geminiResponse = JsonSerializer.Deserialize<GeminiStreamingResponse>(jsonString);
|
||||
chunk = geminiResponse?.Candidates?.FirstOrDefault()?.Content?.Parts?.FirstOrDefault()?.Text;
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
// 3. Si falla, lo registramos y pasamos al siguiente fragmento.
|
||||
_logger.LogWarning(ex, "No se pudo deserializar un chunk del stream: {JsonChunk}", jsonString);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. El yield return ahora está fuera del bloque try-catch.
|
||||
if (chunk != null)
|
||||
{
|
||||
fullBotReply.Append(chunk);
|
||||
yield return chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- FASE 4: Guardado final ---
|
||||
if (fullBotReply.Length > 0)
|
||||
{
|
||||
await SaveConversationLogAsync(userMessage, fullBotReply.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveConversationLogAsync(string userMessage, string botReply)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var scope = _serviceProvider.CreateScope())
|
||||
{
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<AppContexto>();
|
||||
var logEntry = new ConversacionLog
|
||||
{
|
||||
UsuarioMensaje = userMessage,
|
||||
BotRespuesta = botReply,
|
||||
Fecha = DateTime.UtcNow
|
||||
};
|
||||
dbContext.ConversacionLogs.Add(logEntry);
|
||||
await dbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception logEx)
|
||||
{
|
||||
_logger.LogError(logEx, "Error al guardar el log de la conversación después del streaming.");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("message")]
|
||||
[EnableRateLimiting("fixed")]
|
||||
public async Task<IActionResult> PostMessage([FromBody] ChatRequest request)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(request?.Message))
|
||||
{
|
||||
return BadRequest(new ChatResponse { Reply = "No he recibido ningún mensaje." });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string userMessage = request.Message;
|
||||
string lowerUserMessage = userMessage.ToLowerInvariant();
|
||||
string context;
|
||||
string promptInstructions;
|
||||
|
||||
// 1. Obtenemos el conocimiento desde nuestro nuevo método de caché
|
||||
var knowledgeBase = await GetKnowledgeAsync();
|
||||
string? dbContext = GetContextFromDb(lowerUserMessage, knowledgeBase);
|
||||
|
||||
if (dbContext != null)
|
||||
{
|
||||
context = dbContext;
|
||||
promptInstructions = "Tu tarea es responder la 'PREGUNTA DEL USUARIO' basándote ESTRICTA Y ÚNICAMENTE en el 'CONTEXTO' que contiene información específica (contacto, suscripciones, etc.). No hables de noticias.";
|
||||
}
|
||||
// 2. Si no encontramos nada en la base de conocimiento, buscamos en las noticias.
|
||||
else
|
||||
{
|
||||
context = await GetWebsiteNewsAsync(_siteUrl, 15);
|
||||
promptInstructions = "Tu tarea es responder la 'PREGUNTA DEL USUARIO' basándote ESTRICTA Y ÚNICAMENTE en el 'CONTEXTO DEL SITIO WEB' que contiene una lista de noticias. Si el usuario pide la URL de una noticia, DEBES proporcionarla en formato Markdown: '[texto del enlace](URL)'.";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(context))
|
||||
{
|
||||
return StatusCode(500, new ChatResponse { Reply = "No pude obtener información para responder a tu pregunta." });
|
||||
}
|
||||
|
||||
var promptBuilder = new StringBuilder();
|
||||
promptBuilder.AppendLine("INSTRUCCIONES:");
|
||||
promptBuilder.AppendLine("Eres DiaBot, el asistente virtual del periódico El Día. Tu personalidad es profesional, servicial y concisa. Debes hablar en español 'Rioplatense'.");
|
||||
promptBuilder.AppendLine(promptInstructions); // Instrucciones dinámicas
|
||||
promptBuilder.AppendLine("NUNCA INVENTES información. Si la respuesta no está en el contexto, indica amablemente que no encontraste la información.");
|
||||
promptBuilder.AppendLine("\nCONTEXTO:\n---");
|
||||
promptBuilder.AppendLine(context);
|
||||
promptBuilder.AppendLine("---\n\nPREGUNTA DEL USUARIO:\n---");
|
||||
promptBuilder.AppendLine(userMessage);
|
||||
promptBuilder.AppendLine("---\n\nRESPUESTA:");
|
||||
string finalPrompt = promptBuilder.ToString();
|
||||
|
||||
var requestData = new GeminiRequest { Contents = new[] { new Content { Parts = new[] { new Part { Text = finalPrompt } } } } };
|
||||
var response = await _httpClient.PostAsJsonAsync(_apiUrl, requestData);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
_logger.LogWarning("La API de Gemini devolvió un error. Status: {StatusCode}, Content: {ErrorContent}", response.StatusCode, errorContent);
|
||||
return StatusCode(500, new ChatResponse { Reply = "Hubo un error al comunicarse con el asistente de IA." });
|
||||
}
|
||||
|
||||
var geminiResponse = await response.Content.ReadFromJsonAsync<GeminiResponse>();
|
||||
string botReply = geminiResponse?.Candidates?.FirstOrDefault()?.Content?.Parts?.FirstOrDefault()?.Text?.Trim() ?? "Lo siento, no pude procesar una respuesta.";
|
||||
|
||||
_logger.LogInformation($"[DEBUG] Respuesta de Gemini: '{botReply}'");
|
||||
|
||||
try
|
||||
{
|
||||
// Usamos el IServiceProvider para crear un scope de DbContext temporal y seguro.
|
||||
using (var scope = _serviceProvider.CreateScope())
|
||||
{
|
||||
// Renombramos la variable para evitar conflictos de ámbito.
|
||||
var scopedDbContext = scope.ServiceProvider.GetRequiredService<AppContexto>();
|
||||
|
||||
var logEntry = new ConversacionLog
|
||||
{
|
||||
UsuarioMensaje = userMessage,
|
||||
BotRespuesta = botReply,
|
||||
Fecha = DateTime.UtcNow
|
||||
};
|
||||
|
||||
scopedDbContext.ConversacionLogs.Add(logEntry);
|
||||
await scopedDbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception logEx)
|
||||
{
|
||||
_logger.LogError(logEx, "Error al intentar guardar el log de la conversación en la base de datos.");
|
||||
}
|
||||
return Ok(new ChatResponse { Reply = botReply });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error inesperado al procesar el mensaje del usuario.");
|
||||
return StatusCode(500, new ChatResponse { Reply = "Lo siento, estoy teniendo un problema técnico." });
|
||||
}
|
||||
}
|
||||
|
||||
// Método para buscar contexto en la caché de la DB
|
||||
|
||||
private string? GetContextFromDb(string lowerUserMessage, Dictionary<string, string> knowledgeBase)
|
||||
{
|
||||
// 1. Definimos una lista de palabras comunes a ignorar para hacer la búsqueda más precisa.
|
||||
var stopWords = new HashSet<string>
|
||||
{
|
||||
"el", "la", "los", "las", "un", "una", "unos", "unas", "de", "del", "a", "ante",
|
||||
"con", "contra", "desde", "en", "entre", "hacia", "hasta", "para", "por", "segun",
|
||||
"sin", "sobre", "tras", "y", "o", "que", "cual", "cuales", "como", "cuando", "donde",
|
||||
"quien", "es", "soy", "estoy", "mi", "mis", "quiero", "necesito", "saber", "dime", "dame",
|
||||
"informacion", "acerca", "mas"
|
||||
};
|
||||
|
||||
// 2. Separamos el mensaje del usuario en palabras individuales, eliminando las stop words.
|
||||
var userWords = lowerUserMessage
|
||||
.Split(new[] { ' ', ',', '.', '?', '!', '¿', '¡' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Where(word => !stopWords.Contains(word))
|
||||
.ToHashSet(); // Usamos un HashSet para búsquedas de palabras muy rápidas.
|
||||
|
||||
if (!userWords.Any())
|
||||
{
|
||||
return null; // Si solo había stop words, no buscamos nada.
|
||||
}
|
||||
|
||||
// 3. Iteramos sobre la base de conocimiento para encontrar la mejor coincidencia.
|
||||
foreach (var kvp in knowledgeBase)
|
||||
{
|
||||
// Separamos las claves compuestas ("contacto_telefono" -> ["contacto", "telefono"])
|
||||
var keywords = kvp.Key.Split('_');
|
||||
|
||||
// 4. Comprobamos si ALGUNA de las palabras clave de la BD coincide con ALGUNA de las palabras del usuario.
|
||||
if (keywords.Any(k => userWords.Contains(k)))
|
||||
{
|
||||
_logger.LogInformation("Contexto encontrado por coincidencia de palabra clave. Clave de BD: '{DbKey}', Palabra de usuario encontrada: '{MatchedWord}'",
|
||||
kvp.Key,
|
||||
string.Join(", ", keywords.Where(k => userWords.Contains(k))));
|
||||
|
||||
return kvp.Value; // Devolvemos el valor correspondiente a la primera clave que coincida.
|
||||
}
|
||||
}
|
||||
|
||||
return null; // No se encontró ninguna coincidencia.
|
||||
}
|
||||
|
||||
private async Task<string> GetWebsiteNewsAsync(string url, int cantidad)
|
||||
{
|
||||
try
|
||||
{
|
||||
var web = new HtmlWeb();
|
||||
var doc = await web.LoadFromWebAsync(url);
|
||||
var nodosDeEnlace = doc.DocumentNode.SelectNodes("//article[contains(@class, 'item')]/a[@href]");
|
||||
|
||||
if (nodosDeEnlace == null || !nodosDeEnlace.Any()) return string.Empty;
|
||||
|
||||
var contextBuilder = new StringBuilder();
|
||||
contextBuilder.AppendLine("Lista de noticias principales extraídas de la página:");
|
||||
var urlsProcesadas = new HashSet<string>();
|
||||
int count = 0;
|
||||
|
||||
foreach (var nodoEnlace in nodosDeEnlace)
|
||||
{
|
||||
var urlRelativa = nodoEnlace.GetAttributeValue("href", string.Empty);
|
||||
if (string.IsNullOrEmpty(urlRelativa) || urlRelativa == "#" || urlsProcesadas.Contains(urlRelativa)) continue;
|
||||
|
||||
var nodoTitulo = nodoEnlace.SelectSingleNode(".//h1 | .//h2");
|
||||
if (nodoTitulo != null)
|
||||
{
|
||||
var textoLimpio = CleanTitleText(nodoTitulo.InnerText);
|
||||
var urlCompleta = urlRelativa.StartsWith("/") ? new Uri(new Uri(url), urlRelativa).ToString() : urlRelativa;
|
||||
contextBuilder.AppendLine($"- Título: \"{textoLimpio}\", URL: {urlCompleta}");
|
||||
urlsProcesadas.Add(urlRelativa);
|
||||
count++;
|
||||
}
|
||||
if (count >= cantidad) break;
|
||||
}
|
||||
return contextBuilder.ToString();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "No se pudo descargar o procesar la URL {Url}", url);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private string CleanTitleText(string texto)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(texto)) return string.Empty;
|
||||
var textoDecodificado = WebUtility.HtmlDecode(texto).Trim();
|
||||
foreach (var prefijo in PrefijosAQuitar)
|
||||
{
|
||||
if (textoDecodificado.StartsWith(prefijo, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
textoDecodificado = textoDecodificado.Substring(prefijo.Length).Trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return textoDecodificado;
|
||||
}
|
||||
private async Task<Dictionary<string, string>> GetKnowledgeAsync()
|
||||
{
|
||||
// Intenta obtener el diccionario de la caché.
|
||||
// Si no existe, el segundo argumento (la función factory) se ejecutará.
|
||||
return await _cache.GetOrCreateAsync(_knowledgeCacheKey, async entry =>
|
||||
{
|
||||
_logger.LogInformation("La caché de conocimiento no existe o ha expirado. Recargando desde la base de datos...");
|
||||
|
||||
// Establecemos un tiempo de expiración para la caché.
|
||||
// Después de 5 minutos, la caché se considerará inválida y se recargará en la siguiente petición.
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
|
||||
|
||||
// Usamos IServiceProvider para crear un 'scope' de servicios temporal.
|
||||
// Esto es necesario porque el DbContext tiene un tiempo de vida por petición (scoped),
|
||||
// y esta función factory podría ejecutarse fuera de ese contexto.
|
||||
using (var scope = _serviceProvider.CreateScope())
|
||||
{
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<AppContexto>();
|
||||
var knowledge = await dbContext.ContextoItems
|
||||
.AsNoTracking() // Mejora el rendimiento para consultas de solo lectura
|
||||
.ToDictionaryAsync(item => item.Clave, item => item.Valor);
|
||||
|
||||
_logger.LogInformation($"Caché actualizada con {knowledge.Count} items.");
|
||||
return knowledge;
|
||||
}
|
||||
}) ?? new Dictionary<string, string>(); // Si todo falla, devuelve un diccionario vacío.
|
||||
}
|
||||
}
|
||||
}
|
||||
13
ChatbotApi/Data/AppContexto.cs
Normal file
13
ChatbotApi/Data/AppContexto.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
// ChatbotApi/Data/Models/AppContexto.cs
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
|
||||
namespace ChatbotApi.Data.Models
|
||||
{
|
||||
public class AppContexto : IdentityDbContext
|
||||
{
|
||||
public AppContexto(DbContextOptions<AppContexto> options) : base(options) { }
|
||||
|
||||
public DbSet<ContextoItem> ContextoItems { get; set; } = null!;
|
||||
public DbSet<ConversacionLog> ConversacionLogs { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
25
ChatbotApi/Data/Models/ContextoItem.cs
Normal file
25
ChatbotApi/Data/Models/ContextoItem.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
// Data/Models/ContextoItem.cs
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ChatbotApi.Data.Models
|
||||
{
|
||||
public class ContextoItem
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(100)]
|
||||
public string Clave { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[MaxLength(2000)]
|
||||
public string Valor { get; set; } = null!;
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? Descripcion { get; set; }
|
||||
|
||||
public DateTime FechaActualizacion { get; set; }
|
||||
}
|
||||
}
|
||||
18
ChatbotApi/Data/Models/ConversacionLog.cs
Normal file
18
ChatbotApi/Data/Models/ConversacionLog.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace ChatbotApi.Data.Models
|
||||
{
|
||||
public class ConversacionLog
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string UsuarioMensaje { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
public string BotRespuesta { get; set; } = null!;
|
||||
|
||||
public DateTime Fecha { get; set; }
|
||||
}
|
||||
}
|
||||
58
ChatbotApi/Migrations/20251117132337_InitialCreate.Designer.cs
generated
Normal file
58
ChatbotApi/Migrations/20251117132337_InitialCreate.Designer.cs
generated
Normal file
@@ -0,0 +1,58 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using ChatbotApi.Data.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ChatbotApi.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppContexto))]
|
||||
[Migration("20251117132337_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("ChatbotApi.Data.Models.ContextoItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Clave")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Descripcion")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("FechaActualizacion")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Valor")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ContextoItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
38
ChatbotApi/Migrations/20251117132337_InitialCreate.cs
Normal file
38
ChatbotApi/Migrations/20251117132337_InitialCreate.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ChatbotApi.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ContextoItems",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
Clave = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
|
||||
Valor = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Descripcion = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
FechaActualizacion = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ContextoItems", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ContextoItems");
|
||||
}
|
||||
}
|
||||
}
|
||||
60
ChatbotApi/Migrations/20251117161250_AddValidationLimitsToContextoItem.Designer.cs
generated
Normal file
60
ChatbotApi/Migrations/20251117161250_AddValidationLimitsToContextoItem.Designer.cs
generated
Normal file
@@ -0,0 +1,60 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using ChatbotApi.Data.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ChatbotApi.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppContexto))]
|
||||
[Migration("20251117161250_AddValidationLimitsToContextoItem")]
|
||||
partial class AddValidationLimitsToContextoItem
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("ChatbotApi.Data.Models.ContextoItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Clave")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Descripcion")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime>("FechaActualizacion")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Valor")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ContextoItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ChatbotApi.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddValidationLimitsToContextoItem : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Valor",
|
||||
table: "ContextoItems",
|
||||
type: "nvarchar(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(max)");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Descripcion",
|
||||
table: "ContextoItems",
|
||||
type: "nvarchar(500)",
|
||||
maxLength: 500,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(max)",
|
||||
oldNullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Valor",
|
||||
table: "ContextoItems",
|
||||
type: "nvarchar(max)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Descripcion",
|
||||
table: "ContextoItems",
|
||||
type: "nvarchar(max)",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "nvarchar(500)",
|
||||
oldMaxLength: 500,
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
309
ChatbotApi/Migrations/20251117173123_AddIdentityTables.Designer.cs
generated
Normal file
309
ChatbotApi/Migrations/20251117173123_AddIdentityTables.Designer.cs
generated
Normal file
@@ -0,0 +1,309 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using ChatbotApi.Data.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ChatbotApi.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppContexto))]
|
||||
[Migration("20251117173123_AddIdentityTables")]
|
||||
partial class AddIdentityTables
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("ChatbotApi.Data.Models.ContextoItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Clave")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Descripcion")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime>("FechaActualizacion")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Valor")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ContextoItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex")
|
||||
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
224
ChatbotApi/Migrations/20251117173123_AddIdentityTables.cs
Normal file
224
ChatbotApi/Migrations/20251117173123_AddIdentityTables.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ChatbotApi.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddIdentityTables : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
NormalizedName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUsers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
UserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
NormalizedUserName = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
Email = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
NormalizedEmail = table.Column<string>(type: "nvarchar(256)", maxLength: 256, nullable: true),
|
||||
EmailConfirmed = table.Column<bool>(type: "bit", nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
SecurityStamp = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
PhoneNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
PhoneNumberConfirmed = table.Column<bool>(type: "bit", nullable: false),
|
||||
TwoFactorEnabled = table.Column<bool>(type: "bit", nullable: false),
|
||||
LockoutEnd = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
|
||||
LockoutEnabled = table.Column<bool>(type: "bit", nullable: false),
|
||||
AccessFailedCount = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoleClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
RoleId = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "AspNetRoles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserLogins",
|
||||
columns: table => new
|
||||
{
|
||||
LoginProvider = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
ProviderKey = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
ProviderDisplayName = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserRoles",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
RoleId = table.Column<string>(type: "nvarchar(450)", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "AspNetRoles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetUserTokens",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
LoginProvider = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
Name = table.Column<string>(type: "nvarchar(450)", nullable: false),
|
||||
Value = table.Column<string>(type: "nvarchar(max)", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
|
||||
table.ForeignKey(
|
||||
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetRoleClaims_RoleId",
|
||||
table: "AspNetRoleClaims",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "RoleNameIndex",
|
||||
table: "AspNetRoles",
|
||||
column: "NormalizedName",
|
||||
unique: true,
|
||||
filter: "[NormalizedName] IS NOT NULL");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserClaims_UserId",
|
||||
table: "AspNetUserClaims",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserLogins_UserId",
|
||||
table: "AspNetUserLogins",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AspNetUserRoles_RoleId",
|
||||
table: "AspNetUserRoles",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "EmailIndex",
|
||||
table: "AspNetUsers",
|
||||
column: "NormalizedEmail");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UserNameIndex",
|
||||
table: "AspNetUsers",
|
||||
column: "NormalizedUserName",
|
||||
unique: true,
|
||||
filter: "[NormalizedUserName] IS NOT NULL");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoleClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserLogins");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserRoles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUserTokens");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetRoles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "AspNetUsers");
|
||||
}
|
||||
}
|
||||
}
|
||||
333
ChatbotApi/Migrations/20251118140720_AddConversacionLogTable.Designer.cs
generated
Normal file
333
ChatbotApi/Migrations/20251118140720_AddConversacionLogTable.Designer.cs
generated
Normal file
@@ -0,0 +1,333 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using ChatbotApi.Data.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ChatbotApi.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppContexto))]
|
||||
[Migration("20251118140720_AddConversacionLogTable")]
|
||||
partial class AddConversacionLogTable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("ChatbotApi.Data.Models.ContextoItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Clave")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Descripcion")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime>("FechaActualizacion")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Valor")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ContextoItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ChatbotApi.Data.Models.ConversacionLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("BotRespuesta")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("Fecha")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("UsuarioMensaje")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ConversacionLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex")
|
||||
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ChatbotApi.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddConversacionLogTable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ConversacionLogs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
UsuarioMensaje = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
BotRespuesta = table.Column<string>(type: "nvarchar(max)", nullable: false),
|
||||
Fecha = table.Column<DateTime>(type: "datetime2", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ConversacionLogs", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ConversacionLogs");
|
||||
}
|
||||
}
|
||||
}
|
||||
330
ChatbotApi/Migrations/AppContextoModelSnapshot.cs
Normal file
330
ChatbotApi/Migrations/AppContextoModelSnapshot.cs
Normal file
@@ -0,0 +1,330 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using ChatbotApi.Data.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ChatbotApi.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppContexto))]
|
||||
partial class AppContextoModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "10.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("ChatbotApi.Data.Models.ContextoItem", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("Clave")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Descripcion")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<DateTime>("FechaActualizacion")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Valor")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ContextoItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ChatbotApi.Data.Models.ConversacionLog", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("BotRespuesta")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime>("Fecha")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("UsuarioMensaje")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ConversacionLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex")
|
||||
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
8
ChatbotApi/Models/ChatRequest.cs
Normal file
8
ChatbotApi/Models/ChatRequest.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class ChatRequest
|
||||
{
|
||||
[Required]
|
||||
[MaxLength(200)]
|
||||
public required string Message { get; set; }
|
||||
}
|
||||
7
ChatbotApi/Models/ChatResponse.cs
Normal file
7
ChatbotApi/Models/ChatResponse.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace ChatbotApi.Models
|
||||
{
|
||||
public class ChatResponse
|
||||
{
|
||||
public required string Reply { get; set; }
|
||||
}
|
||||
}
|
||||
144
ChatbotApi/Program.cs
Normal file
144
ChatbotApi/Program.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using ChatbotApi.Data.Models;
|
||||
using DotNetEnv;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.Text;
|
||||
using System.Threading.RateLimiting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
// Cargar variables de entorno desde el archivo .env
|
||||
Env.Load();
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Definimos una política de CORS para permitir solicitudes desde nuestro frontend de Vite
|
||||
var myAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy(name: myAllowSpecificOrigins,
|
||||
policy =>
|
||||
{
|
||||
policy.WithOrigins("http://localhost:5173", "http://localhost:5174") // La URL de tu frontend
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 1. Añadimos el DbContext para Entity Framework
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||
builder.Services.AddDbContext<AppContexto>(options =>
|
||||
options.UseSqlServer(connectionString));
|
||||
|
||||
// 2. Añadimos ASP.NET Core Identity
|
||||
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
|
||||
{
|
||||
options.Password.RequireDigit = true;
|
||||
options.Password.RequiredLength = 8;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.Password.RequireUppercase = true;
|
||||
options.Password.RequireLowercase = false;
|
||||
})
|
||||
.AddEntityFrameworkStores<AppContexto>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
builder.Services.AddMemoryCache();
|
||||
|
||||
// =========== INICIO DE CONFIGURACIÓN JWT ===========
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
||||
ValidAudience = builder.Configuration["Jwt:Audience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(
|
||||
builder.Configuration["Jwt:Key"] ?? throw new InvalidOperationException("La clave JWT no está configurada.")
|
||||
))
|
||||
};
|
||||
});
|
||||
|
||||
// RATE LIMITING
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
options.AddFixedWindowLimiter(policyName: "fixed", limiterOptions =>
|
||||
{
|
||||
limiterOptions.PermitLimit = 10; // Permitir 10 peticiones...
|
||||
limiterOptions.Window = TimeSpan.FromMinutes(1); // ...por cada minuto.
|
||||
limiterOptions.QueueLimit = 2; // Poner en cola hasta 2 peticiones si se excede el límite brevemente.
|
||||
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
|
||||
});
|
||||
|
||||
// Esta función se ejecuta cuando una petición es rechazada
|
||||
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||
});
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
|
||||
builder.Services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Chatbot API", Version = "v1" });
|
||||
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Description = "Por favor, introduce 'Bearer' seguido de un espacio y el token JWT",
|
||||
Name = "Authorization",
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
Scheme = "Bearer"
|
||||
});
|
||||
|
||||
options.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
new string[] {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
// =========== CONFIGURACIÓN Y USO DEL MIDDLEWARE DE ENCABEZADOS DE SEGURIDAD ===========
|
||||
app.UseSecurityHeaders(policy =>
|
||||
{
|
||||
policy.AddDefaultSecurityHeaders(); // Añade los encabezados por defecto
|
||||
policy.AddContentSecurityPolicy(builder =>
|
||||
{
|
||||
builder.AddDefaultSrc().Self();
|
||||
|
||||
// Permisos necesarios para Swagger UI
|
||||
builder.AddScriptSrc().Self().UnsafeInline();
|
||||
builder.AddStyleSrc().Self().UnsafeInline();
|
||||
});
|
||||
});
|
||||
app.UseCors(myAllowSpecificOrigins);
|
||||
app.UseRateLimiter();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.Run();
|
||||
23
ChatbotApi/Properties/launchSettings.json
Normal file
23
ChatbotApi/Properties/launchSettings.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5126",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7140;http://localhost:5126",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
ChatbotApi/appsettings.json
Normal file
20
ChatbotApi/appsettings.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Gemini": {
|
||||
"GeminiApiUrl": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-lite:streamGenerateContent?alt=sse&key=",
|
||||
"GeminiApiKey": ""
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
},
|
||||
"Jwt": {
|
||||
"Key": "",
|
||||
"Issuer": "",
|
||||
"Audience": ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user