v1.2 Panel Técnico Completo 06 Abr 2026
Ingeniería Inversa Completa

Systema v1.2 — Panel Técnico Oficial

Mapa exhaustivo de toda la arquitectura: módulos, endpoints, flujos de datos, conexiones, pipeline PDF/Email, dashboard, status, base de datos, licencias y Update Engine.

96 archivos core 364 KB ZIP 15 tablas BD ~75 endpoints REST PHP 7.4+ nativo HMAC-SHA256 VeriFactu Fase A Apps dinámicas Token anti-enumeración
364 KB
Core ZIP
96
Archivos core
~75
Endpoints REST
15
Tablas BD
10
Sectores status

Ficha Técnica de Identidad

Systema v1.2Actualizado 06 Abr 2026

Identificación del Sistema

NombreSystema / Cloude
Versión auditada1.2
TipoERP ultra-ligero SaaS multisite
Repositorio actualizacionesrafelgil.com/systema-updates
Servidor de licenciasrafelgil.com/licencias
Fecha de release5 de abril de 2026
AutorRafel Gil · [email protected]

Stack Tecnológico

BackendPHP nativo 7.4+ / 8.x — sin frameworks
BDMySQL / MariaDB — PDO + prepared statements
PDFdompdf v2.0.4 + FPDF 1.86 + FPDI
EmailPHPMailer 3.x (incluido en core)
HTML ParserMasterminds html5-php v2.7.6
FrontendVanilla JS + CSS variables (dark/light)
SeguridadHMAC-SHA256, CSRF tokens, rate limiting

Desglose de Peso del Core

Core ZIP (distribuible)
364 KB
Vendor (dompdf, etc.)
~6.5 MB
SHA256 del ZIP
212c02408ba2d503e5e387b3f8948f086b99dfc9
aa4a1ed478a2d73f74df6ee1
i
Arquitectura de archivos independiente de BD: el core ZIP contiene exactamente 96 archivos PHP/HTML/CSS/JS explícitamente enumerados. Ningún iterador de filesystem (glob) accede a él — esto garantiza reproducibilidad total y evita incluir archivos de datos o configuración del cliente.

Mapa Total del Sistema — Todas las Conexiones

Diagrama completo
Arquitectura completa de Systema v1.2 — capas, módulos y servicios externos
BROWSER PRESENTACIÓN LÓGICA REST DATOS SERVICIOS EXT Navegador Usuario ERP Navegador Contable resultado_publico.php Email Client track_open pixel AI Client ai_webhook (v0.8) Navegador Cliente presupuestos/pdf_render?token index.php Login +Licencia dashboard.php KPIs·Gráficas Alertas venc. facturacion.php Lista+Filtros PDF·Email clientes.php leads.php v0.6.0 rediseño bulk·Excel presupuestos.php PDF·Email·Token Tracking·Bulk v0.6.0 completo resultados.php Cierres anuales ZIPs T1-T4 presentador.php AEAT M303 M130·M390 actualizaciones .php Update Engine status.php 10 Sectores Mantenimiento configuracion.php historial.php changelog.php layout.php — Sidebar · Header · Zoom UI · Theme · Auth Guard ($_SESSION['systema_auth']) modules/_db.php — getDB() · jsonIn() · jsonOut() · authCheck() · PDO centralizado facturacion/ create·update·delete duplicate·get_items update_estado·pdf_export pdf_render·send_email track_open·import·import_sql clientes/ create update delete · import export (XLSX) update_estado ★ leads/ create update · delete convert bulk convert ★ export Excel ★ presupuestos/ create·update·delete get_items·duplicate convert·update_estado send_email·track_open pdf_render·pdf_export resultados/ create·delete generate (BG) get_facturas send·status update ★ · cleanup presentador/ calcular calcular_inline generar_pdf generar_txt historial configuracion/ save_fiscal·save_smtp save_plantilla·save_marca save_ui·save_verifactu get_marcas·test_smtp save_seguridad·reply_to ★ config/settings.php DB creds · licencia_token licencia_cache · ui_zoom config/version.php SYSTEMA_VERSION $WHATS_NEW array MySQL 15 tablas facturas·clientes·leads 2 vistas dashboard assets/ _backups/ resultados/ core_exports/ vendor/ dompdf·fpdf·fpdi PHPMailer·masterminds presentador/templates/ m303.pdf m130.pdf·m390.pdf rafelgil.com/licencias check.php · HMAC-SHA256 Rate limit 30r/min rafelgil.com/systema-updates manifest.json SHA256 · ZIPs versionados SMTP Externo PHPMailer SSL/TLS · Auth Google Charts CDN Dashboard gráficas Donut·Líneas QR Service (cURL) verifactu_qr_base_url curl_multi paralelo Groq / LLM (v0.8) ai_webhook.php X-Systema-Key CRON · cleanup.php 0 3 * * * → modules/resultados/cleanup.php — limpia ZIPs de assets/resultados/ caducados (+14 días) activate.php — inyección única del token HMAC desde panel central · timestamp ±120s · autodesactiva tras primer uso OK

Flujos de Datos — Todos los Procesos

