Clinica Dental Pro es una aplicacion de escritorio para Windows construida con Electron. Sigue una arquitectura de 3 procesos con responsabilidades claramente separadas:
┌──────────────────────────────────────────────────────────────┐
│ USUARIO (Windows PC) │
└──────────────────────────────────────────────────────────────┘
│
│ click en "Clinica Dental Pro.exe"
▼
┌──────────────────────────────────────────────────────────────┐
│ PROCESO PRINCIPAL (Electron Main) │
│ - electron/main.js │
│ - electron/preload.js (IPC bridge) │
│ - Crea ventana, carga frontend, monitorea salud │
└──────────────────────────────────────────────────────────────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────────┐ ┌─────────────┐ ┌─────────────────────┐
│ FRONTEND │ │ BACKEND │ │ WHATSAPP RUNNER │
│ (Renderer) │ │ (Express) │ │ (Node.js child) │
│ React + Vite │ │ Port 18234 │ │ Port 3002 │
│ localhost:5173 │ │ │ │ puerto interno │
│ (dev) / asar │ │ │ │ │
│ │ │ │ │ whatsapp-web.js │
│ - UI │ │ - API REST │ │ + puppeteer │
│ - logica cliente │ │ - SQLite │ │ + Chrome │
│ - estado React │ │ - PDFs │ │ - gestion QR │
└─────────────────────┘ └─────────────┘ └─────────────────────┘
│
▼
┌─────────────────────┐
│ SQLite Database │
│ clinica.db │
│ ~5 MB │
│ win-unpacked/ │
│ resources/data/ │
└─────────────────────┘
Archivo: electron/main.js
Responsabilidades:
- Crear ventana principal del sistema operativo
- Verificar dependencias del sistema (VC++ Redist, Chrome, Node.js)
- Spawn del Backend como proceso hijo
- Spawn del WhatsApp Runner como proceso hijo
- Manejo de salud de procesos hijos (reinicio automatico)
- IPC con Frontend via preload (contextBridge)
- Manejo de errores no recuperables
Archivos clave:
electron/main.js— orquestador principalelectron/preload.js— puente seguro entre main y rendererelectron/openwaSetup.js— deteccion/instalacion de Chrome y Node.jselectron/healthCheck.js— verificacion periodica de procesos hijos
Stack: React 18 + Vite + CSS3 (sin frameworks UI)
Archivos clave:
frontend/src/App.jsx— router por estado (no usa react-router)frontend/src/context/AuthContext.jsx— estado de autenticacion globalfrontend/src/components/— 21 componentesfrontend/src/services/api.js— cliente HTTP para backendfrontend/src/services/whatsapp.js— cliente HTTP para runner
Patron de navegacion:
// App.jsx
const [view, setView] = useState('dashboard');
// view puede ser: 'dashboard', 'pacientes', 'historia', 'sesion', etc.
return (
<AuthProvider>
{view === 'dashboard' && <Dashboard onNavigate={setView} />}
{view === 'pacientes' && <Pacientes onNavigate={setView} />}
{/* ... */}
</AuthProvider>
);Comunicacion con backend:
fetch()directo ahttp://localhost:18234/api/...- Token JWT en header
Authorization: Bearer ...
Comunicacion con WhatsApp:
fetch()directo ahttp://localhost:3002/...(proxy a runner)- QR, status, sendMessage vienen del runner
Stack: Node.js + Express + better-sqlite3
Archivo entrada: backend/src/index.js
Puerto: 18234 (configurable via PORT)
Endpoints principales (15 rutas):
| Path | Metodos | Descripcion |
|---|---|---|
/api/auth/login |
POST | Login con email + password |
/api/auth/verify |
GET | Validar token JWT |
/api/pacientes |
GET, POST | Listar y crear pacientes |
/api/pacientes/:id |
GET, PUT, DELETE | CRUD de un paciente |
/api/historias |
GET, POST | Listar y crear historias clinicas |
/api/historias/:id |
GET, PUT, DELETE | CRUD historia clinica |
/api/consultas |
GET, POST | Consultas medicas |
/api/odontogramas |
GET, POST, PUT | Odontograma de paciente |
/api/recetas |
GET, POST, PUT, DELETE | Recetas medicas |
/api/tratamientos |
GET, POST, PUT, DELETE | Tratamientos y abonos |
/api/pagos |
GET, POST, PUT | Pagos y comprobantes |
/api/imagenes |
GET, POST, DELETE | Imagenes de consultas |
/api/pdf/:tipo/:id |
GET | Generar PDF (historia, receta, pago) |
/api/whatsapp/* |
* | Proxy al runner |
/api/importar |
POST | Importar Excel/CSV |
/api/exportar |
GET | Exportar Excel/CSV |
/api/backup |
GET, POST | Backup completo SQLite |
Controllers (15 archivos):
authController.js— JWT, hash passwordspacienteController.js— CRUD pacienteshistoriaController.js— historias clinicasconsultaController.js— consultasodontogramaController.js— piezas dentalesrecetaController.js— recetas medicastratamientoController.js— planes de tratamientopagoController.js— pagos y abonosimagenController.js— archivos de imagenpdfController.js— generacion de PDFswhatsappController.js— proxy al runnerimportacionController.js— Excel/CSVexportacionController.js— Excel/CSVbackupController.js— backup/restore SQLitedashboardController.js— metricas
Base de datos:
SQLite con better-sqlite3 (sincronico, ~3x mas rapido que sqlite3).
Tablas principales:
usuarios— login, rolespacientes— datos personaleshistorias_clinicas— N°HCLXconsultas— una por consulta medicaodontogramas— por pacienterecetas— medicamentostratamientos— planes y abonospagos— comprobantesimagenes— archivos adjuntosnecesidades— diagnostico pendienteaudit_log— cambios importantes
Ubicacion DB:
- Dev:
backend/data/clinica.db - Prod:
resources/data/clinica.db(junto al asar)
Migraciones: Manuales en backend/src/db/migrations/ si las hay.
Archivo: electron/openwa-runner.js
Puerto: 3002 (interno, no expuesto a Windows)
Stack: whatsapp-web.js 1.34.7 + puppeteer 24 + Express
Responsabilidades:
- Iniciar sesion de WhatsApp (QR + multi-device)
- Mantener sesion activa en
%APPDATA%\Clinica Dental Pro\wwebjs_auth - Exponer API HTTP para que el backend consulte estado y envie mensajes
- Reintentar conexion ante caidas
- Matar procesos Chrome zombie antes de reintentar
- Logging detallado a archivo
Endpoints (proxy):
GET /— health checkGET /status— estado actual de la sesionGET /qr— QR actual en base64POST /send— enviar mensajePOST /send-pdf— enviar PDF adjuntoPOST /logout— cerrar sesion
Patron de inicio:
// electron/openwa-runner.js
async function initWhatsApp() {
const client = new Client({
authStrategy: new LocalAuth({ clientId: 'clinicadentalpro' }),
puppeteer: {
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
executablePath: getChromePath() // 6 rutas posibles
}
});
client.on('qr', (qr) => qrData = qr);
client.on('ready', () => isReady = true);
client.on('auth_failure', () => killChromeProcesses().then(retry));
client.on('disconnected', () => killChromeProcesses().then(retry));
await client.initialize();
}Patch critico: patches/whatsapp-web.js+1.34.7.patch (issue #5754)
Retries: MAX_INIT_ATTEMPTS = 5 con backoff de 5 segundos
[Usuario click "Enviar PDF" en UI]
│
▼
[Frontend: services/whatsapp.js]
POST http://localhost:18234/api/whatsapp/send-pdf
│
▼
[Backend: routes/whatsapp.js → whatsappController.js]
1. Genera PDF: pdfController → pdfTemplates/historiaHtmlPdf.js
2. Genera PDF via puppeteer-core
│
▼
[Backend llama a Runner]
POST http://localhost:3002/send-pdf
body: { telefono: "+54911...", pdfBase64: "...", filename: "..." }
│
▼
[Runner: openwa-runner.js]
1. Verifica isReady
2. Convierte base64 a buffer
3. WhatsApp client.sendMessage() con PDF como documento
│
▼
[WhatsApp → Servidor WA → Telefono destino]
Tiempos esperados:
- Generar PDF: 1-3 segundos
- Llamar runner: <100ms
- Enviar a WhatsApp: 1-2 segundos
- Total: 3-6 segundos end-to-end
- MVC clasico: rutas → controllers → servicios → DB
- Repository implicit: cada controller accede directo a SQLite
- Middleware para auth:
authMiddleware.jsvalida JWT - Validacion manual:
if (!req.body.email) return res.status(400)...
- Context para estado global:
AuthContext - Props para estado de componente
- Services para llamadas HTTP
- Hooks personalizados:
useApi,useDebounce,useToast
- Electron IPC (preload + contextBridge) para Main ↔ Renderer
- HTTP (puerto fijo) para Main ↔ Backend ↔ Runner
- Archivos compartidos (
%APPDATA%\Clinica Dental Pro\) para estado persistente
- JWT con secret en variable de entorno (o constante)
- Passwords hasheados con bcrypt
- Context Isolation en Electron (
nodeIntegration: false) - CORS restrictivo (solo localhost)
- Validacion de input basica en controllers
- No hay HTTPS (es localhost, no expuesto)
- No hay rate limiting
- No hay CSRF tokens (no es web public)
- No hay sanitizacion XSS en PDFs (Puppeteer escapea)
- No hay 2FA
- No hay encriptacion at-rest de la DB
La app corre en localhost, no esta expuesta a internet. La unica superficie de ataque es:
- USB/CD que copie la carpeta del usuario
- Otro usuario con acceso a la PC
- Malware en la PC que lea
%APPDATA%\Clinica Dental Pro\
No es un sistema de alto riesgo (no maneja datos bancarios, no se expone online).
- asar: 9.7 MB (solo codigo de app)
- asar.unpacked: 383 MB (todas las deps)
- win-unpacked total: ~393 MB
- 7z comprimido: 255 MB
- SFX .exe: 262 MB
- Empaquetado: 2-3 minutos
- Inicio de la app: 5-10 segundos (incluye spawn de backend y runner)
- Generar PDF: 1-3 segundos
- Enviar WhatsApp: 1-2 segundos despues de PDF
better-sqlite3(sincronico, 3x mas rapido)- Cache de paciente activo en memoria
- Reuse de puppeteer (no crea browser nuevo cada envio)
- Lazy load de modulos pesados (puppeteer solo en send-pdf)
- Cache de PDFs frecuentes
- Indice en SQLite para busquedas por nombre
- Code splitting en frontend (vite ya lo hace parcialmente)
- Compression gzip en respuestas API (no critico en localhost)
odontologia-sistema/
├── package.json # Root, scripts de build
├── package-lock.json
├── patches/
│ └── whatsapp-web.js+1.34.7.patch
├── scripts/
│ └── copy-portable.js # postbuild hook
├── portable/
│ ├── Iniciar Clinica Dental Pro.bat
│ ├── VERIFICAR.bat
│ └── README.txt
├── electron/
│ ├── main.js
│ ├── preload.js
│ ├── openwaSetup.js
│ ├── openwa-runner.js
│ └── ...
├── backend/
│ ├── package.json
│ ├── src/
│ │ ├── index.js # Entry
│ │ ├── db/ # SQLite setup
│ │ ├── controllers/ # 15 archivos
│ │ ├── routes/ # 15 archivos
│ │ ├── services/
│ │ │ └── pdfTemplates/ # 11 archivos
│ │ ├── middleware/
│ │ └── utils/
│ └── data/ # Dev DB
├── frontend/
│ ├── package.json
│ ├── vite.config.js
│ ├── src/
│ │ ├── main.jsx
│ │ ├── App.jsx
│ │ ├── App.css # 5,493 lineas
│ │ ├── context/
│ │ ├── components/ # 21 archivos
│ │ └── services/
│ ├── public/
│ └── dist/ # Build output
├── resources/ # Prod: data + assets
├── dist-electron/ # Build output Electron
└── documentacion/
├── ARQUITECTURA.md # Este archivo
├── GUIA_DESARROLLO.md
└── SOLUCION_PROBLEMAS.md
resources/data/evidencias/
├── {paciente_id}/
│ ├── {consulta_id}/
│ │ ├── 2026-06-08_14-30_foto-diente-16.jpg
│ │ ├── 2026-06-08_14-32_radiografia-panoramica.png
│ │ └── ...
│ └── general/
│ └── foto-general.jpg
└── ...
| Método | Uso | Seguridad |
|---|---|---|
| Drag & Drop | Desktop — arrastrar imagen al wizard/galería | JWT auth |
| QR Code | Celular — escanea QR, sube desde página móvil | Token temporal (15min, single-use) |
| Celular — envía foto al chat de la clínica | Auto-ingest por número conocido | |
| Web Upload | Celular — login + upload vía navegador (WiFi local) | JWT auth |
- No express-static: Las imágenes se sirven vía endpoint autenticado (Bearer JWT)
- Hash SHA256: Cada imagen tiene hash en tabla
imagenespara verificar integridad - Backup incluido: El backup completo incluye la carpeta
evidencias/ - Referencia DB: La tabla
imagenesreferenciapaciente_id+consulta_id
ALTER TABLE imagenes ADD COLUMN paciente_id INTEGER REFERENCES pacientes(id);
ALTER TABLE imagenes ADD COLUMN consulta_id INTEGER REFERENCES consultas(id);
ALTER TABLE imagenes ADD COLUMN hash_sha256 TEXT DEFAULT '';
ALTER TABLE imagenes ADD COLUMN tipo TEXT DEFAULT 'foto'; -- foto, radiografia, panorama, intraoral
ALTER TABLE imagenes ADD COLUMN descripcion TEXT DEFAULT '';Express es el mas conocido, mejor documentado, suficiente para localhost. Fastify seria mas rapido pero la ganancia es despreciable en una API local.
- La app es local-first, no necesita servidor de DB
- SQLite es perfecto para 1-2 PCs simultaneas
- better-sqlite3 es 3x mas rapido que sqlite3
- Backups son un solo archivo
.db
- Vite es mas rapido (HMR instantaneo)
- Mas ligero (mejor para Electron)
- Soporta code splitting nativo
- CRA esta deprecado
- Solo hay 10 vistas
- La navegacion es jerarquica (Sesion Clinica wizard, Importar pasos)
- Un
useState('view')es mas simple que configurar rutas
- whatsapp-web.js es la mas mantenida
- API similar a la oficial de WhatsApp Business
- Soporta multi-device
- Mejor documentacion