Explicación Detallada de los Programas y su Funcionamiento
🛠 PROGRAMA 1: NODE.JS
¿QUÉ ES?
Node.js es un entorno de ejecución para JavaScript del lado del servidor. Es el motor principal que hace posible que Electron funcione.
FUNCIÓN PRINCIPAL
- Permite ejecutar JavaScript fuera del navegador
- Proporciona acceso al sistema operativo y hardware
- Gestiona operaciones de entrada/salida (I/O) de forma asíncrona
CÓMO FUNCIONA EN NUESTRO PROYECTO
En el Proceso Principal:
// Esto es Node.js en acción
const fs = require('fs'); // Acceso al sistema de archivos
const path = require('path'); // Manejo de rutas del SO
Características clave:
- Event Loop: Maneja operaciones asíncronas eficientemente
- Módulos nativos: fs, path, os, child_process, etc.
- Gestión de paquetes: npm (Node Package Manager)
INSTALACIÓN Y CONFIGURACIÓN
# Descargar desde nodejs.org (versión LTS)
# Verificar instalación:
node --version # Debe mostrar v18.x o superior
npm --version # Debe mostrar 9.x o superior
POR QUÉ ES ESENCIAL
Sin Node.js, Electron no podría:
- Acceder al sistema de archivos
- Ejecutar procesos del sistema
- Usar módulos nativos del SO
- Gestionar ventanas nativas
⚡ PROGRAMA 2: ELECTRON
¿QUÉ ES?
Electron es un framework que permite crear aplicaciones de escritorio usando tecnologías web (HTML, CSS, JavaScript).
FUNCIÓN PRINCIPAL
- Combina Chromium (navegador) con Node.js
- Proporciona APIs nativas del sistema operativo
- Empaqueta tu aplicación web como software de escritorio
ARQUITECTURA DE ELECTRON
Proceso Principal (Main Process):
// main.js - Controla el ciclo de vida de la app
const { app, BrowserWindow } = require('electron');
app.whenReady().then(() => {
const win = new BrowserWindow({
width: 1200,
height: 800
});
win.loadFile('index.html');
});
Proceso de Renderizado (Renderer Process):
<!-- index.html - Interfaz de usuario normal -->
<!DOCTYPE html>
<html>
<body>
<h1>Mi Aplicación</h1>
<script>
// JavaScript normal del navegador
</script>
</body>
</html>
CÓMO FUNCIONA INTERNAMENTE
- Inicialización:
- Electron inicia un proceso Node.js (main)
- El proceso main crea ventanas usando Chromium
- Comunicación:
- IPC (Inter-Process Communication) conecta main y renderer
- Preload scripts actúan como puente seguro
- Renderizado:
- Cada ventana es una instancia de Chromium
- Ejecuta HTML/CSS/JS como en un navegador web
COMPONENTES CLAVE
Chromium:
- Motor de renderizado de Google Chrome
- Soporte para HTML5, CSS3, JavaScript moderno
- DevTools integrados para desarrollo
Node.js Integration:
- Acceso a APIs del sistema operativo
- Módulos nativos de Node.js
- Operaciones de archivo y red
📦 PROGRAMA 3: ELECTRON-BUILDER
¿QUÉ ES?
Electron-builder es una herramienta que empaqueta y distribuye aplicaciones Electron.
FUNCIÓN PRINCIPAL
- Convierte tu código en ejecutables instalables
- Crea instaladores para Windows, macOS y Linux
- Gestiona actualizaciones automáticas
CÓMO FUNCIONA
Proceso de Build:
// package.json
{
"build": {
"appId": "com.miempresa.miapp",
"productName": "Mi App",
"directories": {
"output": "dist"
},
"files": [
"src/**/*",
"node_modules/**/*"
]
}
}
Comando:
npm run build
# Resultado: crea instaladores en carpeta /dist
FORMATOS DE SALIDA POR PLATAFORMA
Windows:
.exe(NSIS installer).exe(portable).msi(Windows Installer)
macOS:
.dmg(Disk Image).app(Application bundle)
Linux:
.AppImage(Portable).deb(Debian/Ubuntu).rpm(Red Hat/Fedora)
CONFIGURACIÓN AVANZADA
Iconos y Metadatos:
{
"build": {
"win": {
"icon": "build/icon.ico",
"publisherName": "Mi Empresa"
},
"mac": {
"icon": "build/icon.icns",
"category": "public.app-category.productivity"
}
}
}
Auto Updates:
// Soporte para actualizaciones automáticas
const { autoUpdater } = require('electron-updater');
autoUpdater.checkForUpdatesAndNotify();
📧 PROGRAMA 4: NODEMAILER
¿QUÉ ES?
Nodemailer es un módulo para Node.js que permite enviar emails fácilmente.
FUNCIÓN PRINCIPAL
- Envío de emails via SMTP, Sendmail, o Amazon SES
- Soporte para adjuntos, HTML, y autenticación
- Configuración simple para diferentes proveedores
CÓMO FUNCIONA EN NUESTRA APP
Configuración básica:
const nodemailer = require('nodemailer');
// Crear transporter
const transporter = nodemailer.createTransporter({
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: 'tu@gmail.com',
pass: 'tu-password'
}
});
// Enviar email
await transporter.sendMail({
from: '"Mi App" <tu@gmail.com>',
to: 'cliente@email.com',
subject: 'Factura Mensual',
html: '<h1>Su factura está lista</h1>'
});
USO EN MYCOMUNIDADAPP
Para enviar facturas:
- Configurar SMTP del administrador
- Generar factura en PDF
- Enviar email con factura adjunta
- Fallback a
mailto:si no hay configuración SMTP
Ejemplo de implementación:
class EmailService {
async sendInvoice(invoice, clientEmail) {
if (this.hasSMTPConfig()) {
return await this.sendViaSMTP(invoice, clientEmail);
} else {
return await this.openMailClient(invoice, clientEmail);
}
}
}
📄 PROGRAMA 5: PDFKIT
¿QUÉ ES?
PDFKit es una librería para generar documentos PDF en Node.js y el navegador.
FUNCIÓN PRINCIPAL
- Crear PDFs programáticamente
- Agregar texto, imágenes, tablas
- Control total sobre el diseño del documento
CÓMO FUNCIONA
Crear PDF básico:
const PDFDocument = require('pdfkit');
const fs = require('fs');
// Crear documento
const doc = new PDFDocument();
// Pipe a un archivo
doc.pipe(fs.createWriteStream('factura.pdf'));
// Agregar contenido
doc.fontSize(25)
.text('FACTURA #001', 100, 100);
// Finalizar
doc.end();
USO EN FACTURAS
Estructura típica de una factura:
- Encabezado: Logo, información de la empresa
- Cliente: Datos del destinatario
- Detalles: Tabla de productos/servicios
- Totales: Cálculos y sumatorias
- Pie: Términos y condiciones
Ejemplo de tabla:
function createInvoiceTable(doc, items) {
let yPosition = 200;
// Encabezados de tabla
doc.fontSize(10)
.text('Descripción', 50, yPosition)
.text('Cantidad', 250, yPosition)
.text('Precio', 350, yPosition)
.text('Total', 450, yPosition);
// Líneas de items
items.forEach((item, index) => {
yPosition += 20;
doc.text(item.description, 50, yPosition)
.text(item.quantity.toString(), 250, yPosition)
.text(`$${item.price}`, 350, yPosition)
.text(`$${item.total}`, 450, yPosition);
});
}
🔧 PROGRAMA 6: FS-EXTRA
¿QUÉ ES?
fs-extra es una mejora del módulo nativo fs de Node.js, con métodos adicionales y soporte para promesas.
FUNCIÓN PRINCIPAL
- Operaciones de archivo más sencillas
- Métodos como
copy,remove,ensureDir - Soporte nativo para async/await
CÓMO FUNCIONA
Comparación con fs nativo:
// Con fs nativo (callbacks)
fs.readFile('config.json', (err, data) => {
if (err) throw err;
console.log(JSON.parse(data));
});
// Con fs-extra (promesas)
const data = await fs.readJson('config.json');
console.log(data);
MÉTODOS ÚTILES PARA NUESTRA APP
Gestión de configuración:
const fs = require('fs-extra');
// Leer configuración
const config = await fs.readJson('config.json');
// Guardar configuración
await fs.writeJson('config.json', config, { spaces: 2 });
// Crear directorio si no existe
await fs.ensureDir('/ruta/a/mi/carpeta');
Backup de datos:
async function backupData() {
const backupDir = path.join(__dirname, 'backups');
await fs.ensureDir(backupDir);
const backupFile = path.join(backupDir, `backup-${Date.now()}.json`);
await fs.writeJson(backupFile, appData);
return backupFile;
}
🎨 PROGRAMA 7: VS CODE (EDITOR)
¿QUÉ ES?
Visual Studio Code es un editor de código fuente desarrollado por Microsoft.
FUNCIÓN PRINCIPAL
- Edición de código con syntax highlighting
- Debugging integrado
- Control de versiones (Git)
- Extensiones para productividad
CONFIGURACIÓN PARA PROYECTOS ELECTRON
Extensiones recomendadas:
- ES6 String HTML: Resalta HTML en strings JavaScript
- Auto Rename Tag: Renombra tags HTML/XML automáticamente
- Bracket Pair Colorizer: Colorea pares de corchetes
- GitLens: Mejora la integración con Git
- Electron Snippets: Snippets para desarrollo Electron
Configuración del workspace:
{
"files.associations": {
"*.js": "javascript",
"*.css": "css",
"*.html": "html"
},
"emmet.includeLanguages": {
"javascript": "html"
}
}
DEBUGGING ELECTRON
Launch configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Electron Main",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/src/main.js",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
}
}
]
}
🌐 PROGRAMA 8: GIT (CONTROL DE VERSIONES)
¿QUÉ ES?
Git es un sistema de control de versiones distribuido.
FUNCIÓN PRINCIPAL
- Seguimiento de cambios en el código
- Colaboración entre desarrolladores
- Ramificación y fusión de características
FLUJO DE TRABAJO RECOMENDADO
Inicialización:
git init
git add .
git commit -m "Initial commit"
Estructura de branches:
main: Código estable listo para produccióndevelop: Desarrollo en cursofeature/nueva-funcionalidad: Funcionalidades nuevashotfix/error-critico: Correcciones urgentes
.gitignore para Electron:
node_modules/
dist/
build/
*.log
.DS_Store
Thumbs.db
🔄 FLUJO COMPLETO DE DESARROLLO
1. CONFIGURACIÓN INICIAL
# 1. Instalar Node.js
# 2. Crear proyecto y package.json
npm init -y
# 3. Instalar dependencias
npm install electron --save-dev
npm install electron-builder --save-dev
npm install nodemailer pdfkit fs-extra
2. DESARROLLO
# Ejecutar en modo desarrollo
npm start
# Con debugging
npm run dev
3. CONSTRUCCIÓN
# Build para desarrollo
npm run build
# Build para producción
npm run dist
# Build específico por plataforma
npm run build:win
npm run build:mac
npm run build:linux
4. DISTRIBUCIÓN
# Los instaladores se generan en /dist
# Distribuir según la plataforma:
# - Windows: .exe o .msi
# - macOS: .dmg
# - Linux: .AppImage o .deb
🎯 RESUMEN DE INTERACCIÓN ENTRE PROGRAMAS
[VS CODE] ← Edita → [ARCHIVOS .JS/.HTML/.CSS]
↓
[GIT] ← Controla versiones → [CÓDIGO FUENTE]
↓
[NODE.JS] ← Ejecuta → [ELECTRON]
↓
[ELECTRON] ← Usa → [CHROMIUM + NODE.JS]
↓
[APP] ← Genera → [PDFs con PDFKIT]
↓
[APP] ← Envía → [EMAILs con NODEMAILER]
↓
[ELECTRON-BUILDER] ← Empaqueta → [INSTALADORES]
Cada programa tiene una función específica y complementaria que, cuando se combinan, crean un entorno de desarrollo robusto para aplicaciones de escritorio multiplataforma.
Explicación Detallada de Cada Paso para Crear Software para PC
1. PRERREQUISITOS – Fundamentos Necesarios
¿En qué consiste?
Antes de empezar a programar, necesitas preparar tu entorno de desarrollo con las herramientas esenciales.
¿Cómo hacerlo?
Node.js y npm:
- Descarga Node.js desde nodejs.org (versión LTS recomendada)
- La instalación incluye npm (Node Package Manager)
- Verifica con
node --versionynpm --versionen tu terminal
Editor de código:
- Instala VS Code (gratuito y con excelentes extensiones para JavaScript)
- Extensiones recomendadas:
- ES6 snippets
- Auto Rename Tag
- Live Server (para pruebas web)
- Electron snippets
Conceptos que debes entender:
- JavaScript ES6+ (clases, async/await, módulos)
- HTML5 y CSS3
- Conceptos básicos de Node.js
- Sistema de archivos de tu SO
2. ESTRUCTURA DEL PROYECTO – Organización del Código
¿En qué consiste?
Definir una arquitectura limpia y mantenible para tu aplicación. Electron tiene dos procesos principales: el proceso principal (main) y el proceso de renderizado (renderer).
¿Cómo hacerlo?
Proceso Principal (Main Process):
- Controla el ciclo de vida de la aplicación
- Crea y gestiona ventanas
- Interactúa con el sistema operativo
- Archivo:
src/main.js
Proceso de Renderizado (Renderer Process):
- Ejecuta la interfaz de usuario (Chromium)
- No tiene acceso directo a Node.js por seguridad
- Se comunica con el proceso principal via IPC
- Archivos en
src/renderer/
Estructura recomendada:
proyecto/
├── src/ # Código fuente
├── assets/ # Recursos estáticos
├── dist/ # Builds (no versionar)
└── docs/ # Documentación
Reglas importantes:
- Separar claramente lógica de negocio de la UI
- Usar preload.js para APIs seguras
- Mantener nodeIntegration: false por seguridad
3. CONFIGURACIÓN INICIAL – Setup del Proyecto
¿En qué consiste?
Preparar el esqueleto básico del proyecto con todas las dependencias y configuraciones necesarias.
¿Cómo hacerlo?
Inicialización:
mkdir mi-proyecto
cd mi-proyecto
npm init -y
package.json crítico:
main: Punto de entrada de Electronscripts: Comandos para desarrollo y builddevDependencies: Electron y herramientas de builddependencies: Librerías que usa tu app
Dependencias esenciales:
electron: Runtime principalelectron-builder: Para crear instaladoresnodemailer: Envío de emailspdfkit: Generación de PDFs
Configuración de seguridad:
nodeIntegration: false– Previene inyección de códigocontextIsolation: true– Aísla el contextopreload.js– Puente seguro entre procesos
4. PROCESO PRINCIPAL – El Corazón de Electron
¿En qué consiste?
El proceso principal es el «cerebro» de tu aplicación. Controla todo lo relacionado con el sistema operativo y gestiona las ventanas.
¿Cómo hacerlo?
Crear ventana principal:
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
Manejar eventos de la app:
app.whenReady()– Cuando Electron está listoapp.on('window-all-closed')– Comportamiento al cerrarapp.on('activate')– Comportamiento en macOS
IPC (Comunicación entre procesos):
ipcMain.handle()– Maneja peticiones del rendereripcMain.on()– Escucha eventos del renderer
Funciones del sistema:
dialog– Diálogos nativos (abrir/guardar archivos)shell– Interactuar con el SO (abrir URLs, etc.)Menu– Menús nativos de la aplicación
5. PRELOAD SCRIPT – Puente Seguro
¿En qué consiste?
Un script que se ejecuta en el contexto del renderer pero tiene acceso limitado a Node.js. Actúa como intermediario seguro.
¿Cómo hacerlo?
contextBridge:
contextBridge.exposeInMainWorld('electronAPI', {
// Expone APIs seguras al renderer
openFile: () => ipcRenderer.invoke('dialog:openFile')
});
Qué exponer:
- Funciones que necesiten acceso al sistema
- APIs para diálogos del sistema
- Comunicación con el proceso principal
- Información del sistema
Seguridad:
- Nunca exponer
requireoprocesscompletos - Validar y sanitizar todos los inputs
- Usar TypeScript para mejor control de tipos
6. INTERFAZ DE USUARIO – Frontend de la App
¿En qué consiste?
La parte visual de tu aplicación. Usa tecnologías web estándar (HTML, CSS, JavaScript) pero con capacidades mejoradas.
¿Cómo hacerlo?
HTML Estructura:
- Diseño modular con componentes
- Semántica HTML5 apropiada
- Atributos data-* para funcionalidad
CSS Moderno:
- Variables CSS para theming
- Flexbox/Grid para layouts
- Diseño responsive
- Animaciones CSS para mejor performance
JavaScript en Renderer:
- Clases ES6 para organización
- Async/await para operaciones
- Event delegation para eficiencia
- Separar lógica de presentación
Comunicación con Main Process:
// En renderer
const result = await window.electronAPI.llamarFuncion();
// Manejar respuestas
window.electronAPI.onEvento((event, data) => {
// Actualizar UI
});
7. FUNCIONALIDADES PRINCIPALES – Lógica de Negocio
¿En qué consiste?
Implementar las características específicas de tu aplicación. En el caso de MyComunidadApp: gestión de apartamentos, facturas, contabilidad, etc.
¿Cómo hacerlo?
Gestión de Estado:
- Clases para manejar datos
- Persistencia en sistema de archivos
- Validación de datos
- Backup y restore
Operaciones del Sistema:
- Sistema de archivos: Leer/escribir configuraciones, backups
- Impresión: Usar
webContents.print()para imprimir facturas - Email: Configurar SMTP o usar mailto para envío
- PDFs: Generar documentos con datos de la app
Ejemplo – Gestión de Facturas:
class InvoiceManager {
constructor() {
this.invoices = [];
this.loadInvoices();
}
async createInvoice(data) {
const invoice = new Invoice(data);
this.invoices.push(invoice);
await this.saveInvoices();
return invoice;
}
async generatePDF(invoice) {
if (window.electronAPI) {
return await window.electronAPI.generatePDF(invoice);
}
// Fallback para web
return this.generatePDFWeb(invoice);
}
}
8. EMPAQUETADO – Crear Ejecutables
¿En qué consiste?
Convertir tu código en aplicaciones instalables para diferentes sistemas operativos.
¿Cómo hacerlo?
electron-builder configuración:
- Especificar archivos a incluir
- Configurar iconos por plataforma
- Definir metadatos de la app
- Configurar targets de build
Configuración por plataforma:
- Windows: NSIS (instalador) o portable
- macOS: DMG (disco de instalación)
- Linux: AppImage (portable) o DEB/RPM
Optimizaciones:
- Excluir node_modules no necesarios
- Comprimir recursos
- Incluir solo archivos necesarios
- Configurar auto-updates
Comandos de build:
# Desarrollo
npm start
# Build para producción
npm run build
# Build específico por plataforma
npm run build:win
npm run build:mac
npm run build:linux
9. DISTRIBUCIÓN – Llegar a Usuarios
¿En qué consiste?
Hacer que tu aplicación esté disponible para los usuarios finales y facilitar su instalación.
¿Cómo hacerlo?
Preparar instaladores:
- Crear iconos profesionales (256×256 mínimo)
- Configurar información de la empresa
- Definir estructura de instalación
- Configurar shortcuts y asociaciones
Estrategias de distribución:
- Directa: Descarga desde tu website
- Stores: Microsoft Store, Mac App Store
- Auto-updates: Implementar actualizaciones automáticas
Documentación:
- README con instrucciones de instalación
- Changelog con cambios por versión
- Manual de usuario
- Troubleshooting común
Pruebas multiplataforma:
- Probar en diferentes versiones de Windows
- Verificar en macOS (si es posible)
- Testear en distribuciones Linux populares
10. MANTENIMIENTO – Desarrollo Continuo
¿En qué consiste?
Mantener tu aplicación actualizada, segura y compatible con nuevas versiones de Electron y sistemas operativos.
¿Cómo hacerlo?
Actualizaciones de Electron:
- Seguir el calendario de releases
- Probar cambios breaking
- Actualizar dependencias regularmente
Manejo de errores:
- Implementar logging robusto
- Capturar y reportar crashes
- Proporcionar feedback al usuario
Mejoras continuas:
- Recoger feedback de usuarios
- Monitorear uso de características
- Planificar nuevas funcionalidades
Seguridad:
- Revisar vulnerabilidades en dependencias
- Mantener prácticas de seguridad actualizadas
- Validar inputs y sanitizar datos
CONCEPTOS CLAVE PARA ENTENDER
Arquitectura de Electron:
Sistema Operativo
↓
Proceso Principal (Node.js)
↓
Procesos de Renderizado (Chromium)
↓
Interfaz de Usuario (HTML/CSS/JS)
Flujo de Comunicación:
Renderer Process → Preload Script → IPC → Main Process → Sistema Operativo
Seguridad:
- Context Isolation: Separa el código de la app del código de Electron
- Node Integration: Desactivado por defecto para prevenir exploits
- Preload Scripts: Única forma segura de acceder a Node.js desde el renderer
Performance:
- Multi-proceso: Cada ventana es un proceso separado
- Memory Management: Cerrar procesos cuando no se usen
- Asset Optimization: Comprimir imágenes y recursos
🎯 ¿Qué vas a aprender?
Vas a crear MyComunidadApp – un software completo de gestión de condominios que funciona en Windows, macOS y Linux.
📋 Tabla de Contenidos
- Prerrequisitos
- Estructura del Proyecto
- Paso 1: Configuración Inicial
- Paso 2: Crear la Aplicación Electron
- Paso 3: Interfaz de Usuario
- Paso 4: Funcionalidades Principales
- Paso 5: Empacar para Distribución
- Paso 6: Instalador y Distribución
🛠 Prerrequisitos
Software necesario:
- Node.js (versión 16 o superior) – Descargar aquí
- Editor de código (VS Code recomendado) – Descargar aquí
- Git (opcional) – Descargar aquí
Verificar instalación:
node --version
npm --version
📁 Estructura del Proyecto
mycomunidadapp/
├── src/
│ ├── main.js # Proceso principal de Electron
│ ├── preload.js # Script de preload seguro
│ └── renderer/
│ ├── index.html # Interfaz principal
│ ├── styles.css # Estilos
│ └── renderer.js # Lógica del frontend
├── assets/
│ ├── icons/ # Iconos de la aplicación
│ └── templates/ # Plantillas
├── dist/ # Archivos compilados (auto-generado)
├── package.json
└── README.md
🚀 Paso 1: Configuración Inicial
1.1 Crear carpeta del proyecto
mkdir mycomunidadapp
cd mycomunidadapp
1.2 Inicializar proyecto Node.js
npm init -y
1.3 Instalar dependencias
# Dependencias principales
npm install electron --save-dev
npm install electron-builder --save-dev
# Dependencias para funcionalidades
npm install nodemailer pdfkit fs-extra
1.4 Configurar package.json
{
"name": "mycomunidadapp",
"version": "1.0.0",
"description": "Sistema de gestión para comunidades de propietarios",
"main": "src/main.js",
"scripts": {
"start": "electron .",
"build": "electron-builder",
"build-windows": "electron-builder --win",
"build-mac": "electron-builder --mac",
"build-linux": "electron-builder --linux",
"dist": "npm run build"
},
"keywords": ["condominio", "comunidad", "facturas", "gestión"],
"author": "Tu Nombre",
"license": "MIT",
"devDependencies": {
"electron": "^latest",
"electron-builder": "^latest"
},
"dependencies": {
"nodemailer": "^6.9.0",
"pdfkit": "^0.13.0",
"fs-extra": "^11.0.0"
},
"build": {
"appId": "com.tudominio.mycomunidadapp",
"productName": "MyComunidadApp",
"directories": {
"output": "dist"
},
"files": [
"src/**/*",
"assets/**/*",
"node_modules/**/*"
],
"win": {
"target": "nsis",
"icon": "assets/icons/icon.ico"
},
"mac": {
"target": "dmg",
"icon": "assets/icons/icon.icns"
},
"linux": {
"target": "AppImage",
"icon": "assets/icons/icon.png"
}
}
}
⚡ Paso 2: Crear la Aplicación Electron
2.1 Crear archivo principal (src/main.js)
const { app, BrowserWindow, ipcMain, dialog, shell, Menu } = require('electron');
const path = require('path');
const fs = require('fs-extra');
const nodemailer = require('nodemailer');
// Mantener referencia global de la ventana
let mainWindow;
function createWindow() {
// Crear la ventana principal
mainWindow = new BrowserWindow({
width: 1400,
height: 900,
minWidth: 1200,
minHeight: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
},
icon: path.join(__dirname, '../assets/icons/icon.png'),
show: false // Mostrar cuando esté lista
});
// Cargar la aplicación
mainWindow.loadFile('src/renderer/index.html');
// Mostrar cuando esté lista
mainWindow.once('ready-to-show', () => {
mainWindow.show();
// En desarrollo: abrir herramientas de desarrollador
if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools();
}
});
// Manejar cierre de ventana
mainWindow.on('closed', () => {
mainWindow = null;
});
// Crear menú de la aplicación
createApplicationMenu();
}
function createApplicationMenu() {
const template = [
{
label: 'Archivo',
submenu: [
{
label: 'Nueva Factura',
accelerator: 'Ctrl+N',
click: () => {
mainWindow.webContents.send('menu-nueva-factura');
}
},
{ type: 'separator' },
{
label: 'Salir',
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
click: () => {
app.quit();
}
}
]
},
{
label: 'Editar',
submenu: [
{ role: 'undo', label: 'Deshacer' },
{ role: 'redo', label: 'Rehacer' },
{ type: 'separator' },
{ role: 'cut', label: 'Cortar' },
{ role: 'copy', label: 'Copiar' },
{ role: 'paste', label: 'Pegar' }
]
},
{
label: 'Ver',
submenu: [
{ role: 'reload', label: 'Recargar' },
{ role: 'forceReload', label: 'Forzar Recarga' },
{ role: 'toggleDevTools', label: 'Herramientas Desarrollo' },
{ type: 'separator' },
{ role: 'resetZoom', label: 'Tamaño Normal' },
{ role: 'zoomIn', label: 'Acercar' },
{ role: 'zoomOut', label: 'Alejar' },
{ type: 'separator' },
{ role: 'togglefullscreen', label: 'Pantalla Completa' }
]
},
{
label: 'Ventana',
submenu: [
{ role: 'minimize', label: 'Minimizar' },
{ role: 'close', label: 'Cerrar' }
]
},
{
label: 'Ayuda',
submenu: [
{
label: 'Documentación',
click: async () => {
await shell.openExternal('https://github.com/tuusuario/mycomunidadapp');
}
},
{
label: 'Acerca de',
click: () => {
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Acerca de MyComunidadApp',
message: 'MyComunidadApp v1.0.0',
detail: 'Sistema de gestión para comunidades de propietarios\n\nDesarrollado con Electron'
});
}
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
// Evento cuando la aplicación está lista
app.whenReady().then(() => {
createWindow();
// En macOS es común recrear la ventana cuando se hace click en el icono
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// Salir cuando todas las ventanas estén cerradas (excepto en macOS)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// IPC Handlers - Comunicación con el renderer process
// Manejar guardado de archivos
ipcMain.handle('dialog:saveFile', async (event, defaultPath) => {
const result = await dialog.showSaveDialog(mainWindow, {
defaultPath: defaultPath,
filters: [
{ name: 'PDF Files', extensions: ['pdf'] },
{ name: 'All Files', extensions: ['*'] }
]
});
return result;
});
// Manejar selección de carpeta
ipcMain.handle('dialog:selectFolder', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openDirectory'],
title: 'Seleccionar carpeta para guardar archivos'
});
return result;
});
// Manejar apertura de archivos
ipcMain.handle('dialog:openFile', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
properties: ['openFile'],
filters: [
{ name: 'JSON Files', extensions: ['json'] },
{ name: 'All Files', extensions: ['*'] }
],
title: 'Abrir archivo de respaldo'
});
return result;
});
// Configuración de la aplicación
ipcMain.handle('config:get', async () => {
const configPath = path.join(app.getPath('userData'), 'config.json');
try {
const config = await fs.readJson(configPath);
return config;
} catch (error) {
// Configuración por defecto
return {
carpetaGuardado: app.getPath('documents'),
emailConfig: null,
empresa: {
nombre: 'Mi Comunidad',
direccion: '',
telefono: ''
}
};
}
});
ipcMain.handle('config:set', async (event, config) => {
const configPath = path.join(app.getPath('userData'), 'config.json');
try {
await fs.ensureDir(path.dirname(configPath));
await fs.writeJson(configPath, config, { spaces: 2 });
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
// Funcionalidad de impresión
ipcMain.handle('print:invoice', async () => {
mainWindow.webContents.print({
silent: false,
printBackground: true,
color: true,
margins: {
marginType: 'printableArea'
}
}, (success, errorType) => {
if (!success) {
console.log('Error de impresión:', errorType);
}
});
});
// Funcionalidad de email
ipcMain.handle('email:send', async (event, options) => {
try {
const config = await getConfig();
if (!config.emailConfig) {
// Abrir cliente de email por defecto
const mailtoLink = `mailto:${options.to}?subject=${encodeURIComponent(options.subject)}&body=${encodeURIComponent(options.body)}`;
shell.openExternal(mailtoLink);
return { success: true, method: 'mailto' };
}
// Enviar email via SMTP
const transporter = nodemailer.createTransporter({
host: config.emailConfig.smtpHost,
port: config.emailConfig.smtpPort,
secure: config.emailConfig.secure,
auth: {
user: config.emailConfig.email,
pass: config.emailConfig.password
}
});
const mailOptions = {
from: config.emailConfig.email,
to: options.to,
subject: options.subject,
html: options.html,
attachments: options.attachments || []
};
const result = await transporter.sendMail(mailOptions);
return { success: true, method: 'smtp', messageId: result.messageId };
} catch (error) {
return { success: false, error: error.message };
}
});
// Funciones auxiliares
async function getConfig() {
const configPath = path.join(app.getPath('userData'), 'config.json');
try {
return await fs.readJson(configPath);
} catch (error) {
return {
carpetaGuardado: app.getPath('documents'),
emailConfig: null
};
}
}
2.2 Crear preload script (src/preload.js)
const { contextBridge, ipcRenderer } = require('electron');
// Exponer APIs seguras al renderer process
contextBridge.exposeInMainWorld('electronAPI', {
// Diálogos del sistema
showSaveDialog: (defaultPath) => ipcRenderer.invoke('dialog:saveFile', defaultPath),
showOpenDialog: () => ipcRenderer.invoke('dialog:openFile'),
selectFolder: () => ipcRenderer.invoke('dialog:selectFolder'),
// Configuración
getConfig: () => ipcRenderer.invoke('config:get'),
setConfig: (config) => ipcRenderer.invoke('config:set', config),
// Funcionalidades
printInvoice: () => ipcRenderer.invoke('print:invoice'),
sendEmail: (options) => ipcRenderer.invoke('email:send', options),
// Sistema de archivos
readFile: async (filePath) => {
const fs = require('fs-extra');
return await fs.readJson(filePath);
},
writeFile: async (filePath, data) => {
const fs = require('fs-extra');
await fs.ensureDir(require('path').dirname(filePath));
return await fs.writeJson(filePath, data, { spaces: 2 });
},
// Eventos del menú
onMenuNewInvoice: (callback) => ipcRenderer.on('menu-nueva-factura', callback),
// Información del sistema
platform: process.platform,
appVersion: require('../../package.json').version
});
🎨 Paso 3: Interfaz de Usuario
3.1 HTML Principal (src/renderer/index.html)
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MyComunidadApp - Sistema de Gestión de Condominios</title>
<link rel="stylesheet" href="styles.css">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
</head>
<body>
<div class="app-container">
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<h2><i class="fas fa-building"></i> MyComunidadApp</h2>
<div class="app-version" id="app-version">v1.0.0</div>
</div>
<nav class="sidebar-nav">
<a href="#" class="nav-item active" data-section="dashboard">
<i class="fas fa-tachometer-alt"></i> Dashboard
</a>
<a href="#" class="nav-item" data-section="building">
<i class="fas fa-building"></i> Edificio
</a>
<a href="#" class="nav-item" data-section="apartments">
<i class="fas fa-home"></i> Apartamentos
</a>
<a href="#" class="nav-item" data-section="invoices">
<i class="fas fa-file-invoice"></i> Facturas
</a>
<a href="#" class="nav-item" data-section="accounting">
<i class="fas fa-calculator"></i> Contabilidad
</a>
<a href="#" class="nav-item" data-section="reports">
<i class="fas fa-chart-bar"></i> Informes
</a>
<a href="#" class="nav-item" data-section="backup">
<i class="fas fa-database"></i> Respaldo
</a>
<a href="#" class="nav-item" data-section="settings">
<i class="fas fa-cog"></i> Configuración
</a>
</nav>
<div class="sidebar-footer">
<div class="user-info">
<i class="fas fa-user"></i>
<span>Administrador</span>
</div>
</div>
</div>
<!-- Main Content -->
<div class="main-content">
<header class="main-header">
<div class="header-left">
<button class="btn-menu-toggle">
<i class="fas fa-bars"></i>
</button>
<h1 id="page-title">Dashboard</h1>
</div>
<div class="header-right">
<button class="btn-notifications">
<i class="fas fa-bell"></i>
<span class="notification-badge">3</span>
</button>
<div class="system-info">
<span id="current-date"></span>
</div>
</div>
</header>
<div class="content-area">
<!-- Secciones de contenido se cargarán aquí dinámicamente -->
<div id="dashboard" class="content-section active">
<!-- Contenido del dashboard -->
</div>
<div id="building" class="content-section">
<!-- Configuración del edificio -->
</div>
<div id="apartments" class="content-section">
<!-- Gestión de apartamentos -->
</div>
<div id="invoices" class="content-section">
<!-- Gestión de facturas -->
</div>
<div id="accounting" class="content-section">
<!-- Contabilidad -->
</div>
<div id="reports" class="content-section">
<!-- Informes -->
</div>
<div id="backup" class="content-section">
<!-- Respaldo de datos -->
</div>
<div id="settings" class="content-section">
<!-- Configuración del sistema -->
</div>
</div>
</div>
</div>
<!-- Modal para configuración de email -->
<div id="email-config-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>Configuración de Email</h3>
<button class="modal-close">×</button>
</div>
<div class="modal-body">
<!-- Formulario de configuración de email -->
</div>
</div>
</div>
<script src="renderer.js"></script>
</body>
</html>
3.2 Estilos CSS (src/renderer/styles.css)
/* Variables CSS */
:root {
--primary-color: #2c3e50;
--secondary-color: #3498db;
--accent-color: #e74c3c;
--success-color: #27ae60;
--warning-color: #f39c12;
--danger-color: #e74c3c;
--light-color: #ecf0f1;
--dark-color: #34495e;
--sidebar-width: 250px;
--header-height: 60px;
}
/* Reset y base */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f8f9fa;
color: #333;
overflow: hidden;
}
.app-container {
display: flex;
height: 100vh;
}
/* Sidebar */
.sidebar {
width: var(--sidebar-width);
background-color: var(--primary-color);
color: white;
display: flex;
flex-direction: column;
transition: width 0.3s ease;
}
.sidebar-header {
padding: 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.sidebar-header h2 {
font-size: 1.2rem;
margin-bottom: 5px;
}
.sidebar-header h2 i {
margin-right: 10px;
}
.app-version {
font-size: 0.8rem;
opacity: 0.7;
}
.sidebar-nav {
flex: 1;
padding: 20px 0;
}
.nav-item {
display: flex;
align-items: center;
padding: 12px 20px;
color: var(--light-color);
text-decoration: none;
transition: all 0.3s;
border-left: 4px solid transparent;
}
.nav-item:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-item.active {
background-color: var(--secondary-color);
border-left-color: white;
color: white;
}
.nav-item i {
width: 20px;
margin-right: 10px;
}
.sidebar-footer {
padding: 20px;
border-top: 1px solid rgba(255,255,255,0.1);
}
.user-info {
display: flex;
align-items: center;
font-size: 0.9rem;
}
.user-info i {
margin-right: 8px;
}
/* Main Content */
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.main-header {
height: var(--header-height);
background: white;
border-bottom: 1px solid #e0e0e0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header-left {
display: flex;
align-items: center;
}
.btn-menu-toggle {
background: none;
border: none;
font-size: 1.2rem;
margin-right: 15px;
cursor: pointer;
color: var(--dark-color);
}
.header-right {
display: flex;
align-items: center;
gap: 15px;
}
.btn-notifications {
position: relative;
background: none;
border: none;
font-size: 1.2rem;
cursor: pointer;
color: var(--dark-color);
}
.notification-badge {
position: absolute;
top: -5px;
right: -5px;
background: var(--danger-color);
color: white;
border-radius: 50%;
width: 18px;
height: 18px;
font-size: 0.7rem;
display: flex;
align-items: center;
justify-content: center;
}
.system-info {
font-size: 0.9rem;
color: #666;
}
/* Content Area */
.content-area {
flex: 1;
overflow: auto;
padding: 20px;
}
.content-section {
display: none;
}
.content-section.active {
display: block;
}
/* Cards */
.card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
margin-bottom: 20px;
overflow: hidden;
}
.card-header {
background: var(--primary-color);
color: white;
padding: 15px 20px;
border-bottom: 1px solid #e0e0e0;
}
.card-body {
padding: 20px;
}
/* Botones */
.btn {
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 0.9rem;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: var(--secondary-color);
color: white;
}
.btn-primary:hover {
background: #2980b9;
}
.btn-success {
background: var(--success-color);
color: white;
}
.btn-success:hover {
background: #219a52;
}
.btn-danger {
background: var(--danger-color);
color: white;
}
.btn-danger:hover {
background: #c0392b;
}
/* Forms */
.form-group {
margin-bottom: 15px;
}
.form-label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.form-control {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 0.9rem;
}
.form-control:focus {
outline: none;
border-color: var(--secondary-color);
}
/* Tablas */
.table {
width: 100%;
border-collapse: collapse;
}
.table th,
.table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
.table th {
background: var(--light-color);
font-weight: 600;
}
.table tr:hover {
background: #f8f9fa;
}
/* Modales */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 1000;
}
.modal.show {
display: flex;
align-items: center;
justify-content: center;
}
.modal-content {
background: white;
border-radius: 8px;
width: 90%;
max-width: 600px;
max-height: 90vh;
overflow: auto;
}
.modal-header {
padding: 20px;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
}
.modal-body {
padding: 20px;
}
/* Responsive */
@media (max-width: 768px) {
.sidebar {
width: 60px;
}
.sidebar-header h2 span,
.nav-item span,
.user-info span {
display: none;
}
.nav-item {
justify-content: center;
padding: 15px;
}
.nav-item i {
margin-right: 0;
}
}
/* Utilidades */
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-success { color: var(--success-color); }
.text-danger { color: var(--danger-color); }
.text-warning { color: var(--warning-color); }
.mt-1 { margin-top: 0.5rem; }
.mt-2 { margin-top: 1rem; }
.mt-3 { margin-top: 1.5rem; }
.mb-1 { margin-bottom: 0.5rem; }
.mb-2 { margin-bottom: 1rem; }
.mb-3 { margin-bottom: 1.5rem; }
.d-flex { display: flex; }
.justify-content-between { justify-content: space-between; }
.align-items-center { align-items: center; }
🔧 Paso 4: Funcionalidades Principales
4.1 JavaScript del Renderer (src/renderer/renderer.js)
// Aplicación principal
class MyComunidadApp {
constructor() {
this.config = {};
this.apartments = [];
this.invoices = [];
this.currentSection = 'dashboard';
this.init();
}
async init() {
await this.loadConfig();
this.setupEventListeners();
this.loadCurrentSection();
this.updateUI();
// Mostrar versión de la app
if (window.electronAPI) {
document.getElementById('app-version').textContent =
`v${window.electronAPI.appVersion}`;
}
}
async loadConfig() {
try {
if (window.electronAPI) {
this.config = await window.electronAPI.getConfig();
} else {
// Fallback para navegador web
const saved = localStorage.getItem('mycomunidadapp-config');
this.config = saved ? JSON.parse(saved) : {};
}
} catch (error) {
console.error('Error cargando configuración:', error);
this.config = {};
}
}
async saveConfig() {
try {
if (window.electronAPI) {
await window.electronAPI.setConfig(this.config);
} else {
localStorage.setItem('mycomunidadapp-config', JSON.stringify(this.config));
}
} catch (error) {
console.error('Error guardando configuración:', error);
}
}
setupEventListeners() {
// Navegación
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
const section = item.getAttribute('data-section');
this.showSection(section);
});
});
// Botón menú toggle
document.querySelector('.btn-menu-toggle').addEventListener('click', () => {
document.querySelector('.sidebar').classList.toggle('collapsed');
});
// Eventos del menú de Electron
if (window.electronAPI) {
window.electronAPI.onMenuNewInvoice(() => {
this.showSection('invoices');
// Aquí podrías abrir un modal para nueva factura
});
}
// Actualizar fecha actual
this.updateCurrentDate();
setInterval(() => this.updateCurrentDate(), 60000);
}
updateCurrentDate() {
const now = new Date();
const options = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
};
document.getElementById('current-date').textContent =
now.toLocaleDateString('es-ES', options);
}
showSection(sectionName) {
// Actualizar navegación
document.querySelectorAll('.nav-item').forEach(item => {
item.classList.remove('active');
});
document.querySelector(`[data-section="${sectionName}"]`).classList.add('active');
// Actualizar contenido
document.querySelectorAll('.content-section').forEach(section => {
section.classList.remove('active');
});
document.getElementById(sectionName).classList.add('active');
// Actualizar título
document.getElementById('page-title').textContent =
this.getSectionTitle(sectionName);
this.currentSection = sectionName;
this.loadSectionContent(sectionName);
}
getSectionTitle(section) {
const titles = {
dashboard: 'Dashboard',
building: 'Configuración del Edificio',
apartments: 'Gestión de Apartamentos',
invoices: 'Gestión de Facturas',
accounting: 'Contabilidad',
reports: 'Informes y Reportes',
backup: 'Respaldo de Datos',
settings: 'Configuración del Sistema'
};
return titles[section] || 'MyComunidadApp';
}
async loadSectionContent(sectionName) {
const sectionElement = document.getElementById(sectionName);
switch(sectionName) {
case 'dashboard':
await this.loadDashboard(sectionElement);
break;
case 'apartments':
await this.loadApartments(sectionElement);
break;
case 'invoices':
await this.loadInvoices(sectionElement);
break;
case 'settings':
await this.loadSettings(sectionElement);
break;
// ... otros casos
}
}
async loadDashboard(container) {
container.innerHTML = `
<div class="row">
<div class="col-md-3">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-home"></i> Apartamentos</h3>
</div>
<div class="card-body text-center">
<h2 id="total-apartments">0</h2>
<p>Total registrados</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-file-invoice"></i> Facturas</h3>
</div>
<div class="card-body text-center">
<h2 id="total-invoices">0</h2>
<p>Este mes</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-euro-sign"></i> Ingresos</h3>
</div>
<div class="card-body text-center">
<h2 id="total-income">0€</h2>
<p>Balance mensual</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-exclamation-triangle"></i> Pendientes</h3>
</div>
<div class="card-body text-center">
<h2 id="total-pending">0</h2>
<p>Facturas por pagar</p>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-chart-line"></i> Actividad Reciente</h3>
</div>
<div class="card-body">
<div id="recent-activity">
<p class="text-muted">Cargando actividad...</p>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-bolt"></i> Acciones Rápidas</h3>
</div>
<div class="card-body">
<button class="btn btn-primary btn-block mb-2" onclick="app.showSection('invoices')">
<i class="fas fa-plus"></i> Nueva Factura
</button>
<button class="btn btn-success btn-block mb-2" onclick="app.showSection('apartments')">
<i class="fas fa-home"></i> Agregar Apartamento
</button>
<button class="btn btn-info btn-block mb-2" onclick="app.generateReport()">
<i class="fas fa-chart-bar"></i> Generar Informe
</button>
<button class="btn btn-warning btn-block" onclick="app.showSection('backup')">
<i class="fas fa-database"></i> Respaldar Datos
</button>
</div>
</div>
</div>
</div>
`;
await this.updateDashboardStats();
}
async updateDashboardStats() {
// Aquí cargarías datos reales de tu base de datos
document.getElementById('total-apartments').textContent = this.apartments.length;
document.getElementById('total-invoices').textContent = this.invoices.length;
// Datos de ejemplo
document.getElementById('total-income').textContent = '1,250€';
document.getElementById('total-pending').textContent = '3';
}
async loadSettings(container) {
container.innerHTML = `
<div class="card">
<div class="card-header">
<h3><i class="fas fa-cog"></i> Configuración del Sistema</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h4>Configuración General</h4>
<div class="form-group">
<label class="form-label">Nombre de la Comunidad</label>
<input type="text" class="form-control" id="community-name"
value="${this.config.empresa?.nombre || ''}">
</div>
<div class="form-group">
<label class="form-label">Carpeta de Guardado</label>
<div class="input-group">
<input type="text" class="form-control" id="save-folder"
value="${this.config.carpetaGuardado || ''}" readonly>
<button class="btn btn-outline-secondary" type="button" id="select-folder">
<i class="fas fa-folder-open"></i>
</button>
</div>
</div>
</div>
<div class="col-md-6">
<h4>Configuración de Email</h4>
<div class="form-group">
<label class="form-label">Servidor SMTP</label>
<input type="text" class="form-control" id="smtp-host"
value="${this.config.emailConfig?.smtpHost || ''}">
</div>
<div class="form-group">
<label class="form-label">Email</label>
<input type="email" class="form-control" id="smtp-email"
value="${this.config.emailConfig?.email || ''}">
</div>
<div class="form-group">
<label class="form-label">Contraseña</label>
<input type="password" class="form-control" id="smtp-password"
value="${this.config.emailConfig?.password || ''}">
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<button class="btn btn-primary" id="save-settings">
<i class="fas fa-save"></i> Guardar Configuración
</button>
<button class="btn btn-outline-secondary" id="test-email">
<i class="fas fa-paper-plane"></i> Probar Email
</button>
</div>
</div>
</div>
</div>
`;
// Event listeners para configuración
document.getElementById('select-folder').addEventListener('click', async () => {
if (window.electronAPI) {
const result = await window.electronAPI.selectFolder();
if (!result.canceled && result.filePaths.length > 0) {
document.getElementById('save-folder').value = result.filePaths[0];
}
}
});
document.getElementById('save-settings').addEventListener('click', async () => {
await this.saveSettings();
});
document.getElementById('test-email').addEventListener('click', async () => {
await this.testEmailConfiguration();
});
}
async saveSettings() {
this.config.empresa = {
nombre: document.getElementById('community-name').value
};
this.config.carpetaGuardado = document.getElementById('save-folder').value;
this.config.emailConfig = {
smtpHost: document.getElementById('smtp-host').value,
email: document.getElementById('smtp-email').value,
password: document.getElementById('smtp-password').value,
smtpPort: 587,
secure: false
};
await this.saveConfig();
alert('Configuración guardada correctamente');
}
async testEmailConfiguration() {
if (!window.electronAPI) {
alert('Esta función solo está disponible en la versión de escritorio');
return;
}
const testEmail = document.getElementById('smtp-email').value;
if (!testEmail) {
alert('Por favor, configura primero el email');
return;
}
try {
const result = await window.electronAPI.sendEmail({
to: testEmail,
subject: 'Prueba de configuración - MyComunidadApp',
body: 'Este es un email de prueba para verificar la configuración de correo.',
html: `
<h1>Prueba de Configuración</h1>
<p>Si estás recibiendo este email, la configuración de correo está funcionando correctamente.</p>
<p><strong>MyComunidadApp</strong></p>
`
});
if (result.success) {
alert('Email de prueba enviado correctamente');
} else {
alert('Error enviando email: ' + result.error);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
updateUI() {
// Actualizar interfaz según el estado
}
loadCurrentSection() {
this.showSection(this.currentSection);
}
}
// Inicializar la aplicación cuando el DOM esté listo
document.addEventListener('DOMContentLoaded', () => {
window.app = new MyComunidadApp();
});
// Utilidades CSS para grid system
const style = document.createElement('style');
style.textContent = `
.row {
display: flex;
flex-wrap: wrap;
margin: 0 -10px;
}
.col-md-3, .col-md-4, .col-md-6, .col-md-8 {
padding: 0 10px;
margin-bottom: 20px;
}
.col-md-3 { flex: 0 0 25%; max-width: 25%; }
.col-md-4 { flex: 0 0 33.333%; max-width: 33.333%; }
.col-md-6 { flex: 0 0 50%; max-width: 50%; }
.col-md-8 { flex: 0 0 66.666%; max-width: 66.666%; }
.btn-block {
display: block;
width: 100%;
}
@media (max-width: 768px) {
.col-md-3, .col-md-4, .col-md-6, .col-md-8 {
flex: 0 0 100%;
max-width: 100%;
}
}
`;
document.head.appendChild(style);
📦 Paso 5: Empacar para Distribución
5.1 Configurar electron-builder
Actualiza tu package.json con la configuración completa:
{
"name": "mycomunidadapp",
"version": "1.0.0",
"description": "Sistema de gestión para comunidades de propietarios",
"main": "src/main.js",
"homepage": "./",
"scripts": {
"start": "electron .",
"dev": "NODE_ENV=development electron .",
"build": "electron-builder",
"build-win": "electron-builder --win",
"build-mac": "electron-builder --mac",
"build-linux": "electron-builder --linux",
"build-all": "npm run build-win && npm run build-mac && npm run build-linux",
"dist": "npm run build"
},
"keywords": ["condominio", "comunidad", "facturas", "gestión"],
"author": {
"name": "Tu Nombre",
"email": "tu@email.com"
},
"license": "MIT",
"devDependencies": {
"electron": "^22.0.0",
"electron-builder": "^24.0.0"
},
"dependencies": {
"nodemailer": "^6.9.0",
"pdfkit": "^0.13.0",
"fs-extra": "^11.0.0"
},
"build": {
"appId": "com.tudominio.mycomunidadapp",
"productName": "MyComunidadApp",
"copyright": "Copyright © 2024 ${author}",
"directories": {
"output": "dist",
"buildResources": "assets"
},
"files": [
"src/**/*",
"assets/**/*",
"node_modules/**/*",
"package.json"
],
"extraResources": [
{
"from": "assets/icons",
"to": "icons"
}
],
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64", "ia32"]
},
{
"target": "portable",
"arch": ["x64", "ia32"]
}
],
"icon": "assets/icons/icon.ico",
"publisherName": "Tu Nombre"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "MyComunidadApp"
},
"mac": {
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
}
],
"icon": "assets/icons/icon.icns",
"category": "public.app-category.business"
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
}
],
"icon": "assets/icons/icon.png",
"category": "Office"
}
}
}
5.2 Crear iconos de la aplicación
Crea una carpeta assets/icons/ y añade estos archivos:
icon.ico(Windows, 256×256)icon.icns(macOS, 1024×1024)icon.png(Linux, 512×512)
Consejo: Puedes usar herramientas como:
- ICO Convert para .ico
- Image2icon para .icns
- O crear tus propios iconos con GIMP o Photoshop
5.3 Comandos de construcción
# Construir para Windows
npm run build-win
# Construir para macOS
npm run build-mac
# Construir para Linux
npm run build-linux
# Construir para todas las plataformas
npm run build-all
🚀 Paso 6: Instalador y Distribución
6.1 Estructura final de archivos
Asegúrate de que tu proyecto tenga esta estructura:
mycomunidadapp/
├── src/
│ ├── main.js
│ ├── preload.js
│ └── renderer/
│ ├── index.html
│ ├── styles.css
│ └── renderer.js
├── assets/
│ └── icons/
│ ├── icon.ico
│ ├── icon.icns
│ └── icon.png
├── dist/ # Generado automáticamente
├── package.json
└── README.md
6.2 Comando final para construir
# Instalar dependencias
npm install
# Ejecutar en modo desarrollo
npm start
# Construir instaladores
npm run build
6.3 Archivos generados
En la carpeta dist/ encontrarás:
Windows:
MyComunidadApp Setup 1.0.0.exe(Instalador)MyComunidadApp-1.0.0-portable.exe(Versión portable)
macOS:
MyComunidadApp-1.0.0.dmg(Imagen de disco)
Linux:
MyComunidadApp-1.0.0.AppImage(Aplicación portable)MyComunidadApp_1.0.0_amd64.deb(Paquete Debian)
📋 Resumen del Proceso
- Configuración inicial – Estructura del proyecto y dependencias
- Proceso principal – Ventana de Electron y APIs del sistema
- Interfaz de usuario – HTML, CSS y JavaScript del frontend
- Funcionalidades – Gestión de datos, facturas, email, etc.
- Empaquetado – Crear instaladores para cada plataforma
- Distribución – Compartir el software con usuarios finales
🎉 ¡Felicidades!
Has creado un software completo para PC que:
- ✅ Funciona en Windows, macOS y Linux
- ✅ Tiene interfaz nativa
- ✅ Puede acceder al sistema de archivos
- ✅ Envía emails
- ✅ Genera PDFs
- ✅ Se instala como una aplicación normal
- ✅ Se actualiza automáticamente (con electron-updater)
Próximos pasos:
- Añadir más funcionalidades específicas
- Implementar una base de datos real
- Añadir autenticación de usuarios
- Crear un sistema de actualizaciones automáticas
- Publicar en tu website o stores
Descubre más desde Clasenet
Suscríbete y recibe las últimas entradas en tu correo electrónico.