6 flujos críticos
1
GET pdf_export.php?id=5Usuario solicita PDF de factura ID 5
2
Lee plantilla HTMLSELECT valor FROM configuracion WHERE grupo='plantilla' AND clave='html'
3
Lee datos fiscal + VeriFactu configCarga emisor, NIF, IBAN, config QR desde tabla configuracion
4
Carga factura + clienteJOIN facturas f + clientes c — sustituye todos los {{marcadores}}
5
QR VeriFactu (curl_multi paralelo)Si verifactu_qr_activo=1 → descarga imagen QR base64 desde verifactu_qr_base_url · paralelo con curl_multi
6
dompdf renderiza HTML → PDFdompdf v2.0.4 con Masterminds HTML5 parser · Content-Type: application/pdf
7
Bulk: ?ids=1,2,3Hasta 100 IDs → genera PDF por cada una → empaqueta en .zip con ZipArchive
1
POST send_email.phpBody: {id: 5} · authCheck() + getDB()
2
Carga factura + clienteEmail destino: email_facturacion ?? email (prioridad facturación)
3
Genera token de trackingtoken único → INSERT email_tracking · UPDATE facturas SET email_tracking_id
4
Embebe pixel 1×1URL: track_open.php?t={token} en <img> del HTML del email
5
PHPMailer envía SMTPHTML con adjunto PDF opcional · SSL/TLS configurado en tabla configuracion
6
Tracking aperturaCliente abre email → carga pixel → track_open.php → UPDATE email_tracking SET abierto_at + ip + ua · UPDATE facturas SET email_abierto=1
1
POST resultados/create.phpCrea registro en resultados_anuales · genera token único · lanza generate.php en background
2
generate.php ejecuta (background)set_time_limit(0) · memory_limit 512M · UPDATE zip_status='procesando'
3
Por cada trimestre T1-T4SELECT facturas WHERE QUARTER = N · genera PDF con dompdf · mismo pipeline que pdf_export
4
ZipArchive empaquetaassets/resultados/{año}/T1.zip · T2.zip · T3.zip · T4.zip
5
UPDATE zip_status='preparado'zip_expires_at = NOW() + 14 días
6
Polling cliente: POST status.phpCada 5s — devuelve {zip_status} hasta 'preparado'
7
Envío al contablePOST send.php → PHPMailer → URL portal: resultado_publico.php?token=xxx
8
Cron cleanup diario 03:00cleanup.php elimina ZIPs con zip_expires_at caducado (+14 días)
1
Usuario accede a index.phpLee config/settings.php · comprueba db_ok
2
¿Dominio exento?rafelgil.com · localhost · 127.0.0.1 → bypass total sin llamada externa
3
¿Caché válida? (TTL 7 días)licencia_cache > 0 AND (time() - cache) < 604800 → login OK sin red
4
Caché expirada → cURL al servidorGET rafelgil.com/licencias/check.php?d={dominio}&t={token} · timeout 8s
5
Servidor valida HMAC + estadotoken = hash_hmac('sha256', dominio, LIC_SECRET) · estado = 'activa' · fecha_fin
6
Respuesta {ok:true}Escribe licencia_cache = time() en settings.php · login OK
7
Error de redcurl_error → usa caché anterior (aunque expirada) como fallback gracioso
1
GET actualizaciones.php?action=checkcURL → manifest.json + old/manifest.json · compara versiones con version_compare()
2
GET action=install&version=0.5.6Descarga ZIP vía cURL timeout 120s
3
Verificación SHA256hash_file('sha256', $tmp_zip) === manifest['sha256'] · si NO coincide → cancela
4
Backup del coreZipArchive → assets/_backups/backup_v{local}_{timestamp}.zip · lista explícita 78 archivos
5
Extracción e instalaciónZipArchive itera ZIP · EXCEPCIÓN: config/settings.php NUNCA se sobreescribe
6
Log JSONupdate_log.json: fecha, from, to, files instalados, backup_name, errores
1
Panel central inyecta tokenGET activate.php?d={dom}&t={token}&ts={unix}&sig={firma_hmac}
2
Check 1: ¿Ya tiene token?licencia_token en settings.php vacío · si no → rechaza (no sobreescribe)
3
Check 2: Timestamp ±120sabs(time() - $ts) ≤ 120 · no reutilizable
4
Check 3: Dominio HTTP_HOSTHTTP_HOST (sin www) === dominio recibido
5
Check 4: Firma HMAC-SHA256hash_hmac(sha256, d|ts|t, secret) · hash_equals() resistente a timing attacks
6
Escribe settings.phplicencia_token, licencia_domain, licencia_cache=0 → fuerza validación en próximo login

Arquitectura del Update Engine

actualizaciones.phpUpdate Engine v0.6SHA256
i
Versión del motor: Update Engine v0.6 · Archivo principal: actualizaciones.php · Servidor de distribución: https://rafelgil.com/systema-updates
Flujo de Instalación de Actualización — 5 pasos actualizaciones.php → action=install
1
Consulta manifest.json remoto vía cURLGET https://rafelgil.com/systema-updates/manifest.json · timeout 10s · SSL verificado
El manifest contiene: versión latest, array de versiones, archivo ZIP, SHA256, fecha
2
Descarga el ZIP de la versión solicitadaGET /systema_core_v{X.X.X}.zip · timeout 120s · sigue redirects
Guardado en sys_get_temp_dir() como archivo temporal
3
Verificación SHA-256 del ZIP descargadohash_file('sha256', $tmp_zip) debe coincidir exactamente con manifest['sha256']
Si NO coincide → elimina ZIP temporal + retorna error · la instalación se cancela aquí
4
Backup automático del core actualCrea assets/_backups/backup_v{local_version}_{YYYYMMDD_HHiiss}.zip
Lista explícita de 78 archivos — nunca incluye config/settings.php
Guarda log JSON en _backups/update_log.json
5
Extracción e instalación de archivosZipArchive itera cada fichero del ZIP · crea directorios si no existen
EXCEPCIÓN CRÍTICA: config/settings.php nunca se sobreescribe
Registra: archivos instalados, backup_name, errores
GET
actualizaciones.php?action=check
Conecta con servidor remoto · lee manifest actual + old/manifest · compara versiones · devuelve {local_version, remote_version, update_available, rollback_available, versions[]}
GET
actualizaciones.php?action=install&version=X
Descarga ZIP → verifica SHA256 → backup automático → extrae e instala · NUNCA sobreescribe config/settings.php
GET
actualizaciones.php?action=rollback&backup=NAME
Restaura backup de assets/_backups/{name}.zip · NUNCA sobreescribe config/settings.php
GET
actualizaciones.php?action=list_backups
Lista todos los ZIPs en assets/_backups/ con nombre, tamaño y fecha

El Update Engine implementa doble capa de autenticación completamente separada del ERP:

  • Capa 1 — Sesión ERP: $_SESSION['systema_auth'] debe existir o redirige al login del ERP
  • Capa 2 — Contraseña propia del módulo: $_SESSION['update_auth'] requiere contraseña específica (UPDATE_PASSWORD hardcodeada), independiente del password del ERP
define('UPDATE_PASSWORD', 'Riudoms2026');
define('UPDATE_SERVER',   'https://rafelgil.com/systema-updates');
define('UPDATE_MANIFEST', UPDATE_SERVER.'/manifest.json');

// Guard doble:
if(empty($_SESSION['systema_auth'])){ header('Location: index.php'); exit; }
if(empty($_SESSION['update_auth'])){ /* mostrar login propio */ }

El servidor de distribución mantiene dos niveles de versiones:

Servidor remoto
rafelgil.com/systema-updates/
├── manifest.json        ← versión latest
├── systema_core_vX.X.X.zip
└── old/
    ├── manifest.json    ← versiones antiguas
    └── systema_core_vX.X.X.zip
Servidor del cliente
systema/assets/_backups/
├── backup_v0.5.5_20260318_103045.zip
├── backup_v0.5.4_20260101_120000.zip
└── update_log.json  ← audit trail

El rollback usa action=rollback&backup={nombre}: extrae el ZIP de assets/_backups/ y restaura los archivos. config/settings.php nunca se toca en ninguna dirección.

{
  "latest": "0.5.6",
  "updated": "2026-03-18",
  "versions": [{
    "version": "0.5.6",
    "file": "systema_core_v0.6.0.zip",
    "date": "2026-03-18",
    "sha256": "212c02408ba2d503e5e387b3f8948f086b99...",
    "size_bytes": 332287,
    "files": 78,
    "desc": "Core Systema v0.6.0"
  }]
}

Las versiones antiguas se cargan de /old/manifest.json y se les inyecta el prefijo old/ en la URL. Ambos manifests se fusionan en el cliente para mostrar el historial completo de versiones instalables.

El Sector I del panel status.php genera el paquete listo para subir al servidor de actualizaciones:

  • Generación asíncrona en background con ignore_user_abort(true) — no bloquea el servidor
  • Polling cliente cada 3 segundos hasta completarse
  • Lista explícita de 78 archivos — sin glob() ni iteradores filesystem
  • Calcula SHA256 automáticamente y lo escribe en assets/core_exports/manifest.json
  • ZIP guardado en assets/core_exports/ para descarga directa HTTP estático
