Wails v2 · Go + React + TypeScript

CultivaLeads

App de escritorio multiplataforma para gestión de leads — CULTIVA IA · generado con skill wails-apps-escritorio-go-web
⚡ Wails v2 ◉ Go 1.22 ⚛ React 18 🔷 TypeScript 🗄 SQLite

Vista previa de la aplicación

UI Mockup
CultivaLeads — CULTIVA IA
Filtrar por estado
Todos 5
Nuevo 1
Contactado 1
Propuesta 1
Cerrado 1
Perdido 1
Lead Servicio Estado Fecha
Sara Montiel
Clínica Dental Nórdica
sara@clinica-nordica.com
Automatización IA Nuevo 14 jun 2026
Marcos Ibáñez
Gym Urban Fit
marcos@urbanfit.es
Marketing Contenido Contactado 12 jun 2026
Laura Crespo
SaaS Contableo
laura@contableo.io
Agentes IA Propuesta 10 jun 2026
Javier Ruiz
E-commerce BioBelleza
javier@biobelleza.com
SEO + Contenido Cerrado ✓ 5 jun 2026
Ana Torres
Consultora Nexo
ana@consultora-nexo.com
Web Corporativa Perdido 2 jun 2026
Notas internas — Sara Montiel
Interesada en automatizar flujos de citas. Llamada inicial el 14/06. Enviar propuesta antes del 18/06 — presupuesto estimado €1.800/mes. Contacto previo con otra agencia, precio fue su freno.

Arquitectura Wails

Diagrama
🗄
SQLite
leads.db
offline-first
~/.cultivaleads/
sql.DB
CRUD
Go Backend
App struct
GetLeads / CreateLead
UpdateLead / ExportCSV
TS Bindings
auto-gen
Wails Bridge
Bindings TypeScript
EventsEmit
Dialogs nativos
import
go/main
React Frontend
LeadList / LeadDetail
FilterSidebar
Tailwind CSS

Estructura del proyecto

File tree
cultivaleads/ ├── app.go ← App struct + métodos Go expuestos al frontend ├── main.go ← Wails options, menú nativo, embed assets ├── go.mod ├── wails.json ← Configuración Wails (nombre, version, frontend dir) ├── frontend/ │ ├── src/ │ │ ├── App.tsx ← Root con sidebar + LeadTable + DetailPanel │ │ ├── components/ │ │ │ ├── LeadTable.tsx │ │ │ ├── LeadDetail.tsx │ │ │ ├── FilterSidebar.tsx │ │ │ └── StatsHeader.tsx │ │ ├── hooks/ │ │ │ └── useLeads.ts ← Lógica de estado + llamadas a Go │ │ └── index.css ← Tailwind + tema oscuro │ └── wailsjs/ ← AUTO-GENERADO por Wails (no editar) │ └── go/main/ │ ├── App.d.ts ← Tipos TypeScript desde structs Go │ └── App.js ← Bindings JS para llamar a Go └── build/ ├── bin/ │ └── CultivaLeads.app ← Binario final macOS (bundle único) └── windows/ └── CultivaLeads-setup.exe ← Installer NSIS

Go Backend — app.go

Go
app.go — Struct y métodos expuestos
Go
package main

import (
  "context"
  "database/sql"
  "encoding/csv"
  "os"
  "path/filepath"
  _ "modernc.org/sqlite"
)

// Lead — struct con json tags → TypeScript interface auto-gen
type Lead struct {
  ID        int64  `json:"id"`
  Nombre    string `json:"nombre"`
  Empresa   string `json:"empresa"`
  Email     string `json:"email"`
  Servicio  string `json:"servicio"`
  Estado    string `json:"estado"`
  Notas     string `json:"notas"`
  FechaAlta string `json:"fechaAlta"`
}

type App struct {
  ctx context.Context
  db  *sql.DB
}

func (a *App) startup(ctx context.Context) {
  a.ctx = ctx
  home, _ := os.UserHomeDir()
  dbPath := filepath.Join(home, ".cultivaleads", "leads.db")
  os.MkdirAll(filepath.Dir(dbPath), 0755)
  a.db, _ = sql.Open("sqlite", dbPath)
  a.db.Exec(`CREATE TABLE IF NOT EXISTS leads (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nombre TEXT NOT NULL,
    empresa TEXT,
    email TEXT,
    servicio TEXT,
    estado TEXT DEFAULT 'nuevo',
    notas TEXT DEFAULT '',
    fecha_alta DATETIME DEFAULT CURRENT_TIMESTAMP
  )`)
}

// GetLeads — retorna todos los leads (o filtrados por estado)
func (a *App) GetLeads(estado string) ([]Lead, error) {
  query := "SELECT * FROM leads ORDER BY fecha_alta DESC"
  args := []interface{}{}
  if estado != "" && estado != "todos" {
    query = "SELECT * FROM leads WHERE estado=? ORDER BY fecha_alta DESC"
    args = append(args, estado)
  }
  rows, err := a.db.Query(query, args...)
  // ... scan rows → []Lead
  return leads, err
}

