Emitidas este mes
47
↑ 12% vs anterior
Monto neto mes
$8.42M
IVA: $1.60M
Por cobrar
$4.2M
6 facturas activas
Automáticas activas
5
Próxima: día 1
Facturación 2025Neto mensual
Estado SII
Token authActivo
Certificado .p12Válido
Folios FAC 33312 disp.
Folios NC 6198 disp.
Último DTEhoy 11:47
Documentos recientes
| Folio | Receptor | Neto | Total | Estado | Fecha |
|---|
Facturas Electrónicas (33)
| Folio | Receptor | RUT | Neto | IVA | Total | Estado | Fecha | Cond. pago | Acciones |
|---|
ℹ️ Las NC referencian una factura aceptada. Pueden anular totalmente (motivo 1) o corregir montos (motivo 3).
Notas de Crédito (61)
| Folio NC | Fac. ref. | Receptor | Motivo | Monto | Estado | Fecha |
|---|
Directorio de clientes
| RUT | Razón Social | Giro | Ciudad | Facturas | Por cobrar | Acciones |
|---|
Catálogo
| Código | Descripción | Precio neto | IVA | Total | Stock |
|---|
Proveedores y compras
| RUT | Razón Social | Giro | Facturas recibidas | Saldo pendiente | Acciones |
|---|
Total por cobrar
$6.8M
12 facturas
Vencidas +30d
$2.1M
4 facturas
Cobrado mes
$5.3M
↑ 8%
Cartera de cobranza
Cobrado este mes
$5.3M
Pagado proveedores
$1.8M
Pendiente cobro
$6.8M
Pendiente pago
$1.6M
💰 Pagos recibidos
💳 Pagos a proveedores
↻ Se emiten el día configurado de cada mes. El XML se firma con tu certificado .p12 y se envía al SII + correo del receptor.
Enviados mes
52
Entregados
50
Fallidos
2
Historial de envíos XML
| Fecha/Hora | Destinatario | Asunto | Folio | Tipo | Estado | Adjunto |
|---|
Documentos
47
Monto neto
$8.42M
IVA débito fiscal
$1.60M
Libro de Ventas — Mayo 2025
| Folio | Tipo | RUT | Razón Social | Neto | IVA | Total | Fecha |
|---|
⚠️ Referencial. Verifica con tu contador antes de declarar.
📊 Resumen IVA Mayo 2025
Débito fiscal ventas$1.600.200
Ajuste NC (61)−$79.800
IVA a declarar$1.520.400
📅 Calendario tributario
F29 Mayo 202512 Jun
Libro ventas electrónico15 Jun
F29 Abril 2025Presentado
Libro ventas AbrilPresentado
Usuarios y permisos
Roles disponibles
AdministradorAcceso total al sistema, configuración SII, usuarios
ContadorFacturas, NC, reportes, libro de ventas, F29
FacturadorCrear facturas, NC, clientes — sin configuración
Solo lecturaVer reportes y documentos únicamente
⚠️ Estos datos aparecen en todos los documentos tributarios emitidos.
🏛 Datos tributarios
🎨 Personalización facturas
📧 Correo empresa
XML al receptor automático
Alerta rechazo SII
Resumen mensual por email
🔑 Cambiar Contraseña
👥 Usuarios activos
⚠️ Producción activa — palena.sii.cl. Los DTE emitidos tienen validez legal.
🔐 Certificado digital
🔗 Test de conexión SII
Endpointpalena.sii.cl
Semilla—
Firma RSA-SHA1—
Token—
Obtén CAF en misiir.cl → DTE → Solicitar folios. Sube el XML aquí.
Factura (33)
312
Folios 501–900 · próximo: 589
NC (61)
98
Folios 1–200 · próximo: 103
Subir archivo CAF
📂
Arrastra el archivo CAF aquí
XML descargado desde misiir.cl · tipo detectado automáticamente
Esta guía está diseñada específicamente para tu Contabo VPS con Ubuntu. Sigue los pasos en orden.
① Instalar
② .env
③ Auth SII
④ DTE Builder
⑤ Scheduler
⑥ Nginx + SSL
📦 1. Preparar el VPS Contabo
# Conecta a tu VPS por SSH
ssh root@TU_IP_CONTABO
# Actualizar sistema
apt update && apt upgrade -y
# Instalar Node.js 20 LTS
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs
# Verificar versiones
node --version # debe ser v20.x
npm --version # debe ser 10.x
# Instalar PM2 (gestor de procesos)
npm install -g pm2
# Instalar Git
apt install -y git
# Crear directorio del proyecto
mkdir -p /opt/poscam-backend
cd /opt/poscam-backend
📦 2. Instalar dependencias Node
cd /opt/poscam-backend
npm init -y
# Dependencias principales
npm install express cors dotenv
npm install node-forge # firma RSA-SHA1
npm install xmlbuilder2 xml2js # XML DTE
npm install axios # HTTP al SII
npm install nodemailer # envío email con XML
npm install node-cron # facturas automáticas
npm install mongoose # base de datos
npm install multer # subida .p12 y CAF
npm install bcryptjs jsonwebtoken # auth usuarios
npm install helmet express-rate-limit # seguridad
# Instalar MongoDB
apt install -y mongodb
systemctl enable mongodb
systemctl start mongodb
# Estructura del proyecto
mkdir -p certs routes services models
touch index.js .env
🔑 Archivo .env — completo para Contabo
# /opt/poscam-backend/.env
# NUNCA subir a Git — agregar .env a .gitignore
# ── SII PRODUCCIÓN ──────────────────────────
SII_AMBIENTE=produccion
SII_HOST=palena.sii.cl
SII_URL_SEED=https://palena.sii.cl/DTEWS/CrSeed.jws
SII_URL_TOKEN=https://palena.sii.cl/DTEWS/GetTokenFromSeed.jws
SII_URL_UPLOAD=https://palena.sii.cl/cgi_dte/UPL_DTE
SII_URL_ESTADO=https://palena.sii.cl/cgi_dte/UPL_DTE
# ── CERTIFICADO DIGITAL ─────────────────────
CERT_PATH=./certs/certificado.p12
CERT_PASSWORD=tu_contraseña_aqui
RUT_FIRMA=12345678-9 # representante legal
# ── DATOS EMPRESA ───────────────────────────
EMPRESA_RUT=76543210-K
EMPRESA_RAZON=POSCAM SPA
EMPRESA_GIRO=INSTALACION Y SERVICIO DE ACCESORIOS PARA VEHICULOS
EMPRESA_DIR=PASAJE NUMPAY 1641 STA. TERESA DE COLIN
EMPRESA_CIUDAD=Talca
RES_SII=
# ── CORREO (SMTP) ───────────────────────────
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=[email protected]
SMTP_PASS=abcd_efgh_ijkl_mnop # App Password Gmail
EMAIL_FROM="POSCAM ERP <[email protected]>"
EMAIL_NOTIFY=[email protected]
EMAIL_CC=[email protected]
# ── BASE DE DATOS ───────────────────────────
MONGODB_URI=mongodb://localhost:27017/poscam
# ── SERVIDOR ────────────────────────────────
PORT=3000
JWT_SECRET=poscam_secreto_muy_largo_y_seguro_2025
FRONTEND_URL=https://poscam.miempresa.cl
🔐 services/sii-auth.js
const forge = require('node-forge');
const axios = require('axios');
const fs = require('fs');
const xml2js = require('xml2js');
let _token = null;
let _tokenExp = null;
async function getToken() {
// Reusar token si no ha expirado (1 hora)
if (_token && Date.now() < _tokenExp)
return _token;
// 1. Cargar certificado .p12
const p12Der = fs.readFileSync(process.env.CERT_PATH);
const p12b64 = forge.util.encode64(p12Der.toString('binary'));
const p12Asn1 = forge.asn1.fromDer(forge.util.decode64(p12b64));
const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, process.env.CERT_PASSWORD);
// 2. Extraer clave privada
const keyBags = p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag });
const privKey = keyBags[forge.pki.oids.pkcs8ShroudedKeyBag][0].key;
// 3. Obtener semilla del SII
const seedResp = await axios.get(process.env.SII_URL_SEED);
const seedParsed = await xml2js.parseStringPromise(seedResp.data);
const seed = seedParsed.SII_BODY.SEMILLA[0];
// 4. Firmar semilla RSA-SHA1
const md = forge.md.sha1.create();
md.update(seed, 'utf8');
const sig64 = forge.util.encode64(privKey.sign(md));
// 5. Solicitar token
const xml = `<getToken>
<item>
<Semilla>${seed}</Semilla>
<Signature>${sig64}</Signature>
</item>
</getToken>`;
const tokResp = await axios.post(process.env.SII_URL_TOKEN, xml,
{ headers: { 'Content-Type': 'text/xml' }});
const tokParsed = await xml2js.parseStringPromise(tokResp.data);
_token = tokParsed.SII_BODY.TOKEN[0];
_tokenExp = Date.now() + 3500000; // ~58 min
return _token;
}
module.exports = { getToken };
📤 services/mailer.js
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: +process.env.SMTP_PORT,
secure: false,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
});
async function sendXML({ to, folio, tipo, xmlContent, razonReceptor }) {
const label = tipo === '61' ? 'Nota de Crédito' : 'Factura Electrónica';
await transporter.sendMail({
from: process.env.EMAIL_FROM,
to,
cc: process.env.EMAIL_CC || undefined,
subject: `${label} N°${folio} — ${process.env.EMPRESA_RAZON}`,
html: `<p>Estimado(a) ${razonReceptor},</p>
<p>Adjunto encontrará su ${label.toLowerCase()} N°${folio}
emitida por <strong>${process.env.EMPRESA_RAZON}</strong>.</p>
<p>Puede verificar la validez del documento en misiir.cl</p>`,
attachments: [{
filename: `DTE_${tipo}_${folio}.xml`,
content: xmlContent,
contentType: 'application/xml'
}]
});
}
module.exports = { sendXML };
📄 services/dte-builder.js — Constructor DTE completo
const { create } = require('xmlbuilder2');
const forge = require('node-forge');
const fs = require('fs');
// Carga el certificado una sola vez
function loadCert() {
const p12Der = fs.readFileSync(process.env.CERT_PATH);
const p12 = forge.pkcs12.pkcs12FromAsn1(
forge.asn1.fromDer(p12Der.toString('binary')),
process.env.CERT_PASSWORD
);
const keyBags = p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag });
const certBags = p12.getBags({ bagType: forge.pki.oids.certBag });
return {
key: keyBags[forge.pki.oids.pkcs8ShroudedKeyBag][0].key,
cert: certBags[forge.pki.oids.certBag][0].cert
};
}
function buildDTE(factura) {
const cfg = {
rut: process.env.EMPRESA_RUT,
razon: process.env.EMPRESA_RAZON,
giro: process.env.EMPRESA_GIRO,
dir: process.env.EMPRESA_DIR,
ciudad:process.env.EMPRESA_CIUDAD,
res: process.env.RES_SII
};
const doc = create({ version: '1.0', encoding: 'ISO-8859-1' })
.ele('DTE', { version: '1.0', xmlns: 'http://www.sii.cl/SiiDte' })
.ele('Documento', { ID: `DTE-${factura.tipo}-${factura.folio}` })
.ele('Encabezado')
.ele('IdDoc')
.ele('TipoDTE').txt(factura.tipo).up()
.ele('Folio').txt(factura.folio).up()
.ele('FchEmis').txt(factura.fecha).up()
.ele('IndMntNeto').txt('2').up()
.ele('TpoTranVenta').txt('1').up()
.ele('FmaPago').txt('1').up()
.up()
.ele('Emisor')
.ele('RUTEmisor').txt(cfg.rut).up()
.ele('RznSoc').txt(cfg.razon).up()
.ele('GiroEmis').txt(cfg.giro).up()
.ele('DirOrigen').txt(cfg.dir).up()
.ele('CmnaOrigen').txt(cfg.ciudad).up()
.ele('CiudadOrigen').txt(cfg.ciudad).up()
.up()
.ele('Receptor')
.ele('RUTRecep').txt(factura.rutReceptor).up()
.ele('RznSocRecep').txt(factura.razonReceptor).up()
.ele('GiroRecep').txt(factura.giroReceptor || '').up()
.ele('DirRecep').txt(factura.dirReceptor || '').up()
.up()
.ele('Totales')
.ele('MntNeto').txt(factura.neto).up()
.ele('TasaIVA').txt('19.00').up()
.ele('IVA').txt(factura.iva).up()
.ele('MntTotal').txt(factura.total).up()
.up()
.up();
// Agregar líneas de detalle
factura.items.forEach((item, i) => {
doc.ele('Detalle')
.ele('NroLinDet').txt(i + 1).up()
.ele('NmbItem').txt(item.desc).up()
.ele('QtyItem').txt(item.qty).up()
.ele('PrcItem').txt(item.precio).up()
.ele('MontoItem').txt(Math.round(item.qty * item.precio)).up()
.up();
});
// Si es NC (61), agregar referencia
if (factura.tipo === '61' && factura.folioRef) {
doc.ele('Referencia')
.ele('NroLinRef').txt('1').up()
.ele('TpoDocRef').txt('33').up()
.ele('FolioRef').txt(factura.folioRef).up()
.ele('CodRef').txt(factura.motivoRef || '1').up()
.up();
}
return doc.end({ prettyPrint: true });
}
module.exports = { buildDTE, loadCert };
⏰ services/scheduler.js — Automáticas
const cron = require('node-cron');
const Programada = require('../models/Programada');
const { emitirFactura } = require('./emisor');
// Corre todos los días a las 08:00 AM
cron.schedule('0 8 * * *', async () => {
const hoy = new Date();
const diaHoy = hoy.getDate();
const ultimoDia = new Date(
hoy.getFullYear(), hoy.getMonth()+1, 0
).getDate();
// Buscar todas las automáticas activas
const progs = await Programada.find({ activa: true });
for (const p of progs) {
const diaEmision = p.dia === 'ultimo'
? ultimoDia : parseInt(p.dia);
if (diaHoy === diaEmision) {
try {
await emitirFactura({
rutReceptor: p.rut,
razonReceptor: p.razon,
emailReceptor: p.email,
neto: p.monto,
iva: Math.round(p.monto * 0.19),
total: p.monto + Math.round(p.monto * 0.19),
items: [{
desc: p.descripcion,
qty: 1,
precio: p.monto
}],
enviarXML: p.enviarXML,
notificar: p.notificar
});
console.log(`✅ Emitida: ${p.nombre} folio OK`);
} catch(e) {
console.error(`❌ Error: ${p.nombre}:`, e.message);
}
}
}
}, { timezone: 'America/Santiago' });
📡 routes/programadas.js — CRUD completo
// GET todas las automatizaciones
router.get('/', async (req, res) => {
const list = await Programada.find();
res.json(list);
});
// POST nueva automatización
router.post('/', async (req, res) => {
const prog = new Programada(req.body);
await prog.save();
res.json(prog);
});
// PUT editar — monto, desc, dia, receptor
router.put('/:id', async (req, res) => {
const { nombre, dia, monto, descripcion,
rut, razon, email, activa,
enviarXML, notificar } = req.body;
const updated = await Programada.findByIdAndUpdate(
req.params.id,
{ nombre, dia, monto, descripcion,
rut, razon, email, activa,
enviarXML, notificar,
updatedAt: new Date() },
{ new: true }
);
res.json(updated);
});
// PATCH activar/pausar individual
router.patch('/:id/toggle', async (req, res) => {
const prog = await Programada.findById(req.params.id);
prog.activa = !prog.activa;
await prog.save();
res.json({ activa: prog.activa });
});
// DELETE eliminar
router.delete('/:id', async (req, res) => {
await Programada.findByIdAndDelete(req.params.id);
res.json({ ok: true });
});
🌐 Nginx como proxy inverso
# Instalar Nginx
apt install -y nginx
# Crear configuración para POSCAM
nano /etc/nginx/sites-available/poscam
# ── Contenido del archivo ──────────────────
server {
listen 80;
server_name poscam.miempresa.cl;
# Redirigir HTTP a HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name poscam.miempresa.cl;
ssl_certificate /etc/letsencrypt/live/poscam.miempresa.cl/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/poscam.miempresa.cl/privkey.pem;
# API backend Node.js
location /api/ {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# Frontend ERP (archivo HTML)
location / {
root /opt/poscam-frontend;
index index.html;
try_files $uri $uri/ /index.html;
}
}
# ───────────────────────────────────────────
# Activar el sitio
ln -s /etc/nginx/sites-available/poscam /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
🔒 SSL gratuito + PM2
# Instalar Certbot para SSL gratuito
apt install -y certbot python3-certbot-nginx
# Obtener certificado SSL (Let's Encrypt)
certbot --nginx -d poscam.miempresa.cl
# Renovación automática (ya incluida)
systemctl status certbot.timer
# ── PM2 — Gestión del proceso Node.js ──────
cd /opt/poscam-backend
# Iniciar la aplicación
pm2 start index.js --name poscam-api
# Ver logs en tiempo real
pm2 logs poscam-api
# Reiniciar si hay cambios
pm2 restart poscam-api
# Configurar para arrancar con el servidor
pm2 startup systemd
pm2 save
# ── Firewall (UFW) ──────────────────────────
ufw allow 22 # SSH
ufw allow 80 # HTTP
ufw allow 443 # HTTPS
ufw enable
# ── Subir el ERP frontend ───────────────────
mkdir -p /opt/poscam-frontend
# Copia POSCAM-ERP.html como index.html:
cp /ruta/POSCAM-ERP.html /opt/poscam-frontend/index.html
# Actualizar la URL de la API en el JS del ERP:
# Busca: const API_URL = '...'
# Cambia a: const API_URL = 'https://poscam.miempresa.cl/api'
# ── Verificar todo funciona ─────────────────
curl https://poscam.miempresa.cl/api/health
# Debe responder: { "status": "ok", "sii": "connected" }