!
config/settings.php nunca se toca: tanto en instalación como en rollback, este archivo se salta explícitamente con if($name === 'config/settings.php') continue;. Las credenciales del cliente están siempre protegidas.

Lógica del Sistema de Licencias HMAC-SHA256

index.php + check.phpactivate.phpCaché 7 días
Flujo de Handshake Completo — Activación → Validación periódica index.php en cada login
CLIENTE · index.php SERVIDOR · rafelgil.com RESULTADO ① Lee licencia_cache timestamp en settings.php caché válida → bypass (TTL 7d) cache > 7d ② check.php recibe d+t ?d=dominio&t=hmac_token ③ Rate limit: 30 req/min por IP · tmp file counter ④ HMAC-SHA256 check hash_hmac(sha256, d, SECRET) ⑤ Estado: activa? fecha_fin superada → expirada {"ok": true} actualiza licencia_cache {"ok": false} acceso denegado al ERP válido inválido ⑥ Fallback de red curl_error → usa caché old escribe licencia_cache = time() en settings.php

Estados de Licencia

EstadoDescripciónEfecto en cliente
activaLicencia operativaAcceso completo al ERP
pausadaSuspendida manualmente desde panelFalla tras 7 días de caché local
pendienteSin token inyectado aúnBloqueo inmediato en login
expiradafecha_fin superadaAuto-update BD central · bloqueo

Campos en config/settings.php

// Campos de licencia
'licencia_secret' => 'Sy5t3m4_L1c_S3cr3t0_R4f3lG1l...',
'licencia_token'  => '',  // inyectado por activate.php
'licencia_domain' => '',  // rellenado automáticamente
'licencia_cache'  => 0,   // timestamp Unix última OK
// TTL caché: 604800s = 7 días
Dominios exentos: systemafacil.comrafelgil.com, localhost, 127.0.0.1 bypasean toda validación — desarrollo local sin licencia.
Protocolo de Activación Remota — activate.php Se autodesactiva tras primer uso exitoso

El panel central inyecta el token directamente en el servidor del cliente vía HTTP. activate.php ejecuta 5 checks en cadena antes de escribir en settings.php:

Check 1
Sin token previo
No sobreescribe licencias ya activas
Check 2
Timestamp ±120s
Ventana 2 min — no reutilizable
Check 3
Dominio HTTP_HOST
URL debe coincidir con el host real
Check 4 · Principal
Firma HMAC-SHA256
hash_hmac(sha256, d|ts|t, SECRET)
Check 5
Token derivado
hash_hmac(sha256, dominio, SECRET)
Post-activación
Escribe settings.php
token + domain + cache=0 → fuerza validación inmediata
GET
rafelgil.com/licencias/check.php?d={dom}&t={token}
Endpoint servidor central · rate limit 30r/min/IP · HMAC-SHA256 · valida estado + fecha_fin · actualiza ultimo_check · devuelve {ok:true/false}
GET
activate.php?d=&t=&ts=&sig=
Inyección única desde panel central · 5 checks en cadena · escribe licencia_token + licencia_domain + licencia_cache=0 en settings.php · 1 solo uso

Login Landing — Routing por email

La carpeta LOGIN LANDING/ contiene dos archivos para el routing de clientes desde systemafacil.com:

Este sistema no autentica al usuario — solo hace routing. La autenticación ocurre en la instalación destino.

Layout, Auth Guard y Configuración Visual

layout.phpauth.phpconfig/version.php

layout.php

  • Sidebar de navegación global
  • Aplica ui_zoom desde settings.php en <head> sin FOUC
  • Dark/Light mode toggle nativo CSS variables
  • Modal de novedades (SYSTEMA_VERSION !== whats_new_seen)
  • Carga assets/core.css + assets/core.js
  • Apps dinámicas (v1.2): escanea /apps/ automáticamente — si existe subcarpeta con index.php, aparece grupo "Apps" en sidebar sin tocar el core

auth.php / Guards

  • $_SESSION['systema_auth'] — guard ERP global
  • $_SESSION['update_auth'] — guard Update Engine
  • $_SESSION['status_auth'] — guard Status.php
  • authCheck() en cada módulo REST
  • HTTP 401 si no autenticado en módulos AJAX

config/version.php

  • define('SYSTEMA_VERSION', '1.2')
  • $WHATS_NEW — array de novedades por grupo
  • Dispara modal en primer login post-actualización
  • mark_seen.php guarda versión en settings.php
  • Usado por actualizaciones.php para comparar versiones

Recuperación de contraseña

El archivo reset_password.php gestiona la recuperación de acceso. Flujo completo:

  1. El usuario hace clic en “He olvidado mi contraseña” en la pantalla de login
  2. reset_password.php busca el email en licencias.cliente_email en la BD central de licencias
  3. Genera una contraseña temporal segura de 8 caracteres con random_int()
  4. La hashea con password_hash() y la guarda en config/settings.php
  5. Envía la contraseña temporal al email del cliente con mail()
  6. Respuesta genérica — no revela si el email existe o no en el sistema

Dashboard & KPI Engine

dashboard.phpkpi_engine.phpGoogle Charts

dashboard.php es la página principal post-login. Carga el layout y delega todas las queries al kpi_engine.php vía fetch AJAX. Google Charts renderiza los gráficos vía CDN.

kpi_engine.php — Motor AJAX

GET
kpi_engine.php?tipo=kpis&filtro_global=anio_actual
KPIs principales: total facturado, pendiente, pagado, vencidas, leads activos. Filtro: total | 2026 | 2025 | T1 | T2 | T3 | T4
GET
kpi_engine.php?tipo=grafica&subfiltro=anio|mes|semana
Datos para gráfico de líneas temporal. Devuelve labels + dataset para Google Charts LineChart
GET
kpi_engine.php?tipo=donut&subfiltro=anio
Datos para gráfico Donut de estados de factura (pagada/pendiente/vencida). Google Charts PieChart
GET
kpi_engine.php?tipo=modal&alerta=vencidas|pendientes&pagina=N
Lista paginada de facturas vencidas o pendientes para modal de alerta del dashboard

Datos cargados en dashboard.php (PHP inicial)

QueryPropósito
SELECT DISTINCT YEAR(fecha_emision)Años disponibles para selector global
SELECT f.id,f.numero,f.total,f.estado,c.nombre FROM facturas JOIN clientes LIMIT 6Últimas 6 facturas recientes (tabla rápida)
Vista v_dashboard_facturasAgregados pre-calculados por estado
Vista v_dashboard_presupuestosAgregados de presupuestos activos
i
El dashboard usa Cache-Control: no-store — nunca se cachea. Las dos vistas SQL (v_dashboard_facturas, v_dashboard_presupuestos) optimizan las consultas agregadas más costosas.

Módulo Facturación — Todos los Endpoints

