Tutorial Completo: Crear Software para PC con Electron

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

  1. Inicialización:
  • Electron inicia un proceso Node.js (main)
  • El proceso main crea ventanas usando Chromium
  1. Comunicación:
  • IPC (Inter-Process Communication) conecta main y renderer
  • Preload scripts actúan como puente seguro
  1. 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:

  1. Configurar SMTP del administrador
  2. Generar factura en PDF
  3. Enviar email con factura adjunta
  4. 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:

  1. Encabezado: Logo, información de la empresa
  2. Cliente: Datos del destinatario
  3. Detalles: Tabla de productos/servicios
  4. Totales: Cálculos y sumatorias
  5. 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ón
  • develop: Desarrollo en curso
  • feature/nueva-funcionalidad: Funcionalidades nuevas
  • hotfix/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 --version y npm --version en 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 Electron
  • scripts: Comandos para desarrollo y build
  • devDependencies: Electron y herramientas de build
  • dependencies: Librerías que usa tu app

Dependencias esenciales:

  • electron: Runtime principal
  • electron-builder: Para crear instaladores
  • nodemailer: Envío de emails
  • pdfkit: Generación de PDFs

Configuración de seguridad:

  • nodeIntegration: false – Previene inyección de código
  • contextIsolation: true – Aísla el contexto
  • preload.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á listo
  • app.on('window-all-closed') – Comportamiento al cerrar
  • app.on('activate') – Comportamiento en macOS

IPC (Comunicación entre procesos):

  • ipcMain.handle() – Maneja peticiones del renderer
  • ipcMain.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 require o process completos
  • 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

  1. Prerrequisitos
  2. Estructura del Proyecto
  3. Paso 1: Configuración Inicial
  4. Paso 2: Crear la Aplicación Electron
  5. Paso 3: Interfaz de Usuario
  6. Paso 4: Funcionalidades Principales
  7. Paso 5: Empacar para Distribución
  8. Paso 6: Instalador y Distribución

🛠 Prerrequisitos

Software necesario:

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">&times;</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:

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

  1. Configuración inicial – Estructura del proyecto y dependencias
  2. Proceso principal – Ventana de Electron y APIs del sistema
  3. Interfaz de usuario – HTML, CSS y JavaScript del frontend
  4. Funcionalidades – Gestión de datos, facturas, email, etc.
  5. Empaquetado – Crear instaladores para cada plataforma
  6. 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.

Miguel Ángel Urbaez
Miguel Ángel Urbaez
Artículos: 102

Deja un comentario