1. Stack Tecnológico
Frontend
| Tecnología | Versión |
|---|---|
| React | 19.2.3 |
| Vite | 7.2.4 |
| TypeScript | 5.9.3 |
| Tailwind CSS | 4.1.17 |
| Recharts | 3.7.0 |
| Lucide React | 0.563.0 |
| SheetJS (xlsx) | 0.18.5 |
Backend / Infraestructura
| Tecnología | Detalle |
|---|---|
| Supabase | @supabase/supabase-js 2.95 |
| PostgreSQL | 14+ (Supabase managed) |
| Auth | Supabase Auth + RLS |
| Edge Functions | Deno (alertas email) |
| Deploy | Vercel (rama main) |
| Build | vite-plugin-singlefile |
2. Arquitectura del Sistema
Diagrama de capas:
┌─────────────────────────────────────────────────────────┐
│ FRONTEND (SPA) │
│ React 19 + TypeScript + Tailwind + Recharts │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌───────────┐ │
│ │Components│ │ Hooks │ │ Types │ │ Utils │ │
│ └─────┬────┘ └─────┬─────┘ └──────────┘ └───────────┘ │
│ │ │ │
│ ┌─────▼────────────▼─────────────────────────────────┐ │
│ │ Service Layer (services.ts) │ │
│ │ vpService │ valorService │ periodoService │ ... │ │
│ └────────────────────┬───────────────────────────────┘ │
│ │ │
│ ┌────────────────────▼───────────────────────────────┐ │
│ │ Supabase Client (supabase.ts + auth.ts) │ │
│ └────────────────────┬───────────────────────────────┘ │
└───────────────────────┼─────────────────────────────────┘
│ HTTPS / WebSocket
┌───────────────────────▼─────────────────────────────────┐
│ SUPABASE CLOUD │
│ ┌──────────┐ ┌───────────┐ ┌────────────────────────┐ │
│ │ Auth │ │ PostgREST│ │ Edge Functions │ │
│ │ (JWT) │ │ (API) │ │ (send-alert-email) │ │
│ └────┬─────┘ └─────┬─────┘ └────────────────────────┘ │
│ │ │ │
│ ┌────▼─────────────▼────────────────────────────────┐ │
│ │ PostgreSQL 14+ │ │
│ │ 21 tablas │ 3 vistas │ 4 funciones │ RLS activo │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Patrón arquitectónico: SPA client-side con backend serverless (BaaS). Toda la lógica de negocio vive en la capa de servicios del frontend y en triggers/funciones de PostgreSQL. No hay servidor de aplicaciones intermedio.
3. Estructura del Proyecto
src/
├── App.tsx # Componente raíz, navegación state-driven
├── main.tsx # Punto de entrada React
├── index.css # Estilos globales + Tailwind
├── components/ # 27 pantallas/vistas
│ ├── LoginScreen.tsx # Login + conexión Supabase/Demo
│ ├── DashboardGlobal.tsx # Vista presidencia
│ ├── VPView.tsx # Vista vicepresidencia
│ ├── GerenciaView.tsx # Vista gerencia + carga datos
│ ├── CorporateScorecard # BSC con 4 perspectivas
│ ├── AnalisisHistorico # Gráficas de evolución
│ ├── AnalisisPredictivo # Proyecciones ML
│ ├── AlertasEmail.tsx # Configuración alertas email
│ ├── AlertasGestion.tsx # Notificaciones en pantalla
│ ├── AdminUsuarios.tsx # CRUD usuarios
│ ├── ReporteEjecutivo # Reportes consolidados
│ ├── GestionRiesgos # Matriz de riesgos
│ ├── ImportadorDatos # Importación CSV global
│ ├── CargaMasiva.tsx # Carga CSV por gerencia
│ └── ... # 12 componentes más
├── lib/
│ ├── supabase.ts # Cliente Supabase + tipos DB
│ ├── auth.ts # Servicio autenticación
│ └── services.ts # 18 servicios de negocio
├── types/
│ └── index.ts # Tipos TypeScript frontend
├── data/
│ ├── organizacion.ts # Estructura VP/Gerencias hardcoded (demo)
│ └── corporateScorecard # Datos BSC demo
├── hooks/
│ └── useSupabase.ts # Hook conexión Supabase
├── config/
│ └── navigation.ts # Configuración menú lateral
└── utils/
└── cn.ts # Utilidad clases Tailwind condicionales
database/
├── schema.sql # DDL completo (21 tablas, 3 vistas, triggers)
├── functions/
│ └── is_admin.sql # Función SECURITY DEFINER
├── policies/
│ ├── base_multitenant_policies.sql # RLS todas las tablas
│ └── usuarios_prod.sql # RLS tabla usuarios
├── seeds/ # Datos iniciales
├── migrations/ # Migraciones históricas
└── maintenance/ # Scripts de limpieza
4. Base de Datos (PostgreSQL)
4.1 Tablas Principales
| Tabla | Columnas Clave | Relaciones |
|---|---|---|
| vicepresidencias | id, codigo UNIQUE, nombre,
icono, color, orden, activo
|
→ gerencias (1:N) |
| gerencias | id, vicepresidencia_id FK CASCADE,
codigo UNIQUE, nombre
|
→ vicepresidencias (N:1) |
| roles | id, nombre UNIQUE, permisos JSONB
|
→ usuarios (1:N) |
| usuarios | email UNIQUE, rol_id FK,
vicepresidencia_id FK, gerencia_id FK,
auth_user_id
|
→ roles, vicepresidencias, gerencias |
| perspectivas_bsc | codigo UNIQUE (FIN/CLI/PRO/APR), nombre,
color, orden
|
→ indicadores_definicion (1:N) |
| indicadores_definicion | codigo UNIQUE, perspectiva_id,
gerencia_id, meta_* DECIMAL(15,4),
umbral_rojo/amarillo, tendencia, nivel
|
→ perspectivas, gerencias, VP |
| periodos | año+mes UNIQUE, estado
CHECK(abierto/cerrado/bloqueado/pendiente) |
→ indicadores_valores (1:N) |
| indicadores_valores | indicador_id+periodo_id UNIQUE,
valor_meta/real, porcentaje_cumplimiento,
estado, estado_validacion
|
→ indicadores_definicion, periodos |
| planes_accion | indicador_id+periodo_id UNIQUE,
gerencia_id, prioridad, estado,
progreso 0-100
|
→ indicadores, periodos, gerencias |
| riesgos | codigo UNIQUE, probabilidad/impacto 1-5,
nivel GENERATED, indicadores_asociados UUID[]
|
→ vicepresidencias |
| alertas | severidad, titulo, leida,
reconocida
|
→ VP, gerencia, indicador_valor |
| correlaciones | indicador_causa_id+efecto_id UNIQUE,
tipo_relacion, fuerza, validada
|
→ indicadores_definicion (2x) |
4.2 Vistas Materializadas
v_indicadores_actual — KPIs con valores del período abierto + joins a todas las
tablas de lookup.
v_resumen_vp — Conteo de indicadores por estado (verde/amarillo/rojo) y promedio
de cumplimiento por VP.
v_alertas_pendientes — Alertas no leídas ordenadas por severidad.
4.3 Índices (16)
-- Principales índices para rendimiento
idx_gerencias_vp_id ON gerencias(vicepresidencia_id)
idx_usuarios_email ON usuarios(email)
idx_usuarios_rol ON usuarios(rol_id)
idx_ind_def_perspectiva ON indicadores_definicion(perspectiva_id)
idx_ind_def_gerencia ON indicadores_definicion(gerencia_id)
idx_ind_val_indicador ON indicadores_valores(indicador_id)
idx_ind_val_periodo ON indicadores_valores(periodo_id)
idx_ind_val_estado ON indicadores_valores(estado)
idx_alertas_severidad ON alertas(severidad)
idx_alertas_leida ON alertas(leida)
idx_planes_gerencia_periodo ON planes_accion(gerencia_id, periodo_id)
5. Seguridad — Row-Level Security
5.1 Funciones Helper (SECURITY DEFINER)
-- Determina si el usuario actual es admin
is_admin(p_uid uuid) → boolean
-- Obtiene el rol del usuario autenticado
get_rol_actual() → text
-- Obtiene la VP asignada al usuario
get_vp_actual() → uuid
-- Obtiene la gerencia asignada al usuario
get_gerencia_actual() → uuid
5.2 Políticas por Tabla
| Tabla | SELECT | INSERT | UPDATE | DELETE |
|---|---|---|---|---|
| vicepresidencias | Todos | Admin | Admin | Admin |
| gerencias | Todos | Admin | Admin | — |
| indicadores_definicion | Según rol/área | Admin | Admin | — |
| indicadores_valores | Según rol/área | Admin + Gerencia propia | Según rol + estado_validacion | — |
| periodos | Todos | Admin | Admin | Admin |
| usuarios | Admin ∨ propio | Admin | Admin ∨ propio | — |
Importante:
admin_gerencia solo puede actualizar valores cuando
estado_validacion IN ('pendiente','rechazado'). Esto impide modificar datos ya
validados o en proceso de aprobación.
6. Autenticación
El sistema usa Supabase Auth con JWT. El flujo de login:
1. Usuario ingresa email + password
2. supabase.auth.signInWithPassword() → JWT + Session
3. authService.login() busca en tabla 'usuarios' por email
4. Si auth_user_id es NULL → auto-vincula auth.uid() al registro
5. Verifica usuario.activo === true
6. Carga joins: rol, vicepresidencia, gerencia
7. Retorna { auth: AuthResponse, usuario: DBUsuario }
Métodos del authService
| Método | Descripción |
|---|---|
login(email, password) |
Autenticar + vincular + cargar usuario completo |
logout() |
Cerrar sesión Supabase Auth |
getSession() |
Obtener sesión activa (JWT) |
restoreUser() |
Restaurar usuario desde sesión persistida |
requestPasswordReset(email) |
Enviar enlace de reset por email |
updatePassword(newPassword) |
Actualizar contraseña post-reset |
7. Capa de Servicios
Todos los servicios están en src/lib/services.ts.
Cada entidad del dominio tiene su propio objeto de servicio con métodos CRUD y operaciones
especializadas.
Servicios Disponibles (18)
vpService
CRUD Vicepresidencias (soft-delete)
gerenciaService
CRUD Gerencias con joins a VP
indicadorService
CRUD definiciones de KPIs
valorService
CRUD valores + validación + aprobación
periodoService
Gestión períodos de medición
usuarioService
CRUD usuarios + último acceso
planAccionService
Planes correctivos con progreso
riesgoService
Matriz de riesgos CRUD
alertaService
Alertas en pantalla + reconocimiento
alertaEmailService
Plantillas + configs + Edge Function
correlacionService
Correlaciones cruzadas entre KPIs
dashboardService
Queries agregadas para dashboards
bscService
Perspectivas + objetivos estratégicos
cargaMasivaService
Procesamiento CSV → indicadores + valores
auditService
Consulta audit_log con filtros
calidadDatosService
Resumen calidad + anomalías
rolService
Catálogo de roles
perspectivaService
Catálogo perspectivas BSC
valorService — Métodos Clave
El servicio más complejo, maneja el ciclo de vida completo de los valores:
// Flujo de validación
valorService.enviarAValidacion(gerenciaId, periodoId) // pendiente → en_validacion
valorService.aprobarGerencia(gerenciaId, periodoId, validadoPorId) // → validado
valorService.rechazarValor(valorId, motivoRechazo, rechazadoPorId) // → rechazado
// Bulk operations
valorService.upsert(valores[]) // Conflict on indicador_id+periodo_id
valorService.deleteByPeriodo(periodoId, gerenciaId?) // Returns count
// Queries especializadas
valorService.getAllHistorico(limit=5000) // Para análisis de tendencias
valorService.getPendientesAprobacionPorVP(vpId, periodoId) // Para VP
8. Sistema de Tipos
Dos capas de tipos: DB types en
supabase.ts (prefijo DB*) para comunicación con Supabase, y
frontend types en types/index.ts para lógica de presentación.
Tipos Clave
// Estado del semáforo
type EstadoSemaforo = 'verde' | 'amarillo' | 'rojo'
// Perspectivas BSC
type PerspectivaBSC = 'financiera' | 'clientes' | 'procesos' | 'aprendizaje'
// Roles de usuario
type RolUsuario = 'presidencia' | 'vicepresidencia' | 'gerencia'
| 'admin_global' | 'admin_vp' | 'admin_gerencia' | 'visualizador'
// Tendencia de indicador (DB)
type Tendencia = 'mayor_mejor' | 'menor_mejor' | 'objetivo'
// Estado de período (DB)
type EstadoPeriodo = 'abierto' | 'cerrado' | 'bloqueado' | 'pendiente'
// Estado de validación (DB)
type EstadoValidacion = 'pendiente' | 'en_validacion' | 'validado' | 'rechazado'
9. Triggers y Funciones PostgreSQL
Función: calcular_estado_indicador
calcular_estado_indicador(
p_valor_real DECIMAL,
p_meta DECIMAL,
p_umbral_rojo INT,
p_umbral_amarillo INT,
p_tendencia TEXT
) → TEXT
-- Lógica:
-- Si valor_real IS NULL → 'sin_dato'
-- Si tendencia = 'mayor_mejor': cumplimiento = (real / meta) * 100
-- Si tendencia = 'menor_mejor': cumplimiento = LEAST(100, (meta / real) * 100)
-- cumplimiento >= umbral_amarillo → 'verde'
-- cumplimiento >= umbral_rojo → 'amarillo'
-- else → 'rojo'
Triggers Automáticos
| Trigger | Tabla | Evento | Acción |
|---|---|---|---|
tr_calcular_estado |
indicadores_valores | BEFORE INSERT/UPDATE | Calcula porcentaje_cumplimiento, desviacion,
estado
|
tr_crear_alerta |
indicadores_valores | AFTER INSERT/UPDATE | Crea alerta automática cuando estado = 'rojo' |
tr_updated_at_* |
5 tablas | BEFORE UPDATE | Actualiza updated_at = NOW() |
10. Edge Functions (Supabase)
send-alert-email
Función Deno desplegada en Supabase Edge Functions. Gestiona el envío de correos electrónicos de alerta.
// Modos de invocación:
// 1. Envío de prueba
{ configId: "uuid", test: true }
// 2. Evaluación masiva de todas las reglas configuradas
{ evalAll: true }
// 3. Envío directo ad-hoc
{ direct: true, to: ["email@..."], subject: "...", html: "..." }
Las plantillas de email se
configuran desde la pantalla "Alertas Email" con variables dinámicas como
{{indicador}}, {{valor}}, {{meta}}, etc.
11. Caché y Rendimiento
El sistema implementa un caché en memoria con TTL de 30 segundos para reducir llamadas redundantes a Supabase.
// Implementación en services.ts
const cache = new Map<string, { data: any, timestamp: number }>()
const CACHE_TTL = 30_000 // 30 segundos
function getCached<T>(key: string): T | null
function setCache(key: string, data: any): void
function invalidateCache(prefix?: string): void // Limpia por prefijo
Servicios con caché:
valorService.getByPeriodo,
valorService.getAllHistorico. Las mutaciones (create/update/delete) invalidan el caché
automáticamente.
12. Variables de Entorno
| Variable | Requerida | Descripción |
|---|---|---|
VITE_SUPABASE_URL |
Sí | URL del proyecto Supabase
(https://xxx.supabase.co) |
VITE_SUPABASE_ANON_KEY |
Sí | Clave pública (anon key) del proyecto Supabase |
Las variables con prefijo
VITE_ se exponen al cliente. La seguridad real la proporciona RLS en PostgreSQL,
no la ocultación de la anon key.
13. Despliegue
Vercel (Producción)
# Build command
npm run build
# Output: dist/ → single HTML file (vite-plugin-singlefile)
# Rama de deploy: main
# Repo: hermeshs34/Indicadores_Empresariales
Desarrollo Local
# Instalar dependencias
npm install
# Crear .env con las variables de Supabase
echo "VITE_SUPABASE_URL=https://fciaudxeuycqtuzyurnb.supabase.co" > .env
echo "VITE_SUPABASE_ANON_KEY=tu-anon-key" >> .env
# Iniciar servidor de desarrollo
npm run dev
# Verificar tipos TypeScript
npm run typecheck
# Ejecutar tests
npm test
14. Testing
El proyecto usa Vitest como framework de testing con
@testing-library/react para componentes.
# Ejecutar tests
npm test
# Tests con coverage
npx vitest --coverage
# Archivo de tests: tests/smoke.test.ts
Estrategia de testing:
- Smoke tests para verificar que la app renderiza sin errores
- Tests de servicios mockeanodo el cliente Supabase
- Validación de tipos con
tsc --noEmit
15. Migraciones
Las migraciones se aplican manualmente en el SQL Editor de
Supabase. Se mantienen en database/migrations/.
| Migración | Descripción |
|---|---|
2026-03-28-add-auth-user-id.sql |
Agrega columna auth_user_id a tabla
usuarios para vincular con Supabase Auth
|
Procedimiento de migración:
- Crear archivo SQL en
database/migrations/con fecha en nombre - Probar en Supabase Dashboard → SQL Editor
- Verificar que no rompe RLS policies existentes
- Actualizar
schema.sqlpara reflejar el estado final