estadisticas de osp expandida

This commit is contained in:
2026-04-16 16:01:45 -04:00
parent 8f207e675c
commit 548bb0cdb2
4 changed files with 374 additions and 20 deletions

View File

@@ -105,13 +105,21 @@ export class TrainingService {
// Ejecutamos todas las consultas en paralelo con Promise.all para mayor velocidad // Ejecutamos todas las consultas en paralelo con Promise.all para mayor velocidad
const [ const [
totalOspsResult, totalOspsResult,
totalProducersResult, // totalProducersResult,
totalProductsResult, // Nuevo: Calculado desde el JSON totalProductsResult,
statusDistribution, statusDistribution,
activityDistribution, activityDistribution,
typeDistribution, typeDistribution,
stateDistribution, stateDistribution,
yearDistribution, yearDistribution,
ecoSectorDistribution,
productiveSectorDistribution,
centralActivityDistribution,
mainActivityDistribution,
structureTypeDistribution,
isOpenSpaceDistribution,
hasTransportDistribution,
genderResult,
] = await Promise.all([ ] = await Promise.all([
// 1. Total OSPs // 1. Total OSPs
this.drizzle this.drizzle
@@ -120,12 +128,12 @@ export class TrainingService {
.where(whereCondition), .where(whereCondition),
// 2. Total Productores (Columna plana que mantuviste) // 2. Total Productores (Columna plana que mantuviste)
this.drizzle // this.drizzle
.select({ // .select({
sum: sql<number>`SUM(${trainingSurveys.womenCount} + ${trainingSurveys.menCount})`, // sum: sql<number>`SUM(${trainingSurveys.womenCount} + ${trainingSurveys.menCount})`,
}) // })
.from(trainingSurveys) // .from(trainingSurveys)
.where(whereCondition), // .where(whereCondition),
// 3. NUEVO: Total Productos (Contamos el largo del array JSON productList) // 3. NUEVO: Total Productos (Contamos el largo del array JSON productList)
this.drizzle this.drizzle
@@ -145,7 +153,7 @@ export class TrainingService {
.where(whereCondition) .where(whereCondition)
.groupBy(trainingSurveys.currentStatus), .groupBy(trainingSurveys.currentStatus),
// 5. Distribución por Actividad // 5. Distribución por Actividad (General)
this.drizzle this.drizzle
.select({ .select({
name: trainingSurveys.productiveActivity, name: trainingSurveys.productiveActivity,
@@ -188,11 +196,90 @@ export class TrainingService {
.where(whereCondition) .where(whereCondition)
.groupBy(trainingSurveys.companyConstitutionYear) .groupBy(trainingSurveys.companyConstitutionYear)
.orderBy(trainingSurveys.companyConstitutionYear), .orderBy(trainingSurveys.companyConstitutionYear),
// 9. Distribución por Sector Económico
this.drizzle
.select({
name: trainingSurveys.ecoSector,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.ecoSector),
// 10. Distribución por Sector Productivo
this.drizzle
.select({
name: trainingSurveys.productiveSector,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.productiveSector),
// 11. Distribución por Actividad Central Productiva
this.drizzle
.select({
name: trainingSurveys.centralProductiveActivity,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.centralProductiveActivity),
// 12. Distribución por Actividad Productiva Principal
this.drizzle
.select({
name: trainingSurveys.mainProductiveActivity,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.mainProductiveActivity),
// 13. Distribución por Tipo de Estructura
this.drizzle
.select({
name: trainingSurveys.structureType,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.structureType),
// 14. Distribución por Espacio Abierto
this.drizzle
.select({
name: sql<string>`case when ${trainingSurveys.isOpenSpace} then 'Sí' else 'No' end`,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.isOpenSpace),
// 15. Distribución por Transporte
this.drizzle
.select({
name: sql<string>`case when ${trainingSurveys.hasTransport} then 'Sí' else 'No' end`,
value: sql<number>`count(*)`,
})
.from(trainingSurveys)
.where(whereCondition)
.groupBy(trainingSurveys.hasTransport),
// 16. Distribución por Género
this.drizzle
.select({
women: sql<number>`sum(${trainingSurveys.womenCount})`,
men: sql<number>`sum(${trainingSurveys.menCount})`,
})
.from(trainingSurveys)
.where(whereCondition),
]); ]);
return { return {
totalOsps: Number(totalOspsResult[0]?.count || 0), totalOsps: Number(totalOspsResult[0]?.count || 0),
totalProducers: Number(totalProducersResult[0]?.sum || 0), // totalProducers: Number(totalProducersResult[0]?.sum || 0),
totalProducts: Number(totalProductsResult[0]?.sum || 0), // Dato extraído del JSON totalProducts: Number(totalProductsResult[0]?.sum || 0), // Dato extraído del JSON
statusDistribution: statusDistribution.map((item) => ({ statusDistribution: statusDistribution.map((item) => ({
@@ -215,6 +302,38 @@ export class TrainingService {
...item, ...item,
value: Number(item.value), value: Number(item.value),
})), })),
ecoSectorDistribution: ecoSectorDistribution.map((item) => ({
...item,
value: Number(item.value),
})),
productiveSectorDistribution: productiveSectorDistribution.map((item) => ({
...item,
value: Number(item.value),
})),
centralActivityDistribution: centralActivityDistribution.map((item) => ({
...item,
value: Number(item.value),
})),
mainActivityDistribution: mainActivityDistribution.map((item) => ({
...item,
value: Number(item.value),
})),
structureTypeDistribution: structureTypeDistribution.map((item) => ({
...item,
value: Number(item.value),
})),
isOpenSpaceDistribution: isOpenSpaceDistribution.map((item) => ({
...item,
value: Number(item.value),
})),
hasTransportDistribution: hasTransportDistribution.map((item) => ({
...item,
value: Number(item.value),
})),
genderDistribution: [
{ name: 'Mujeres', value: Number(genderResult[0]?.women || 0) },
{ name: 'Hombres', value: Number(genderResult[0]?.men || 0) },
],
}; };
} }

View File

@@ -9,11 +9,11 @@ export const metadata: Metadata = {
export default function SocioproductivaStatisticsPage() { export default function SocioproductivaStatisticsPage() {
return ( return (
<PageContainer> // <PageContainer>
<div className="w-full"> <div className="w-full p-6">
<h1 className="text-2xl font-bold mb-6">Estadísticas Socioproductivas</h1> <h1 className="text-2xl font-bold mb-6">Estadísticas Socioproductivas</h1>
<TrainingStatistics /> <TrainingStatistics />
</div> </div>
</PageContainer> // </PageContainer>
); );
} }

View File

@@ -103,12 +103,20 @@ export function TrainingStatistics() {
const { const {
totalOsps, totalOsps,
totalProducers, // totalProducers,
statusDistribution, statusDistribution,
activityDistribution, activityDistribution,
typeDistribution, typeDistribution,
stateDistribution, stateDistribution,
yearDistribution, yearDistribution,
ecoSectorDistribution,
productiveSectorDistribution,
centralActivityDistribution,
mainActivityDistribution,
structureTypeDistribution,
isOpenSpaceDistribution,
hasTransportDistribution,
genderDistribution,
} = data; } = data;
const COLORS = [ const COLORS = [
@@ -230,7 +238,8 @@ export function TrainingStatistics() {
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
<Card>
{/* <Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium"> <CardTitle className="text-sm font-medium">
Total de Productores Total de Productores
@@ -242,7 +251,8 @@ export function TrainingStatistics() {
Productores asociados Productores asociados
</p> </p>
</CardContent> </CardContent>
</Card> </Card> */}
<Card className="col-span-full"> <Card className="col-span-full">
<CardHeader> <CardHeader>
@@ -367,6 +377,223 @@ export function TrainingStatistics() {
</ResponsiveContainer> </ResponsiveContainer>
</CardContent> </CardContent>
</Card> </Card>
{/* ECO SECTOR DISTRIBUTION */}
<Card className="col-span-full">
<CardHeader>
<CardTitle>Sector Económico</CardTitle>
<CardDescription>
Distribución por sector económico
</CardDescription>
</CardHeader>
<CardContent className="h-[400px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={ecoSectorDistribution}
layout="vertical"
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis type="number" />
<YAxis dataKey="name" type="category" width={150} />
<Tooltip wrapperStyle={{ color: '#000' }} />
<Legend />
<Bar dataKey="value" fill="#0088FE" name="Cantidad" />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* PRODUCTIVE SECTOR DISTRIBUTION */}
<Card className="col-span-full">
<CardHeader>
<CardTitle>Sector Productivo</CardTitle>
<CardDescription>
Distribución por sector productivo
</CardDescription>
</CardHeader>
<CardContent className="h-[400px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={productiveSectorDistribution}
layout="vertical"
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis type="number" />
<YAxis dataKey="name" type="category" width={150} />
<Tooltip wrapperStyle={{ color: '#000' }} />
<Legend />
<Bar dataKey="value" fill="#00C49F" name="Cantidad" />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* CENTRAL PRODUCTIVE ACTIVITY DISTRIBUTION */}
<Card className="col-span-full">
<CardHeader>
<CardTitle>Actividad Central Productiva</CardTitle>
<CardDescription>
Distribución por actividad central productiva
</CardDescription>
</CardHeader>
<CardContent className="h-[400px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={centralActivityDistribution}
layout="vertical"
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis type="number" />
<YAxis dataKey="name" type="category" width={150} />
<Tooltip wrapperStyle={{ color: '#000' }} />
<Legend />
<Bar dataKey="value" fill="#FF8042" name="Cantidad" />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* MAIN PRODUCTIVE ACTIVITY DISTRIBUTION */}
<Card className="col-span-full">
<CardHeader>
<CardTitle>Actividad Productiva Principal</CardTitle>
<CardDescription>
Distribución por actividad productiva principal
</CardDescription>
</CardHeader>
<CardContent className="h-[400px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={mainActivityDistribution}
layout="vertical"
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis type="number" />
<YAxis dataKey="name" type="category" width={150} />
<Tooltip wrapperStyle={{ color: '#000' }} />
<Legend />
<Bar dataKey="value" fill="#8884d8" name="Cantidad" />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* STRUCTURE TYPE DISTRIBUTION */}
<Card className="col-span-full lg:col-span-1">
<CardHeader>
<CardTitle>Tipo de Estructura</CardTitle>
<CardDescription>Distribución física</CardDescription>
</CardHeader>
<CardContent className="h-[400px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={structureTypeDistribution}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip wrapperStyle={{ color: '#000' }} />
<Legend />
<Bar dataKey="value" fill="#82ca9d" name="Cantidad" />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* GENDER DISTRIBUTION */}
<Card className="col-span-full lg:col-span-1">
<CardHeader>
<CardTitle>Distribución de Género</CardTitle>
<CardDescription>Conteo total por género</CardDescription>
</CardHeader>
<CardContent className="h-[400px]">
<ResponsiveContainer width="100%" height="100%">
<BarChart
data={genderDistribution}
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="name" />
<YAxis />
<Tooltip wrapperStyle={{ color: '#000' }} />
<Legend />
<Bar dataKey="value" fill="#8884d8" name="Personas" />
</BarChart>
</ResponsiveContainer>
</CardContent>
</Card>
{/* OPEN SPACE AND TRANSPORT (PIE CHARTS) */}
<div className="col-span-full grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<CardHeader>
<CardTitle>Espacio Abierto</CardTitle>
<CardDescription>¿Poseen áreas libres?</CardDescription>
</CardHeader>
<CardContent className="h-[300px]">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={isOpenSpaceDistribution}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
fill="#8884d8"
paddingAngle={5}
dataKey="value"
>
{isOpenSpaceDistribution.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={COLORS[(index + 2) % COLORS.length]}
/>
))}
</Pie>
<Tooltip wrapperStyle={{ color: '#000' }} />
<Legend />
</PieChart>
</ResponsiveContainer>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Transporte</CardTitle>
<CardDescription>¿Tienen vehículo propio?</CardDescription>
</CardHeader>
<CardContent className="h-[300px]">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={hasTransportDistribution}
cx="50%"
cy="50%"
innerRadius={60}
outerRadius={80}
fill="#8884d8"
paddingAngle={5}
dataKey="value"
>
{hasTransportDistribution.map((entry, index) => (
<Cell
key={`cell-${index}`}
fill={COLORS[(index + 4) % COLORS.length]}
/>
))}
</Pie>
<Tooltip wrapperStyle={{ color: '#000' }} />
<Legend />
</PieChart>
</ResponsiveContainer>
</CardContent>
</Card>
</div>
</div> </div>
</div> </div>
); );

View File

@@ -10,13 +10,21 @@ export const statisticsItemSchema = z.object({
export const trainingStatisticsSchema = z.object({ export const trainingStatisticsSchema = z.object({
totalOsps: z.number(), totalOsps: z.number(),
totalProducers: z.number(), // totalProducers: z.number(),
totalProducts: z.number(), totalProducts: z.number(),
statusDistribution: z.array(statisticsItemSchema), statusDistribution: z.array(statisticsItemSchema),
activityDistribution: z.array(statisticsItemSchema), activityDistribution: z.array(statisticsItemSchema),
typeDistribution: z.array(statisticsItemSchema), typeDistribution: z.array(statisticsItemSchema),
stateDistribution: z.array(statisticsItemSchema), stateDistribution: z.array(statisticsItemSchema),
yearDistribution: z.array(statisticsItemSchema), yearDistribution: z.array(statisticsItemSchema),
ecoSectorDistribution: z.array(statisticsItemSchema),
productiveSectorDistribution: z.array(statisticsItemSchema),
centralActivityDistribution: z.array(statisticsItemSchema),
mainActivityDistribution: z.array(statisticsItemSchema),
structureTypeDistribution: z.array(statisticsItemSchema),
isOpenSpaceDistribution: z.array(statisticsItemSchema),
hasTransportDistribution: z.array(statisticsItemSchema),
genderDistribution: z.array(statisticsItemSchema),
}); });
export type TrainingStatisticsData = z.infer<typeof trainingStatisticsSchema>; export type TrainingStatisticsData = z.infer<typeof trainingStatisticsSchema>;