facturacion.phpmodules/facturacion/13 endpoints
POST
modules/facturacion/create.php
Crea factura nueva con items · asigna número según serie/marca · devuelve {success, id, numero}
BD escritura
POST
modules/facturacion/update.php
Actualiza cabecera + items de una factura · DELETE items existentes + INSERT nuevos
BD
POST
modules/facturacion/delete.php
Elimina factura + sus items + registros de email_tracking asociados
Borrado
POST
modules/facturacion/duplicate.php
Duplica factura con nuevo número · copia todos los items · devuelve {id, numero} del duplicado
BD
POST
modules/facturacion/update_estado.php
Cambia estado: pendiente | pagada | vencida | cancelada
Estado
POST
modules/facturacion/update_vencimiento.php
Actualiza fecha de vencimiento de una factura
v0.5.5
GET
modules/facturacion/get_items.php?id=N
Devuelve array de items (líneas) de una factura para editor inline
Lectura
GET
modules/facturacion/pdf_render.php?token=XXX&id=N
Visor HTML público de la factura · token validado contra id concreto en BD — imposible ver facturas ajenas cambiando ?id= · sin sesión requerida (v1.2 parche RGPD)
Token públicoRGPD v1.2
GET
modules/facturacion/pdf_export.php?id=N
Genera PDF con dompdf v2 · Content-Type: application/pdf · incluye QR VeriFactu base64
PDF
GET
modules/facturacion/pdf_export.php?ids=1,2,3
Bulk PDF → hasta 100 IDs → genera ZIP con ZipArchive → Content-Type: application/zip
ZIP
POST
modules/facturacion/send_email.php
Envío por PHPMailer SMTP · token tracking pixel 1×1 · email_facturacion ?? email · adjunto PDF opcional
Email
GET
modules/facturacion/track_open.php?t={token}
Pixel GIF 1×1 transparente · registra apertura, IP, user-agent en email_tracking + facturas.email_abierto
Tracking
POST
modules/facturacion/import.php
Importación masiva desde Excel · crea facturas + items en bulk
v0.5.2
POST
modules/facturacion/import_sql.php
Ejecución SQL segura para migración de datos · validación de queries antes de ejecutar
v0.5.2

Pipeline PDF, Email y Tracking

dompdf v2.0.4PHPMailerPixel 1×1

Marcadores de plantilla soportados

{{numero}}Número de factura
{{fecha_emision}}Fecha de emisión formateada
{{cliente_nombre}}Nombre del cliente
{{emisor_nif}}NIF del emisor desde fiscal
{{base_imponible}}Base imponible en €
{{iva_porcentaje}}% IVA aplicado
{{irpf_fila}}Fila IRPF (oculta si 0%)
{{lineas_tabla}}HTML tabla de líneas
{{qr_code}}QR VeriFactu base64 (img)
{{total}}Total factura en €

Pipeline de Email con Tracking

1
Genera tracking_id únicobin2hex(random_bytes(16)) — INSERT email_tracking
2
Construye HTML emailEmbebe <img src="track_open.php?t={id}" width="1" height="1">
3
PHPMailer → SMTP SSL/TLSDe: {email_from} · Para: cliente · Asunto configurable
4
Cliente abre emailEmail client carga pixel → track_open.php registra abierto_at + ip + ua
La factura en lista muestra el indicador de apertura cuando email_abierto=1, con timestamp de apertura.

Dos motores PDF según caso de uso

Motor 1 — dompdf v2 (facturas usuario)

  • Renderiza plantilla HTML personalizada del usuario
  • Masterminds html5-php v2.7.6 como parser HTML5
  • QR VeriFactu generado con curl_multi en paralelo
  • Bulk: hasta 100 PDFs + ZIP automático
  • memory_limit 512M en generate.php

Motor 2 — FPDF + FPDI (modelos AEAT)

  • Superpone datos sobre plantillas PDF oficiales de la AEAT
  • M303 (IVA), M130 (IRPF), M390 (resumen anual)
  • Plantillas en presentador/templates/
  • Genera también TXT con formato AEAT
  • Guarda snapshot JSON en presentaciones_historial

Módulo Clientes

clientes.phpmodules/clientes/v0.6.0
POST
modules/clientes/create.php
Crea cliente con datos fiscales · nombre, NIF/CIF, email, email_facturacion, dirección, CP, teléfono
POST
modules/clientes/update.php
Actualiza todos los campos del cliente · modal de edición actualiza el DOM en tiempo real sin reload de página
POST
modules/clientes/delete.php
Elimina cliente · validación: no elimina si tiene facturas asociadas
POST
modules/clientes/update_estado.php
Toggle activo/inactivo de un cliente · soporta bulk (array de IDs) · actualiza campo activo · devuelve {success, nuevo_estado}
v0.6.0 ★
POST
modules/clientes/import.php
Importación masiva desde Excel · crea o actualiza clientes por NIF/CIF
GET
modules/clientes/export.php
Exporta todos los clientes a XLSX nativo (SheetJS client-side) — fix v0.6.0: antiguo CSV con separador ";" en notas eliminado, migrado a .xlsx nativo
Fix v0.6.0
i
El campo email_facturacion en clientes tiene prioridad sobre email en el envío de facturas. Si está vacío, se usa el email normal. Los clientes inactivos se diferencian visualmente en tabla y quedan excluidos del buscador de nueva factura/presupuesto.

Módulo Leads & Pipeline CRM

leads.phpmodules/leads/v0.6.0 rediseño
Módulo rediseñado en v0.6.0: multiselect con bulk eliminar y bulk convertir a cliente, exportar Excel, paginación real, click en fila abre modal precargado. KPIs: Total → Valor activo (€) → Cualificados → Convertidos. Icono € en lugar de $.
POST
modules/leads/create.php
Crea lead con nombre, empresa, email, teléfono, estado del pipeline, notas
POST
modules/leads/update.php
Actualiza datos del lead · incluye cambio de estado: nuevo / contactado / cualificado / perdido / convertido
POST
modules/leads/delete.php
Elimina lead del pipeline · soporta bulk (array de IDs) para eliminación masiva desde multiselect
POST
modules/leads/convert.php
Convierte 1 o varios leads en cliente · INSERT INTO clientes con datos del lead · soporta bulk conversión masiva · devuelve {cliente_id} o array de {lead_id, cliente_id} · actualiza estado → convertido
ConversiónBulk v0.6.0

Módulo Presupuestos

