Fix Mapa Municipios - Limpieza y Optimización de Workers
This commit is contained in:
@@ -324,7 +324,6 @@ export const getHomeResumenNacional = async (eleccionId: number, categoriaId: nu
|
|||||||
eleccionId: eleccionId.toString(),
|
eleccionId: eleccionId.toString(),
|
||||||
categoriaId: categoriaId.toString(),
|
categoriaId: categoriaId.toString(),
|
||||||
});
|
});
|
||||||
// Apunta al nuevo endpoint que creamos
|
|
||||||
const url = `/elecciones/home-resumen-nacional?${queryParams.toString()}`;
|
const url = `/elecciones/home-resumen-nacional?${queryParams.toString()}`;
|
||||||
const { data } = await apiClient.get(url);
|
const { data } = await apiClient.get(url);
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -1011,24 +1011,29 @@ public class ResultadosController : ControllerBase
|
|||||||
[HttpGet("panel/{ambitoId?}")]
|
[HttpGet("panel/{ambitoId?}")]
|
||||||
public async Task<IActionResult> GetPanelElectoral(int eleccionId, string? ambitoId, [FromQuery] int categoriaId)
|
public async Task<IActionResult> GetPanelElectoral(int eleccionId, string? ambitoId, [FromQuery] int categoriaId)
|
||||||
{
|
{
|
||||||
|
// Caso 1: Sin ID -> Vista Nacional
|
||||||
if (string.IsNullOrEmpty(ambitoId))
|
if (string.IsNullOrEmpty(ambitoId))
|
||||||
{
|
{
|
||||||
// CASO 1: No hay ID -> Vista Nacional
|
|
||||||
return await GetPanelNacional(eleccionId, categoriaId);
|
return await GetPanelNacional(eleccionId, categoriaId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASO 2: El ID es un número (y no un string corto como "02") -> Vista Municipal
|
// Intenta interpretar el ambitoId como un ID numérico de municipio.
|
||||||
// La condición clave es que los IDs de distrito son cortos. Los IDs de BD son más largos.
|
if (int.TryParse(ambitoId, out int idNumerico))
|
||||||
// O simplemente, un ID de distrito nunca será un ID de municipio.
|
|
||||||
if (int.TryParse(ambitoId, out int idNumerico) && ambitoId.Length > 2)
|
|
||||||
{
|
{
|
||||||
return await GetPanelMunicipal(eleccionId, idNumerico, categoriaId);
|
// Consulta a la BD para verificar si existe un municipio (NivelId=30) con este ID.
|
||||||
}
|
// Esta consulta es muy rápida y resuelve la ambigüedad.
|
||||||
else
|
bool esMunicipio = await _dbContext.AmbitosGeograficos
|
||||||
{
|
.AnyAsync(a => a.Id == idNumerico && a.NivelId == 30);
|
||||||
// CASO 3: El ID es un string corto como "02" o "06" -> Vista Provincial
|
|
||||||
return await GetPanelProvincial(eleccionId, ambitoId, categoriaId);
|
if (esMunicipio)
|
||||||
|
{
|
||||||
|
// Si es un municipio válido, llama al método municipal.
|
||||||
|
return await GetPanelMunicipal(eleccionId, idNumerico, categoriaId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// Si no es un ID de municipio válido (o no es un número),
|
||||||
|
// se asume que es un distritoId provincial y se llama al método provincial.
|
||||||
|
return await GetPanelProvincial(eleccionId, ambitoId, categoriaId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IActionResult> GetPanelMunicipal(int eleccionId, int ambitoId, int categoriaId)
|
private async Task<IActionResult> GetPanelMunicipal(int eleccionId, int ambitoId, int categoriaId)
|
||||||
@@ -1179,7 +1184,6 @@ public class ResultadosController : ControllerBase
|
|||||||
var todosLosOverrides = await _dbContext.CandidatosOverrides.AsNoTracking().Where(c => c.EleccionId == eleccionId || c.EleccionId == 0).ToListAsync();
|
var todosLosOverrides = await _dbContext.CandidatosOverrides.AsNoTracking().Where(c => c.EleccionId == eleccionId || c.EleccionId == 0).ToListAsync();
|
||||||
var todosLosLogos = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().Where(l => l.EleccionId == eleccionId || l.EleccionId == 0).ToListAsync();
|
var todosLosLogos = await _dbContext.LogosAgrupacionesCategorias.AsNoTracking().Where(l => l.EleccionId == eleccionId || l.EleccionId == 0).ToListAsync();
|
||||||
|
|
||||||
|
|
||||||
var resultadosAgregados = await _dbContext.ResultadosVotos.AsNoTracking()
|
var resultadosAgregados = await _dbContext.ResultadosVotos.AsNoTracking()
|
||||||
.Where(r => r.EleccionId == eleccionId && r.CategoriaId == categoriaId)
|
.Where(r => r.EleccionId == eleccionId && r.CategoriaId == categoriaId)
|
||||||
.GroupBy(r => r.AgrupacionPolitica)
|
.GroupBy(r => r.AgrupacionPolitica)
|
||||||
@@ -1202,7 +1206,7 @@ public class ResultadosController : ControllerBase
|
|||||||
Color = g.Agrupacion.Color,
|
Color = g.Agrupacion.Color,
|
||||||
Votos = g.TotalVotos,
|
Votos = g.TotalVotos,
|
||||||
Porcentaje = totalVotosNacional > 0 ? (g.TotalVotos / totalVotosNacional) * 100 : 0,
|
Porcentaje = totalVotosNacional > 0 ? (g.TotalVotos / totalVotosNacional) * 100 : 0,
|
||||||
NombreCandidato = null,
|
NombreCandidato = candidatoMatch?.NombreCandidato, // Mantenemos la lógica de candidato por si se usa a futuro
|
||||||
LogoUrl = logoMatch?.LogoUrl
|
LogoUrl = logoMatch?.LogoUrl
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -1210,7 +1214,7 @@ public class ResultadosController : ControllerBase
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var estadoRecuento = await _dbContext.EstadosRecuentosGenerales.AsNoTracking()
|
var estadoRecuento = await _dbContext.EstadosRecuentosGenerales.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(e => e.EleccionId == eleccionId && e.CategoriaId == categoriaId && e.AmbitoGeografico.NivelId == 0);
|
.FirstOrDefaultAsync(e => e.EleccionId == eleccionId && e.CategoriaId == categoriaId && (e.AmbitoGeografico.NivelId == 1 || e.AmbitoGeografico.NivelId == 0));
|
||||||
|
|
||||||
var respuesta = new PanelElectoralDto
|
var respuesta = new PanelElectoralDto
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Api")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+ae846f2d4834f3cd03079e91a8225e9f74cd073b")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+2b7fb927e2f0d9ff06dffa820bc9809d6e138b01")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
|
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Api")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Api")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","UIntj4QoiyGr7bnJN8KK5PGrhQd89m\u002BLfh4T8VKPxAk=","J\u002Bfv/j3QyIW9bxolc46wDka8641F622/QgIllt0Re80=","Y/o0rakw9VYzEfz9M659qW77P9kvz\u002B2gTe1Lv3zgUDE=","/UJFXzVO5Y6TKX\u002BD5m/A1RI/tbK98BAoQFTS7wCUAJI=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","YO7Xv4ZJWkAJrh9hWzESGMBLWiXQVbzGiJis/zKzy9k="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"b5T/+ta4fUd8qpIzUTm3KyEwAYYUsU5ASo+CSFM3ByE=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","UIntj4QoiyGr7bnJN8KK5PGrhQd89m\u002BLfh4T8VKPxAk=","J\u002Bfv/j3QyIW9bxolc46wDka8641F622/QgIllt0Re80=","Y/o0rakw9VYzEfz9M659qW77P9kvz\u002B2gTe1Lv3zgUDE=","8QWUReqP8upfOnmA5lMNgBxAfYJ1z3zv/WYBUXBEiog=","TKLv8wZzSutso/ZGIrOh0nruXDmGN\u002BWPGW7DXc2UOM8=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","9jkfcKbUkkVKrX75MjakO0Et3SVlP1YOXw\u002Bfn\u002B5Hu3g="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -1 +1 @@
|
|||||||
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","5WogJu\u002BUPlF\u002BE5mq/ILtDXpVwqwmhHtsEB13nmT5JJk=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","UIntj4QoiyGr7bnJN8KK5PGrhQd89m\u002BLfh4T8VKPxAk=","J\u002Bfv/j3QyIW9bxolc46wDka8641F622/QgIllt0Re80=","Y/o0rakw9VYzEfz9M659qW77P9kvz\u002B2gTe1Lv3zgUDE=","/UJFXzVO5Y6TKX\u002BD5m/A1RI/tbK98BAoQFTS7wCUAJI=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","YO7Xv4ZJWkAJrh9hWzESGMBLWiXQVbzGiJis/zKzy9k="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
{"GlobalPropertiesHash":"tJTBjV/i0Ihkc6XuOu69wxL8PBac9c9Kak6srMso4pU=","FingerprintPatternsHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["YB39loxHH43S4MF8aTOiogcIbBAIq5Qj3dlJkIfYVxI=","t631p0kaOa0gMRIcaPzz1ZVPZ1kuq4pq4kYPWQgoPcM=","PA/Beu9jJpOBY5r5Y1CiSyqrARA2j7LHeWYUnEZpQO8=","E2ODTAlJxzsXY1iP1eB/02NIUK\u002BnQveGlWAOHY1cpgA=","dcHQRkttjMjo2dvhL7hA9t4Pg\u002B7OnjZpkFmakT4QR9U=","Of8nTYw5l\u002BgiAJo7z6XYIntG2tUtCFcILzHbTiiXn\u002Bw=","PDy\u002BTiayvNAoXXBEgwC/kCojpgOOMI6RQOIoSXs3LJc=","ePXrkee3hv3wHUr8S7aYmRVvXUTxQf76zApKGv3/l3o=","DXx5dQywLo3UsY2zQaUG\u002BbW4ObiYbybxPBWxeJD2bhk=","muVh5sjH3sgdvuz4TbuTwTggX1uDnsWXgoosMKST/r4=","nrP5gSIA5vzgp8v12CAOr943QYLxU4Til6oiCcWSNI8=","yMd45U9BK07I3b3fBQ627PWTYyZ2ZjrmFc5VD\u002BQVx1Q=","xKskvcoJU0RVRN1a5dRqKRM7IP5vmmbraUaPFYjhnCc=","p7BjQw7aSZjfOCqmKm7/kPO9qegEQZBfirMjlOx/I1I=","MI0hVVLYavEhzHq/Z1UbajfrxanA1aET19aOH8G2ImI=","2dY8CqW9fAY8yN0foa\u002BZp2gc0RfPoPmB/tKSj1QoTw0=","79rfGLH4UjfTPvc//\u002BZjnBqdz585pUtYZ0/hwE2iEic=","PUqgvMdfTQkF5lpBVtHv2teQLV5WaEH0xMKTmINe2YQ=","\u002BFI0b4ppdxel/pby/y/xKImHrtdxo2g83OhskdREyIg=","jEESu6\u002BhbDvNMjLt/6OufuK\u002B9cHmzx\u002BTCIn4fWa9nSc=","UaCPJEvR4nVxxGCB5CUnRlJiw4drDW3Q3Nss\u002Bya2cv4=","ZqF13CT3rok/Gzl\u002BMsw3q9X1nf65bwEVD670efE3k\u002Bk=","gH3W7phPzBCY1DAVn4YnP4SA8Uaq73TpctS0yFSvzNM=","u5F4J4\u002BLHUIOCz5ze5NSF42mDeAaAfi\u002BKN3Ay3rKLY8=","GeUUID0ymF5rrBWdX7YHzWA5GiGkNWCNUog4sp4xL3c=","3BxX4I0JXoDqmE8m0BrRZhixBRlHEueS3jAlmUXE/I8=","IlET7uqumshgFxIEvfKRskON\u002BeAKZ7OfD/kCeAwn0PM=","NN2rS\u002B89ZAITWlNODPcF/lHIh3ZNmAHvUX4EjqSkX4s=","OE89N/FsYhRU1Dy5Ne83ehzSwlNc/RcxHrJpHxPHfqY=","QI7IL4TkYEqfUiIEXQiVCaZx4vrM9/wZlvOrhnUd4jQ=","UIntj4QoiyGr7bnJN8KK5PGrhQd89m\u002BLfh4T8VKPxAk=","J\u002Bfv/j3QyIW9bxolc46wDka8641F622/QgIllt0Re80=","Y/o0rakw9VYzEfz9M659qW77P9kvz\u002B2gTe1Lv3zgUDE=","8QWUReqP8upfOnmA5lMNgBxAfYJ1z3zv/WYBUXBEiog=","TKLv8wZzSutso/ZGIrOh0nruXDmGN\u002BWPGW7DXc2UOM8=","BY4GeeFiQbYpWuSzb2XIY4JatmLNOZ6dhKs4ZT92nsM=","P8JRhYPpULTLMAydvl3Ky\u002B92/tYDIjui0l66En4aXuQ=","9jkfcKbUkkVKrX75MjakO0Et3SVlP1YOXw\u002Bfn\u002B5Hu3g="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Core")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4bc257df43f5813ec432b89b47fa078c1cfa1fc8")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+2b7fb927e2f0d9ff06dffa820bc9809d6e138b01")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")]
|
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Core")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Core")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Database")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4bc257df43f5813ec432b89b47fa078c1cfa1fc8")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+2b7fb927e2f0d9ff06dffa820bc9809d6e138b01")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")]
|
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Database")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Database")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("Elecciones.Infrastructure")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4bc257df43f5813ec432b89b47fa078c1cfa1fc8")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+2b7fb927e2f0d9ff06dffa820bc9809d6e138b01")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")]
|
[assembly: System.Reflection.AssemblyProductAttribute("Elecciones.Infrastructure")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("Elecciones.Infrastructure")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//Elecciones.Worker/CriticalDataWorker.cs
|
// src/Elecciones.Worker/CriticalDataWorker.cs
|
||||||
|
|
||||||
using Elecciones.Database;
|
using Elecciones.Database;
|
||||||
using Elecciones.Database.Entities;
|
using Elecciones.Database.Entities;
|
||||||
using Elecciones.Infrastructure.Services;
|
using Elecciones.Infrastructure.Services;
|
||||||
@@ -15,6 +16,7 @@ public class CriticalDataWorker : BackgroundService
|
|||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IElectoralApiService _apiService;
|
private readonly IElectoralApiService _apiService;
|
||||||
private readonly WorkerConfigService _configService;
|
private readonly WorkerConfigService _configService;
|
||||||
|
private static readonly ConcurrentDictionary<string, DateTime> _lastUpdateTracker = new();
|
||||||
|
|
||||||
public CriticalDataWorker(
|
public CriticalDataWorker(
|
||||||
ILogger<CriticalDataWorker> logger,
|
ILogger<CriticalDataWorker> logger,
|
||||||
@@ -32,20 +34,18 @@ public class CriticalDataWorker : BackgroundService
|
|||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Worker de Datos Críticos iniciado.");
|
_logger.LogInformation("Worker de Datos Críticos (Sondeo Inteligente) iniciado.");
|
||||||
|
await Task.Delay(TimeSpan.FromMinutes(2), stoppingToken);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromMinutes(2), stoppingToken);
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException) { return; }
|
|
||||||
|
|
||||||
int cicloContador = 0;
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
var cicloInicio = DateTime.UtcNow;
|
var settings = await _configService.GetSettingsAsync();
|
||||||
cicloContador++;
|
if (!settings.ResultadosActivado)
|
||||||
_logger.LogInformation("--- Iniciando Ciclo de Datos Críticos #{ciclo} ---", cicloContador);
|
{
|
||||||
|
_logger.LogInformation("El sondeo de resultados está desactivado. Esperando 1 minuto.");
|
||||||
|
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var authToken = await _tokenService.GetValidAuthTokenAsync(stoppingToken);
|
var authToken = await _tokenService.GetValidAuthTokenAsync(stoppingToken);
|
||||||
if (string.IsNullOrEmpty(authToken))
|
if (string.IsNullOrEmpty(authToken))
|
||||||
@@ -55,336 +55,160 @@ public class CriticalDataWorker : BackgroundService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings = await _configService.GetSettingsAsync();
|
_logger.LogInformation("--- Iniciando Ciclo de Sondeo Inteligente ---");
|
||||||
|
await SondearEstadosYDispararActualizaciones(authToken, stoppingToken);
|
||||||
if (settings.Prioridad == "Resultados" && settings.ResultadosActivado)
|
_logger.LogInformation("--- Ciclo de Sondeo Inteligente completado. ---");
|
||||||
{
|
|
||||||
_logger.LogInformation("Ejecutando tareas de Resultados en alta prioridad.");
|
|
||||||
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
|
||||||
await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
|
|
||||||
await SondearResumenProvincialAsync(authToken, stoppingToken);
|
|
||||||
}
|
|
||||||
else if (settings.Prioridad == "Telegramas" && settings.BajasActivado)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Ejecutando tareas de Baja Prioridad en alta prioridad.");
|
|
||||||
await SondearProyeccionBancasAsync(authToken, stoppingToken);
|
|
||||||
//await SondearNuevosTelegramasAsync(authToken, stoppingToken);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Worker de alta prioridad inactivo según la configuración.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var cicloFin = DateTime.UtcNow;
|
|
||||||
var duracionCiclo = cicloFin - cicloInicio;
|
|
||||||
_logger.LogInformation("--- Ciclo de Datos Críticos #{ciclo} completado en {duration:N2} segundos. ---", cicloContador, duracionCiclo.TotalSeconds);
|
|
||||||
|
|
||||||
var tiempoDeEspera = TimeSpan.FromSeconds(30) - duracionCiclo;
|
|
||||||
if (tiempoDeEspera < TimeSpan.Zero) tiempoDeEspera = TimeSpan.Zero;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(tiempoDeEspera, stoppingToken);
|
await Task.Delay(TimeSpan.FromSeconds(45), stoppingToken);
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException) { break; }
|
catch (TaskCanceledException) { break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private async Task SondearEstadosYDispararActualizaciones(string authToken, CancellationToken stoppingToken)
|
||||||
/// Sondea la proyección de bancas a nivel Provincial y por Sección Electoral.
|
|
||||||
/// Esta versión es completamente robusta: maneja respuestas de API vacías o con fechas mal formadas,
|
|
||||||
/// guarda la CategoriaId y usa una transacción atómica para la escritura en base de datos.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="authToken">El token de autenticación válido para la sesión.</param>
|
|
||||||
/// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
|
|
||||||
private async Task SondearProyeccionBancasAsync(string authToken, CancellationToken stoppingToken)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var scope = _serviceProvider.CreateScope();
|
using var scope = _serviceProvider.CreateScope();
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
||||||
|
|
||||||
var categoriasDeBancas = await dbContext.CategoriasElectorales
|
var ambitosProvinciales = await dbContext.AmbitosGeograficos.AsNoTracking().Where(a => a.NivelId == 10 && a.DistritoId != null).ToListAsync(stoppingToken);
|
||||||
.AsNoTracking()
|
var ambitoNacional = await dbContext.AmbitosGeograficos.AsNoTracking().FirstOrDefaultAsync(a => a.NivelId == 1 || a.NivelId == 0, stoppingToken);
|
||||||
.Where(c => c.Nombre.Contains("SENADORES") || c.Nombre.Contains("DIPUTADOS"))
|
var categorias = await dbContext.CategoriasElectorales.AsNoTracking().ToListAsync(stoppingToken);
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
var ambitosASondear = await dbContext.AmbitosGeograficos
|
if (!ambitosProvinciales.Any() || !categorias.Any()) return;
|
||||||
.AsNoTracking()
|
|
||||||
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
if (!categoriasDeBancas.Any() || !ambitosASondear.Any())
|
// --- PASO 1: SONDEO PROVINCIAL ---
|
||||||
{
|
foreach (var provincia in ambitosProvinciales)
|
||||||
_logger.LogWarning("No se encontraron categorías de bancas o ámbitos provinciales en la BD. Omitiendo sondeo de bancas.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Iniciando sondeo de Bancas a nivel Provincial para {count} provincias...", ambitosASondear.Count);
|
|
||||||
|
|
||||||
var todasLasProyecciones = new List<ProyeccionBanca>();
|
|
||||||
bool hasReceivedAnyNewData = false;
|
|
||||||
var agrupacionesEnDb = await dbContext.AgrupacionesPoliticas.ToDictionaryAsync(a => a.Id, a => a, stoppingToken);
|
|
||||||
|
|
||||||
foreach (var ambito in ambitosASondear)
|
|
||||||
{
|
{
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
foreach (var categoria in categoriasDeBancas)
|
foreach (var categoria in categorias)
|
||||||
{
|
{
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
|
|
||||||
var repartoBancasDto = await _apiService.GetBancasAsync(authToken, ambito.DistritoId!, ambito.SeccionProvincialId, categoria.Id);
|
var resultadosDto = await _apiService.GetResultadosAsync(authToken, provincia.DistritoId!, null, null, categoria.Id);
|
||||||
|
if (resultadosDto == null) continue;
|
||||||
|
|
||||||
if (repartoBancasDto?.RepartoBancas is { Count: > 0 } bancas)
|
// --- Guardar los datos agregados inmediatamente ---
|
||||||
|
await GuardarDatosAgregadosAsync(dbContext, provincia.Id, categoria.Id, resultadosDto, stoppingToken);
|
||||||
|
|
||||||
|
if (DateTime.TryParse(resultadosDto.FechaTotalizacion, out var fechaApi))
|
||||||
{
|
{
|
||||||
hasReceivedAnyNewData = true;
|
var trackerKey = $"{provincia.Id}-{categoria.Id}";
|
||||||
DateTime fechaTotalizacion;
|
_lastUpdateTracker.TryGetValue(trackerKey, out var ultimaFechaConocida);
|
||||||
if (!DateTime.TryParse(repartoBancasDto.FechaTotalizacion, out var parsedDate))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se pudo parsear FechaTotalizacion ('{dateString}') para bancas. Usando la hora actual.", repartoBancasDto.FechaTotalizacion);
|
|
||||||
fechaTotalizacion = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fechaTotalizacion = parsedDate.ToUniversalTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var banca in bancas)
|
if (fechaApi > ultimaFechaConocida)
|
||||||
{
|
{
|
||||||
if (!agrupacionesEnDb.ContainsKey(banca.IdAgrupacion))
|
_logger.LogInformation("¡Cambio detectado para {Provincia} - {Categoria}! Actualizando detalles en segundo plano...", provincia.Nombre, categoria.Nombre);
|
||||||
{
|
_lastUpdateTracker[trackerKey] = fechaApi;
|
||||||
_logger.LogWarning("Agrupación con ID {AgrupacionId} ('{Nombre}') no encontrada. Creándola desde los datos de bancas.", banca.IdAgrupacion, banca.NombreAgrupacion);
|
_ = Task.Run(() => ActualizarResultadosDeProvincia(authToken, provincia.DistritoId!, categoria.Id), stoppingToken);
|
||||||
var nuevaAgrupacion = new AgrupacionPolitica
|
|
||||||
{
|
|
||||||
Id = banca.IdAgrupacion,
|
|
||||||
Nombre = banca.NombreAgrupacion,
|
|
||||||
IdTelegrama = banca.IdAgrupacionTelegrama ?? string.Empty
|
|
||||||
};
|
|
||||||
await dbContext.AgrupacionesPoliticas.AddAsync(nuevaAgrupacion, stoppingToken);
|
|
||||||
agrupacionesEnDb.Add(nuevaAgrupacion.Id, nuevaAgrupacion);
|
|
||||||
}
|
|
||||||
|
|
||||||
todasLasProyecciones.Add(new ProyeccionBanca
|
|
||||||
{
|
|
||||||
EleccionId = EleccionId,
|
|
||||||
AmbitoGeograficoId = ambito.Id,
|
|
||||||
AgrupacionPoliticaId = banca.IdAgrupacion,
|
|
||||||
NroBancas = banca.NroBancas,
|
|
||||||
CategoriaId = categoria.Id,
|
|
||||||
FechaTotalizacion = fechaTotalizacion
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasReceivedAnyNewData)
|
// --- PASO 2: SONDEO NACIONAL ---
|
||||||
{
|
if (ambitoNacional != null && !stoppingToken.IsCancellationRequested)
|
||||||
_logger.LogInformation("Se recibieron datos válidos de bancas. Procediendo a actualizar la base de datos...");
|
|
||||||
await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
|
|
||||||
await dbContext.SaveChangesAsync(stoppingToken); // Guardar nuevas agrupaciones si las hay
|
|
||||||
await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ProyeccionesBancas WHERE EleccionId = {0}", EleccionId, stoppingToken); // CORRECCIÓN: Borrar solo para la elección actual
|
|
||||||
await dbContext.ProyeccionesBancas.AddRangeAsync(todasLasProyecciones, stoppingToken);
|
|
||||||
await dbContext.SaveChangesAsync(stoppingToken);
|
|
||||||
await transaction.CommitAsync(stoppingToken);
|
|
||||||
_logger.LogInformation("La tabla de proyecciones ha sido actualizada con {count} registros.", todasLasProyecciones.Count);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeo de Bancas completado. No se encontraron datos nuevos, la tabla no fue modificada.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeo de bancas cancelado.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Bancas.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Busca y descarga nuevos telegramas de forma masiva y concurrente.
|
|
||||||
/// Este método crea una lista de todas las combinaciones de Partido/Categoría,
|
|
||||||
/// las consulta a la API con un grado de paralelismo controlado, y cada tarea concurrente
|
|
||||||
/// maneja su propia lógica de descarga y guardado en la base de datos.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="authToken">El token de autenticación válido para la sesión.</param>
|
|
||||||
/// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
|
|
||||||
private async Task SondearNuevosTelegramasAsync(string authToken, CancellationToken stoppingToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogInformation("--- Iniciando sondeo de Nuevos Telegramas (modo de bajo perfil) ---");
|
|
||||||
|
|
||||||
using var scope = _serviceProvider.CreateScope();
|
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
// La obtención de partidos y categorías no cambia
|
|
||||||
var partidos = await dbContext.AmbitosGeograficos.AsNoTracking()
|
|
||||||
.Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null)
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
var categorias = await dbContext.CategoriasElectorales.AsNoTracking().ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
if (!partidos.Any() || !categorias.Any()) return;
|
|
||||||
|
|
||||||
foreach (var partido in partidos)
|
|
||||||
{
|
{
|
||||||
|
_logger.LogInformation("Sondeando totales a nivel Nacional...");
|
||||||
foreach (var categoria in categorias)
|
foreach (var categoria in categorias)
|
||||||
{
|
{
|
||||||
if (stoppingToken.IsCancellationRequested) return;
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
|
var resultadosNacionalDto = await _apiService.GetResultadosAsync(authToken, "", null, null, categoria.Id);
|
||||||
var listaTelegramasApi = await _apiService.GetTelegramasTotalizadosAsync(authToken, partido.DistritoId!, partido.SeccionId!, categoria.Id);
|
if (resultadosNacionalDto != null)
|
||||||
|
|
||||||
if (listaTelegramasApi is { Count: > 0 })
|
|
||||||
{
|
{
|
||||||
// Creamos el DbContext para la operación de guardado
|
await GuardarDatosAgregadosAsync(dbContext, ambitoNacional.Id, categoria.Id, resultadosNacionalDto, stoppingToken);
|
||||||
using var innerScope = _serviceProvider.CreateScope();
|
}
|
||||||
var innerDbContext = innerScope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
var idsYaEnDb = await innerDbContext.Telegramas
|
|
||||||
.Where(t => listaTelegramasApi.Contains(t.Id))
|
|
||||||
.Select(t => t.Id).ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
var nuevosTelegramasIds = listaTelegramasApi.Except(idsYaEnDb).ToList();
|
|
||||||
|
|
||||||
if (nuevosTelegramasIds.Any())
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Se encontraron {count} telegramas nuevos en '{partido}' para '{cat}'. Descargando...", nuevosTelegramasIds.Count, partido.Nombre, categoria.Nombre);
|
|
||||||
|
|
||||||
var originalTimeout = innerDbContext.Database.GetCommandTimeout();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
innerDbContext.Database.SetCommandTimeout(180);
|
|
||||||
_logger.LogDebug("Timeout de BD aumentado a 180s para descarga de telegramas.");
|
|
||||||
|
|
||||||
int contadorLote = 0;
|
|
||||||
const int tamanoLote = 100;
|
|
||||||
|
|
||||||
foreach (var mesaId in nuevosTelegramasIds)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) return;
|
|
||||||
|
|
||||||
var telegramaFile = await _apiService.GetTelegramaFileAsync(authToken, mesaId);
|
|
||||||
if (telegramaFile != null)
|
|
||||||
{
|
|
||||||
var ambitoMesa = await innerDbContext.AmbitosGeograficos.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(a => a.MesaId == mesaId, stoppingToken);
|
|
||||||
|
|
||||||
if (ambitoMesa != null)
|
|
||||||
{
|
|
||||||
var nuevoTelegrama = new Telegrama
|
|
||||||
{
|
|
||||||
EleccionId = EleccionId,
|
|
||||||
Id = telegramaFile.NombreArchivo,
|
|
||||||
AmbitoGeograficoId = ambitoMesa.Id,
|
|
||||||
ContenidoBase64 = telegramaFile.Imagen,
|
|
||||||
FechaEscaneo = DateTime.Parse(telegramaFile.FechaEscaneo).ToUniversalTime(),
|
|
||||||
FechaTotalizacion = DateTime.Parse(telegramaFile.FechaTotalizacion).ToUniversalTime()
|
|
||||||
};
|
|
||||||
await innerDbContext.Telegramas.AddAsync(nuevoTelegrama, stoppingToken);
|
|
||||||
contadorLote++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se encontró un ámbito geográfico para la mesa con MesaId {MesaId}. El telegrama no será guardado.", mesaId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Task.Delay(250, stoppingToken);
|
|
||||||
|
|
||||||
if (contadorLote >= tamanoLote)
|
|
||||||
{
|
|
||||||
await innerDbContext.SaveChangesAsync(stoppingToken);
|
|
||||||
_logger.LogInformation("Guardado un lote de {count} telegramas.", contadorLote);
|
|
||||||
contadorLote = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contadorLote > 0)
|
|
||||||
{
|
|
||||||
await innerDbContext.SaveChangesAsync(stoppingToken);
|
|
||||||
_logger.LogInformation("Guardado el último lote de {count} telegramas.", contadorLote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
innerDbContext.Database.SetCommandTimeout(originalTimeout);
|
|
||||||
_logger.LogDebug("Timeout de BD restaurado a su valor original ({timeout}s).", originalTimeout);
|
|
||||||
}
|
|
||||||
} // Fin del if (nuevosTelegramasIds.Any())
|
|
||||||
|
|
||||||
// Movemos el delay aquí para que solo se ejecute si hubo telegramas en la respuesta de la API
|
|
||||||
await Task.Delay(100, stoppingToken);
|
|
||||||
} // Fin del if (listaTelegramasApi is not null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_logger.LogInformation("Sondeo de Telegramas completado.");
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeo de telegramas cancelado.");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Telegramas.");
|
_logger.LogError(ex, "Error en el ciclo de sondeo de estados.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SondearResultadosMunicipalesAsync(string authToken, CancellationToken stoppingToken)
|
// --- FUNCIÓN AUXILIAR PARA GUARDAR DATOS AGREGADOS ---
|
||||||
|
private async Task GuardarDatosAgregadosAsync(EleccionesDbContext dbContext, int ambitoId, int categoriaId, Core.DTOs.ResultadosDto resultadosDto, CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
|
||||||
|
|
||||||
|
// 1. Actualizar EstadoRecuentoGeneral
|
||||||
|
var estadoGeneral = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { ambitoId, categoriaId }, stoppingToken);
|
||||||
|
if (estadoGeneral == null)
|
||||||
|
{
|
||||||
|
estadoGeneral = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = ambitoId, CategoriaId = categoriaId };
|
||||||
|
dbContext.EstadosRecuentosGenerales.Add(estadoGeneral);
|
||||||
|
}
|
||||||
|
estadoGeneral.FechaTotalizacion = DateTime.Parse(resultadosDto.FechaTotalizacion);
|
||||||
|
estadoGeneral.MesasEsperadas = resultadosDto.EstadoRecuento.MesasEsperadas;
|
||||||
|
estadoGeneral.MesasTotalizadas = resultadosDto.EstadoRecuento.MesasTotalizadas;
|
||||||
|
estadoGeneral.MesasTotalizadasPorcentaje = resultadosDto.EstadoRecuento.MesasTotalizadasPorcentaje;
|
||||||
|
estadoGeneral.CantidadElectores = resultadosDto.EstadoRecuento.CantidadElectores;
|
||||||
|
estadoGeneral.CantidadVotantes = resultadosDto.EstadoRecuento.CantidadVotantes;
|
||||||
|
estadoGeneral.ParticipacionPorcentaje = resultadosDto.EstadoRecuento.ParticipacionPorcentaje;
|
||||||
|
|
||||||
|
// 2. Actualizar ResumenesVotos
|
||||||
|
if (resultadosDto.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos)
|
||||||
|
{
|
||||||
|
await dbContext.ResumenesVotos
|
||||||
|
.Where(rv => rv.AmbitoGeograficoId == ambitoId && rv.CategoriaId == categoriaId)
|
||||||
|
.ExecuteDeleteAsync(stoppingToken);
|
||||||
|
|
||||||
|
foreach (var voto in nuevosVotos)
|
||||||
|
{
|
||||||
|
dbContext.ResumenesVotos.Add(new ResumenVoto
|
||||||
|
{
|
||||||
|
EleccionId = EleccionId,
|
||||||
|
AmbitoGeograficoId = ambitoId,
|
||||||
|
CategoriaId = categoriaId,
|
||||||
|
AgrupacionPoliticaId = voto.IdAgrupacion,
|
||||||
|
Votos = voto.Votos,
|
||||||
|
VotosPorcentaje = voto.VotosPorcentaje
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(stoppingToken);
|
||||||
|
await transaction.CommitAsync(stoppingToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ActualizarResultadosDeProvincia(string authToken, string distritoId, int categoriaId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var scope = _serviceProvider.CreateScope();
|
using var scope = _serviceProvider.CreateScope();
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
||||||
|
|
||||||
var municipiosASondear = await dbContext.AmbitosGeograficos
|
var municipiosDeLaProvincia = await dbContext.AmbitosGeograficos
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null)
|
.Where(a => a.NivelId == 30 && a.DistritoId == distritoId && a.SeccionId != null)
|
||||||
.ToListAsync(stoppingToken);
|
.ToListAsync();
|
||||||
|
|
||||||
var todasLasCategorias = await dbContext.CategoriasElectorales
|
_logger.LogInformation("Iniciando descarga detallada para {Count} municipios del distrito {DistritoId}...", municipiosDeLaProvincia.Count, distritoId);
|
||||||
.AsNoTracking()
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
if (!municipiosASondear.Any() || !todasLasCategorias.Any())
|
foreach (var municipio in municipiosDeLaProvincia)
|
||||||
{
|
{
|
||||||
_logger.LogWarning("No se encontraron Partidos (NivelId 30) o Categorías para sondear resultados.");
|
var resultados = await _apiService.GetResultadosAsync(authToken, municipio.DistritoId!, municipio.SeccionId!, null, categoriaId);
|
||||||
return;
|
if (resultados != null)
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Iniciando sondeo de resultados para {m} municipios y {c} categorías...", municipiosASondear.Count, todasLasCategorias.Count);
|
|
||||||
|
|
||||||
foreach (var municipio in municipiosASondear)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
|
|
||||||
var tareasCategoria = todasLasCategorias.Select(async categoria =>
|
|
||||||
{
|
{
|
||||||
var resultados = await _apiService.GetResultadosAsync(authToken, municipio.DistritoId!, municipio.SeccionId!, null, categoria.Id);
|
await GuardarResultadosDeAmbitoAsync(dbContext, municipio.Id, categoriaId, resultados, CancellationToken.None);
|
||||||
|
}
|
||||||
if (resultados != null)
|
|
||||||
{
|
|
||||||
using var innerScope = _serviceProvider.CreateScope();
|
|
||||||
var innerDbContext = innerScope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
// --- LLAMADA CORRECTA ---
|
|
||||||
await GuardarResultadosDeAmbitoAsync(innerDbContext, municipio.Id, categoria.Id, resultados, stoppingToken);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.WhenAll(tareasCategoria);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Descarga detallada para el distrito {DistritoId} completada.", distritoId);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Ocurrió un error inesperado durante el sondeo de resultados municipales.");
|
_logger.LogError(ex, "Error en la tarea de actualización de resultados para el distrito {DistritoId}.", distritoId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GuardarResultadosDeAmbitoAsync(
|
private async Task GuardarResultadosDeAmbitoAsync(
|
||||||
EleccionesDbContext dbContext, int ambitoId, int categoriaId,
|
EleccionesDbContext dbContext, int ambitoId, int categoriaId,
|
||||||
Elecciones.Core.DTOs.ResultadosDto resultadosDto, CancellationToken stoppingToken)
|
Elecciones.Core.DTOs.ResultadosDto resultadosDto, CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
var estadoRecuento = await dbContext.EstadosRecuentos.FindAsync(new object[] { ambitoId, categoriaId }, stoppingToken);
|
var estadoRecuento = await dbContext.EstadosRecuentos.FindAsync(new object[] { ambitoId, categoriaId }, stoppingToken);
|
||||||
|
|
||||||
@@ -394,7 +218,7 @@ public class CriticalDataWorker : BackgroundService
|
|||||||
dbContext.EstadosRecuentos.Add(estadoRecuento);
|
dbContext.EstadosRecuentos.Add(estadoRecuento);
|
||||||
}
|
}
|
||||||
|
|
||||||
estadoRecuento.FechaTotalizacion = DateTime.Parse(resultadosDto.FechaTotalizacion).ToUniversalTime();
|
estadoRecuento.FechaTotalizacion = DateTime.Parse(resultadosDto.FechaTotalizacion);
|
||||||
estadoRecuento.MesasEsperadas = resultadosDto.EstadoRecuento.MesasEsperadas;
|
estadoRecuento.MesasEsperadas = resultadosDto.EstadoRecuento.MesasEsperadas;
|
||||||
estadoRecuento.MesasTotalizadas = resultadosDto.EstadoRecuento.MesasTotalizadas;
|
estadoRecuento.MesasTotalizadas = resultadosDto.EstadoRecuento.MesasTotalizadas;
|
||||||
estadoRecuento.MesasTotalizadasPorcentaje = resultadosDto.EstadoRecuento.MesasTotalizadasPorcentaje;
|
estadoRecuento.MesasTotalizadasPorcentaje = resultadosDto.EstadoRecuento.MesasTotalizadasPorcentaje;
|
||||||
@@ -470,211 +294,4 @@ public class CriticalDataWorker : BackgroundService
|
|||||||
_logger.LogError(ex, "DbUpdateException al guardar resultados para AmbitoId {ambitoId} y CategoriaId {categoriaId}", ambitoId, categoriaId);
|
_logger.LogError(ex, "DbUpdateException al guardar resultados para AmbitoId {ambitoId} y CategoriaId {categoriaId}", ambitoId, categoriaId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// <summary>
|
|
||||||
/// Obtiene y actualiza el resumen de votos y el estado del recuento a nivel provincial para CADA categoría.
|
|
||||||
/// Este método itera sobre todas las provincias y categorías, obteniendo sus resultados consolidados
|
|
||||||
/// y guardándolos en las tablas 'ResumenesVotos' y 'EstadosRecuentosGenerales'.
|
|
||||||
/// </summary>
|
|
||||||
private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Iniciando sondeo de Resúmenes Provinciales...");
|
|
||||||
|
|
||||||
using var scope = _serviceProvider.CreateScope();
|
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
var provinciasASondear = await dbContext.AmbitosGeograficos
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
var todasLasCategorias = await dbContext.CategoriasElectorales
|
|
||||||
.AsNoTracking()
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
if (!provinciasASondear.Any() || !todasLasCategorias.Any())
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se encontraron Provincias o Categorías para sondear resúmenes.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var provincia in provinciasASondear)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
|
|
||||||
foreach (var categoria in todasLasCategorias)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
|
|
||||||
// Usamos GetResultados sin seccionId/municipioId para obtener el resumen del distrito.
|
|
||||||
var resultadosDto = await _apiService.GetResultadosAsync(authToken, provincia.DistritoId!, null, null, categoria.Id);
|
|
||||||
|
|
||||||
if (resultadosDto?.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos)
|
|
||||||
{
|
|
||||||
// Usamos una transacción para asegurar que el borrado y la inserción sean atómicos.
|
|
||||||
await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
|
|
||||||
|
|
||||||
// A. Borrar los resúmenes viejos SOLO para esta provincia y categoría.
|
|
||||||
await dbContext.ResumenesVotos
|
|
||||||
.Where(rv => rv.AmbitoGeograficoId == provincia.Id && rv.CategoriaId == categoria.Id)
|
|
||||||
.ExecuteDeleteAsync(stoppingToken);
|
|
||||||
|
|
||||||
// B. Añadir los nuevos resúmenes.
|
|
||||||
foreach (var voto in nuevosVotos)
|
|
||||||
{
|
|
||||||
dbContext.ResumenesVotos.Add(new ResumenVoto
|
|
||||||
{
|
|
||||||
EleccionId = EleccionId,
|
|
||||||
AmbitoGeograficoId = provincia.Id,
|
|
||||||
CategoriaId = categoria.Id,
|
|
||||||
AgrupacionPoliticaId = voto.IdAgrupacion,
|
|
||||||
Votos = voto.Votos,
|
|
||||||
VotosPorcentaje = voto.VotosPorcentaje
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// C. Guardar los cambios en la tabla ResumenesVotos.
|
|
||||||
await dbContext.SaveChangesAsync(stoppingToken);
|
|
||||||
|
|
||||||
// No es necesario actualizar EstadosRecuentosGenerales aquí,
|
|
||||||
// ya que el método SondearEstadoRecuentoGeneralAsync se encarga de eso
|
|
||||||
// de forma más específica y eficiente.
|
|
||||||
|
|
||||||
await transaction.CommitAsync(stoppingToken);
|
|
||||||
}
|
|
||||||
} // Fin bucle categorías
|
|
||||||
} // Fin bucle provincias
|
|
||||||
|
|
||||||
_logger.LogInformation("Sondeo de Resúmenes Provinciales completado.");
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeo de resúmenes provinciales cancelado.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Resúmenes Provinciales.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Obtiene y actualiza el estado general del recuento a nivel provincial para CADA categoría electoral.
|
|
||||||
/// Esta versión es robusta: consulta dinámicamente las categorías, usa la clave primaria compuesta
|
|
||||||
/// de la base de datos y guarda todos los cambios en una única transacción al final.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="authToken">El token de autenticación válido para la sesión.</param>
|
|
||||||
/// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
|
|
||||||
private async Task SondearEstadoRecuentoGeneralAsync(string authToken, CancellationToken stoppingToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var scope = _serviceProvider.CreateScope();
|
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
var provinciasASondear = await dbContext.AmbitosGeograficos
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
// Busca NivelId 1 (País) o 0 como fallback.
|
|
||||||
var ambitoNacional = await dbContext.AmbitosGeograficos
|
|
||||||
.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(a => a.NivelId == 1 || a.NivelId == 0, stoppingToken);
|
|
||||||
|
|
||||||
var categoriasParaSondear = await dbContext.CategoriasElectorales
|
|
||||||
.AsNoTracking()
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
if (!provinciasASondear.Any() || !categoriasParaSondear.Any())
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se encontraron Provincias o Categorías para sondear estado general.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Iniciando sondeo de Estado Recuento General para {provCount} provincias, el total nacional y {catCount} categorías...", provinciasASondear.Count, categoriasParaSondear.Count);
|
|
||||||
|
|
||||||
// Sondeo a nivel provincial
|
|
||||||
foreach (var provincia in provinciasASondear)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
foreach (var categoria in categoriasParaSondear)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
|
|
||||||
var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id);
|
|
||||||
if (estadoDto != null)
|
|
||||||
{
|
|
||||||
var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { provincia.Id, categoria.Id }, stoppingToken);
|
|
||||||
if (registroDb == null)
|
|
||||||
{
|
|
||||||
registroDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id };
|
|
||||||
dbContext.EstadosRecuentosGenerales.Add(registroDb);
|
|
||||||
}
|
|
||||||
registroDb.FechaTotalizacion = DateTime.UtcNow;
|
|
||||||
registroDb.MesasEsperadas = estadoDto.MesasEsperadas;
|
|
||||||
registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas;
|
|
||||||
registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje;
|
|
||||||
registroDb.CantidadElectores = estadoDto.CantidadElectores;
|
|
||||||
registroDb.CantidadVotantes = estadoDto.CantidadVotantes;
|
|
||||||
registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bloque para el sondeo a nivel nacional
|
|
||||||
if (ambitoNacional != null && !stoppingToken.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeando totales a nivel Nacional (Ambito ID: {ambitoId})...", ambitoNacional.Id);
|
|
||||||
foreach (var categoria in categoriasParaSondear)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
|
|
||||||
var estadoNacionalDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, "", categoria.Id);
|
|
||||||
|
|
||||||
if (estadoNacionalDto != null)
|
|
||||||
{
|
|
||||||
var registroNacionalDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { ambitoNacional.Id, categoria.Id }, stoppingToken);
|
|
||||||
if (registroNacionalDb == null)
|
|
||||||
{
|
|
||||||
registroNacionalDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = ambitoNacional.Id, CategoriaId = categoria.Id };
|
|
||||||
dbContext.EstadosRecuentosGenerales.Add(registroNacionalDb);
|
|
||||||
}
|
|
||||||
registroNacionalDb.FechaTotalizacion = DateTime.UtcNow;
|
|
||||||
registroNacionalDb.MesasEsperadas = estadoNacionalDto.MesasEsperadas;
|
|
||||||
registroNacionalDb.MesasTotalizadas = estadoNacionalDto.MesasTotalizadas;
|
|
||||||
registroNacionalDb.MesasTotalizadasPorcentaje = estadoNacionalDto.MesasTotalizadasPorcentaje;
|
|
||||||
registroNacionalDb.CantidadElectores = estadoNacionalDto.CantidadElectores;
|
|
||||||
registroNacionalDb.CantidadVotantes = estadoNacionalDto.CantidadVotantes;
|
|
||||||
registroNacionalDb.ParticipacionPorcentaje = estadoNacionalDto.ParticipacionPorcentaje;
|
|
||||||
_logger.LogInformation("Datos nacionales para categoría '{catNombre}' actualizados.", categoria.Nombre);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (ambitoNacional == null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se encontró el ámbito geográfico para el Nivel Nacional (NivelId 1 o 0). No se pueden capturar los totales del país.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Guardar todos los cambios
|
|
||||||
if (dbContext.ChangeTracker.HasChanges())
|
|
||||||
{
|
|
||||||
await dbContext.SaveChangesAsync(stoppingToken);
|
|
||||||
_logger.LogInformation("Sondeo de Estado Recuento General completado. Se han guardado los cambios en la base de datos.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeo de Estado Recuento General completado. No se detectaron cambios.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeo de Estado Recuento General cancelado.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
//Elecciones.Worker/LowPriorityDataWorker.cs
|
// src/Elecciones.Worker/LowPriorityDataWorker.cs
|
||||||
|
|
||||||
using Elecciones.Database;
|
using Elecciones.Database;
|
||||||
using Elecciones.Database.Entities;
|
using Elecciones.Database.Entities;
|
||||||
using Elecciones.Infrastructure.Services;
|
using Elecciones.Infrastructure.Services;
|
||||||
@@ -15,15 +16,12 @@ public class LowPriorityDataWorker : BackgroundService
|
|||||||
private readonly IElectoralApiService _apiService;
|
private readonly IElectoralApiService _apiService;
|
||||||
private readonly WorkerConfigService _configService;
|
private readonly WorkerConfigService _configService;
|
||||||
|
|
||||||
// Una variable para rastrear la tarea de telegramas, si está en ejecución.
|
|
||||||
private Task? _telegramasTask;
|
|
||||||
|
|
||||||
public LowPriorityDataWorker(
|
public LowPriorityDataWorker(
|
||||||
ILogger<LowPriorityDataWorker> logger,
|
ILogger<LowPriorityDataWorker> logger,
|
||||||
SharedTokenService tokenService,
|
SharedTokenService tokenService,
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
IElectoralApiService apiService,
|
IElectoralApiService apiService,
|
||||||
WorkerConfigService configService)
|
WorkerConfigService configService)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_tokenService = tokenService;
|
_tokenService = tokenService;
|
||||||
@@ -34,14 +32,20 @@ public class LowPriorityDataWorker : BackgroundService
|
|||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Worker de Baja Prioridad iniciado.");
|
_logger.LogInformation("Worker de Baja Prioridad (Proyecciones) iniciado.");
|
||||||
|
|
||||||
// La sincronización inicial sigue siendo un paso de bloqueo, es necesario.
|
// Sincronización inicial de catálogos (solo se ejecuta una vez)
|
||||||
await SincronizarCatalogosMaestrosAsync(stoppingToken);
|
await SincronizarCatalogosMaestrosAsync(stoppingToken);
|
||||||
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("--- Iniciando Ciclo de Datos de Baja Prioridad ---");
|
var settings = await _configService.GetSettingsAsync();
|
||||||
|
if (!settings.BajasActivado)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("El sondeo de proyecciones está desactivado. Esperando 5 minutos.");
|
||||||
|
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var authToken = await _tokenService.GetValidAuthTokenAsync(stoppingToken);
|
var authToken = await _tokenService.GetValidAuthTokenAsync(stoppingToken);
|
||||||
if (string.IsNullOrEmpty(authToken))
|
if (string.IsNullOrEmpty(authToken))
|
||||||
@@ -51,25 +55,10 @@ public class LowPriorityDataWorker : BackgroundService
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings = await _configService.GetSettingsAsync();
|
_logger.LogInformation("--- Iniciando Ciclo de Sondeo de Proyección de Bancas ---");
|
||||||
|
await SondearProyeccionBancasAsync(authToken, stoppingToken);
|
||||||
|
_logger.LogInformation("--- Ciclo de Sondeo de Proyección de Bancas completado. ---");
|
||||||
|
|
||||||
if (settings.Prioridad == "Telegramas" && settings.ResultadosActivado)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Ejecutando tareas de Resultados en baja prioridad.");
|
|
||||||
await SondearEstadoRecuentoGeneralAsync(authToken, stoppingToken);
|
|
||||||
await SondearResultadosMunicipalesAsync(authToken, stoppingToken);
|
|
||||||
await SondearResumenProvincialAsync(authToken, stoppingToken);
|
|
||||||
}
|
|
||||||
else if (settings.Prioridad == "Resultados" && settings.BajasActivado)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Ejecutando tareas de Baja Prioridad en baja prioridad.");
|
|
||||||
await SondearProyeccionBancasAsync(authToken, stoppingToken);
|
|
||||||
//await SondearNuevosTelegramasAsync(authToken, stoppingToken);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Worker de baja prioridad inactivo según la configuración.");
|
|
||||||
}
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
||||||
@@ -81,349 +70,126 @@ public class LowPriorityDataWorker : BackgroundService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SondearResultadosMunicipalesAsync(string authToken, CancellationToken stoppingToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var scope = _serviceProvider.CreateScope();
|
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
var municipiosASondear = await dbContext.AmbitosGeograficos
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null)
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
var todasLasCategorias = await dbContext.CategoriasElectorales
|
|
||||||
.AsNoTracking()
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
if (!municipiosASondear.Any() || !todasLasCategorias.Any())
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se encontraron Partidos (NivelId 30) o Categorías para sondear resultados.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Iniciando sondeo de resultados para {m} municipios y {c} categorías...", municipiosASondear.Count, todasLasCategorias.Count);
|
|
||||||
|
|
||||||
foreach (var municipio in municipiosASondear)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
|
|
||||||
var tareasCategoria = todasLasCategorias.Select(async categoria =>
|
|
||||||
{
|
|
||||||
var resultados = await _apiService.GetResultadosAsync(authToken, municipio.DistritoId!, municipio.SeccionId!, null, categoria.Id);
|
|
||||||
|
|
||||||
if (resultados != null)
|
|
||||||
{
|
|
||||||
using var innerScope = _serviceProvider.CreateScope();
|
|
||||||
var innerDbContext = innerScope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
await GuardarResultadosDeAmbitoAsync(innerDbContext, municipio.Id, categoria.Id, resultados, stoppingToken);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await Task.WhenAll(tareasCategoria);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Ocurrió un error inesperado durante el sondeo de resultados municipales.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task GuardarResultadosDeAmbitoAsync(
|
|
||||||
EleccionesDbContext dbContext, int ambitoId, int categoriaId,
|
|
||||||
Elecciones.Core.DTOs.ResultadosDto resultadosDto, CancellationToken stoppingToken)
|
|
||||||
{
|
|
||||||
var estadoRecuento = await dbContext.EstadosRecuentos.FindAsync(new object[] { ambitoId, categoriaId }, stoppingToken);
|
|
||||||
|
|
||||||
if (estadoRecuento == null)
|
|
||||||
{
|
|
||||||
estadoRecuento = new EstadoRecuento { EleccionId = EleccionId, AmbitoGeograficoId = ambitoId, CategoriaId = categoriaId };
|
|
||||||
dbContext.EstadosRecuentos.Add(estadoRecuento);
|
|
||||||
}
|
|
||||||
|
|
||||||
estadoRecuento.FechaTotalizacion = DateTime.Parse(resultadosDto.FechaTotalizacion).ToUniversalTime();
|
|
||||||
estadoRecuento.MesasEsperadas = resultadosDto.EstadoRecuento.MesasEsperadas;
|
|
||||||
estadoRecuento.MesasTotalizadas = resultadosDto.EstadoRecuento.MesasTotalizadas;
|
|
||||||
estadoRecuento.MesasTotalizadasPorcentaje = resultadosDto.EstadoRecuento.MesasTotalizadasPorcentaje;
|
|
||||||
estadoRecuento.CantidadElectores = resultadosDto.EstadoRecuento.CantidadElectores;
|
|
||||||
estadoRecuento.CantidadVotantes = resultadosDto.EstadoRecuento.CantidadVotantes;
|
|
||||||
estadoRecuento.ParticipacionPorcentaje = resultadosDto.EstadoRecuento.ParticipacionPorcentaje;
|
|
||||||
|
|
||||||
if (resultadosDto.ValoresTotalizadosOtros != null)
|
|
||||||
{
|
|
||||||
estadoRecuento.VotosEnBlanco = resultadosDto.ValoresTotalizadosOtros.VotosEnBlanco;
|
|
||||||
estadoRecuento.VotosEnBlancoPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosEnBlancoPorcentaje;
|
|
||||||
estadoRecuento.VotosNulos = resultadosDto.ValoresTotalizadosOtros.VotosNulos;
|
|
||||||
estadoRecuento.VotosNulosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosNulosPorcentaje;
|
|
||||||
estadoRecuento.VotosRecurridos = resultadosDto.ValoresTotalizadosOtros.VotosRecurridos;
|
|
||||||
estadoRecuento.VotosRecurridosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosRecurridosPorcentaje;
|
|
||||||
estadoRecuento.VotosComando = resultadosDto.ValoresTotalizadosOtros.VotosComando;
|
|
||||||
estadoRecuento.VotosComandoPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosComandoPorcentaje;
|
|
||||||
estadoRecuento.VotosImpugnados = resultadosDto.ValoresTotalizadosOtros.VotosImpugnados;
|
|
||||||
estadoRecuento.VotosImpugnadosPorcentaje = resultadosDto.ValoresTotalizadosOtros.VotosImpugnadosPorcentaje;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var votoPositivoDto in resultadosDto.ValoresTotalizadosPositivos)
|
|
||||||
{
|
|
||||||
// PASO 1: VERIFICAR SI LA AGRUPACIÓN YA EXISTE EN NUESTRA BD
|
|
||||||
var agrupacion = await dbContext.AgrupacionesPoliticas.FindAsync(votoPositivoDto.IdAgrupacion);
|
|
||||||
|
|
||||||
// PASO 2: SI NO EXISTE, LA CREAMOS "SOBRE LA MARCHA"
|
|
||||||
if (agrupacion == null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Agrupación con ID {AgrupacionId} ('{Nombre}') no encontrada en el catálogo local. Creándola desde los datos de resultados.",
|
|
||||||
votoPositivoDto.IdAgrupacion, votoPositivoDto.NombreAgrupacion);
|
|
||||||
|
|
||||||
agrupacion = new AgrupacionPolitica
|
|
||||||
{
|
|
||||||
Id = votoPositivoDto.IdAgrupacion,
|
|
||||||
Nombre = votoPositivoDto.NombreAgrupacion,
|
|
||||||
// El IdTelegrama puede ser nulo, usamos el operador '??' para asignar un string vacío si es así.
|
|
||||||
IdTelegrama = votoPositivoDto.IdAgrupacionTelegrama ?? string.Empty
|
|
||||||
};
|
|
||||||
await dbContext.AgrupacionesPoliticas.AddAsync(agrupacion, stoppingToken);
|
|
||||||
// No es necesario llamar a SaveChangesAsync aquí, se hará al final.
|
|
||||||
}
|
|
||||||
|
|
||||||
// PASO 3: CONTINUAR CON LA LÓGICA DE GUARDADO DEL VOTO
|
|
||||||
var resultadoVoto = await dbContext.ResultadosVotos.FirstOrDefaultAsync(
|
|
||||||
rv => rv.AmbitoGeograficoId == ambitoId &&
|
|
||||||
rv.CategoriaId == categoriaId &&
|
|
||||||
rv.AgrupacionPoliticaId == votoPositivoDto.IdAgrupacion,
|
|
||||||
stoppingToken
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resultadoVoto == null)
|
|
||||||
{
|
|
||||||
resultadoVoto = new ResultadoVoto
|
|
||||||
{
|
|
||||||
EleccionId = EleccionId,
|
|
||||||
AmbitoGeograficoId = ambitoId,
|
|
||||||
CategoriaId = categoriaId,
|
|
||||||
AgrupacionPoliticaId = votoPositivoDto.IdAgrupacion
|
|
||||||
};
|
|
||||||
dbContext.ResultadosVotos.Add(resultadoVoto);
|
|
||||||
}
|
|
||||||
resultadoVoto.CantidadVotos = votoPositivoDto.Votos;
|
|
||||||
resultadoVoto.PorcentajeVotos = votoPositivoDto.VotosPorcentaje;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await dbContext.SaveChangesAsync(stoppingToken);
|
|
||||||
}
|
|
||||||
catch (DbUpdateException ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "DbUpdateException al guardar resultados para AmbitoId {ambitoId} y CategoriaId {categoriaId}", ambitoId, categoriaId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Obtiene y actualiza el resumen de votos y el estado del recuento a nivel provincial para CADA categoría.
|
/// Sondea la proyección de bancas a nivel Provincial y por Sección Electoral.
|
||||||
/// Este método itera sobre todas las provincias y categorías, obteniendo sus resultados consolidados
|
/// Esta versión es completamente robusta: maneja respuestas de API vacías o con fechas mal formadas,
|
||||||
/// y guardándolos en las tablas 'ResumenesVotos' y 'EstadosRecuentosGenerales'.
|
/// guarda la CategoriaId y usa una transacción atómica para la escritura en base de datos.
|
||||||
/// </summary>
|
|
||||||
private async Task SondearResumenProvincialAsync(string authToken, CancellationToken stoppingToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Iniciando sondeo de Resúmenes Provinciales...");
|
|
||||||
|
|
||||||
using var scope = _serviceProvider.CreateScope();
|
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
var provinciasASondear = await dbContext.AmbitosGeograficos
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
var todasLasCategorias = await dbContext.CategoriasElectorales
|
|
||||||
.AsNoTracking()
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
if (!provinciasASondear.Any() || !todasLasCategorias.Any())
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se encontraron Provincias o Categorías para sondear resúmenes.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var provincia in provinciasASondear)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
|
|
||||||
foreach (var categoria in todasLasCategorias)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
|
|
||||||
// Usamos GetResultados sin seccionId/municipioId para obtener el resumen del distrito.
|
|
||||||
var resultadosDto = await _apiService.GetResultadosAsync(authToken, provincia.DistritoId!, null, null, categoria.Id);
|
|
||||||
|
|
||||||
if (resultadosDto?.ValoresTotalizadosPositivos is { Count: > 0 } nuevosVotos)
|
|
||||||
{
|
|
||||||
// Usamos una transacción para asegurar que el borrado y la inserción sean atómicos.
|
|
||||||
await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
|
|
||||||
|
|
||||||
// A. Borrar los resúmenes viejos SOLO para esta provincia y categoría.
|
|
||||||
await dbContext.ResumenesVotos
|
|
||||||
.Where(rv => rv.AmbitoGeograficoId == provincia.Id && rv.CategoriaId == categoria.Id)
|
|
||||||
.ExecuteDeleteAsync(stoppingToken);
|
|
||||||
|
|
||||||
// B. Añadir los nuevos resúmenes.
|
|
||||||
foreach (var voto in nuevosVotos)
|
|
||||||
{
|
|
||||||
dbContext.ResumenesVotos.Add(new ResumenVoto
|
|
||||||
{
|
|
||||||
EleccionId = EleccionId,
|
|
||||||
AmbitoGeograficoId = provincia.Id,
|
|
||||||
CategoriaId = categoria.Id,
|
|
||||||
AgrupacionPoliticaId = voto.IdAgrupacion,
|
|
||||||
Votos = voto.Votos,
|
|
||||||
VotosPorcentaje = voto.VotosPorcentaje
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// C. Guardar los cambios en la tabla ResumenesVotos.
|
|
||||||
await dbContext.SaveChangesAsync(stoppingToken);
|
|
||||||
|
|
||||||
// No es necesario actualizar EstadosRecuentosGenerales aquí,
|
|
||||||
// ya que el método SondearEstadoRecuentoGeneralAsync se encarga de eso
|
|
||||||
// de forma más específica y eficiente.
|
|
||||||
|
|
||||||
await transaction.CommitAsync(stoppingToken);
|
|
||||||
}
|
|
||||||
} // Fin bucle categorías
|
|
||||||
} // Fin bucle provincias
|
|
||||||
|
|
||||||
_logger.LogInformation("Sondeo de Resúmenes Provinciales completado.");
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeo de resúmenes provinciales cancelado.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Resúmenes Provinciales.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Obtiene y actualiza el estado general del recuento a nivel provincial para CADA categoría electoral.
|
|
||||||
/// Esta versión es robusta: consulta dinámicamente las categorías, usa la clave primaria compuesta
|
|
||||||
/// de la base de datos y guarda todos los cambios en una única transacción al final.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="authToken">El token de autenticación válido para la sesión.</param>
|
/// <param name="authToken">El token de autenticación válido para la sesión.</param>
|
||||||
/// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
|
/// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
|
||||||
private async Task SondearEstadoRecuentoGeneralAsync(string authToken, CancellationToken stoppingToken)
|
private async Task SondearProyeccionBancasAsync(string authToken, CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var scope = _serviceProvider.CreateScope();
|
using var scope = _serviceProvider.CreateScope();
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
||||||
|
|
||||||
var provinciasASondear = await dbContext.AmbitosGeograficos
|
var categoriasDeBancas = await dbContext.CategoriasElectorales
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(c => c.Nombre.Contains("SENADORES") || c.Nombre.Contains("DIPUTADOS"))
|
||||||
|
.ToListAsync(stoppingToken);
|
||||||
|
|
||||||
|
var ambitosASondear = await dbContext.AmbitosGeograficos
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
||||||
.ToListAsync(stoppingToken);
|
.ToListAsync(stoppingToken);
|
||||||
|
|
||||||
// Busca NivelId 1 (País) o 0 como fallback.
|
if (!categoriasDeBancas.Any() || !ambitosASondear.Any())
|
||||||
var ambitoNacional = await dbContext.AmbitosGeograficos
|
|
||||||
.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(a => a.NivelId == 1 || a.NivelId == 0, stoppingToken);
|
|
||||||
|
|
||||||
var categoriasParaSondear = await dbContext.CategoriasElectorales
|
|
||||||
.AsNoTracking()
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
if (!provinciasASondear.Any() || !categoriasParaSondear.Any())
|
|
||||||
{
|
{
|
||||||
_logger.LogWarning("No se encontraron Provincias o Categorías para sondear estado general.");
|
_logger.LogWarning("No se encontraron categorías de bancas o ámbitos provinciales en la BD. Omitiendo sondeo de bancas.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("Iniciando sondeo de Estado Recuento General para {provCount} provincias, el total nacional y {catCount} categorías...", provinciasASondear.Count, categoriasParaSondear.Count);
|
_logger.LogInformation("Iniciando sondeo de Bancas a nivel Provincial para {count} provincias...", ambitosASondear.Count);
|
||||||
|
|
||||||
// Sondeo a nivel provincial
|
var todasLasProyecciones = new List<ProyeccionBanca>();
|
||||||
foreach (var provincia in provinciasASondear)
|
bool hasReceivedAnyNewData = false;
|
||||||
|
var agrupacionesEnDb = await dbContext.AgrupacionesPoliticas.ToDictionaryAsync(a => a.Id, a => a, stoppingToken);
|
||||||
|
|
||||||
|
foreach (var ambito in ambitosASondear)
|
||||||
{
|
{
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
foreach (var categoria in categoriasParaSondear)
|
foreach (var categoria in categoriasDeBancas)
|
||||||
{
|
{
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
if (stoppingToken.IsCancellationRequested) break;
|
||||||
|
|
||||||
var estadoDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, provincia.DistritoId!, categoria.Id);
|
var repartoBancasDto = await _apiService.GetBancasAsync(authToken, ambito.DistritoId!, ambito.SeccionProvincialId, categoria.Id);
|
||||||
if (estadoDto != null)
|
if (repartoBancasDto?.RepartoBancas is { Count: > 0 } bancas)
|
||||||
{
|
{
|
||||||
var registroDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { provincia.Id, categoria.Id }, stoppingToken);
|
hasReceivedAnyNewData = true;
|
||||||
if (registroDb == null)
|
DateTime fechaTotalizacion;
|
||||||
|
if (!DateTime.TryParse(repartoBancasDto.FechaTotalizacion, out var parsedDate))
|
||||||
{
|
{
|
||||||
registroDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = provincia.Id, CategoriaId = categoria.Id };
|
fechaTotalizacion = DateTime.Now;
|
||||||
dbContext.EstadosRecuentosGenerales.Add(registroDb);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fechaTotalizacion = parsedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var banca in bancas)
|
||||||
|
{
|
||||||
|
if (!agrupacionesEnDb.ContainsKey(banca.IdAgrupacion))
|
||||||
|
{
|
||||||
|
var nuevaAgrupacion = new AgrupacionPolitica
|
||||||
|
{
|
||||||
|
Id = banca.IdAgrupacion,
|
||||||
|
Nombre = banca.NombreAgrupacion,
|
||||||
|
IdTelegrama = banca.IdAgrupacionTelegrama ?? string.Empty
|
||||||
|
};
|
||||||
|
await dbContext.AgrupacionesPoliticas.AddAsync(nuevaAgrupacion, stoppingToken);
|
||||||
|
agrupacionesEnDb.Add(nuevaAgrupacion.Id, nuevaAgrupacion);
|
||||||
|
}
|
||||||
|
todasLasProyecciones.Add(new ProyeccionBanca
|
||||||
|
{
|
||||||
|
EleccionId = EleccionId,
|
||||||
|
AmbitoGeograficoId = ambito.Id,
|
||||||
|
AgrupacionPoliticaId = banca.IdAgrupacion,
|
||||||
|
NroBancas = banca.NroBancas,
|
||||||
|
CategoriaId = categoria.Id,
|
||||||
|
FechaTotalizacion = fechaTotalizacion
|
||||||
|
});
|
||||||
}
|
}
|
||||||
registroDb.FechaTotalizacion = DateTime.UtcNow;
|
|
||||||
registroDb.MesasEsperadas = estadoDto.MesasEsperadas;
|
|
||||||
registroDb.MesasTotalizadas = estadoDto.MesasTotalizadas;
|
|
||||||
registroDb.MesasTotalizadasPorcentaje = estadoDto.MesasTotalizadasPorcentaje;
|
|
||||||
registroDb.CantidadElectores = estadoDto.CantidadElectores;
|
|
||||||
registroDb.CantidadVotantes = estadoDto.CantidadVotantes;
|
|
||||||
registroDb.ParticipacionPorcentaje = estadoDto.ParticipacionPorcentaje;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bloque para el sondeo a nivel nacional
|
if (hasReceivedAnyNewData)
|
||||||
if (ambitoNacional != null && !stoppingToken.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Sondeando totales a nivel Nacional (Ambito ID: {ambitoId})...", ambitoNacional.Id);
|
_logger.LogInformation("Se recibieron datos válidos de bancas. Procediendo a actualizar la base de datos...");
|
||||||
foreach (var categoria in categoriasParaSondear)
|
await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
|
||||||
|
|
||||||
|
// Guardar nuevas agrupaciones si las hay
|
||||||
|
if (dbContext.ChangeTracker.HasChanges())
|
||||||
{
|
{
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
await dbContext.SaveChangesAsync(stoppingToken);
|
||||||
|
|
||||||
var estadoNacionalDto = await _apiService.GetEstadoRecuentoGeneralAsync(authToken, "", categoria.Id);
|
|
||||||
|
|
||||||
if (estadoNacionalDto != null)
|
|
||||||
{
|
|
||||||
var registroNacionalDb = await dbContext.EstadosRecuentosGenerales.FindAsync(new object[] { ambitoNacional.Id, categoria.Id }, stoppingToken);
|
|
||||||
if (registroNacionalDb == null)
|
|
||||||
{
|
|
||||||
registroNacionalDb = new EstadoRecuentoGeneral { EleccionId = EleccionId, AmbitoGeograficoId = ambitoNacional.Id, CategoriaId = categoria.Id };
|
|
||||||
dbContext.EstadosRecuentosGenerales.Add(registroNacionalDb);
|
|
||||||
}
|
|
||||||
registroNacionalDb.FechaTotalizacion = DateTime.UtcNow;
|
|
||||||
registroNacionalDb.MesasEsperadas = estadoNacionalDto.MesasEsperadas;
|
|
||||||
registroNacionalDb.MesasTotalizadas = estadoNacionalDto.MesasTotalizadas;
|
|
||||||
registroNacionalDb.MesasTotalizadasPorcentaje = estadoNacionalDto.MesasTotalizadasPorcentaje;
|
|
||||||
registroNacionalDb.CantidadElectores = estadoNacionalDto.CantidadElectores;
|
|
||||||
registroNacionalDb.CantidadVotantes = estadoNacionalDto.CantidadVotantes;
|
|
||||||
registroNacionalDb.ParticipacionPorcentaje = estadoNacionalDto.ParticipacionPorcentaje;
|
|
||||||
_logger.LogInformation("Datos nacionales para categoría '{catNombre}' actualizados.", categoria.Nombre);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (ambitoNacional == null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se encontró el ámbito geográfico para el Nivel Nacional (NivelId 1 o 0). No se pueden capturar los totales del país.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Guardar todos los cambios
|
// --- LÍNEA CORREGIDA ---
|
||||||
if (dbContext.ChangeTracker.HasChanges())
|
// Se reemplaza ExecuteSqlRawAsync por ExecuteDeleteAsync, que es type-safe
|
||||||
{
|
// y maneja correctamente el CancellationToken.
|
||||||
|
await dbContext.ProyeccionesBancas
|
||||||
|
.Where(p => p.EleccionId == EleccionId)
|
||||||
|
.ExecuteDeleteAsync(stoppingToken);
|
||||||
|
// --- FIN DE LA CORRECCIÓN ---
|
||||||
|
|
||||||
|
await dbContext.ProyeccionesBancas.AddRangeAsync(todasLasProyecciones, stoppingToken);
|
||||||
await dbContext.SaveChangesAsync(stoppingToken);
|
await dbContext.SaveChangesAsync(stoppingToken);
|
||||||
_logger.LogInformation("Sondeo de Estado Recuento General completado. Se han guardado los cambios en la base de datos.");
|
await transaction.CommitAsync(stoppingToken);
|
||||||
|
_logger.LogInformation("La tabla de proyecciones ha sido actualizada con {count} registros.", todasLasProyecciones.Count);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Sondeo de Estado Recuento General completado. No se detectaron cambios.");
|
_logger.LogInformation("Sondeo de Bancas completado. No se encontraron datos nuevos, la tabla no fue modificada.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Sondeo de Estado Recuento General cancelado.");
|
_logger.LogInformation("Sondeo de bancas cancelado.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Estado Recuento General.");
|
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Bancas.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,245 +318,4 @@ public class LowPriorityDataWorker : BackgroundService
|
|||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO durante la sincronización de catálogos.");
|
_logger.LogError(ex, "Ocurrió un error CRÍTICO durante la sincronización de catálogos.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sondea la proyección de bancas a nivel Provincial y por Sección Electoral.
|
|
||||||
/// Esta versión es completamente robusta: maneja respuestas de API vacías o con fechas mal formadas,
|
|
||||||
/// guarda la CategoriaId y usa una transacción atómica para la escritura en base de datos.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="authToken">El token de autenticación válido para la sesión.</param>
|
|
||||||
/// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
|
|
||||||
private async Task SondearProyeccionBancasAsync(string authToken, CancellationToken stoppingToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var scope = _serviceProvider.CreateScope();
|
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
var categoriasDeBancas = await dbContext.CategoriasElectorales
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(c => c.Nombre.Contains("SENADORES") || c.Nombre.Contains("DIPUTADOS"))
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
var ambitosASondear = await dbContext.AmbitosGeograficos
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(a => a.NivelId == 10 && a.DistritoId != null)
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
if (!categoriasDeBancas.Any() || !ambitosASondear.Any())
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se encontraron categorías de bancas o ámbitos provinciales en la BD. Omitiendo sondeo de bancas.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Iniciando sondeo de Bancas a nivel Provincial para {count} provincias...", ambitosASondear.Count);
|
|
||||||
|
|
||||||
var todasLasProyecciones = new List<ProyeccionBanca>();
|
|
||||||
bool hasReceivedAnyNewData = false;
|
|
||||||
var agrupacionesEnDb = await dbContext.AgrupacionesPoliticas.ToDictionaryAsync(a => a.Id, a => a, stoppingToken);
|
|
||||||
|
|
||||||
foreach (var ambito in ambitosASondear)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
foreach (var categoria in categoriasDeBancas)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) break;
|
|
||||||
|
|
||||||
var repartoBancasDto = await _apiService.GetBancasAsync(authToken, ambito.DistritoId!, ambito.SeccionProvincialId, categoria.Id);
|
|
||||||
|
|
||||||
if (repartoBancasDto?.RepartoBancas is { Count: > 0 } bancas)
|
|
||||||
{
|
|
||||||
hasReceivedAnyNewData = true;
|
|
||||||
DateTime fechaTotalizacion;
|
|
||||||
if (!DateTime.TryParse(repartoBancasDto.FechaTotalizacion, out var parsedDate))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se pudo parsear FechaTotalizacion ('{dateString}') para bancas. Usando la hora actual.", repartoBancasDto.FechaTotalizacion);
|
|
||||||
fechaTotalizacion = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fechaTotalizacion = parsedDate.ToUniversalTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var banca in bancas)
|
|
||||||
{
|
|
||||||
if (!agrupacionesEnDb.ContainsKey(banca.IdAgrupacion))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Agrupación con ID {AgrupacionId} ('{Nombre}') no encontrada. Creándola desde los datos de bancas.", banca.IdAgrupacion, banca.NombreAgrupacion);
|
|
||||||
var nuevaAgrupacion = new AgrupacionPolitica
|
|
||||||
{
|
|
||||||
Id = banca.IdAgrupacion,
|
|
||||||
Nombre = banca.NombreAgrupacion,
|
|
||||||
IdTelegrama = banca.IdAgrupacionTelegrama ?? string.Empty
|
|
||||||
};
|
|
||||||
await dbContext.AgrupacionesPoliticas.AddAsync(nuevaAgrupacion, stoppingToken);
|
|
||||||
agrupacionesEnDb.Add(nuevaAgrupacion.Id, nuevaAgrupacion);
|
|
||||||
}
|
|
||||||
|
|
||||||
todasLasProyecciones.Add(new ProyeccionBanca
|
|
||||||
{
|
|
||||||
EleccionId = EleccionId,
|
|
||||||
AmbitoGeograficoId = ambito.Id,
|
|
||||||
AgrupacionPoliticaId = banca.IdAgrupacion,
|
|
||||||
NroBancas = banca.NroBancas,
|
|
||||||
CategoriaId = categoria.Id,
|
|
||||||
FechaTotalizacion = fechaTotalizacion
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasReceivedAnyNewData)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Se recibieron datos válidos de bancas. Procediendo a actualizar la base de datos...");
|
|
||||||
await using var transaction = await dbContext.Database.BeginTransactionAsync(stoppingToken);
|
|
||||||
await dbContext.SaveChangesAsync(stoppingToken); // Guardar nuevas agrupaciones si las hay
|
|
||||||
await dbContext.Database.ExecuteSqlRawAsync("DELETE FROM ProyeccionesBancas WHERE EleccionId = {0}", EleccionId, stoppingToken); // CORRECCIÓN: Borrar solo para la elección actual
|
|
||||||
await dbContext.ProyeccionesBancas.AddRangeAsync(todasLasProyecciones, stoppingToken);
|
|
||||||
await dbContext.SaveChangesAsync(stoppingToken);
|
|
||||||
await transaction.CommitAsync(stoppingToken);
|
|
||||||
_logger.LogInformation("La tabla de proyecciones ha sido actualizada con {count} registros.", todasLasProyecciones.Count);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeo de Bancas completado. No se encontraron datos nuevos, la tabla no fue modificada.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeo de bancas cancelado.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Bancas.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Busca y descarga nuevos telegramas de forma masiva y concurrente.
|
|
||||||
/// Este método crea una lista de todas las combinaciones de Partido/Categoría,
|
|
||||||
/// las consulta a la API con un grado de paralelismo controlado, y cada tarea concurrente
|
|
||||||
/// maneja su propia lógica de descarga y guardado en la base de datos.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="authToken">El token de autenticación válido para la sesión.</param>
|
|
||||||
/// <param name="stoppingToken">El token de cancelación para detener la operación.</param>
|
|
||||||
private async Task SondearNuevosTelegramasAsync(string authToken, CancellationToken stoppingToken)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogInformation("--- Iniciando sondeo de Nuevos Telegramas (modo de bajo perfil) ---");
|
|
||||||
|
|
||||||
using var scope = _serviceProvider.CreateScope();
|
|
||||||
var dbContext = scope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
// La obtención de partidos y categorías no cambia
|
|
||||||
var partidos = await dbContext.AmbitosGeograficos.AsNoTracking()
|
|
||||||
.Where(a => a.NivelId == 30 && a.DistritoId != null && a.SeccionId != null)
|
|
||||||
.ToListAsync(stoppingToken);
|
|
||||||
var categorias = await dbContext.CategoriasElectorales.AsNoTracking().ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
if (!partidos.Any() || !categorias.Any()) return;
|
|
||||||
|
|
||||||
foreach (var partido in partidos)
|
|
||||||
{
|
|
||||||
foreach (var categoria in categorias)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) return;
|
|
||||||
|
|
||||||
var listaTelegramasApi = await _apiService.GetTelegramasTotalizadosAsync(authToken, partido.DistritoId!, partido.SeccionId!, categoria.Id);
|
|
||||||
|
|
||||||
if (listaTelegramasApi is { Count: > 0 })
|
|
||||||
{
|
|
||||||
// Creamos el DbContext para la operación de guardado
|
|
||||||
using var innerScope = _serviceProvider.CreateScope();
|
|
||||||
var innerDbContext = innerScope.ServiceProvider.GetRequiredService<EleccionesDbContext>();
|
|
||||||
|
|
||||||
var idsYaEnDb = await innerDbContext.Telegramas
|
|
||||||
.Where(t => listaTelegramasApi.Contains(t.Id))
|
|
||||||
.Select(t => t.Id).ToListAsync(stoppingToken);
|
|
||||||
|
|
||||||
var nuevosTelegramasIds = listaTelegramasApi.Except(idsYaEnDb).ToList();
|
|
||||||
|
|
||||||
if (nuevosTelegramasIds.Any())
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Se encontraron {count} telegramas nuevos en '{partido}' para '{cat}'. Descargando...", nuevosTelegramasIds.Count, partido.Nombre, categoria.Nombre);
|
|
||||||
|
|
||||||
var originalTimeout = innerDbContext.Database.GetCommandTimeout();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
innerDbContext.Database.SetCommandTimeout(180);
|
|
||||||
_logger.LogDebug("Timeout de BD aumentado a 180s para descarga de telegramas.");
|
|
||||||
|
|
||||||
int contadorLote = 0;
|
|
||||||
const int tamanoLote = 100;
|
|
||||||
|
|
||||||
foreach (var mesaId in nuevosTelegramasIds)
|
|
||||||
{
|
|
||||||
if (stoppingToken.IsCancellationRequested) return;
|
|
||||||
|
|
||||||
var telegramaFile = await _apiService.GetTelegramaFileAsync(authToken, mesaId);
|
|
||||||
if (telegramaFile != null)
|
|
||||||
{
|
|
||||||
var ambitoMesa = await innerDbContext.AmbitosGeograficos.AsNoTracking()
|
|
||||||
.FirstOrDefaultAsync(a => a.MesaId == mesaId, stoppingToken);
|
|
||||||
|
|
||||||
if (ambitoMesa != null)
|
|
||||||
{
|
|
||||||
var nuevoTelegrama = new Telegrama
|
|
||||||
{
|
|
||||||
EleccionId = EleccionId,
|
|
||||||
Id = telegramaFile.NombreArchivo,
|
|
||||||
AmbitoGeograficoId = ambitoMesa.Id,
|
|
||||||
ContenidoBase64 = telegramaFile.Imagen,
|
|
||||||
FechaEscaneo = DateTime.Parse(telegramaFile.FechaEscaneo).ToUniversalTime(),
|
|
||||||
FechaTotalizacion = DateTime.Parse(telegramaFile.FechaTotalizacion).ToUniversalTime()
|
|
||||||
};
|
|
||||||
await innerDbContext.Telegramas.AddAsync(nuevoTelegrama, stoppingToken);
|
|
||||||
contadorLote++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogWarning("No se encontró un ámbito geográfico para la mesa con MesaId {MesaId}. El telegrama no será guardado.", mesaId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await Task.Delay(250, stoppingToken);
|
|
||||||
|
|
||||||
if (contadorLote >= tamanoLote)
|
|
||||||
{
|
|
||||||
await innerDbContext.SaveChangesAsync(stoppingToken);
|
|
||||||
_logger.LogInformation("Guardado un lote de {count} telegramas.", contadorLote);
|
|
||||||
contadorLote = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contadorLote > 0)
|
|
||||||
{
|
|
||||||
await innerDbContext.SaveChangesAsync(stoppingToken);
|
|
||||||
_logger.LogInformation("Guardado el último lote de {count} telegramas.", contadorLote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
innerDbContext.Database.SetCommandTimeout(originalTimeout);
|
|
||||||
_logger.LogDebug("Timeout de BD restaurado a su valor original ({timeout}s).", originalTimeout);
|
|
||||||
}
|
|
||||||
} // Fin del if (nuevosTelegramasIds.Any())
|
|
||||||
|
|
||||||
// Movemos el delay aquí para que solo se ejecute si hubo telegramas en la respuesta de la API
|
|
||||||
await Task.Delay(100, stoppingToken);
|
|
||||||
} // Fin del if (listaTelegramasApi is not null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_logger.LogInformation("Sondeo de Telegramas completado.");
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Sondeo de telegramas cancelado.");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Ocurrió un error CRÍTICO en el sondeo de Telegramas.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user