correciones al formulario osp

This commit is contained in:
2026-01-28 22:42:19 -04:00
parent d2908f1e4c
commit 8efe595f73
23 changed files with 9036 additions and 1685 deletions

View File

@@ -74,95 +74,121 @@ export class TrainingService {
const filters: SQL[] = [];
if (startDate) {
if (startDate)
filters.push(gte(trainingSurveys.visitDate, new Date(startDate)));
}
if (endDate) {
if (endDate)
filters.push(lte(trainingSurveys.visitDate, new Date(endDate)));
}
if (stateId) {
filters.push(eq(trainingSurveys.state, stateId));
}
if (municipalityId) {
if (stateId) filters.push(eq(trainingSurveys.state, stateId));
if (municipalityId)
filters.push(eq(trainingSurveys.municipality, municipalityId));
}
if (parishId) {
filters.push(eq(trainingSurveys.parish, parishId));
}
if (ospType) {
filters.push(eq(trainingSurveys.ospType, ospType));
}
if (parishId) filters.push(eq(trainingSurveys.parish, parishId));
if (ospType) filters.push(eq(trainingSurveys.ospType, ospType));
const whereCondition = filters.length > 0 ? and(...filters) : undefined;
const totalOspsResult = await this.drizzle
.select({ count: sql<number>`count(*)` })
.from(trainingSurveys)
.where(whereCondition);
const totalOsps = Number(totalOspsResult[0].count);
// Ejecutamos todas las consultas en paralelo con Promise.all para mayor velocidad
const [
totalOspsResult,
totalProducersResult,
totalProductsResult, // Nuevo: Calculado desde el JSON
statusDistribution,
activityDistribution,
typeDistribution,
stateDistribution,
yearDistribution,
] = await Promise.all([
// 1. Total OSPs
this.drizzle
.select({ count: sql<number>`count(*)` })
.from(trainingSurveys)
.where(whereCondition),
const totalProducersResult = await this.drizzle
.select({ sum: sql<number>`sum(${trainingSurveys.producerCount})` })
.from(trainingSurveys)
.where(whereCondition);
const totalProducers = Number(totalProducersResult[0].sum || 0);
// 2. Total Productores (Columna plana que mantuviste)
this.drizzle
.select({
sum: sql<number>`
SUM(
(
SELECT SUM(
COALESCE((item->>'menCount')::int, 0) +
COALESCE((item->>'womenCount')::int, 0)
)
FROM jsonb_array_elements(${trainingSurveys.productList}) as item
)
)
`,
})
.from(trainingSurveys)
.where(whereCondition),
const statusDistribution = await this.drizzle
.select({
name: trainingSurveys.currentStatus,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.currentStatus);
// 3. NUEVO: Total Productos (Contamos el largo del array JSON productList)
this.drizzle
.select({
sum: sql<number>`sum(jsonb_array_length(${trainingSurveys.productList}))`,
})
.from(trainingSurveys)
.where(whereCondition),
const activityDistribution = await this.drizzle
.select({
name: trainingSurveys.productiveActivity,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.productiveActivity);
// 4. Distribución por Estatus
this.drizzle
.select({
name: trainingSurveys.currentStatus,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.currentStatus),
const typeDistribution = await this.drizzle
.select({
name: trainingSurveys.ospType,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.ospType);
// 5. Distribución por Actividad
this.drizzle
.select({
name: trainingSurveys.productiveActivity,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.productiveActivity),
// New Aggregations
const stateDistribution = await this.drizzle
.select({
name: states.name,
value: sql<number>`count(${trainingSurveys.id})`,
})
.from(trainingSurveys)
.leftJoin(states, eq(trainingSurveys.state, states.id))
.where(whereCondition)
.groupBy(states.name);
// 6. Distribución por Tipo
this.drizzle
.select({
name: trainingSurveys.ospType,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.ospType),
const yearDistribution = await this.drizzle
.select({
name: sql<string>`cast(${trainingSurveys.companyConstitutionYear} as text)`,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.companyConstitutionYear)
.orderBy(trainingSurveys.companyConstitutionYear);
// 7. Distribución por Estado (CORREGIDO con COALESCE)
this.drizzle
.select({
// Si states.name es NULL, devuelve 'Sin Asignar'
name: sql<string>`COALESCE(${states.name}, 'Sin Asignar')`,
value: sql<number>`count(${trainingSurveys.id})`,
})
.from(trainingSurveys)
.leftJoin(states, eq(trainingSurveys.state, states.id))
.where(whereCondition)
// Importante: Agrupar también por el resultado del COALESCE o por states.name
.groupBy(states.name),
// 8. Distribución por Año
this.drizzle
.select({
name: sql<string>`cast(${trainingSurveys.companyConstitutionYear} as text)`,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.companyConstitutionYear)
.orderBy(trainingSurveys.companyConstitutionYear),
]);
return {
totalOsps,
totalProducers,
totalOsps: Number(totalOspsResult[0]?.count || 0),
totalProducers: Number(totalProducersResult[0]?.sum || 0),
totalProducts: Number(totalProductsResult[0]?.sum || 0), // Dato extraído del JSON
statusDistribution: statusDistribution.map((item) => ({
...item,
value: Number(item.value),
@@ -239,16 +265,30 @@ export class TrainingService {
createTrainingDto: CreateTrainingDto,
files: Express.Multer.File[],
) {
// 1. Guardar fotos
const photoPaths = await this.saveFiles(files);
// 2. Extraer solo visitDate para formatearlo.
// Ya NO extraemos state, municipality, etc. porque no vienen en el DTO.
const { visitDate, ...rest } = createTrainingDto;
const [newRecord] = await this.drizzle
.insert(trainingSurveys)
.values({
...createTrainingDto,
visitDate: new Date(createTrainingDto.visitDate),
photo1: photoPaths[0] || '',
photo2: photoPaths[1] || null,
photo3: photoPaths[2] || null,
// Insertamos el resto de datos planos y las listas (arrays)
...rest,
// Conversión de fecha
visitDate: new Date(visitDate),
// 3. Asignar fotos de forma segura
photo1: photoPaths[0] ?? null,
photo2: photoPaths[1] ?? null,
photo3: photoPaths[2] ?? null,
// NOTA: Como las columnas state, municipality, etc. en la BD
// tienen "onDelete: set null" o son nullables, al no pasarlas aquí,
// Postgres automáticamente las guardará como NULL.
})
.returning();