presupuestos.phpmodules/presupuestos/v0.6.0 completoPDFEmailToken
Módulo completo en v0.6.0 — paridad total con Facturación: buscador cliente con dropdown, filtro año + trimestre, paginación 50/página, modal editar con líneas, stats bar, export Excel, chips de estado, tracking email, visor PDF por token, bulk marcar aceptado / convertir / eliminar, duplicar.
POST
modules/presupuestos/create.php
Crea presupuesto con items · misma estructura que factura · número auto con serie · estado: borrador/enviado/leído/aceptado/rechazado/caducado/convertido
POST
modules/presupuestos/update.php
Edita presupuesto + líneas · DELETE + INSERT items igual que facturación · carga previa via get_items.php
v0.6.0 ★
GET
modules/presupuestos/get_items.php
Devuelve las líneas del presupuesto para el modal de edición · mismo patrón que facturacion/get_items.php
v0.6.0 ★
POST
modules/presupuestos/update_estado.php
Actualiza estado del presupuesto a aceptado/rechazado/caducado · equivalente a "cobrar" en facturación · soporta bulk
v0.6.0 ★
POST
modules/presupuestos/duplicate.php
Copia presupuesto + todas sus líneas como nuevo borrador · útil para presupuestos recurrentes
v0.6.0 ★
POST
modules/presupuestos/convert.php
Convierte presupuesto en factura · copia datos + items · asigna número de factura · guarda convertido_factura_id · devuelve {factura_id, numero} · soporta bulk conversión masiva
Conversión
POST
modules/presupuestos/send_email.php
Envío HTML al cliente con diseño gradiente lila, botón CTA "Ver y descargar presupuesto" · genera public_token y email_tracking_id · incluye Reply-To del emisor
Emailv0.6.0 ★
GET
modules/presupuestos/track_open.php
Pixel 1×1 GIF tracking apertura · actualiza email_abierto=1, email_abierto_at en presupuestos y registra en email_tracking con documento_tipo=presupuesto
v0.6.0 ★
GET
modules/presupuestos/pdf_render.php?token=XXX&id=N
Visor HTML público del presupuesto · token validado contra id concreto en BD · sin sesión · plantilla con etiqueta PRESUPUESTO en lila · botón descargar PDF (v1.2 parche RGPD)
Token públicoRGPD v1.2
GET
modules/presupuestos/pdf_export.php
Genera PDF con dompdf · etiqueta "PRESUPUESTO" en lila, datos de pago con IBAN, notas, footer · mismo motor que facturacion/pdf_export.php
PDFv0.6.0 ★
POST
modules/presupuestos/delete.php
Elimina presupuesto y sus items · soporta bulk desde multiselect

Portal público del presupuesto — pdf_render.php

Acceso sin login mediante public_token único generado en el envío por email. El portal muestra:

  • Presupuesto completo con líneas, totales e IBAN del emisor
  • Etiqueta visual "PRESUPUESTO" en lila diferenciada de facturas
  • Botón de descarga PDF (dompdf, mismo motor que facturación)
  • Chip "Leído" en la tabla del panel cuando el cliente abre el email (pixel tracking)

Módulo Resultados Anuales

resultados.phpresultado_publico.phpv0.6.0
POST
modules/resultados/create.php
Crea registro en resultados_anuales · genera token único · lanza generate.php en background con ignore_user_abort · modal cierra inmediatamente, generación ZIP asíncrona
POST
modules/resultados/update.php
Edita email_contable, nombre_contable, observaciones y estado del resultado · click en fila abre modal con datos precargados · botón contextual "Guardar cambios" / "Crear resultado"
v0.6.0 ★
POST
modules/resultados/delete.php
Elimina resultado + ZIPs de assets/resultados/{año}/
GET
modules/resultados/generate.php?id=N
Generación background · set_time_limit(0) · memory 512M · genera PDFs por trimestre → empaqueta T1-T4.zip · actualiza zip_status=preparado · zip_expires_at=+14d · compatible Hostalia sin exec() ni pcntl
Background
POST
modules/resultados/status.php
Polling para el cliente · devuelve {zip_status: procesando|preparado|error} · llamado cada 5s desde el frontend
POST
modules/resultados/get_facturas.php
Devuelve facturas del ejercicio para exportar a Excel desde el portal
POST
modules/resultados/send.php
Envío al contable por PHPMailer · HTML email con URL portal · resultado_publico.php?token={token} · incluye Reply-To del emisor · campos SMTP: usuario, password, puerto, cifrado (fix v0.6.0)
Email
GET
modules/resultados/cleanup.php
CLI/cron · elimina ZIPs caducados (zip_expires_at < NOW()) · recomendado 0 3 * * *
Cron

Portal público del contable — resultado_publico.php

Acceso sin login mediante token único (?token=xxx). Nuevo en v0.6.0: pantalla de autenticación con portal_password antes de mostrar el contenido. El portal muestra:

  • Pantalla de acceso con contraseña — configurada en Configuración → Seguridad · incluye datos de contacto del emisor (email, teléfono, web) para recuperación si el contable pierde la contraseña
  • Datos de la empresa emisora, ejercicio y badge de año
  • 4 mini-cards de estadísticas: total facturas, importe, pagadas, pendientes
  • Botones de descarga de T1.zip · T2.zip · T3.zip · T4.zip (servidos desde assets/resultados/{año}/ vía HTTP estático)
  • Descarga Excel client-side (xlsx-js-style, datos incrustados en HTML, sin sesión requerida)
  • Estado del envío (estado=pendiente|enviado) y observaciones del emisor
  • Botones en fila horizontal — bloque resumen rediseñado en v0.6.0
!
ALTER TABLE pendiente en producción: la tabla resultados_anuales requiere 3 columnas nuevas para tracking email del contable:
ADD COLUMN email_tracking_id VARCHAR(64) NULL AFTER enviado_at
ADD COLUMN email_abierto TINYINT(1) NOT NULL DEFAULT 0 AFTER email_tracking_id
ADD COLUMN email_abierto_at DATETIME NULL AFTER email_abierto

Módulo Presentador AEAT

presentador.phpM303 · M130 · M390FPDF + FPDI

Genera modelos fiscales AEAT superponiéndolos sobre las plantillas PDF oficiales. Guarda snapshots JSON para historial inmutable.

POST
modules/presentador/calcular.php
Calcula base imponible, IVA, IRPF para el período solicitado · consulta facturas del trimestre/año
POST
modules/presentador/calcular_inline.php
Cálculo inline sin guardar · para vista previa antes de presentar
POST
modules/presentador/generar_pdf.php
FPDF+FPDI superpone datos sobre plantilla PDF oficial M303/M130/M390 · devuelve PDF binario
PDF AEAT
POST
modules/presentador/generar_txt.php
Genera fichero TXT con formato de presentación telemática AEAT
POST
modules/presentador/historial.php
Guarda snapshot JSON de la presentación en presentaciones_historial · inmutable, no editable

Módulo Configuración — Todos los Endpoints

