Procedimiento para planificar la instalación
1.
P
Perfecto, me pongo al lío directamente 😊
Voy a **asumir que usamos la OPCIÓN 1 (Nanonets Invoice OCR)** para no frenarnos. Si luego quieres cambiar de OCR, lo adaptamos.
Te propongo en este mensaje algo muy concreto:
1. Tener un **plugin completo de base** (todo en `facturas-preconta.php`) que ya:
* sepa conectarse a **Nextcloud por WebDAV**
* mande facturas a **Nanonets**
* **cree la tabla `wp_facturas_preconta`**
* tenga un **shortcode de prueba** para procesar una factura real de Nextcloud y guardarla en la tabla.
Así, en cuanto pegues esto, ya podrás hacer una prueba completa “de punta a punta” (Nextcloud → OCR → BD).
—
## 🔧 1. Sustituir el contenido del plugin por este código base
Te propongo que, para simplificar, **reemplaces TODO el contenido actual de `facturas-preconta.php` por este** (copia y pega):
> ⚠️ Antes de usarlo, cambia los valores de las constantes de configuración (URL WebDAV, usuario, API key Nanonets, model_id).
«`php
<?php
/*
Plugin Name: Facturas Preconta
Description: Procesa facturas desde Nextcloud, extrae datos por OCR (Nanonets) y prepara información para Contasol.
Version: 1.0
Author: Tú
*/
if ( ! defined( ‘ABSPATH’ ) ) {
exit; // Seguridad
}
/*
* =====================================================
* AJUSTES BÁSICOS: WEBDAV + NANONETS
* =====================================================
*
* AJUSTA ESTOS VALORES A TU ENTORNO REAL
*/
// Nextcloud WebDAV
define( ‘FP_WEBDAV_BASE_URL’, ‘https://tudominio.com/nextcloud/remote.php/dav/files/TU_USUARIO/’ );
define( ‘FP_WEBDAV_USER’, ‘TU_USUARIO’ );
define( ‘FP_WEBDAV_PASS’, ‘TU_PASSWORD’ );
// Nanonets
define( ‘FP_NANONETS_API_KEY’, ‘TU_API_KEY_DE_NANONETS’ );
define( ‘FP_NANONETS_MODEL_ID’,’TU_MODEL_ID_DE_INVOICE’ );
/*
* =====================================================
* ACTIVACIÓN: CREAR TABLA wp_facturas_preconta
* =====================================================
*/
register_activation_hook( __FILE__, ‘fp_facturas_preconta_install’ );
function fp_facturas_preconta_install() {
global $wpdb;
$table_name = $wpdb->prefix . ‘facturas_preconta’;
$charset_collate = $wpdb->get_charset_collate();
$sql = «CREATE TABLE $table_name (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
cliente VARCHAR(191) NOT NULL DEFAULT »,
proveedor VARCHAR(191) NOT NULL DEFAULT »,
nif VARCHAR(50) NOT NULL DEFAULT »,
num_factura VARCHAR(100) NOT NULL DEFAULT »,
fecha_factura DATE NULL,
base DECIMAL(18,2) NOT NULL DEFAULT 0,
iva DECIMAL(18,2) NOT NULL DEFAULT 0,
total DECIMAL(18,2) NOT NULL DEFAULT 0,
cuenta_gasto VARCHAR(20) NOT NULL DEFAULT »,
cuenta_proveedor VARCHAR(20) NOT NULL DEFAULT »,
cuenta_iva VARCHAR(20) NOT NULL DEFAULT »,
ruta_nextcloud TEXT NULL,
json_original LONGTEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_cliente_fecha (cliente(50), fecha_factura)
) $charset_collate;»;
require_once ABSPATH . ‘wp-admin/includes/upgrade.php’;
dbDelta( $sql );
}
/*
* =====================================================
* HELPERS GENERALES: NÚMEROS Y FECHAS
* =====================================================
*/
function fp_normalizar_numero( $valor ) {
if ( $valor === null || $valor === » ) {
return 0;
}
// Quitar espacios
$valor = trim( $valor );
// Reemplazar separador de miles y decimal típico europeo
// 1.234,56 -> 1234.56
$valor = str_replace( array(‘ ‘, ‘.’), array(», »), $valor );
$valor = str_replace( ‘,’, ‘.’, $valor );
if ( ! is_numeric( $valor ) ) {
return 0;
}
return (float) $valor;
}
function fp_normalizar_fecha( $texto ) {
$texto = trim( $texto );
if ( $texto === » ) {
return null;
}
// Formato ISO: 2025-10-15
if ( preg_match( ‘/(\d{4})-(\d{2})-(\d{2})/’, $texto, $m ) ) {
return $m[1] . ‘-‘ . $m[2] . ‘-‘ . $m[3];
}
// Formato europeo: 15/10/2025 o 15-10-2025
if ( preg_match( ‘/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2,4})/’, $texto, $m ) ) {
$d = str_pad( $m[1], 2, ‘0’, STR_PAD_LEFT );
$m2 = str_pad( $m[2], 2, ‘0’, STR_PAD_LEFT );
$y = $m[3];
if ( strlen( $y ) === 2 ) {
$y = ( (int) $y < 50 ? ’20’ : ’19’ ) . $y;
}
return $y . ‘-‘ . $m2 . ‘-‘ . $d;
}
// Si no reconocemos, devolvemos null
return null;
}
/*
* =====================================================
* WEBDAV – DESCARGAR FACTURA DE NEXTCLOUD
* =====================================================
*/
/**
* Descarga un archivo de Nextcloud (WebDAV) a un fichero temporal local.
*
* @param string $relative_path Ruta relativa dentro de Nextcloud, por ejemplo:
* ‘cliente1/proveedor1/2025-0043.pdf’
* @return string|WP_Error Ruta local del fichero temporal o WP_Error en caso de fallo.
*/
function fp_webdav_download_to_temp( $relative_path ) {
$base_url = rtrim( FP_WEBDAV_BASE_URL, ‘/’ ) . ‘/’;
$url = $base_url . ltrim( $relative_path, ‘/’ );
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_USERPWD, FP_WEBDAV_USER . ‘:’ . FP_WEBDAV_PASS );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // según tu entorno, puedes poner true
$data = curl_exec( $ch );
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
$curl_error = curl_error( $ch );
curl_close( $ch );
if ( $data === false || $http_code >= 400 ) {
return new WP_Error(
‘webdav_error’,
‘Error al descargar desde WebDAV: HTTP ‘ . $http_code . ‘ – ‘ . $curl_error
);
}
// Crear fichero temporal
$tmp_file = wp_tempnam( basename( $relative_path ) );
if ( ! $tmp_file ) {
return new WP_Error( ‘tmpfile_error’, ‘No se pudo crear fichero temporal.’ );
}
if ( file_put_contents( $tmp_file, $data ) === false ) {
return new WP_Error( ‘write_error’, ‘No se pudo escribir el fichero temporal.’ );
}
return $tmp_file;
}
/*
* =====================================================
* NANONETS – ENVIAR FACTURA Y OBTENER JSON
* =====================================================
*/
/**
* Llama al modelo de OCR de Nanonets con un archivo local.
*
* Endpoint: https://app.nanonets.com/api/v2/OCR/Model/{model_id}/LabelFile/
*
* @param string $file_path Ruta local del PDF/JPG
* @return array|WP_Error Array con el JSON decodificado o WP_Error
*/
function fp_nanonets_ocr( $file_path ) {
if ( ! file_exists( $file_path ) ) {
return new WP_Error( ‘file_not_found’, ‘El fichero local no existe: ‘ . $file_path );
}
$url = ‘https://app.nanonets.com/api/v2/OCR/Model/’ . FP_NANONETS_MODEL_ID . ‘/LabelFile/’;
$ch = curl_init();
$cfile = new CURLFile(
$file_path,
mime_content_type( $file_path ),
basename( $file_path )
);
$post_fields = array(
‘file’ => $cfile
// Si quieres, puedes añadir ‘output_format’ => ‘JSON’ u otros parámetros
);
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $ch, CURLOPT_POST, true );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_fields );
// Auth básica: api_key:
curl_setopt( $ch, CURLOPT_USERPWD, FP_NANONETS_API_KEY . ‘:’ );
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false ); // ajusta a true si tienes SSL OK
$response = curl_exec( $ch );
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
$curl_error = curl_error( $ch );
curl_close( $ch );
if ( $response === false || $http_code >= 400 ) {
return new WP_Error(
‘nanonets_error’,
‘Error en Nanonets: HTTP ‘ . $http_code . ‘ – ‘ . $curl_error . ‘ – RESPUESTA: ‘ . $response
);
}
$json = json_decode( $response, true );
if ( ! is_array( $json ) ) {
return new WP_Error( ‘json_error’, ‘No se pudo decodificar JSON devuelto por Nanonets.’ );
}
return $json;
}
/*
* =====================================================
* NANONETS – NORMALIZAR CAMPOS DE FACTURA
* =====================================================
*
* Aquí suponemos que en tu modelo de Nanonets has creado labels
* con nombres similares a estos:
* – proveedor / vendor_name / supplier_name
* – vat_number / vat_id / nif / cif
* – invoice_number / num_factura / n_factura
* – invoice_date / fecha / fecha_factura
* – subtotal / net_amount / base_imponible
* – tax_amount / vat_amount / iva
* – total_amount / amount_due / invoice_total
*/
function fp_nanonets_normalizar_factura( array $json ) {
if ( empty( $json[‘result’] ) || ! is_array( $json[‘result’] ) ) {
return array();
}
// Posibles alias de cada campo
$map = array(
‘proveedor’ => array( ‘supplier_name’, ‘vendor_name’, ‘proveedor’, ‘razon_social’, ‘vendor’ ),
‘nif’ => array( ‘vat_number’, ‘vat_id’, ‘tax_id’, ‘nif’, ‘cif’ ),
‘num_factura’ => array( ‘invoice_number’, ‘num_factura’, ‘n_factura’, ‘number’ ),
‘fecha_factura’ => array( ‘invoice_date’, ‘date’, ‘fecha’, ‘fecha_factura’ ),
‘base’ => array( ‘subtotal’, ‘net_amount’, ‘base_imponible’, ‘base’ ),
‘iva’ => array( ‘tax_amount’, ‘vat_amount’, ‘iva’, ‘impuestos’ ),
‘total’ => array( ‘total_amount’, ‘amount_due’, ‘invoice_total’, ‘total’ ),
);
// Convertimos los alias en un índice inverso para lookup rápido
$inverse = array();
foreach ( $map as $campo => $labels ) {
foreach ( $labels as $lbl ) {
$inverse[ strtolower( $lbl ) ] = $campo;
}
}
$resultado = array(
‘proveedor’ => »,
‘nif’ => »,
‘num_factura’ => »,
‘fecha_factura’ => null,
‘base’ => 0,
‘iva’ => 0,
‘total’ => 0,
‘json_original’ => $json,
);
// Recorremos todas las páginas/resultados
foreach ( $json[‘result’] as $page ) {
if ( empty( $page[‘prediction’] ) || ! is_array( $page[‘prediction’] ) ) {
continue;
}
foreach ( $page[‘prediction’] as $pred ) {
if ( empty( $pred[‘label’] ) ) {
continue;
}
$label = strtolower( $pred[‘label’] );
$valor = isset( $pred[‘ocr_text’] ) ? trim( $pred[‘ocr_text’] ) : »;
if ( $valor === » ) {
continue;
}
if ( isset( $inverse[ $label ] ) ) {
$campo = $inverse[ $label ];
switch ( $campo ) {
case ‘fecha_factura’:
if ( empty( $resultado[‘fecha_factura’] ) ) {
$resultado[‘fecha_factura’] = fp_normalizar_fecha( $valor );
}
break;
case ‘base’:
case ‘iva’:
case ‘total’:
if ( $resultado[ $campo ] == 0 ) {
$resultado[ $campo ] = fp_normalizar_numero( $valor );
}
break;
default:
// proveedor, nif, num_factura (solo si vacío)
if ( empty( $resultado[ $campo ] ) ) {
$resultado[ $campo ] = $valor;
}
break;
}
}
}
}
return $resultado;
}
/*
* =====================================================
* BD – GUARDAR FACTURA EN wp_facturas_preconta
* =====================================================
*/
function fp_guardar_factura_en_bd( $cliente, $ruta_nextcloud, array $datos ) {
global $wpdb;
$table_name = $wpdb->prefix . ‘facturas_preconta’;
$insert = array(
‘cliente’ => (string) $cliente,
‘proveedor’ => isset( $datos[‘proveedor’] ) ? $datos[‘proveedor’] : »,
‘nif’ => isset( $datos[‘nif’] ) ? $datos[‘nif’] : »,
‘num_factura’ => isset( $datos[‘num_factura’] ) ? $datos[‘num_factura’] : »,
‘fecha_factura’ => isset( $datos[‘fecha_factura’] ) ? $datos[‘fecha_factura’] : null,
‘base’ => isset( $datos[‘base’] ) ? $datos[‘base’] : 0,
‘iva’ => isset( $datos[‘iva’] ) ? $datos[‘iva’] : 0,
‘total’ => isset( $datos[‘total’] ) ? $datos[‘total’] : 0,
‘cuenta_gasto’ => », // ya las rellenarás luego en la pantalla de revisión
‘cuenta_proveedor’=> »,
‘cuenta_iva’ => »,
‘ruta_nextcloud’ => $ruta_nextcloud,
‘json_original’ => wp_json_encode( $datos[‘json_original’] ?? $datos ),
);
$formats = array(
‘%s’,’%s’,’%s’,’%s’,’%s’,
‘%f’,’%f’,’%f’,
‘%s’,’%s’,’%s’,
‘%s’,’%s’
);
$ok = $wpdb->insert( $table_name, $insert, $formats );
if ( false === $ok ) {
return new WP_Error(
‘db_insert_error’,
‘Error al insertar factura en la base de datos: ‘ . $wpdb->last_error
);
}
return (int) $wpdb->insert_id;
}
/*
* =====================================================
* SHORTCODE DE PRUEBA – PROCESAR 1 FACTURA
* =====================================================
*
* Uso:
* [fp_test_ocr path=»cliente1/proveedor1/2025-0043.pdf» cliente=»cliente1″]
*/
add_shortcode( ‘fp_test_ocr’, ‘fp_shortcode_test_ocr’ );
function fp_shortcode_test_ocr( $atts ) {
$atts = shortcode_atts(
array(
‘path’ => »,
‘cliente’ => ‘cliente1’,
),
$atts,
‘fp_test_ocr’
);
if ( empty( $atts[‘path’] ) ) {
return ‘<p><strong>Facturas Preconta:</strong> Debes indicar el atributo <code>path</code>. Ejemplo:<br>’
. ‘<code>[fp_test_ocr path=»cliente1/proveedor1/2025-0043.pdf» cliente=»cliente1″]</code></p>’;
}
$path_relativo = $atts[‘path’];
$cliente = $atts[‘cliente’];
// 1) Descargar desde Nextcloud
$local_file = fp_webdav_download_to_temp( $path_relativo );
if ( is_wp_error( $local_file ) ) {
return ‘<p><strong>Error WebDAV:</strong> ‘ . esc_html( $local_file->get_error_message() ) . ‘</p>’;
}
// 2) Enviar a Nanonets
$json_ocr = fp_nanonets_ocr( $local_file );
// Borrar fichero temporal
@unlink( $local_file );
if ( is_wp_error( $json_ocr ) ) {
return ‘<p><strong>Error OCR (Nanonets):</strong> ‘ . esc_html( $json_ocr->get_error_message() ) . ‘</p>’;
}
// 3) Normalizar campos
$datos = fp_nanonets_normalizar_factura( $json_ocr );
if ( empty( $datos ) ) {
return ‘<p><strong>OCR correcto pero no se han podido mapear campos.</strong><br>’
. ‘Respuesta completa:<br><pre>’ . esc_html( print_r( $json_ocr, true ) ) . ‘</pre></p>’;
}
// 4) Guardar en BD
$insert_id = fp_guardar_factura_en_bd( $cliente, $path_relativo, $datos );
if ( is_wp_error( $insert_id ) ) {
return ‘<p><strong>Error al guardar en BD:</strong> ‘ . esc_html( $insert_id->get_error_message() ) . ‘</p>’;
}
// 5) Devolver resumen en pantalla
ob_start();
?>
<div class=»wrap»>
<h2>Facturas Preconta – Prueba OCR</h2>
<p><strong>Factura guardada correctamente con ID:</strong> <?php echo (int) $insert_id; ?></p>
<h3>Datos normalizados</h3>
<table class=»widefat striped» style=»max-width: 600px;»>
<tbody>
<tr><th>Cliente</th><td><?php echo esc_html( $cliente ); ?></td></tr>
<tr><th>Proveedor</th><td><?php echo esc_html( $datos[‘proveedor’] ?? » ); ?></td></tr>
<tr><th>NIF</th><td><?php echo esc_html( $datos[‘nif’] ?? » ); ?></td></tr>
<tr><th>Nº factura</th><td><?php echo esc_html( $datos[‘num_factura’] ?? » ); ?></td></tr>
<tr><th>Fecha</th><td><?php echo esc_html( $datos[‘fecha_factura’] ?? » ); ?></td></tr>
<tr><th>Base</th><td><?php echo esc_html( number_format( $datos[‘base’] ?? 0, 2, ‘,’, ‘.’ ) ); ?></td></tr>
<tr><th>IVA</th><td><?php echo esc_html( number_format( $datos[‘iva’] ?? 0, 2, ‘,’, ‘.’ ) ); ?></td></tr>
<tr><th>Total</th><td><?php echo esc_html( number_format( $datos[‘total’] ?? 0, 2, ‘,’, ‘.’ ) ); ?></td></tr>
<tr><th>Ruta Nextcloud</th><td><?php echo esc_html( $path_relativo ); ?></td></tr>
</tbody>
</table>
<h3>JSON original (resumen)</h3>
<pre style=»max-height: 300px; overflow:auto; background:#f9f9f9; border:1px solid #ddd; padding:10px;»>
<?php echo esc_html( print_r( $json_ocr, true ) ); ?>
</pre>
</div>
<?php
return ob_get_clean();
}
«`
—
## ✅ 2. Cómo probarlo paso a paso
1. **Edita el archivo** `wp-content/plugins/facturas-preconta/facturas-preconta.php`
* Sustituye el contenido por el de arriba.
* Rellena:
* `FP_WEBDAV_BASE_URL`
* `FP_WEBDAV_USER`
* `FP_WEBDAV_PASS`
* `FP_NANONETS_API_KEY`
* `FP_NANONETS_MODEL_ID`
2. Entra en **Plugins → Facturas Preconta → Activar**
* Al activarse, crea la tabla `wp_facturas_preconta`.
3. Crea una **página de prueba** en WordPress con este shortcode:
«`text
[fp_test_ocr path=»cliente1/proveedor1/2025-0043.pdf» cliente=»cliente1″]
«`
Ajusta `path` a la ruta real de una factura en Nextcloud.
4. Visita la página:
* Si WebDAV y Nanonets están bien configurados, deberías ver:
* Resumen de la factura
* ID insertado en BD
* trozo del JSON completo.
—
## 🚀 Siguiente paso después de esto
Cuando esto funcione (Nextcloud → OCR → BD), lo siguiente será:
* Construir la **pantalla de revisión en el admin** (ETAPA 6) sobre la tabla `wp_facturas_preconta`.
* Después, la **generación del Excel APU.xlsx** para Contasol (ETAPA 7).
Si quieres, en el siguiente mensaje nos centramos ya en la **pantalla de “Facturas → Precontabilidad”** leyendo directamente de `wp_facturas_preconta`.
Perfecto, cuéntame 😊
Explícame **cómo quieres realmente que funcione el flujo del plugin** (qué hace el cliente, qué haces tú, dónde clicas, qué esperas ver en pantalla, etc.) y a partir de ahí:
* Ajustamos la lógica (si hay que invertir el flujo, usar cron, procesar en lote, por cliente, por proveedor…).
* Replanteamos estructuras de BD si hace falta.
* Adaptamos el código que ya tenemos para que encaje con tu “día a día” real.
Cuando me cuentes *“la dinámica correcta”*, te lo devuelvo ya transformado en código concreto dentro del plugin.
Vale, ahora sí veo muy claro **cómo trabajas tú realmente** con los clientes y qué esperas del plugin. No voy a marearte con preguntas: voy a tomar decisiones razonables por ti y te las explico para que veas que el flujo encaja con tu día a día.
Te resumo tu dinámica en palabras “plugin” y luego ya te doy código concreto para avanzar:
—
## 🧠 Cómo voy a modelar tu flujo en el plugin
### 1️⃣ Estructura real de trabajo
* Cada cliente tiene su carpeta en Nextcloud: `Cliente1`, `Cliente2`, etc.
* El cliente sube ahí:
* **Facturas** (PDF/JPG/lo que sea).
* **Extractos bancarios (Excel)** y otros docs (eso, de momento, los dejamos fuera del plugin y tú los sigues usando como hasta ahora).
* Tú, en WordPress (en `/wp-admin`), necesitas:
1. **Ir a un cliente**.
2. **Procesar facturas** (escanear ↔ OCR).
3. Ver una **tabla de facturas** con campos:
* Nombre del proveedor
* Tu número de cliente en ese proveedor
* Nº factura
* Fecha
* Concepto
* Base imponible
* Tipo de IVA
* Importe IVA
* Total
4. Esa tabla debe permitir:
* **Borrar** una factura.
* **Editar** una factura.
* Y, al editar, **añadir/ajustar** campos contables con desplegables:
* Cuenta de gasto 7 dígitos (ej: `6000000`).
* Cuenta de IVA (ej: `4720000`).
* Cuenta de proveedor (ej: `4000000…`).
5. Finalmente: **generar un Excel “APU.xlsx” para Contasol** que sólo lleve:
* Cuentas (gasto, IVA, proveedor)
* Importes (base, IVA, total)
* Fecha, diario, asiento, etc.
(El resto de datos – proveedor, concepto, etc. – sólo te sirven para identificar, no para el Excel).
Con eso, tú cruzas ese Excel + el extracto bancario y haces los asientos en Contasol mucho más rápido.
—
## 🔧 Lo que voy a hacer ahora mismo
Sobre el plugin que ya te pasé antes voy a:
1. **Ampliar la tabla `wp_facturas_preconta`** para que tenga todos los campos que dijiste.
2. Preparar el terreno para:
* Guardar esos datos.
* Tener una **pantalla en el admin** para ver/editar/borrar facturas.
En este mensaje **dejamos listo el esquema de datos y la base del panel**.
En el siguiente paso podemos ya montar la interfaz completa (lista, editar, borrar y, después, exportar a Excel).
—
## 1️⃣ Ampliar la tabla de facturas
👉 En tu archivo `facturas-preconta.php`, busca la función:
«`php
function fp_facturas_preconta_install() {
…
}
«`
Y sustitúyela COMPLETA por esta versión (incluye campos nuevos que necesitas):
«`php
function fp_facturas_preconta_install() {
global $wpdb;
$table_name = $wpdb->prefix . ‘facturas_preconta’;
$charset_collate = $wpdb->get_charset_collate();
$sql = «CREATE TABLE $table_name (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
cliente VARCHAR(191) NOT NULL DEFAULT »,
proveedor VARCHAR(191) NOT NULL DEFAULT »,
nif VARCHAR(50) NOT NULL DEFAULT »,
num_factura VARCHAR(100) NOT NULL DEFAULT »,
fecha_factura DATE NULL,
base DECIMAL(18,2) NOT NULL DEFAULT 0,
iva DECIMAL(18,2) NOT NULL DEFAULT 0,
total DECIMAL(18,2) NOT NULL DEFAULT 0,
— 👉 NUEVOS CAMPOS CONTABLES / DE CONTROL
codigo_cliente_proveedor VARCHAR(100) NOT NULL DEFAULT »,
tipo_iva DECIMAL(5,2) NOT NULL DEFAULT 0,
concepto TEXT NULL,
cuenta_gasto VARCHAR(20) NOT NULL DEFAULT »,
cuenta_proveedor VARCHAR(20) NOT NULL DEFAULT »,
cuenta_iva VARCHAR(20) NOT NULL DEFAULT »,
ruta_nextcloud TEXT NULL,
json_original LONGTEXT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_cliente_fecha (cliente(50), fecha_factura)
) $charset_collate;»;
require_once ABSPATH . ‘wp-admin/includes/upgrade.php’;
dbDelta( $sql );
}
«`
> ⚠️ Con `dbDelta` no hace falta borrar la tabla: si ya existía, la actualizará y añadirá las columnas nuevas.
Para aplicar los cambios, basta con **desactivar y volver a activar el plugin** desde el panel de plugins.
—
## 2️⃣ Guardar también los nuevos campos (aunque sea en blanco de momento)
En la misma `facturas-preconta.php`, busca la función:
«`php
function fp_guardar_factura_en_bd( $cliente, $ruta_nextcloud, array $datos ) {
…
}
«`
Y sustitúyela por esta versión, que ya contempla los nuevos campos:
«`php
function fp_guardar_factura_en_bd( $cliente, $ruta_nextcloud, array $datos ) {
global $wpdb;
$table_name = $wpdb->prefix . ‘facturas_preconta’;
$insert = array(
‘cliente’ => (string) $cliente,
‘proveedor’ => isset( $datos[‘proveedor’] ) ? $datos[‘proveedor’] : »,
‘nif’ => isset( $datos[‘nif’] ) ? $datos[‘nif’] : »,
‘num_factura’ => isset( $datos[‘num_factura’] ) ? $datos[‘num_factura’] : »,
‘fecha_factura’ => isset( $datos[‘fecha_factura’] ) ? $datos[‘fecha_factura’] : null,
‘base’ => isset( $datos[‘base’] ) ? $datos[‘base’] : 0,
‘iva’ => isset( $datos[‘iva’] ) ? $datos[‘iva’] : 0,
‘total’ => isset( $datos[‘total’] ) ? $datos[‘total’] : 0,
// 👉 nuevos campos: de momento vendrán vacíos desde el OCR
‘codigo_cliente_proveedor’ => isset( $datos[‘codigo_cliente_proveedor’] ) ? $datos[‘codigo_cliente_proveedor’] : »,
‘tipo_iva’ => isset( $datos[‘tipo_iva’] ) ? $datos[‘tipo_iva’] : 0,
‘concepto’ => isset( $datos[‘concepto’] ) ? $datos[‘concepto’] : »,
// estos los rellenarás luego en la pantalla de revisión
‘cuenta_gasto’ => isset( $datos[‘cuenta_gasto’] ) ? $datos[‘cuenta_gasto’] : »,
‘cuenta_proveedor’=> isset( $datos[‘cuenta_proveedor’] ) ? $datos[‘cuenta_proveedor’] : »,
‘cuenta_iva’ => isset( $datos[‘cuenta_iva’] ) ? $datos[‘cuenta_iva’] : »,
‘ruta_nextcloud’ => $ruta_nextcloud,
‘json_original’ => wp_json_encode( $datos[‘json_original’] ?? $datos ),
);
$formats = array(
‘%s’,’%s’,’%s’,’%s’,’%s’, // cliente, proveedor, nif, num_factura, fecha_factura
‘%f’,’%f’,’%f’, // base, iva, total
‘%s’,’%f’,’%s’, // codigo_cliente_proveedor, tipo_iva, concepto
‘%s’,’%s’,’%s’, // cuenta_gasto, cuenta_proveedor, cuenta_iva
‘%s’,’%s’ // ruta_nextcloud, json_original
);
$ok = $wpdb->insert( $table_name, $insert, $formats );
if ( false === $ok ) {
return new WP_Error(
‘db_insert_error’,
‘Error al insertar factura en la base de datos: ‘ . $wpdb->last_error
);
}
return (int) $wpdb->insert_id;
}
«`
> Más adelante, cuando tengas la pantalla de edición, estos campos `codigo_cliente_proveedor`, `tipo_iva`, `concepto` y las cuentas contables se podrán rellenar y actualizar sin problema.
—
## 3️⃣ Preparar el panel de “Facturas Preconta” en el admin
Ahora vamos a añadir una **entrada de menú en el panel de WordPress** que será el centro de mando:
* Menú: **Facturas Preconta**
* Contendrá:
* Un listado de facturas (filtrable por cliente).
* Posibilidad de ir a **editar** una factura.
* Posibilidad de **borrar**.
Te dejo ya el código base del menú y de la estructura principal de la página.
Luego lo iremos enriqueciendo (desplegables de cuentas, exportar Excel, etc.).
👉 Al final de tu fichero `facturas-preconta.php` (antes del `?>` si lo tienes), añade esto:
«`php
/*
* =====================================================
* ADMIN – MENÚ Y PANTALLA PRINCIPAL
* =====================================================
*/
add_action( ‘admin_menu’, ‘fp_facturas_preconta_admin_menu’ );
function fp_facturas_preconta_admin_menu() {
add_menu_page(
‘Facturas Preconta’, // Título de la página
‘Facturas Preconta’, // Texto en el menú
‘manage_options’, // Capacidad requerida
‘fp-facturas-preconta’, // slug
‘fp_facturas_preconta_admin_page’, // callback
‘dashicons-media-spreadsheet’,// icono
26 // posición aproximada
);
}
/**
* Página principal de administración
*/
function fp_facturas_preconta_admin_page() {
if ( ! current_user_can( ‘manage_options’ ) ) {
wp_die( ‘No tienes permisos para acceder a esta página.’ );
}
global $wpdb;
$table_name = $wpdb->prefix . ‘facturas_preconta’;
// — Gestión de acciones (borrar) —
if ( isset( $_GET[‘fp_action’], $_GET[‘id’], $_GET[‘_wpnonce’] ) && $_GET[‘fp_action’] === ‘delete’ ) {
$id = (int) $_GET[‘id’];
$nonce = $_GET[‘_wpnonce’];
if ( wp_verify_nonce( $nonce, ‘fp_delete_factura_’ . $id ) ) {
$wpdb->delete( $table_name, array( ‘id’ => $id ), array( ‘%d’ ) );
echo ‘<div class=»notice notice-success»><p>Factura eliminada correctamente.</p></div>’;
} else {
echo ‘<div class=»notice notice-error»><p>Nonce inválido. No se ha podido borrar la factura.</p></div>’;
}
}
// — Filtro por cliente —
$f_cliente = isset( $_GET[‘fp_cliente’] ) ? sanitize_text_field( $_GET[‘fp_cliente’] ) : »;
$where = ‘ WHERE 1=1 ‘;
$params = array();
if ( $f_cliente !== » ) {
$where .= ‘ AND cliente = %s ‘;
$params[] = $f_cliente;
}
$sql = «SELECT * FROM $table_name $where ORDER BY fecha_factura DESC, id DESC LIMIT 200″;
if ( ! empty( $params ) ) {
$facturas = $wpdb->get_results( $wpdb->prepare( $sql, $params ) );
} else {
$facturas = $wpdb->get_results( $sql );
}
?>
<div class=»wrap»>
<h1>Facturas Preconta</h1>
<form method=»get» style=»margin-bottom:15px;»>
<input type=»hidden» name=»page» value=»fp-facturas-preconta» />
<label for=»fp_cliente»>Cliente:</label>
<input type=»text» id=»fp_cliente» name=»fp_cliente» value=»<?php echo esc_attr( $f_cliente ); ?>» placeholder=»Cliente1, Cliente2…» />
<button class=»button button-secondary» type=»submit»>Filtrar</button>
</form>
<table class=»widefat striped»>
<thead>
<tr>
<th>ID</th>
<th>Cliente</th>
<th>Proveedor</th>
<th>Nº factura</th>
<th>Fecha</th>
<th>Base</th>
<th>IVA</th>
<th>Total</th>
<th>Cuenta gasto</th>
<th>Cuenta IVA</th>
<th>Cuenta proveedor</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<?php if ( empty( $facturas ) ) : ?>
<tr><td colspan=»12″>No hay facturas guardadas para este filtro.</td></tr>
<?php else : ?>
<?php foreach ( $facturas as $f ) : ?>
<tr>
<td><?php echo (int) $f->id; ?></td>
<td><?php echo esc_html( $f->cliente ); ?></td>
<td><?php echo esc_html( $f->proveedor ); ?></td>
<td><?php echo esc_html( $f->num_factura ); ?></td>
<td><?php echo esc_html( $f->fecha_factura ); ?></td>
<td><?php echo esc_html( number_format( (float) $f->base, 2, ‘,’, ‘.’ ) ); ?></td>
<td><?php echo esc_html( number_format( (float) $f->iva, 2, ‘,’, ‘.’ ) ); ?></td>
<td><?php echo esc_html( number_format( (float) $f->total, 2, ‘,’, ‘.’ ) ); ?></td>
<td><?php echo esc_html( $f->cuenta_gasto ); ?></td>
<td><?php echo esc_html( $f->cuenta_iva ); ?></td>
<td><?php echo esc_html( $f->cuenta_proveedor ); ?></td>
<td>
<a href=»<?php echo esc_url( admin_url( ‘admin.php?page=fp-facturas-preconta&editar=’ . (int) $f->id ) ); ?>»>Editar</a>
|
<a href=»<?php echo esc_url(
wp_nonce_url(
admin_url( ‘admin.php?page=fp-facturas-preconta&fp_action=delete&id=’ . (int) $f->id ),
‘fp_delete_factura_’ . (int) $f->id
)
); ?>» onclick=»return confirm(‘¿Seguro que quieres borrar esta factura?’);»>Borrar</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<?php
// Si se ha pedido editar una factura, mostramos formulario debajo
if ( isset( $_GET[‘editar’] ) ) {
$editar_id = (int) $_GET[‘editar’];
fp_facturas_preconta_form_edicion( $editar_id );
}
?>
</div>
<?php
}
«`
Y justo debajo, añade el formulario de edición simple (para empezar a poder cambiar cuentas, luego lo refinamos con desplegables y más campos):
«`php
/**
* Formulario de edición de una factura concreta
*/
function fp_facturas_preconta_form_edicion( $id ) {
global $wpdb;
$table_name = $wpdb->prefix . ‘facturas_preconta’;
$factura = $wpdb->get_row( $wpdb->prepare(
«SELECT * FROM $table_name WHERE id = %d»,
$id
) );
if ( ! $factura ) {
echo ‘<div class=»notice notice-error»><p>Factura no encontrada.</p></div>’;
return;
}
// Procesar guardado
if ( isset( $_POST[‘fp_editar_factura’] ) && check_admin_referer( ‘fp_editar_factura_’ . $id ) ) {
$update = array(
‘proveedor’ => sanitize_text_field( $_POST[‘proveedor’] ?? » ),
‘nif’ => sanitize_text_field( $_POST[‘nif’] ?? » ),
‘num_factura’ => sanitize_text_field( $_POST[‘num_factura’] ?? » ),
‘fecha_factura’ => sanitize_text_field( $_POST[‘fecha_factura’] ?? » ),
‘base’ => fp_normalizar_numero( $_POST[‘base’] ?? 0 ),
‘iva’ => fp_normalizar_numero( $_POST[‘iva’] ?? 0 ),
‘total’ => fp_normalizar_numero( $_POST[‘total’] ?? 0 ),
‘codigo_cliente_proveedor’=> sanitize_text_field( $_POST[‘codigo_cliente_proveedor’] ?? » ),
‘tipo_iva’ => fp_normalizar_numero( $_POST[‘tipo_iva’] ?? 0 ),
‘concepto’ => sanitize_textarea_field( $_POST[‘concepto’] ?? » ),
‘cuenta_gasto’ => sanitize_text_field( $_POST[‘cuenta_gasto’] ?? » ),
‘cuenta_iva’ => sanitize_text_field( $_POST[‘cuenta_iva’] ?? » ),
‘cuenta_proveedor’ => sanitize_text_field( $_POST[‘cuenta_proveedor’] ?? » ),
);
$wpdb->update(
$table_name,
$update,
array( ‘id’ => $id ),
array(
‘%s’,’%s’,’%s’,’%s’,
‘%f’,’%f’,’%f’,
‘%s’,’%f’,’%s’,
‘%s’,’%s’,’%s’
),
array( ‘%d’ )
);
echo ‘<div class=»notice notice-success»><p>Factura actualizada correctamente.</p></div>’;
// recargar datos
$factura = $wpdb->get_row( $wpdb->prepare(
«SELECT * FROM $table_name WHERE id = %d»,
$id
) );
}
?>
<h2>Editar factura ID <?php echo (int) $id; ?></h2>
<form method=»post»>
<?php wp_nonce_field( ‘fp_editar_factura_’ . $id ); ?>
<table class=»form-table»>
<tr>
<th>Proveedor</th>
<td><input type=»text» name=»proveedor» value=»<?php echo esc_attr( $factura->proveedor ); ?>» class=»regular-text» /></td>
</tr>
<tr>
<th>NIF</th>
<td><input type=»text» name=»nif» value=»<?php echo esc_attr( $factura->nif ); ?>» class=»regular-text» /></td>
</tr>
<tr>
<th>Nº factura</th>
<td><input type=»text» name=»num_factura» value=»<?php echo esc_attr( $factura->num_factura ); ?>» class=»regular-text» /></td>
</tr>
<tr>
<th>Fecha factura (YYYY-MM-DD)</th>
<td><input type=»text» name=»fecha_factura» value=»<?php echo esc_attr( $factura->fecha_factura ); ?>» class=»regular-text» /></td>
</tr>
<tr>
<th>Base imponible</th>
<td><input type=»text» name=»base» value=»<?php echo esc_attr( $factura->base ); ?>» class=»regular-text» /></td>
</tr>
<tr>
<th>Tipo IVA (%)</th>
<td><input type=»text» name=»tipo_iva» value=»<?php echo esc_attr( $factura->tipo_iva ); ?>» class=»regular-text» /></td>
</tr>
<tr>
<th>Importe IVA</th>
<td><input type=»text» name=»iva» value=»<?php echo esc_attr( $factura->iva ); ?>» class=»regular-text» /></td>
</tr>
<tr>
<th>Total</th>
<td><input type=»text» name=»total» value=»<?php echo esc_attr( $factura->total ); ?>» class=»regular-text» /></td>
</tr>
<tr>
<th>Código cliente en proveedor</th>
<td><input type=»text» name=»codigo_cliente_proveedor» value=»<?php echo esc_attr( $factura->codigo_cliente_proveedor ); ?>» class=»regular-text» /></td>
</tr>
<tr>
<th>Concepto</th>
<td><textarea name=»concepto» rows=»3″ class=»large-text»><?php echo esc_textarea( $factura->concepto ); ?></textarea></td>
</tr>
<tr>
<th>Cuenta gasto</th>
<td><input type=»text» name=»cuenta_gasto» value=»<?php echo esc_attr( $factura->cuenta_gasto ); ?>» class=»regular-text» placeholder=»6000000, 6280000…» /></td>
</tr>
<tr>
<th>Cuenta IVA</th>
<td><input type=»text» name=»cuenta_iva» value=»<?php echo esc_attr( $factura->cuenta_iva ); ?>» class=»regular-text» placeholder=»4720000…» /></td>
</tr>
<tr>
<th>Cuenta proveedor</th>
<td><input type=»text» name=»cuenta_proveedor» value=»<?php echo esc_attr( $factura->cuenta_proveedor ); ?>» class=»regular-text» placeholder=»4000000…» /></td>
</tr>
</table>
<p>
<button type=»submit» name=»fp_editar_factura» class=»button button-primary»>Guardar cambios</button>
</p>
</form>
<?php
}
«`
Con esto ya tendrás:
* Menú **“Facturas Preconta”** en el admin.
* Listado básico de facturas que vienen del OCR.
* Posibilidad de borrar una factura.
* Formulario de **edición** con todos los campos que describiste (incluidos los contables).
—
## Qué puedes hacer ahora mismo
1. Actualiza el plugin con las tres partes anteriores:
* Nueva función `fp_facturas_preconta_install`.
* Nueva función `fp_guardar_factura_en_bd`.
* Bloque del admin (menú + listado + edición).
2. Desactiva y reactiva el plugin.
3. Procesa una factura como antes con el shortcode `[fp_test_ocr …]`.
4. Entra en **“Facturas Preconta”** en el menú del panel y deberías ver la factura, poder editarla y borrarla.
Cuando tengas esto funcionando, el siguiente paso natural es:
* Añadir los **desplegables de cuentas** (con tus cuentas favoritas por defecto).
* Implementar la **exportación a Excel tipo APU** para Contasol usando esos campos.
Cuando me digas que ya ves el listado y puedes editar/borrar, seguimos y te doy directamente el código de exportación a APU.xlsx.
Perfecto, te explico exactamente **dónde está y cómo conseguir tu `FP_NANONETS_MODEL_ID`** dentro de tu cuenta de Nanonets.
—
# ✅ **Cómo obtener tu Nanonets Model ID (Invoice OCR)**
Sigue estos pasos, es muy sencillo:
—
## **1️⃣ Entra en tu cuenta de Nanonets**
👉 [https://app.nanonets.com/](https://app.nanonets.com/)
Inicia sesión.
—
## **2️⃣ Ve al menú “Models” (Modelos)**
Lo verás en la columna izquierda.
—
## **3️⃣ Selecciona tu modelo de OCR de facturas**
Normalmente se llama:
* **Invoice OCR**
* o **Invoice Extraction**
* o el nombre que le hayas puesto tú.
Haz clic para entrar dentro del modelo.
—
## **4️⃣ UNA VEZ DENTRO: el Model ID aparece arriba**
Dentro del panel del modelo verás algo como:
«`
Model ID: 123abc45-6789-xxxx-yyyy-zzzz11223344
«`
o
«`
Model Key: xxxxxxxxxxxxxxxxx
«`
Ese valor es EXACTAMENTE el que debes copiar en:
«`php
define( ‘FP_NANONETS_MODEL_ID’,’AQUÍ-TU-MODEL-ID’ );
«`
—
# 🔍 ¿Y si no aparece? (caso común)
A veces aparece en una URL como esta:
«`
https://app.nanonets.com/invoices/123abc45-6789-xxxx-yyyy-zzzz11223344
«`
En ese caso, el **Model ID** es:
«`
123abc45-6789-xxxx-yyyy-zzzz11223344
«`
—
# 🧪 ¿Quieres que verifique que el ID es correcto?
Puedes decirme:
* “Mi model ID es: XXXXX”
* Y te confirmo si tiene el formato correcto para la API.
—
# 📌 Si todavía no tienes un modelo creado
Puedes crearlo así:
1. Entra a Nanonets → **Models**
2. Clic en **Create Model**
3. Selecciona plantilla: **Invoice OCR**
4. Cuando acabe, entra dentro → ahí verás tu **Model ID**
—
## Si quieres, me puedes hacer una captura o copiar la URL del modelo, y yo mismo te digo cuál es tu Model ID exacto.
¿Quieres que revisemos juntos tu panel de Nanonets?