VisiΓ³n TΓ©cnica
Stack
Arquitectura
Frontend
Backend
DB
API
PWA
Seguridad
Despliegue
Roadmap
1 VisiΓ³n TΓ©cnica General
Objetivo: Construir una PWA robusta, escalable y offline-first para gestiΓ³n de ayuda humanitaria en emergencias.
β‘ Principios: Offline First Β· Real Time Β· Mobile First Β· Seguridad RLS Β· Escalabilidad Β· CΓ³digo Limpio
π¦
Offline First
Funcionalidad sin conexiΓ³n
β‘
Real Time
Actualizaciones en vivo
π±
Mobile First
Prioridad mΓ³vil
π
Seguridad
RLS + JWT + Zod
Restricciones TΓ©cnicas
Hosting: cPanel con recursos limitados
Equipo: Voluntarios sin experiencia tΓ©cnica
Tiempo: ImplementaciΓ³n urgente (dΓas, no meses)
Presupuesto: Cero ($0) - Todo gratuito/open source
2 Stack TecnolΓ³gico
Frontend
βοΈ
React
18.3.1
Framework principal
β‘
Vite
5.0.0
Build tool rΓ‘pido
π¨
Tailwind CSS
3.4.0
Estilos rΓ‘pidos
π§
React Router
6.23.0
NavegaciΓ³n SPA
πΊοΈ
Leaflet
1.9.4
Mapas gratuitos
π
React Hook Form
7.51.0
Formularios eficientes
β
Zod
3.23.0
ValidaciΓ³n TypeScript
π¦
React Query
5.40.0
CachΓ© y estado
Backend & Servicios
ποΈ
Supabase
Postgres + Auth + Storage + Realtime
π
PostgreSQL
Base de datos relacional
π
Supabase Auth
JWT + Email/Password
π
OpenStreetMap
Mapas gratuitos sin lΓmites
3 Arquitectura de la AplicaciΓ³n
Diagrama de Arquitectura
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β CLIENTE (PWA) β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β β React SPA - Vite Build β β
β β ββββββββββββββββββββββββββββββββββββββ β β
β β βDashboardβInventarβ Mapa βReport β β β
β β ββββββββββββββββββββββββββββββββββββββ β β
β β ββββββββββββββββββββββββββββββββββββββ β β
β β βAfectadoβDonacionβVoluntarβ Ayuda β β β
β β ββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β β React Query (CachΓ© y Estado) β β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β β Supabase Client (API) β β
β ββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββ
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β SUPABASE (Backend) β
β ββββββββββββββββββββββββββββββββββββββββββββ β
β β PostgreSQL + RLS + Auth + Storage β β
β ββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Estructura de Carpetas
src/
βββ components/
β βββ ui/ # Botones, inputs, cards
β βββ layout/ # Navbar, Sidebar, Footer
β βββ dashboard/
β βββ inventario/
β βββ afectados/
β βββ donaciones/
β βββ voluntarios/
β βββ asignaciones/
β βββ mapas/
βββ lib/
β βββ supabase.js # Cliente Supabase
β βββ queries.js # Consultas
β βββ mutations.js # Mutaciones
β βββ helpers.js
βββ hooks/
β βββ useAuth.js
β βββ useInventario.js
β βββ useGeolocation.js
βββ pages/
β βββ Dashboard.jsx
β βββ Afectados.jsx
β βββ Inventario.jsx
β βββ Donaciones.jsx
β βββ Voluntarios.jsx
β βββ Asignaciones.jsx
β βββ Mapa.jsx
β βββ Perfil.jsx
β βββ Login.jsx
βββ context/
β βββ AuthContext.jsx
βββ utils/
β βββ validations.js # Zod schemas
β βββ formats.js
β βββ constants.js
βββ styles/
β βββ index.css
βββ App.jsx
4 Frontend - Especificaciones TΓ©cnicas
Vite Configuration
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
build: {
outDir: 'dist' ,
sourcemap: true ,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react' , 'react-dom' , 'react-router-dom' ],
supabase: ['@supabase/supabase-js' ],
maps: ['leaflet' , 'react-leaflet' ]
}
}
}
},
server: { port: 5173 , host: true }
})
Cliente Supabase
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: { persistSession: true , autoRefreshToken: true },
realtime: { params: { eventsPerSecond: 10 } }
})
export const getCurrentUser = async () => {
const { data: { user } } = await supabase.auth.getUser()
return user
}
Tailwind Config
export default {
content: ['./index.html' , './src/**/*.{js,ts,jsx,tsx}' ],
theme: {
extend: {
colors: {
primary: { DEFAULT: '#EF4444' , light: '#FCA5A5' , dark: '#B91C1C' },
success: '#10B981' ,
warning: '#F59E0B' ,
danger: '#EF4444'
},
fontFamily: { sans: ['Inter' , 'sans-serif' ] }
}
},
plugins: []
}
5 Backend - Especificaciones TΓ©cnicas
ConfiguraciΓ³n de Supabase
URL
https://nvxgygnrgjnddrtboskr.supabase.co
RegiΓ³n
Americas (us-east-1)
SesiΓ³n
7 dΓas con refresh token
Roles de Usuario
Rol Acceso
admin Acceso total al sistema
voluntario Acceso a datos operativos
public Solo registro de afectados
PolΓticas RLS
CREATE POLICY "Voluntarios ver afectados" ON afectados
FOR SELECT USING (auth.role() IN ('admin' , 'voluntario' ));
CREATE POLICY "Publico registrar afectados" ON afectados
FOR INSERT WITH CHECK (true);
CREATE POLICY "Admins gestionar voluntarios" ON voluntarios
FOR ALL USING (auth.role() = 'admin' );
6 Base de Datos - DiseΓ±o Detallado
Tablas del Sistema
# Tabla PropΓ³sito
1 ubicaciones Centros de acopio
2 categorias_productos CategorΓas de productos
3 productos CatΓ‘logo de productos
4 voluntarios Datos de voluntarios
5 afectados Datos de afectados
6 donaciones Registro de donaciones
7 inventario_ubicacion Stock por ubicaciΓ³n
8 movimientos_inventario Historial de movimientos
9 asignaciones AsignaciΓ³n de ayudas
10 alertas_inventario Alertas automΓ‘ticas
11 seguimiento_afectados Historial de seguimiento
Triggers AutomΓ‘ticos
CREATE OR REPLACE FUNCTION actualizar_inventario()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.tipo_movimiento = 'ingreso' THEN
INSERT INTO inventario_ubicacion (producto_id, ubicacion_id, cantidad)
VALUES (NEW.producto_id, NEW.ubicacion_destino_id, NEW.cantidad)
ON CONFLICT (producto_id, ubicacion_id)
DO UPDATE SET cantidad = inventario_ubicacion.cantidad + EXCLUDED.cantidad;
ELSIF NEW.tipo_movimiento = 'salida' THEN
UPDATE inventario_ubicacion
SET cantidad = cantidad - NEW.cantidad
WHERE producto_id = NEW.producto_id AND ubicacion_id = NEW.ubicacion_origen_id;
END IF ;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
Datos Iniciales (Seed)
INSERT INTO categorias_productos (nombre, descripcion, unidad_medida, icono) VALUES
('Alimentos' , 'Alimentos no perecederos' , 'kg' , 'π₯«' ),
('Agua' , 'Agua potable' , 'litros' , 'π§' ),
('Medicinas' , 'Medicamentos' , 'unidades' , 'π' ),
('Ropa' , 'Ropa y calzado' , 'prendas' , 'π' ),
('Higiene' , 'Higiene personal' , 'unidades' , 'π§΄' ),
('BebΓ©s' , 'Productos infantiles' , 'unidades' , 'πΌ' );
INSERT INTO ubicaciones (nombre, direccion, tipo) VALUES
('Catia La Mar' , 'Catia La Mar, La Guaira' , 'centro_acopio' ),
('Caraballeda' , 'Caraballeda, La Guaira' , 'centro_acopio' ),
('MaiquetΓa' , 'MaiquetΓa, La Guaira' , 'centro_acopio' );
7 API - DiseΓ±o y Endpoints
Endpoints Principales
MΓ©todo Endpoint PropΓ³sito
GET /afectados Listar afectados
POST /afectados Registrar afectado
PATCH /afectados?id=eq.{id} Actualizar afectado
GET /inventario_ubicacion Ver inventario
POST /movimientos_inventario Registrar movimiento
GET /voluntarios Listar voluntarios
POST /donaciones Registrar donaciΓ³n
GET /asignaciones Ver asignaciones
POST /asignaciones Crear asignaciΓ³n
Ejemplo: Registrar Afectado
const registrarAfectado = async (data) => {
const { data: result, error } = await supabase
.from('afectados' )
.insert([{
cedula: data.cedula,
nombre: data.nombre,
latitud: data.latitud,
longitud: data.longitud,
adultos: data.adultos,
ninos: data.ninos,
necesidades: data.necesidades
}])
.select()
return { result, error }
}
Tiempo Real (WebSockets)
const subscribeInventario = (callback) => {
return supabase
.channel('inventario' )
.on('postgres_changes' ,
{ event: '*' , schema: 'public' , table: 'inventario_ubicacion' },
callback
)
.subscribe()
}
8 PWA - ConfiguraciΓ³n y Estrategia
Manifest
{
"name" : "Rutas de Solidaridad" ,
"short_name" : "RutasSolidaridad" ,
"display" : "standalone" ,
"background_color" : "#FFFFFF" ,
"theme_color" : "#EF4444" ,
"orientation" : "portrait" ,
"icons" : [
{ "src" : "/icon-192.png" , "sizes" : "192x192" },
{ "src" : "/icon-512.png" , "sizes" : "512x512" }
]
}
Estrategia Offline
Cache First
HTML, JS, CSS, imΓ‘genes
Stale While Revalidate
Datos de API
Network First
Operaciones dinΓ‘micas
Queue Offline
SincronizaciΓ³n al reconectar
const pendingOperations = []
const syncOffline = async () => {
if (navigator.onLine) {
for (const op of pendingOperations) {
await supabase.from(op.table).insert(op.data)
}
pendingOperations = []
localStorage.setItem('pendingOps' , JSON.stringify(pendingOperations))
}
}
9 Seguridad - ImplementaciΓ³n
π
AutenticaciΓ³n
Supabase Auth + JWT
π‘οΈ
AutorizaciΓ³n
RLS Row Level Security
β
ValidaciΓ³n
Zod en frontend y backend
π
HTTPS
Forzado en producciΓ³n
ValidaciΓ³n con Zod
import { z } from 'zod'
const afectadoSchema = z.object({
cedula: z.string().min(6 , 'CΓ©dula invΓ‘lida' ),
nombre: z.string().min(2 , 'Nombre requerido' ),
telefono: z.string().optional(),
adultos: z.number().min(0 ).default(0 ),
ninos: z.number().min(0 ).default(0 ),
necesidades: z.array(z.string()).default([])
})
const validarAfectado = (data) => {
try {
return afectadoSchema.parse(data)
} catch (error) {
return { error: error.errors }
}
}
10 Despliegue - Estrategia
Entornos
Entorno URL PropΓ³sito
Dev localhost:5173 Desarrollo local
Staging voluntariado.foresve.com/staging Pruebas
Prod voluntariado.foresve.com ProducciΓ³n
Proceso de Despliegue
npm run build
npm run preview
.htaccess para SPA + CachΓ©
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
"\.(js|css|png|jpg|jpeg|gif|svg|ico)$">
Header set Cache-Control "max-age=31536000, public"
11 Rendimiento - Optimizaciones
Γrea TΓ©cnica Impacto
Carga Lazy loading de rutas -40% carga inicial
ImΓ‘genes OptimizaciΓ³n con Vite -50% tamaΓ±o
CΓ³digo Code splitting automΓ‘tico -30% bundle
CachΓ© Service Worker Carga instantΓ‘nea
Data React Query con cachΓ© -70% llamadas API
Mapas Carga bajo demanda -60% tiempo
MΓ©tricas Objetivo
12 Testing - Estrategia
Unit Testing
Componentes y funciones
Integration
Conexiones con Supabase
E2E
Flujos completos (opcional)
import { render, screen } from '@testing-library/react'
import { RegistroAfectado } from './RegistroAfectado'
test('renderiza formulario de afectado' , () => {
render( )
expect(screen.getByText('Registrar Afectado' )).toBeInTheDocument()
})
13 Monitoreo - Herramientas
Supabase Dashboard
BD y rendimiento
React DevTools
Debugging frontend
Sentry
Errores en producciΓ³n
const logActivity = (action, data) => {
console.log(`[ ${new Date().toISOString()}] ${action}:` , data)
}
15 Roadmap TΓ©cnico
π΄ Semana 1 - FundaciΓ³n
DΓas 1-3
Configurar proyecto Vite
Conectar con Supabase
Estructura de carpetas
AutenticaciΓ³n bΓ‘sica
Layout base
π‘ Semana 1 - Funcionalidad
DΓas 4-7
Registro de afectados
Dashboard bΓ‘sico
Inventario simple
CRUD de productos
Movimientos inventario
π’ Semana 2 - Operaciones
DΓas 8-14
GestiΓ³n de donaciones
AsignaciΓ³n de ayudas
Mapa de necesidades
Seguimiento entregas
Perfil voluntarios
π΅ Semana 3 - PWA
DΓas 15-21
ConfiguraciΓ³n PWA
Modo offline
SincronizaciΓ³n
Notificaciones push
Reportes exportables
βͺ Semana 4 - ProducciΓ³n
DΓas 22-28
Testing completo
OptimizaciΓ³n rendimiento
DocumentaciΓ³n final
Despliegue a producciΓ³n
CapacitaciΓ³n
π Checklist TΓ©cnico Final
Repositorio con estructura definida
ConexiΓ³n a Supabase funcionando
Variables de entorno configuradas
AutenticaciΓ³n implementada
RLS configurado y probado
PWA configurada
Modo offline funcionando
Mapa integrado
Inventario en tiempo real
Registro de afectados funcionando
Sistema de donaciones completo
Asignaciones operativas
Reportes exportables
DocumentaciΓ³n completa
Despliegue en producciΓ³n