configuracion.phpmodules/configuracion/v0.6.0
POST
modules/configuracion/save_fiscal.php
Guarda datos fiscales del emisor en tabla configuracion (grupo='fiscal'): razon_social, nif_cif, direccion, cp, telefono, email, iban, forma_pago
POST
modules/configuracion/save_smtp.php
Guarda config SMTP (grupo='smtp'): host, port, user, pass, from_name, secure=tls|ssl · v0.6.0: añadido campo reply_to — todos los emails de facturación, presupuestos y resultados incluyen Reply-To del usuario emisor. Clave para instalaciones multitenant.
+reply_to v0.6.0
POST
modules/configuracion/save_plantilla.php
Guarda plantilla HTML de factura (grupo='plantilla', clave='html') con todos los {{marcadores}}
POST
modules/configuracion/save_marca.php
Crea/edita marca emisora con prefijo, color, series de numeración · fix v0.5.2: incluye prefijo en INSERT
POST
modules/configuracion/save_verifactu.php
Guarda config VeriFactu: qr_activo (0/1) y qr_base_url · INSERT OR UPDATE en tabla configuracion grupo='verifactu'
POST
modules/configuracion/save_ui.php
Guarda zoom UI en config/settings.php: ui_zoom = '80'|'90'|'100'|'110'|'120' · aplica sin flash en head
v0.5.5
POST
modules/configuracion/save_seguridad.php
Guarda portal_password en tabla configuracion grupo='seguridad' · protege el portal del contable con contraseña de acceso previa al contenido
v0.6.0 ★
POST
modules/configuracion/save_acceso.php
Guarda la contraseña de acceso al panel principal de Systema · independiente de STATUS_PASSWORD
GET
modules/configuracion/get_marcas.php
Devuelve array de marcas activas con id, nombre, prefijo, color_hex, series
POST
modules/configuracion/test_smtp.php
Envía email de prueba al email_from configurado · verifica SMTP completo · devuelve {success, error}
POST
modules/configuracion/mark_seen.php
Guarda whats_new_seen = SYSTEMA_VERSION en settings.php · evita que el modal de novedades vuelva a aparecer
POST
modules/configuracion/save_db.php
Guarda credenciales BD en settings.php · db_host, db_port, db_name, db_user, db_pass
Crítico
POST
modules/configuracion/test_db.php
Prueba conexión PDO con credenciales dadas · devuelve {success, version_mysql}
POST
modules/configuracion/disconnect_db.php
Limpia credenciales BD de settings.php · vuelve al wizard de configuración inicial
DEL
modules/configuracion/delete_marca.php
Elimina una marca. Verifica que no tenga facturas ni presupuestos vinculados antes de borrar (restricción RESTRICT). Devuelve error descriptivo si hay documentos vinculados.
POST
modules/configuracion/save_acceso.php
Guarda la contraseña de acceso del cliente hasheada con password_hash() en config/settings.php. Acepta la contraseña maestra como “contraseña actual” para que el administrador siempre pueda cambiarla.

Status.php — Panel de Mantenimiento (10 Sectores)

Acceso restringidoSTATUS_PASSWORDGenerador Core ZIPv0.6.0
i
Acceso de doble capa: $_SESSION['systema_auth'] + STATUS_PASSWORD independiente. Diseñado exclusivamente para el administrador del sistema.
Sector A
DB Engine
Conexión MySQL, versión del motor, uptime del servidor
Sector B
Index Integrity
Verifica los 20 índices de rendimiento v0.4 · botón REINDEX ALL
Sector C
Table Sizes
Tamaño en KB y número de filas de cada tabla
Sector D
SMTP Stack
Config SMTP, test de handshake TCP, TEST SEND EMAIL completo
Sector E
PDF Engine
dompdf v2, Masterminds html5-php, FPDF 1.86, FPDI, /tmp escribible
Sector F
AEAT Presentador
Plantillas M303, M130, M390 · lectura y validez de archivos
Sector G
PHP Runtime
Versión PHP, memory_limit, max_execution_time, upload_max_filesize
Sector H
Extensions
PDO_MySQL · cURL · OpenSSL · mbstring · JSON · GD · ZipArchive
Sector I
Disk & Storage
Espacio libre en disco, /tmp escribible, config/settings.php accesible · Botón Descargar Core ZIP
Sector J ★ v0.6.0
Core File Manifest
Lista los 94 archivos del core en 3 columnas con indicador verde/rojo de existencia en disco en tiempo real — detecta archivos faltantes o corruptos al instante

Generador de Core ZIP (Sector I)

El botón "Descargar Core ZIP" en Sector I genera el ZIP + manifest.json listo para subir al servidor de actualizaciones:

  • Generación asíncrona en background con polling cliente cada 3 segundos
  • Lista explícita de 94 archivos del core — sin glob, sin iteradores filesystem
  • Calcula SHA256 del ZIP automáticamente y lo escribe en assets/core_exports/manifest.json
  • ZIP guardado en assets/core_exports/systema_core_v{version}.zip para descarga directa HTTP
  • El ZIP anterior se guarda en assets/core_exports/old/

Archivos nuevos en v1.0 (respecto al manifest v0.6.0)

ArchivoGrupoDescripción
reset_password.phpRaízRecuperación de contraseña vía BD de Licencias central. Genera pwd temporal, la hashea en settings.php y la envía por mail(). Posición alfabética: tras logout.php.
modules/configuracion/save_acceso.phpmodules/configuracion/Guarda contraseña de acceso del cliente hasheada con password_hash() en config/settings.php. Acepta la contraseña maestra como “contraseña actual”.
modules/configuracion/delete_marca.phpmodules/configuracion/Elimina una marca. Verifica restricción RESTRICT antes de borrar (no elimina si tiene documentos vinculados).

Base de Datos — 14 Tablas + 2 Vistas

MySQL 8.4PDO + Prepared StatementsMigración v0.6.0 ✓ aplicada
TablaFilas claveUso en sistemaMódulo principal
facturasnumero, cliente_id, estado, public_token, email_tracking_id, verifactu, email_abiertoCabeceras de facturas emitidas + tracking email + VeriFactufacturacion.php
factura_itemsfactura_id, descripcion, cantidad, precio, iva_pct, irpf_pctLíneas de facturas. DELETE + INSERT en cada UPDATEfacturacion/update
clientesnombre, nif_cif, email, email_facturacion, direccion, cp, activoBase de clientes · email_facturacion con prioridad en envíos · campo activo para toggle activo/inactivo v0.6.0clientes.php
presupuestosnumero, cliente_id, estado, convertido_factura_id, public_token, email_tracking_id, email_abiertoPresupuestos completos · conversión a factura · portal público por token · tracking emailpresupuestos.php
presupuesto_itemspresupuesto_id, descripcion, cantidad, precioLíneas de presupuestos. Misma estructura que factura_itemspresupuestos/create
leadsnombre, empresa, email, estado, valor_estimado, cliente_idPipeline CRM · estado: nuevo/contactado/cualificado/perdido/convertido · bulk convert en v0.6.0leads.php
marcasnombre, prefijo, color_hex, activaMarcas emisoras para series de numeración múltiplesconfiguracion.php
series_numeracionmarca_id, ejercicio, ultimo_numeroContador de números por marca y añofacturacion/create
configuraciongrupo (fiscal/smtp/plantilla/verifactu/ia/seguridad), clave, valorKV Store global · INSERT ON DUPLICATE KEY UPDATE · grupo seguridad añadido en v0.6.0 (portal_password, reply_to)configuracion.php
email_trackingtracking_id, documento_tipo, documento_id, abierto_at, ip_apertura, user_agentLog de aperturas de email via pixel 1×1 · documento_tipo: factura o presupuesto (v0.6.0)track_open.php
presentaciones_historialmodelo, ejercicio, periodo, snapshot_json, created_atHistorial inmutable de modelos AEAT presentadospresentador/historial
resultados_anuales v0.6.0ejercicio, token, zip_status, zip_expires_at, email_contable, estado, email_tracking_id, email_abierto, email_abierto_atCierres fiscales anuales · portal contable con password · ZIPs trimestrales · tracking email contableresultados.php
producto_categorias v1.0 — módulo a medidaid, nombre, margen_pct, activa, created_atCategorías del catálogo de productos con margen de beneficio por defecto. Tabla vacía — cada instalación configura las suyas.módulo productos (pendiente UI)
productos v1.0 — módulo a medidaid, sku, descripcion, unidad, coste_neto, categoria_id, proveedor, activoCatálogo maestro de productos/materiales. Búsqueda por SKU o descripción al crear líneas en presupuestos y facturas. PVP calculado automáticamente: coste_neto × (1 + margen_pct/100)módulo productos (pendiente UI)
Migración v0.6.0 aplicada en producción (19 Mar 2026):
1. resultados_anuales — 3 columnas de tracking email añadidas + charset utf8mb4 + 3 índices restaurados.
2. configuracion — 7 claves SMTP obsoletas eliminadas (ids 13-19: smtp_host, smtp_puerto, smtp_usuario, smtp_password, smtp_encriptacion, smtp_from_nombre, smtp_from_email).
3. producto_categorias + productos — tablas del módulo productos creadas vacías y listas.