// ExportCSV — escribe CSV y devuelve la ruta al frontend
func (a *App) ExportCSV() (string, error) {
  leads, _ := a.GetLeads("todos")
  path := filepath.Join(os.TempDir(), "cultivaleads-export.csv")
  f, _ := os.Create(path)
  w := csv.NewWriter(f)
  w.Write([]string{"ID", "Nombre", "Empresa", "Email", "Servicio", "Estado", "FechaAlta"})
  for _, l := range leads {
    w.Write([]string{fmt.Sprint(l.ID), l.Nombre, l.Empresa,
      l.Email, l.Servicio, l.Estado, l.FechaAlta})
  }
  w.Flush()
  return path, nil
}
frontend/src/hooks/useLeads.ts — Lógica de estado
TSX
// Bindings auto-generados por Wails — importar como paquetes Go
import {
  GetLeads, CreateLead,
  UpdateLead, DeleteLead,
  ExportCSV
} from "../../wailsjs/go/main/App"
import { main } from "../../wailsjs/go/models"
import { useState, useCallback } from "react"

export function useLeads() {
  const [leads, setLeads] = useState<main.Lead[]>([])
  const [filter, setFilter] = useState("todos")
  const [selected, setSelected] = useState<main.Lead | null>(null)

  const load = useCallback(async () => {
    // Llama directo al método Go — retorna typed Lead[]
    const data = await GetLeads(filter)
    setLeads(data ?? [])
  }, [filter])

  const create = async (nombre: string) => {
    const lead = await CreateLead(nombre, "", "", "")
    if (lead) { setSelected(lead); await load() }
  }

  const update = async (l: main.Lead) => {
    await UpdateLead(l.id, l.nombre, l.empresa,
      l.email, l.servicio, l.estado, l.notas)
    await load()
  }

  const exportCSV = async () => {
    const path = await ExportCSV()
    // Abrir Finder/Explorer en la carpeta del CSV
    ShowInFinder(path)
  }

  return { leads, selected, filter,
    setFilter, setSelected, load,
    create, update, exportCSV }
}

main.go — Wails App Runner

Go
main.go — Configuración Wails, menú nativo y embed
Go
package main

import (
  "embed"
  "github.com/wailsapp/wails/v2"
  "github.com/wailsapp/wails/v2/pkg/menu"
  "github.com/wailsapp/wails/v2/pkg/options"
  "github.com/wailsapp/wails/v2/pkg/options/assetserver"
  "github.com/wailsapp/wails/v2/pkg/runtime"
  "github.com/wailsapp/wails/v2/pkg/keys"
)

//go:embed all:frontend/dist
var assets embed.FS   // Frontend React compilado embebido en el binario

func main() {
  app := &App{}

  // Menú nativo (macOS / Windows)
  appMenu := menu.NewMenu()
  fileMenu := appMenu.AddSubmenu("Archivo")
  fileMenu.AddText("Nuevo lead", keys.CmdOrCtrl("n"), func(_ *menu.CallbackData) {
    runtime.EventsEmit(app.ctx, "menu:new-lead")   // Evento → React
  })
  fileMenu.AddSeparator()
  fileMenu.AddText("Exportar CSV", keys.CmdOrCtrl("e"), func(_ *menu.CallbackData) {
    runtime.EventsEmit(app.ctx, "menu:export-csv")
  })
  fileMenu.AddSeparator()
  fileMenu.AddText("Salir", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) {
    runtime.Quit(app.ctx)
  })

  err := wails.Run(&options.App{
    Title:     "CultivaLeads",
    Width:     1200,
    Height:    720,
    MinWidth:  800,
    MinHeight: 500,
    AssetServer: &assetserver.Options{Assets: assets},
    OnStartup: app.startup,
    Menu:      appMenu,
    Bind:      []interface{}{app},   // ← Expone todos los métodos de App al frontend
    // Ventana frameless con título personalizado
    Frameless: false,
    DisableResize: false,
  })
  if err != nil { panic(err) }
}

Ciclo de desarrollo y distribución

CLI
1
Instalar Wails CLI
Prerequisito: Go 1.21+ y Node 18+
go install github.com/wailsapp/wails/v2/cmd/wails@latest
2
Crear proyecto
Template React + TypeScript con estructura completa
wails init -n cultivaleads -t react-ts
3
Modo desarrollo
Hot reload Go + React simultáneo. Bindings regenerados al guardar.
wails dev
4
Build & distribuir
Binario único macOS/Windows/Linux. Installer NSIS opcional.
wails build -platform darwin/amd64
wails build -nsis