Vistas SQL optimizadas

v_dashboard_facturas

Pre-calcula agregados de facturas por estado (pagadas/pendientes/vencidas) y totales €. Optimiza las queries más costosas del KPI engine.

v_dashboard_presupuestos

Agrega presupuestos activos por estado. Usada en el KPI widget de presupuestos del dashboard.

VeriFactu & IA Webhook

Fase A activaFase B pendienteIA v0.8

VeriFactu Fase A — Activo en v0.5

QR{{qr_code}} → base64 via curl_multi
Configsave_verifactu.php → qr_activo + qr_base_url
BDtabla configuracion grupo='verifactu'
PDFQR incrustado en dompdf vía img base64
Fase BEnvío XML AEAT — pendiente normativa v0.7

ai_webhook.php — Endpoint IA (Fase 2)

POST ai_webhook.php
Headers:
  Content-Type: application/json
  X-Systema-Key: {api_key}
Body:
  {"instruction": "Crea una factura
   para cliente X por diseño 1500€"}

Planificado para v0.8 con Groq. El endpoint ya existe y acepta OPTIONS para CORS. Parsea lenguaje natural → crea factura vía modules/facturacion/create.php.

Análisis de Optimización — ¿Por qué <300KB?

364 KB Core ZIP78 archivos

Sin framework PHP

PHP nativo con PDO. Sin Laravel, Symfony ni dependencias. Ahorro: ~15-50MB de vendor comparado con un ERP Laravel típico.

Vendor separado del core

dompdf (~6MB), Masterminds (~1MB) y FPDF están en /vendor/, excluida del core ZIP. El core solo incluye PHPMailer (3 archivos).

CSS variables nativo

Un solo core.css gestiona dark/light mode y zoom UI. Sin Tailwind ni Bootstrap en producción. Ahorro: ~300KB.

Endpoints como archivos PHP

Cada operación es un archivo independiente sin clases ni herencia. Sin overhead de instanciación ni autoloading complejo.

config/settings.php como array

return array() escrito con var_export(). OPcache lo compila directamente. 0ms de parsing YAML/XML/JSON.

Generación asíncrona

ZIPs fiscales y Core ZIP generan en background con ignore_user_abort(true). Sin timeouts PHP, sin bloqueos de servidor.

ComponentePesoIncluido en core ZIP
Core ZIP v0.1.0364 KBSí — 94 archivos
vendor/dompdf~5.5 MBNo — instalar aparte
vendor/masterminds~1.2 MBNo — instalar aparte
vendor/fpdf + FPDI~800 KBNo — en vendor/
PHPMailer (3 archivos)~180 KBSí — en core ZIP
presentador/templates (3 PDFs AEAT)~2 MBNo — en presentador/

Comparativa: ERP clásico vs Systema

AspectoERP Típico (Laravel/Symfony)Systema v1.0
Core distribuible5–50 MB364 KB
Tiempo de despliegue15–60 min (Composer, migraciones, .env)FTP + importar SQL + config web
ActualizaciónManual, CI/CD o Composer update1 click · backup automático · rollback
Dependencias runtimeComposer + 100+ paquetesPHP 7.4+ + MySQL + cURL
Configuración.env + config YAML/JSON + migrationsconfig/settings.php — array PHP, OPcache nativo
PDFLibrería incluida (aumenta tamaño core)dompdf externo · FPDF 1.86 incluido
Auth multitenantJWT / OAuth / Passport / SanctumHMAC-SHA256 propio · caché 7d · fallback
Actualizaciones automáticasRequiere CI/CD o acceso SSHUpdate Engine integrado · SHA256 · rollback

Estado del Proyecto y Roadmap

v1.0 actualv0.7 pendientev0.8 planificado

Funcionalidades completadas

VeriFactu Fase A — QR verificable
v0.5 · marcador {{qr_code}} en plantillas PDF · curl_multi paralelo
Licencias multisite HMAC-SHA256
v0.5.1 · activación remota · caché 7 días · fallback de red
Modal de novedades post-actualización
v0.5.2 · version.php dispara modal en primer login
Importador masivo Excel + SQL
v0.5.2 · facturas y clientes · import_sql.php con validación
Dashboard filtros inteligentes + KPI engine
v0.5.3 · filtro global año/trimestre · 4 tipos de query AJAX
Facturación — filtros, Excel, buscador
v0.5.4 · bulk PDF ZIP · update_vencimiento
Módulo Resultados Anuales + Portal contable
v0.5.5 · ZIPs T1-T4 · token público · generación background
Zoom UI configurable + notificaciones vencidas
v0.5.5 · settings.php · modal alertas dashboard
Motor de auto-actualización + Generador Core ZIP
v0.5.6 · SHA256 · backup automático · rollback · manifest.json
Design System unificado — lenguaje de color semántico global
v0.6.0 · azul=total, naranja=pendiente, rojo=urgente, verde=positivo · KPIs reordenados en todos los módulos
Clientes — activo/inactivo + export XLSX nativo
v0.6.0 · toggle + bulk · filtro toolbar · KPI real · SheetJS client-side
Leads — rediseño completo: multiselect, bulk, Excel
v0.6.0 · bulk eliminar + convertir · exportar Excel · paginación · estados nuevo/contactado/cualificado/perdido/convertido
Presupuestos — módulo completo con paridad a Facturación
v0.6.0 · PDF + email + tracking + token público · bulk · duplicar · filtros + paginación · 11 endpoints
Portal contable — contraseña de acceso + Excel client-side
v0.6.0 · save_seguridad.php · portal_password · datos de contacto emisor · xlsx-js-style sin sesión
SMTP Reply-To global + fix crítico toast/localStorage
v0.6.0 · reply_to en todos los emails · layout_end.php en core ZIP · localStorage con try/catch en core.js
Status.php — Sector J Core File Manifest
v0.6.0 · 94 archivos en 3 columnas · indicador verde/rojo existencia en disco en tiempo real

Próximas versiones

v0.7 — VeriFactu Fase B · Envío XML AEAT
Pendiente publicación de normativa técnica definitiva · módulo facturacion/submit_verifactu.php
v0.8 — IA / Groq Chatbot
Facturas por lenguaje natural · ai_webhook.php como base · módulo modules/ia/chat.php
Puntos de integración IA disponibles hoy
  • ai_webhook.php — receptor de eventos IA ya presente en core
  • Tabla configuracion (KV store) → clave ia documentada
  • getDB() centralizado — acceso universal a todos los datos
  • Módulos REST independientes — añadible sin modificar el core
  • Design system CSS con variables — UI chatbot integrable en layout.php
Ventaja arquitectónica: tanto VeriFactu Fase B como el chatbot IA se implementan añadiendo nuevos endpoints en /modules/ sin modificar el núcleo. El Update Engine distribuirá estas versiones automáticamente a todos los clientes.

Historial completo de versiones

VersiónFechaFeatures principalesEstado
v0.12025Core ERP: facturación, clientes, leads, configuración, PDF básicoAlpha
v0.4202520 índices BD de rendimiento, presupuestos, marcas/series numeraciónAlpha
v0.52025VeriFactu Fase A — QR en facturas, dompdf v2, plantilla personalizableAlpha
v0.5.12025Licencias multisite HMAC-SHA256, activate.php, caché 7 díasAlpha
v0.5.22025Modal novedades, importador Excel/SQL, fix marcas prefijoAlpha
v0.5.32025Dashboard filtros inteligentes año/trimestreAlpha
v0.5.42025Facturación filtros avanzados, export Excel, bulk PDF ZIPAlpha
v0.5.5Mar 2026Módulo Resultados Anuales, Portal contable token, Zoom UI, Core ZIP v1Alpha
v0.5.619 Mar 2026Update Engine v0.6, SHA256, backup automático, rollback, Core ZIP v2Alpha
v0.6.019 Mar 2026Design System unificado · Presupuestos completo (11 endpoints, PDF/email/token/bulk) · Leads rediseño · Clientes activo/inactivo · Portal contable con password · SMTP Reply-To · Sector J Core Manifest · fix toast + localStorageBeta — Actual
v0.7PendienteVeriFactu Fase B — Envío XML AEATPendiente normativa
v0.8Fase 2IA / Groq — facturas por lenguaje naturalPlanificado

Futuras Mejoras — Módulos a Medida

v1.0 pendienteActivable por instalación

Funcionalidades diseñadas y con estructura de BD lista en producción, pendientes de implementación de UI. Disponibles como módulos a medida por cliente — la base está hecha, solo falta construir la interfaz.

📦 Módulo Productos — Catálogo Maestro

¿Qué es?

Catálogo de productos y materiales por instalación. Permite al usuario buscar un artículo por SKU o descripción al añadir líneas en Presupuestos y Facturas. El sistema calcula automáticamente el PVP aplicando el margen de la categoría.

Pensado para autónomos de oficios: un electricista tendrá "Cables", "Mecanismos", "Tubos"; un fontanero tendrá "Tubería", "Griferías", "Accesorios". Cada instalación configura sus propias categorías y productos.

Tablas creadas en producción (19 Mar 2026): producto_categorias y productos están vacías y listas. Solo falta implementar la UI y los endpoints.

Lógica financiera

Todos los precios se almacenan sin IVA. El IVA se aplica solo al cerrar el documento.

1. Lee coste_neto del producto (ej: 10,00 €)
2. Busca margen_pct de su categoría (ej: Cerrajería = 20%)
3. Calcula PVP: 10,00 × (1 + 20/100) = 12,00 €
4. Inyecta 12,00 € en el campo Precio de la línea
5. Al cerrar: 12,00 + 21% IVA = 14,52 € total

El precio inyectado en presupuesto_items.precio_unitario queda grabado en el documento. Si mañana cambias el margen de una categoría, los documentos anteriores no se ven afectados.

Estructura de tablas (ya en producción)

producto_categorias

CampoTipoDescripción
idINT UNSIGNED PKAutoincremental
nombreVARCHAR(100)Ej: Cerrajería, Fontanería, Mano de obra
margen_pctDECIMAL(5,2)Margen de beneficio en %. Ej: 20.00 = 20%
activaTINYINT(1)Soft delete — desactivar sin borrar
created_atTIMESTAMPFecha de creación

productos

CampoTipoDescripción
idINT UNSIGNED PKAutoincremental
skuVARCHAR(50) UNIQUECódigo proveedor. Ej: Saltoki 1234
descripcionVARCHAR(500)Ej: Bombín T60 30x30 Latonado
unidadVARCHAR(20)ud, m, m², h, kg, l...
coste_netoDECIMAL(12,4)Precio de coste sin IVA
categoria_idINT UNSIGNED FK→ producto_categorias.id
proveedorVARCHAR(100)Saltoki, Propio, etc.
activoTINYINT(1)Soft delete — desactivar sin borrar
created_atTIMESTAMPFecha de creación
updated_atTIMESTAMPÚltima modificación (auto)

Índices y relaciones

  • UNIQUE KEY uq_sku (sku) — no puede haber dos productos con el mismo código
  • KEY idx_descripcion (descripcion(100)) — búsqueda rápida por texto parcial
  • KEY idx_categoria (categoria_id) — filtrado por categoría
  • KEY idx_activo (activo) — filtrado de productos activos en búsqueda
  • FOREIGN KEY (categoria_id) → producto_categorias(id) ON DELETE RESTRICT — no se puede borrar una categoría si tiene productos

Implementación UI pendiente — qué hay que construir

Backend — 1 endpoint

GET modules/productos/search.php?q=bombin&limit=10
Busca en sku y descripcion simultáneamente. Devuelve JSON con los campos id, sku, descripcion, unidad, coste_neto, margen_pct, pvp. El pvp se calcula en el servidor: coste_neto × (1 + margen_pct/100). Solo devuelve productos activos.
SELECT p.*, c.margen_pct,
  ROUND(p.coste_neto*(1+c.margen_pct/100),2) AS pvp
FROM productos p
JOIN producto_categorias c ON p.categoria_id=c.id
WHERE p.activo=1
  AND (p.sku LIKE :q OR p.descripcion LIKE :q)
LIMIT :limit

Frontend — ~30 líneas JS

Al escribir en el campo descripción de una línea de presupuesto o factura:

  • Trigger a partir de 2 caracteres
  • Llamada al endpoint con debounce de 200ms
  • Dropdown con resultados: SKU · descripción · precio
  • Al seleccionar: inyecta descripción, unidad y PVP en los campos de la línea
  • El usuario puede sobreescribir el precio manualmente
No usa <datalist> nativo — demasiado limitado para mostrar SKU + descripción + precio. Se implementa con un <div> dropdown custom ligado al input.

Restricciones técnicas de diseño

  • Sin importador de Excel en esta fase — solo estructura de tablas y búsqueda. El importador masivo (Saltoki CSV) se planifica para una versión posterior.
  • Fiscalidad — todos los cálculos en BD son sin IVA. El IVA solo se aplica al total del documento en el momento de cierre.
  • Peso — el endpoint y el JS deben mantener el ERP por debajo de 300 KB en el core ZIP.
  • No destructivo — no modifica ninguna tabla existente. Solo añade dos tablas nuevas y un endpoint.
  • Sin datos por defecto — las tablas se entregan vacías. Cada cliente configura sus categorías y productos según su oficio.