Herramientas Informaticas

Etiqueta: PHP8 Página 1 de 11

🔧 PHP + SAP HANA ODBC: solución cuando odbc_fetch_array() no devuelve todos los registros

Entrada fija

¿Te ha pasado que una consulta en SAP HANA devuelve cientos de registros correctamente, pero al consumirla desde PHP mediante ODBC solamente recibes uno?

Eso fue exactamente lo que ocurrió en este caso: una consulta en SAP HANA tenía 902 registros, pero desde PHP, al incluir la descripción del artículo OITM."ItemName", odbc_fetch_array() solamente recuperaba 1 registro.

Después de varias pruebas se pudo determinar que no era un problema de memoria, SQL, JOIN, PHP ni del while.

La solución fue configurar correctamente el controlador SAP HANA ODBC para manejar los caracteres mediante UTF-8:

CHAR_AS_UTF8=true

manteniendo:

EnableArrayFetch=1

La configuración se aplicó tanto en Linux Mint como en Windows 10.


🚨 El problema

La consulta original era similar a:

SELECT
    a."ItemCode",
    b."ItemName",
    a."Price",
    a."PriceList",
    c."ListName"
FROM ITM1 a
INNER JOIN OITM b
    ON a."ItemCode" = b."ItemCode"
INNER JOIN OPLN c
    ON a."PriceList" = c."ListNum"
WHERE a."PriceList" = 1
  AND IFNULL(a."Price", 0) > 0;

En SAP HANA la consulta contenía 902 artículos con precio.

Sin embargo, PHP solamente recibía:

1 registro

y no aparecía ningún error ODBC.

El código PHP era completamente normal:

while ($row = odbc_fetch_array($stmt)) {

    $data[] = [
        'ItemCode'  => $row['ItemCode'],
        'ItemName'  => $row['ItemName'],
        'Price'     => (float) $row['Price'],
        'PriceList' => (int) $row['PriceList'],
        'ListName'  => $row['ListName'],
    ];
}

🔍 Primero: comprobar si realmente existen los registros

Antes de culpar a PHP u ODBC, se comprobó directamente en HANA:

SELECT COUNT(*) AS "Total"
FROM ITM1
WHERE "PriceList" = 1
  AND IFNULL("Price", 0) > 0;

Resultado:

902

Por lo tanto, los datos sí existían.


🧪 Diagnóstico paso a paso

Para encontrar el problema se fue simplificando la consulta.

1️⃣ Solamente ITM1

SELECT
    "ItemCode",
    "Price",
    "PriceList"
FROM ITM1
WHERE "PriceList" = 1
  AND IFNULL("Price", 0) > 0;

Desde PHP:

TOTAL: 902

ITM1 funcionaba correctamente.


2️⃣ Agregar el JOIN con OITM

Se añadió:

INNER JOIN OITM b
    ON a."ItemCode" = b."ItemCode"

pero solamente se recuperó el código:

SELECT
    a."ItemCode",
    b."ItemCode"
FROM ITM1 a
INNER JOIN OITM b
    ON a."ItemCode" = b."ItemCode"
WHERE a."PriceList" = 1
  AND IFNULL(a."Price", 0) > 0;

Resultado:

TOTAL: 902

✅ El JOIN con OITM también funcionaba.


⚠️ 3️⃣ Agregar ItemName

El problema apareció cuando se añadió:

b."ItemName"

La consulta:

SELECT
    a."ItemCode",
    b."ItemName"
FROM ITM1 a
INNER JOIN OITM b
    ON a."ItemCode" = b."ItemCode"
WHERE a."PriceList" = 1
  AND IFNULL(a."Price", 0) > 0;

Desde PHP solamente devolvía:

TOTAL: 1

🎯 Aquí quedó localizado el problema.

No era el JOIN.

No era ITM1.

No era OPLN.

No era memoria.

El comportamiento aparecía específicamente al recuperar el contenido de:

OITM."ItemName"

🧠 Una pista importante: los caracteres especiales

Durante las pruebas también apareció algo muy revelador.

En SAP HANA el texto era:

REV 14±2

pero PHP recibía:

REV 14±2

Eso indicaba claramente un problema de codificación de caracteres entre:

SAP HANA
   ↓
HDBODBC
   ↓
PHP
   ↓
JSON
   ↓
DataTables

Además, el problema no generaba un error ODBC tradicional.

Por eso era especialmente difícil de localizar.


🐧 Solución en Linux Mint

Primero se comprobó la configuración de unixODBC:

odbcinst -j

La salida mostraba:

DRIVERS............: /etc/odbcinst.ini
SYSTEM DATA SOURCES: /etc/odbc.ini
USER DATA SOURCES..: /home/usuario/.odbc.ini

El DSN utilizado estaba en:

/etc/odbc.ini

La configuración original era:

[mi_dsn_hana]
Driver=SAP HANA
ServerNode=SERVIDOR_HANA:30015

Se añadió:

CHAR_AS_UTF8=true

Quedando:

[mi_dsn_hana]
Driver=SAP HANA
ServerNode=SERVIDOR_HANA:30015
CHAR_AS_UTF8=true

El driver utilizado estaba definido en:

/etc/odbcinst.ini

con:

[HANA_ODBC]
Description=SAP HANA ODBC Driver
Driver=/ruta/al/cliente-hana/libodbcHDB.so

🔎 Comandos útiles para localizar ODBC en Linux

Para saber dónde están los archivos:

odbcinst -j

Para ver el DSN:

cat /etc/odbc.ini

Para ver los drivers:

cat /etc/odbcinst.ini

Para buscar configuraciones relacionadas con HANA:

grep -RniE "HDB|HANA|CHAR_AS_UTF8|char_as_utf8|ServerNode|Driver" \
/etc/odbc.ini \
/etc/odbcinst.ini \
~/.odbc.ini 2>/dev/null

Para listar los DSN:

odbcinst -q -s

🪟 Solución en Windows 10

En Windows se comprobó primero la arquitectura de PHP:

php -i | findstr /I "Architecture"

Resultado:

Architecture => x64

Por lo tanto se utilizó el administrador ODBC de 64 bits:

C:\Windows\System32\odbcad32.exe

El DSN utilizado era:

mi_dsn_hana

y estaba registrado en:

HKLM\SOFTWARE\ODBC\ODBC.INI\mi_dsn_hana

Al consultar el registro:

reg query "HKLM\SOFTWARE\ODBC\ODBC.INI\mi_dsn_hana" /s

se encontró, entre otros valores:

Driver              C:\Program Files\SAP\hdbclient\libodbcHDB.dll
Host                SERVIDOR_HANA
PortNumber          30015
EnableArrayFetch    1
ArrayFetchSize      5000

💾 Antes de modificar el registro: hacer respaldo

Siempre es recomendable guardar una copia del DSN.

reg export "HKLM\SOFTWARE\ODBC\ODBC.INI\mi_dsn_hana" "%USERPROFILE%\Desktop\mi_dsn_hana-backup.reg"

Windows responderá:

La operación se completó correctamente.

Así se dispone de un archivo de respaldo en el escritorio.


⚙️ Agregar CHAR_AS_UTF8 en Windows

Se agregó directamente al registro:

reg add "HKLM\SOFTWARE\ODBC\ODBC.INI\mi_dsn_hana" /v CHAR_AS_UTF8 /t REG_SZ /d true /f

Para comprobarlo:

reg query "HKLM\SOFTWARE\ODBC\ODBC.INI\mi_dsn_hana" /v CHAR_AS_UTF8

El resultado esperado es:

CHAR_AS_UTF8    REG_SZ    true

✅ Configuración aplicada correctamente.


🔄 Reiniciar Apache

Después de modificar el DSN, es importante reiniciar Apache/XAMPP para que PHP cree una nueva conexión ODBC.

Desde XAMPP:

Stop Apache
Start Apache

O, si Apache está instalado como servicio:

net stop Apache2.4
net start Apache2.4

El nombre del servicio puede variar según la instalación.


⚙️ ¿Qué pasó con EnableArrayFetch?

El DSN de Windows ya tenía:

EnableArrayFetch=1

y:

ArrayFetchSize=5000

Durante las pruebas se decidió mantenerlo activado.

La configuración final quedó:

CHAR_AS_UTF8=true
EnableArrayFetch=1

Esto mismo se mantuvo tanto en Linux como en Windows.

No fue necesario desactivar EnableArrayFetch.


💻 La función PHP no necesitó cambios especiales

Una vez solucionada la configuración del ODBC, la consulta PHP puede seguir siendo una consulta normal:

static public function ctrMostrarListaPrecio($conn, int $id)
{
    $sql = '
        SELECT
            a."ItemCode",
            b."ItemName",
            a."Price",
            a."PriceList",
            c."ListName"
        FROM ITM1 a
        INNER JOIN OITM b
            ON a."ItemCode" = b."ItemCode"
        INNER JOIN OPLN c
            ON a."PriceList" = c."ListNum"
        WHERE a."PriceList" = ?
          AND IFNULL(a."Price", 0) > 0
    ';

    $stmt = odbc_prepare($conn, $sql);

    if (!$stmt) {
        throw new Exception(
            'Error ODBC prepare: ' . odbc_errormsg($conn)
        );
    }

    if (!odbc_execute($stmt, [$id])) {
        throw new Exception(
            'Error ODBC execute: ' . odbc_errormsg($conn)
        );
    }

    $data = [];

    while ($row = odbc_fetch_array($stmt)) {

        $data[] = [
            'ItemCode'  => $row['ItemCode'],
            'ItemName'  => $row['ItemName'],
            'Price'     => (float) $row['Price'],
            'PriceList' => (int) $row['PriceList'],
            'ListName'  => $row['ListName'],
        ];
    }

    return $data;
}

La solución estuvo en la configuración del HDBODBC, no en cambiar la consulta.


🧪 Otra prueba importante: comprobar si era memoria

También se revisó la memoria de PHP:

echo memory_get_usage(true);

Durante las pruebas se obtuvo aproximadamente:

2097152 bytes

Y el pico:

2097152 bytes

No hubo incremento importante al procesar los registros.

Por lo tanto:

❌ No era falta de memoria.

❌ No era que 902 registros fueran demasiados.

❌ No era el while.

❌ No era odbc_prepare().

❌ No era odbc_execute().

❌ No era el JOIN.

✅ El comportamiento estaba relacionado con el tratamiento de caracteres por HDBODBC.


🛠️ Diagnóstico recomendado para futuros problemas

Cuando PHP y SAP HANA devuelvan menos registros de los esperados, no hay que asumir inmediatamente que el problema está en SQL.

Una buena estrategia es ir agregando las columnas progresivamente.

Primero:

SELECT
    "ItemCode"
FROM ITM1
WHERE "PriceList" = 1;

Después:

SELECT
    a."ItemCode",
    b."ItemCode"
FROM ITM1 a
INNER JOIN OITM b
    ON a."ItemCode" = b."ItemCode"
WHERE a."PriceList" = 1;

Después:

SELECT
    a."ItemCode",
    b."ItemName"
FROM ITM1 a
INNER JOIN OITM b
    ON a."ItemCode" = b."ItemCode"
WHERE a."PriceList" = 1;

De esta forma puedes identificar exactamente qué columna provoca el comportamiento.

En este caso:

ITM1                         → 902
ITM1 + OITM.ItemCode         → 902
ITM1 + OITM.ItemName         → 1

Ese pequeño experimento permitió descubrir rápidamente que había que investigar el tratamiento del campo de texto.


📌 Configuración final

Después de todas las pruebas, la configuración que quedó funcionando fue:

CHAR_AS_UTF8=true
EnableArrayFetch=1

Linux Mint

Archivo:

/etc/odbc.ini

Configuración:

[mi_dsn_hana]
Driver=SAP HANA
ServerNode=SERVIDOR_HANA:30015
CHAR_AS_UTF8=true

Windows 10

Registro:

HKLM\SOFTWARE\ODBC\ODBC.INI\mi_dsn_hana

Valor:

CHAR_AS_UTF8    REG_SZ    true

Manteniendo:

EnableArrayFetch    REG_SZ    1

✅ Conclusión

El problema parecía inicialmente un problema de PHP porque:

while ($row = odbc_fetch_array($stmt))

solamente recuperaba un registro.

Sin embargo, las pruebas demostraron que SAP HANA sí tenía 902 registros y que ODBC podía recorrerlos correctamente mientras no se solicitara directamente el contenido de ItemName.

La pista definitiva fue que los textos con caracteres especiales aparecían alterados, por ejemplo:

14±2

se recibía como:

14±2

La corrección fue configurar el controlador de SAP HANA para trabajar con UTF-8:

CHAR_AS_UTF8=true

manteniendo:

EnableArrayFetch=1

La configuración fue aplicada tanto en Linux Mint como en Windows 10.

💡 Moraleja: cuando PHP + ODBC + SAP HANA devuelve menos filas de las esperadas, especialmente al trabajar con campos NVARCHAR o textos con caracteres especiales, conviene revisar primero la configuración del HDBODBC antes de modificar la consulta o asumir que existe un problema de memoria.


🚀 Resumen rápido

Linux

sudo cp /etc/odbc.ini /etc/odbc.ini.bak
sudo nano /etc/odbc.ini

Agregar:

CHAR_AS_UTF8=true

Windows 10

Respaldar:

reg export "HKLM\SOFTWARE\ODBC\ODBC.INI\mi_dsn_hana" "%USERPROFILE%\Desktop\mi_dsn_hana-backup.reg"

Agregar:

reg add "HKLM\SOFTWARE\ODBC\ODBC.INI\mi_dsn_hana" /v CHAR_AS_UTF8 /t REG_SZ /d true /f

Comprobar:

reg query "HKLM\SOFTWARE\ODBC\ODBC.INI\mi_dsn_hana" /v CHAR_AS_UTF8

Resultado esperado:

CHAR_AS_UTF8    REG_SZ    true

Configuración final:

CHAR_AS_UTF8=true
EnableArrayFetch=1

🔧 PHP + SAP HANA + ODBC + UTF-8: un pequeño parámetro del driver puede hacer toda la diferencia.

🎵 Cómo organizar tu música en Linux y crear tu propio Spotify con Picard y Navidrome

Entrada fija

¿Tienes cientos o miles de canciones desordenadas en carpetas? 😵‍💫🎶
¿Archivos con nombres como Track01.mp3, Audio_023.flac o canciones sin portada ni información?

En Linux podemos solucionar este problema de una forma sencilla utilizando MusicBrainz Picard para identificar y organizar nuestra música y Navidrome para crear nuestro propio servidor de música. 🐧🎧

El resultado: una biblioteca musical ordenada y accesible desde la computadora, celular o navegador. 📱💻

🎼 1. Organiza tu música con MusicBrainz Picard

El primer paso es tener correctamente identificadas nuestras canciones.

Para esto podemos utilizar MusicBrainz Picard, una herramienta gratuita que utiliza la base de datos de MusicBrainz para reconocer nuestros archivos y completar automáticamente sus metadatos.

Con Picard podemos obtener información como:

  • 🎤 Artista
  • 💿 Álbum
  • 🎵 Nombre de la canción
  • 📅 Año
  • 🎼 Género
  • 🔢 Número de pista
  • 🖼️ Portada del álbum
  • 💽 Información del disco

Esto es especialmente útil cuando tenemos una colección antigua de MP3, FLAC u otros formatos y queremos convertirla en una biblioteca musical bien organizada.

📂 Una estructura ordenada

Por ejemplo, podemos terminar con una estructura similar a:

Música/
├── Pink Floyd/
│   └── The Dark Side of the Moon/
│       ├── 01 - Speak to Me.flac
│       ├── 02 - Breathe.flac
│       └── 03 - On the Run.flac
│
├── Queen/
│   └── A Night at the Opera/
│       ├── 01 - Death on Two Legs.flac
│       └── 02 - Lazing on a Sunday Afternoon.flac
│
└── Metallica/
    └── Black Album/
        ├── 01 - Enter Sandman.flac
        └── 02 - Sad but True.flac

🎯 La ventaja es que posteriormente aplicaciones como Navidrome pueden utilizar estos metadatos para mostrar correctamente artistas, álbumes, géneros y canciones.


🏷️ 2. Picard: etiquetas y organización automática

Una vez que agregamos nuestra música a MusicBrainz Picard, el programa analiza las canciones y busca coincidencias en la base de datos.

Podemos revisar la información encontrada y después guardar los cambios. 💾

Así evitamos tener que editar manualmente cientos de canciones.

✅ El flujo sería:

🎵 Música desordenada
⬇️
🔎 MusicBrainz Picard
⬇️
🏷️ Etiquetas correctas
⬇️
📂 Archivos organizados
⬇️
🎧 Navidrome
⬇️
📱 Escuchar nuestra música desde cualquier dispositivo


🎧 3. Instala Navidrome y crea tu propio servidor de música

Una vez que nuestra biblioteca está organizada, podemos utilizar Navidrome.

Navidrome es un servidor de música que nos permite acceder a nuestra colección desde diferentes dispositivos mediante una interfaz web y clientes compatibles.

En lugar de depender exclusivamente de una plataforma de streaming, podemos tener nuestra propia biblioteca almacenada en nuestro servidor. 🖥️🎶

Por ejemplo:

Servidor Linux
      │
      ├── 🎵 Música
      │
      └── 🎧 Navidrome
             │
       ┌─────┼─────┐
       ↓     ↓     ↓
     📱      💻     🌐
   Celular   PC    Navegador

Esto resulta especialmente interesante si ya tienes una gran colección de música comprada, descargada legalmente o digitalizada desde tus propios discos.


🟢 4. ¿Navidrome puede ser una alternativa a Spotify?

Sí, pero son conceptos diferentes. 😉

Spotify es un servicio de streaming con un enorme catálogo disponible mediante su plataforma.

Navidrome, en cambio, está pensado para reproducir tu propia colección de música desde tu servidor.

🟢 Con Navidrome tienes:

✅ Tu propia biblioteca musical
✅ Control sobre tus archivos
✅ Organización mediante metadatos
✅ Acceso desde navegador
✅ Acceso desde dispositivos móviles mediante clientes compatibles
✅ Soporte para diferentes formatos de audio
✅ Sin depender de un catálogo externo para tu música
✅ Posibilidad de alojarlo en tu propio servidor Linux

🔵 Con Spotify tienes:

🎵 Un enorme catálogo de música
🔎 Búsqueda de artistas y canciones
📻 Recomendaciones y playlists
🌐 Servicio administrado por Spotify
📱 Aplicaciones oficiales para diferentes dispositivos

Por eso, Navidrome no busca reemplazar completamente a Spotify, sino ofrecer una alternativa para quienes quieren tener su propia biblioteca musical bajo su control.


🔥 5. ¿Por qué utilizar Picard + Navidrome?

La combinación es muy interesante:

🏷️ MusicBrainz Picard

Se encarga de identificar, etiquetar y organizar nuestra música.

🎧 Navidrome

Se encarga de servir y reproducir esa biblioteca desde nuestro servidor.

Es decir:

Picard organiza tu música. Navidrome te permite disfrutarla. 🎶

Y todo esto puede funcionar perfectamente dentro de un servidor Linux. 🐧


💻 6. Una biblioteca musical completamente organizada

Después de realizar todo el proceso podemos tener algo parecido a:

/home/usuario/Música/
│
├── Artista 1/
│   ├── Álbum 1/
│   └── Álbum 2/
│
├── Artista 2/
│   ├── Álbum 1/
│   └── Álbum 2/
│
└── Artista 3/
    └── Álbum 1/

Navidrome leerá la información de los archivos y podremos navegar por:

🎤 Artistas
💿 Álbumes
🎵 Canciones
🎼 Géneros
⭐ Favoritos
📋 Listas de reproducción


📱 7. Escucha tu música desde el celular

Una de las partes más interesantes de tener Navidrome es poder acceder a nuestra biblioteca desde otros dispositivos.

Por ejemplo:

🖥️ PC → Navidrome
📱 Celular → Cliente compatible
💻 Laptop → Navegador
🌐 Otros dispositivos → Acceso al servidor

Así podemos convertir una computadora o servidor Linux en nuestro centro personal de música. 🎶🐧


🔐 8. Tu música bajo tu control

Una de las principales ventajas de este enfoque es que tú decides dónde almacenar tu biblioteca y cómo administrarla.

Puedes utilizar:

💽 Disco duro
🖥️ PC vieja
🗄️ Servidor casero
☁️ Servidor remoto

Y si configuras correctamente el acceso remoto y la seguridad, también puedes acceder a tu biblioteca cuando estés fuera de casa. 🌎📱


🚀 Conclusión

Si tienes una colección grande de música y quieres dejar atrás el caos de carpetas y archivos mal nombrados, MusicBrainz Picard + Navidrome es una excelente combinación para Linux. 🐧🎵

Picard se encarga de identificar y organizar tus canciones.

Navidrome convierte esa biblioteca en tu propio servicio de música.

Y aunque no reemplaza directamente el enorme catálogo de Spotify, sí puede ser una excelente alternativa para quienes quieren disfrutar de su propia colección musical desde cualquier dispositivo.

🎵 Tu música.
📂 Tu biblioteca.
🖥️ Tu servidor.
🔐 Tu control.

#Linux #Navidrome #MusicBrainzPicard #Picard #Spotify #Musica #LinuxMint #ServidorLinux #SelfHosting #MusicaDigital #FLAC #MP3 #BibliotecaMusical #OpenSource #LinuxServer

📦 Cómo Instalar Nextcloud en Linux Mint con Snap (Guía Paso a Paso)

Entrada fija

¿Quieres tener tu propia nube privada en casa o en tu servidor local? Nextcloud es la solución perfecta para almacenar, sincronizar y compartir archivos sin depender de servicios externos como Google Drive o Dropbox.

En esta guía te enseñaré cómo instalar Nextcloud en Linux Mint utilizando Snap, el sistema de paquetes universales que hace que la instalación sea increíblemente sencilla.


📋 Requisitos Previos

  • Linux Mint instalado (cualquier versión reciente)
  • Conexión a internet
  • Acceso de administrador (sudo)
  • Una terminal abierta (Ctrl + Alt + T)

⚠️ Importante: Linux Mint y Snap

Por defecto, Linux Mint bloquea Snap por motivos de política. Así que primero debemos habilitarlo con unos sencillos pasos.


🚀 Pasos de Instalación

1️⃣ Eliminar el bloqueo de Snap

sudo rm /etc/apt/preferences.d/nosnap.pref

2️⃣ Actualizar los repositorios

sudo apt update

3️⃣ Instalar Snap

sudo apt install snapd

4️⃣ Instalar Nextcloud

sudo snap install nextcloud

¡Listo! La instalación tardará unos minutos. Nextcloud incluye automáticamente:

  • ✅ Servidor Apache
  • ✅ PHP 8.1
  • ✅ MySQL 8
  • ✅ Redis para caché

🌐 Acceder a Nextcloud

Una vez instalado, abre tu navegador y visita:

  • Localmente: http://localhost
  • Desde otros dispositivos en tu red: http://tu-ip-local

Para conocer tu IP local:

hostname -I

🔧 Si cambiaste de IP (por DHCP)

Es posible que necesites agregar tu IP como dominio confiable:

sudo nextcloud.occ config:system:set trusted_domains 1 --value=$(hostname -I | awk '{print $1}')

🔑 Crear Cuenta de Administrador

En tu primera visita a Nextcloud, se te pedirá que crees:

  • 👤 Nombre de usuario
  • 🔒 Contraseña de administrador

¡Guarda estos datos en un lugar seguro!


🛠️ Comandos Útiles para la Gestión

ComandoFunción
sudo snap start nextcloudIniciar Nextcloud
sudo snap stop nextcloudDetener Nextcloud
sudo snap restart nextcloudReiniciar Nextcloud
sudo snap services nextcloudVer estado del servicio
sudo snap logs nextcloudVer logs de errores
sudo snap set nextcloud ports.http=8080Cambiar puerto HTTP
sudo nextcloud.enable-httpsHabilitar HTTPS con Let’s Encrypt
sudo nextcloud.occ statusVer estado de Nextcloud

🔐 Restablecer Contraseña de Administrador

Si olvidaste la contraseña de admin:

sudo nextcloud.occ user:resetpassword admin

🖥️ Acceso a Almacenamiento Externo

Si necesitas usar discos externos montados en /media o /mnt:

sudo snap connect nextcloud:removable-media

🎯 Ventajas de Usar Snap para Nextcloud

  • ✅ Instalación sencilla en un solo comando
  • ✅ Actualizaciones automáticas sin complicaciones
  • ✅ Todo incluido (servidor web, base de datos, caché)
  • ✅ Fácil de gestionar con comandos Snap

❓ Solución de Problemas Comunes

No puedo acceder a Nextcloud

  1. Verifica que el servicio esté activo: sudo snap services nextcloud
  2. Comprueba los puertos disponibles: sudo ss -tulpn | grep snap.nextcloud
  3. Revisa los logs en busca de errores: sudo snap logs nextcloud | tail -20

El puerto 80 ya está en uso

Cambia el puerto HTTP:

sudo snap set nextcloud ports.http=8080

La IP cambia constantemente

Agrega el nuevo IP como dominio confiable (como se explicó arriba).


🎉 ¡Listo!

Ahora tienes tu propio servidor de nube privada funcionando en Linux Mint. Puedes:

  • 📁 Almacenar archivos
  • 📱 Sincronizar con el móvil (aplicación Nextcloud)
  • 👥 Compartir carpetas con otros usuarios
  • 🔄 Sincronizar calendarios y contactos

📚 Recursos Adicionales


¿Te ha sido útil esta guía? Déjame tu comentario y compártela con otros usuarios de Linux. ¡La privacidad y el control de tus datos es cosa de todos! 💪


🔖 Etiquetas

#Nextcloud #LinuxMint #Snap #NubePrivada #SelfHosting #Linux #Tecnología #Almacenamiento #SeguridadDigital

🚀 Guía Completa para Administrar y Validar Conexiones SQL Server desde PHP, CodeIgniter y Linux

Entrada fija



Guía Completa para Administrar y Validar Conexiones SQL Server desde PHP, CodeIgniter y Linux

Las bases de datos son el corazón de los sistemas empresariales modernos. SQL Server es una de las plataformas más utilizadas para almacenar información crítica y su integración con PHP y CodeIgniter permite construir soluciones robustas y escalables.

💻 ¿Por qué es importante administrar conexiones?

Cuando una organización utiliza múltiples servidores SQL Server, mantener un catálogo centralizado de conexiones facilita la administración, mejora la seguridad y reduce errores de configuración.

📋 Información almacenada

  • 🏢 Empresa
  • 🌐 Host o servidor
  • 👤 Usuario
  • 🔑 Contraseña
  • 🗄️ Base de datos
  • 🔌 Puerto

✅ Validación automática

Una de las características más útiles es la capacidad de validar en tiempo real si una conexión es válida antes de utilizarla. Esto permite detectar errores de red, credenciales incorrectas o bases de datos inexistentes.

🔍 Beneficios

  • Ahorro de tiempo
  • Menos errores humanos
  • Mayor productividad
  • Seguridad mejorada
  • Escalabilidad

🐧 Compatibilidad

La solución funciona con Linux y Windows, integrándose con PHP y CodeIgniter 4 para proyectos empresariales modernos.

📦 Repositorio del proyecto

https://github.com/julio101290/boilerplatecompac

🎯 Conclusión

Administrar y validar conexiones SQL Server desde una interfaz centralizada simplifica enormemente el mantenimiento de sistemas empresariales y mejora la confiabilidad de las aplicaciones.

🚀 Implementa tu propio sistema de autorización de comprobantes SAP con CodeIgniter 4 y ODBC (Guía práctica + beneficios)

Entrada fija

📝 Por: julio 101290
Especialista en integraciones SAP – PHP – CodeIgniter

¿Cansado de los atascos en la aprobación de gastos, viáticos y facturas? ¿Los comprobantes se pierden en correos electrónicos o en papeles que nunca llegan? En este artículo te muestro cómo construí un módulo de autorización de comprobantes conectado directamente a SAP HANA usando CodeIgniter 4, ODBC y tecnologías web modernas. Además, te cuento los beneficios reales que obtendrás.


📌 Índice

  1. El problema de siempre: autorización manual
  2. La solución técnica: arquitectura limpia y directa
  3. Beneficios clave (✅ productividad, ✅ seguridad, ✅ movilidad)
  4. Código y componentes principales
  5. Lecciones aprendidas (y errores que evitar)
  6. Mejoras futuras y recomendaciones
  7. Conclusión: ¿por qué deberías implementarlo ya?

1. El problema de siempre: autorización manual 📄❌

Imaginemos el día a día:

  • Un empleado genera un comprobante de gasto en SAP (ej. CV__300000366).
  • El comprobante queda con estado U_Status = 2 (pendiente de autorización).
  • El autorizador recibe un correo, imprime, firma, escanea… o peor, usa un Excel compartido.
  • Contabilidad tarda días en enterarse de la aprobación.
  • No hay trazabilidad: ¿quién aprobó? ¿cuándo? ¿por qué?

🔴 Resultado: retrasos, errores, falta de control y equipos frustrados.


2. La solución técnica: arquitectura limpia y directa 🏗️

Decidí construir una interfaz web que se conecte directamente a las tablas de usuario de SAP (@QSYS_GLO_VOUC y @QSYS_GLO_VODE) usando ODBC. No necesitamos API intermedias ni modificar el núcleo de SAP.

🧱 Stack tecnológico elegido

Componente¿Por qué?
CodeIgniter 4Ligero, rápido, con excelente soporte para ODBC y JSON
ODBCConexión nativa a SAP HANA; sin controladores adicionales complicados
DataTablesTablas dinámicas con búsqueda, ordenamiento y paginación server-side
SweetAlert2Diálogos modernos para confirmar autorizaciones
Bootstrap 4Diseño responsivo (funciona en móvil, tableta y escritorio)
jQuery + AJAXComunicación asíncrona sin recargar la página

🔄 Flujo de trabajo

  1. El autorizador ingresa a la URL /admin/refundsauth.
  2. Se carga una DataTable con todos los comprobantes pendientes (U_Status = 2).
  3. Por cada fila, botones: Autorizar y Detalle.
  4. Al hacer clic en Detalle, se abre un modal responsivo que muestra las líneas del comprobante (tabla VODE).
  5. Al hacer clic en Autorizar, se confirma con SweetAlert y se envía una petición AJAX que actualiza U_Status = 4 directamente en SAP.
  6. La fila desaparece de la tabla y queda registrado en una bitácora local.

Todo en tiempo real, sin papeles, sin correos, sin pérdidas.


3. Beneficios clave (✅ productividad, ✅ seguridad, ✅ movilidad)

🚀 Velocidad y productividad

  • De días a segundos: la autorización se hace con un clic.
  • El autorizador ve solo lo que le corresponde (filtros, búsqueda, orden).
  • Autorización masiva (se puede implementar fácilmente).

🔒 Trazabilidad y auditoría

  • Cada autorización queda registrada en la tabla log con usuario, fecha y detalles.
  • SAP conserva el histórico de U_Status (si se audita, se sabe cuándo cambió a 4).
  • Evita fraudes o aprobaciones sin conocimiento.

📱 Acceso móvil y responsivo

  • El diseño con Bootstrap y DataTables se adapta a cualquier dispositivo.
  • El modal de detalle tiene scroll horizontal (ideal para ver muchas columnas).
  • Los gerentes pueden aprobar gastos desde el móvil mientras viajan.

🧩 Integración sin fricción con SAP

  • No se necesita licencia adicional de Service Layer.
  • Las tablas @... son de usuario → se pueden leer/escribir con ODBC con permisos estándar.
  • La actualización es inmediata y consistente.

🛡️ Seguridad basada en roles

  • Cada usuario del sistema se vincula con un sapuser autorizador (tabla user_sap_link).
  • Solo los usuarios con el permiso adecuado pueden ver y autorizar.

💰 Bajo costo y rápido desarrollo

  • Solución implementada en menos de una semana.
  • Código 100% reutilizable y extensible.
  • No requiere comprar add‑ons caros de terceros.

4. Código y componentes principales (fragmentos clave) 🧩

Aquí te muestro las partes más importantes del controlador RefundsAuthController.php.

📡 Conexión ODBC y consulta de pendientes

$conn = odbc_connect($dataConect['nameODBC'], $dataConect['userODBC'], $dataConect['passwordODBC']);
$tableName = '"' . $dataConect['companyDB'] . '"."@QSYS_GLO_VOUC"';
$sql = "SELECT * FROM $tableName WHERE \"U_Status\" = 2 ORDER BY \"U_Date\" DESC";
$result = odbc_exec($conn, $sql);

✏️ Autorización (update directo)

$sqlUpdate = "UPDATE $tableName SET \"U_Status\" = 4 WHERE \"Code\" = $code";
odbc_exec($conn, $sqlUpdate);

📋 Detalle responsivo (modal con scroll)

$.ajax({
    url: base_url + '/admin/servicelayer/refundsauth/showVoucherDetails',
    data: JSON.stringify({ code: code }),
    success: function(resp) {
        var html = '<div class="table-responsive">' +
                   '<table class="table table-sm table-bordered">' +
                   // ... cabeceras y filas ...
                   '</table></div>';
        $('#modalVoucherDetailsBody').html(html);
    }
});

💡 El código completo lo puedes encontrar en mi repositorio de GitHub (link al final).


5. Lecciones aprendidas (y errores que evitar) 🧠

❌ Error 1: Nombres de tablas con @ sin comillas dobles

Solución: Siempre usar "DBNAME"."@TABLA". HANA es sensible a mayúsculas/minúsculas.

❌ Error 2: Usar LIMIT en ODBC sin ROW_NUMBER()

Solución: Emplear la función analítica ROW_NUMBER() OVER (ORDER BY ...) y filtrar por rn.

❌ Error 3: El modal se quedaba en “Cargando…”

Causa: Los IDs del contenedor en el modal no coincidían con los del JavaScript.
Solución: Sincronizar id="modalVoucherDetailsBody" en el HTML y en el JS.

❌ Error 4: La tabla no hacía scroll en móvil

Solución: Envolver la tabla en <div class="table-responsive"> y dar a la tabla un min-width: 700px mediante CSS.

❌ Error 5: Caracteres extraños en JSON

Solución: Aplicar utf8ize() recursivo a cada fila antes de enviarla.


6. Mejoras futuras y recomendaciones 🧪

Si quieres llevar el sistema al siguiente nivel, considera:

  • Autorización por lotes (seleccionar varios y aprobar con un botón).
  • Notificaciones por correo o WhatsApp cuando llegue un nuevo comprobante.
  • Rechazo con motivo y notificación al empleado.
  • Dashboard de indicadores (tiempo promedio de autorización, montos aprobados, etc.).
  • Firma digital para cumplir normativas.
  • Integración con SAP Business Workflow (si la empresa lo requiere).

7. Conclusión: ¿por qué deberías implementarlo ya? 🎯

La combinación CodeIgniter 4 + ODBC + DataTables permite construir en pocos días un sistema de autorización de comprobantes que:

  • Reduce drásticamente los tiempos de espera.
  • Elimina el papeleo y los correos perdidos.
  • Brinda movilidad a los aprobadores.
  • Aporta transparencia y auditoría.
  • Se integra de forma nativa con SAP sin costes adicionales.

¿Tu empresa sigue aprobando gastos con firmas manuales y hojas de cálculo?
Es hora de dar el salto a la automatización web. Yo ya lo hice para varios clientes, y los resultados han sido increíbles: +80% de agilidad, 0 errores de registro y aprobaciones desde el móvil.


🔗 Recursos adicionales


📢 Comparte este artículo si te ha resultado útil
♻️ ¿Conoces a alguien que todavía autoriza comprobantes en papel? Etiquétalo en los comentarios.

#SAP #CodeIgniter #ODBC #AutorizaciónDeGastos #PHP #DesarrolloWeb #TransformaciónDigital

Cómo usar Composer con symlinks para desarrollo local en CodeIgniter 4

Entrada fija

Cuando trabajas con múltiples paquetes en PHP (como módulos propios), llega un punto donde necesitas debuggear directamente el código del paquete y no una copia dentro de vendor.

Si alguna vez terminaste debuggeando en vendor/ en lugar de tu proyecto real… esto es para ti.


🔥 El problema

Por defecto, Composer:

  • Descarga paquetes desde GitHub
  • Los copia en vendor/
  • Xdebug trabaja sobre esa copia

Resultado:

  • Código duplicado
  • Cambios no se reflejan
  • Debug incómodo

🧠 La solución: usar type: path

{
  "repositories": [
    {
      "type": "path",
      "url": "../boilerplateproducts",
      "options": {
        "symlink": true
      }
    }
  ]
}

⚙️ ¿Qué hace esto?

  • Usa tu carpeta local
  • No descarga desde GitHub
  • Crea un symlink en vendor

🔍 Verificar que funciona

composer update

Debes ver:

Installing julio101290/boilerplateproducts: Symlinking from ../boilerplateproducts

Y luego:

ls -l vendor/julio101290/boilerplateproducts

Resultado esperado:

boilerplateproducts -> ../../../boilerplateproducts

🚀 Beneficios

  • Debug directo en tu código
  • Cambios en tiempo real
  • Sin duplicación
  • Flujo más rápido

⚠️ Error común

No mezcles path y vcs:

{
  "type": "path",
  "url": "../boilerplateproducts"
},
{
  "type": "vcs",
  "url": "https://github.com/usuario/boilerplateproducts"
}

Composer puede ignorar el local y usar GitHub.


✅ Desarrollo correcto

rm -rf vendor composer.lock
composer update

🟢 Desarrollo vs Producción

Desarrollo

  • path
  • symlink
  • debug directo

Producción

  • vcs
  • descarga desde GitHub
  • entorno limpio

⚡ Scripts útiles

dev.sh

#!/bin/bash
rm -rf vendor composer.lock
COMPOSER=composer.local.json composer update

prod.sh

#!/bin/bash
rm -rf vendor composer.lock
composer update

🧠 Tip PRO

{
  "repositories": [
    {
      "type": "path",
      "url": "../boilerplateproducts",
      "options": { "symlink": true }
    }
  ],
  "config": {
    "preferred-install": "source"
  }
}

Ejecutar:

COMPOSER=composer.local.json composer update

💡 IDE

Puedes integrar esto con NetBeans usando scripts o herramientas externas.


✅ Conclusión

  • No debuggees código en vendor
  • Usa symlink
  • Automatiza tu flujo

Resultado: desarrollo más rápido y limpio 🚀

🔧 Arquitectura híbrida con Composer + Git: desarrollo profesional de módulos en CodeIgniter 4

Entrada fija

En el desarrollo moderno de aplicaciones PHP con CodeIgniter 4, uno de los mayores retos es mantener un flujo de trabajo eficiente entre desarrollo local, control de versiones y gestión de dependencias.

Este artículo explica una arquitectura híbrida usando Composer + Git + symlinks que permite trabajar módulos de forma profesional sin depender del directorio vendor.


🧠 El problema clásico

Cuando trabajamos con módulos reutilizables (inventarios, CFDI, pagos, etc.), normalmente terminamos instalando todo en vendor/, lo que genera problemas:

  • No se puede editar código fácilmente
  • Debug complicado
  • Cambios se pierden con composer update
  • Duplicación de código
  • Dificultad para mantener múltiples proyectos

💡 La solución: arquitectura híbrida

La solución moderna combina tres herramientas:

  • Composer como gestor de dependencias
  • Git como control de versiones
  • Symlinks para desarrollo en vivo

Estructura típica:

~/fuentes/
   ci4-project/
   boilerplateInventory/
   boilerplateProducts/

⚙️ Configuración en Composer

En el composer.json del proyecto principal:

"repositories": [
  {
    "type": "path",
    "url": "../boilerplateInventory",
    "options": {
      "symlink": true
    }
  },
  {
    "type": "vcs",
    "url": "https://github.com/julio101290/boilerplateInventory"
  }
]

📁 Path repository (desarrollo local)

Este tipo de repositorio indica a Composer que use una carpeta local en lugar de descargar el paquete.

Beneficios:

  • Cambios en tiempo real
  • Debug directo
  • No depende de vendor

🌐 VCS repository (GitHub)

Sirve como respaldo cuando el path no existe o en servidores de producción.


🔥 El problema de versiones

Muchos módulos requieren:

^1.0.0

Pero en desarrollo usamos:

dev-main

Esto genera conflictos.


🛠️ Solución: alias de versión

La solución correcta es:

"julio101290/boilerplateinventory": "dev-main as 1.2.6"

Esto le dice a Composer que trate el código local como versión estable.


⚡ Resultado del sistema

  • ✔ Desarrollo en vivo
  • ✔ Debug funcional
  • ✔ Compatibilidad con dependencias
  • ✔ Sin tocar vendor
  • ✔ Respaldo en GitHub

🧪 Flujo de trabajo real

cd ~/fuentes/boilerplateInventory
git add .
git commit -m "cambios"
git push

Los cambios se reflejan automáticamente en el proyecto principal.


⚠️ Buenas prácticas

  • Usar nombres consistentes en minúsculas
  • No trabajar dentro de vendor
  • Versionar módulos con tags
git tag v1.2.6
git push origin v1.2.6

🚀 Conclusión

Esta arquitectura permite construir sistemas modulares profesionales con:

  • Desarrollo rápido
  • Debug en vivo
  • Control de versiones limpio
  • Escalabilidad real

Es una forma moderna de trabajar con Composer sin depender de vendor como entorno de desarrollo.

🔥 Cómo configurar ONLYOFFICE en Docker con SSL y Nextcloud Snap (guía práctica sin errores) 🔥

Entrada fija

Después de varios intentos y errores típicos (puertos, certificados, Docker, etc.), finalmente logré dejar funcionando ONLYOFFICE Document Server en Docker con acceso HTTPS y listo para integrarse con Nextcloud instalado vía Snap. Aquí te dejo el proceso completo con datos genéricos 👇


🚧 Problemas comunes

  • Error de conexión (connection refused o connection reset)
  • Certificados SSL no detectados
  • Confusión entre Apache, Nginx y Docker
  • Intentar usar HTTPS dentro del contenedor (mala idea 😅)

🧠 Lo importante que debes entender

  • ONLYOFFICE usa Nginx interno, no Apache
  • Docker debe correr en HTTP interno
  • El SSL se maneja mejor fuera del contenedor
  • Nextcloud Snap ya trae su propio Apache (aislado)

👉 La solución correcta: usar Apache HTTP Server del sistema como proxy inverso


⚙️ Configuración final

🐳 1. Ejecutar ONLYOFFICE en HTTP

docker run -d -p 8080:80 onlyoffice/documentserver

🔐 2. Generar certificado SSL con Certbot

sudo snap stop nextcloud
sudo certbot certonly --standalone -d tudominio.com
sudo snap start nextcloud

👉 Los certificados quedarán en algo como:

/etc/letsencrypt/live/tudominio.com-0001/

🌐 3. Configurar Apache como proxy SSL

Archivo:

/etc/apache2/sites-available/onlyoffice.conf

Contenido:

<VirtualHost *:4443>
    ServerName tudominio.com

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/tudominio.com-0001/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/tudominio.com-0001/privkey.pem

    ProxyPreserveHost On
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/

    RequestHeader set X-Forwarded-Proto "https"
</VirtualHost>

🔌 4. Habilitar el puerto en Apache

Editar:

/etc/apache2/ports.conf

Agregar:

Listen 4443

⚡ 5. Activar módulos necesarios

sudo a2enmod ssl proxy proxy_http headers

🚀 6. Activar sitio y reiniciar

sudo a2ensite onlyoffice.conf
sudo apachectl configtest
sudo systemctl restart apache2

🔥 7. Abrir firewall

sudo ufw allow 4443

✅ Resultado

Acceso funcionando en:

👉 https://tudominio.com:4443

✔ SSL válido
✔ Proxy funcionando
✔ Docker respondiendo correctamente
✔ Listo para integrarse con Nextcloud


🧪 Pruebas clave

curl http://localhost:8080/healthcheck   # → true
curl -k https://localhost:4443           # → 302 (correcto)

🧠 Conclusión

  • ❌ No uses SSL dentro de Docker
  • ✅ Usa proxy inverso en el host
  • ✅ Mantén servicios separados
  • ✅ Evita conflictos con Snap

🚀 Siguiente paso

Integrar ONLYOFFICE con Nextcloud usando JWT correctamente 🔐


Si estás montando tu propio entorno, esta arquitectura te va a ahorrar horas de debugging 💻🔥

Apóyame

Si esta guía te fue útil y quieres apoyar más contenido como este:

👉 https://www.patreon.com/u74078772?

Cómo instalar OnlyOffice en Nextcloud (instalado con Snap) – Guía completa

Entrada fija

Si ya tienes Nextcloud instalado con Snap, el siguiente paso lógico es convertirlo en una suite completa tipo Google Workspace. Para eso, puedes integrar OnlyOffice, una poderosa herramienta que permite editar documentos Word, Excel y PowerPoint directamente desde tu nube.


¿Qué es OnlyOffice?

OnlyOffice es una suite ofimática online que se integra perfectamente con Nextcloud, permitiendo editar documentos en tiempo real, colaborar con otros usuarios y mantener compatibilidad con formatos de Microsoft Office.

  • 📝 Editor de documentos (Word)
  • 📊 Hojas de cálculo (Excel)
  • 📽️ Presentaciones (PowerPoint)
  • 👥 Edición colaborativa en tiempo real

Requisitos

  • Nextcloud instalado vía Snap
  • Servidor Ubuntu (recomendado 2GB RAM mínimo)
  • Acceso SSH con sudo

Instalar OnlyOffice Document Server con Docker

1. Instalar Docker


sudo apt update
sudo apt install docker.io -y
sudo systemctl enable --now docker
  

2. Ejecutar OnlyOffice


sudo docker run -i -t -d -p 8080:80 \
--restart=always \
onlyoffice/documentserver
  

Esto levantará OnlyOffice en:

http://TU_IP:8080

Instalar la aplicación en Nextcloud

  1. Entra a tu panel de Nextcloud
  2. Ve a Apps (Aplicaciones)
  3. Busca: ONLYOFFICE
  4. Instala ONLYOFFICE Docs

Configurar conexión con OnlyOffice

Ve a:

Configuración → Administración → ONLYOFFICE

En “Document Editing Service”, coloca:

http://TU_IP:8080

Configurar seguridad con JWT (recomendado)

Para mayor seguridad, puedes activar autenticación JWT.


sudo docker stop $(sudo docker ps -q)

sudo docker run -i -t -d -p 8080:80 \
-e JWT_ENABLED=true \
-e JWT_SECRET=mi_clave_super_segura \
--restart=always \
onlyoffice/documentserver
  

Luego en Nextcloud:

  • Activa JWT
  • Introduce la misma clave

Solución de problemas (Nextcloud Snap)

Si Nextcloud no se conecta correctamente:


sudo snap set nextcloud ports.http=80
sudo snap set nextcloud ports.https=443
sudo ufw allow 8080
  

Recomendaciones

  • 🔒 Usa HTTPS (Let’s Encrypt)
  • ⚡ Usa mínimo 2GB de RAM
  • 🌐 Configura dominio si lo usarás fuera de red local
  • 📦 Usa Docker para mayor estabilidad

Conclusión

Integrar OnlyOffice con Nextcloud transforma tu servidor en una plataforma completa de productividad, similar a Google Drive o Microsoft 365, pero con control total sobre tus datos.


Apóyame

Si esta guía te fue útil y quieres apoyar más contenido como este:

👉 https://www.patreon.com/u74078772?

Cómo instalar Nextcloud en Ubuntu Server 24.04 (Guía completa + Script automático)

Entrada fija

Si quieres tener tu propia nube privada tipo Google Drive o Dropbox, Nextcloud es una de las mejores opciones. En esta guía te mostraré cómo instalarlo en Ubuntu Server 24.04 de forma sencilla, incluyendo un script automático que hace casi todo por ti.


¿Qué es Nextcloud?

Nextcloud es una plataforma de almacenamiento en la nube de código abierto que te permite guardar archivos, sincronizarlos entre dispositivos, compartirlos y mucho más, todo en tu propio servidor.

  • 📁 Almacenamiento privado
  • 🔄 Sincronización de archivos
  • 🔐 Control total de tus datos
  • 👥 Compartir archivos fácilmente

Requisitos

  • Servidor con Ubuntu Server 24.04
  • Acceso SSH con permisos sudo
  • Conexión a internet

Instalación automática (Script)

Para facilitar todo el proceso, puedes usar el siguiente script que instala Apache, PHP, MariaDB y Nextcloud automáticamente.

#!/bin/bash
# ==========================================
# Instalador automático de Nextcloud
# Ubuntu Server 24.04 (con soporte para PPA sury.org)
# Versión: 4.0 - Definitiva (resuelve conflictos de PHP)
# Adaptado por julio101290
# ==========================================

set -e

# -------------------- CONFIGURACIÓN --------------------
DEFAULT_PORT="${NEXTCLOUD_PORT:-80}"
NC_DIR="${NEXTCLOUD_DIR:-/var/www/nextcloud}"
DB_NAME="${NEXTCLOUD_DB_NAME:-nextcloud}"
DB_USER="${NEXTCLOUD_DB_USER:-ncuser}"

# Generar contraseña segura si no se proporciona
if [ -z "$NEXTCLOUD_DB_PASS" ]; then
    DB_PASS=$(openssl rand -base64 24 | tr -d "=+/" | cut -c1-24)
else
    DB_PASS="$NEXTCLOUD_DB_PASS"
fi

NONINTERACTIVE="${NONINTERACTIVE:-false}"

# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# -------------------- FUNCIONES AUXILIARES --------------------
error_exit() {
    echo -e "${RED}ERROR: $1${NC}" >&2
    exit 1
}

info() {
    echo -e "${GREEN}➡️ $1${NC}"
}

warn() {
    echo -e "${YELLOW}⚠️ $1${NC}"
}

confirm() {
    if [ "$NONINTERACTIVE" = "true" ]; then
        return 0
    fi
    read -p "$1 [S/n]: " -n 1 -r
    echo
    [[ $REPLY =~ ^[Ss]$ ]] || [[ -z $REPLY ]]
}

command_exists() {
    command -v "$1" >/dev/null 2>&1
}

# -------------------- VERIFICACIONES INICIALES --------------------
if [ "$EUID" -ne 0 ]; then
    error_exit "Ejecuta con sudo o como root."
fi

# -------------------- ACTUALIZACIÓN Y REPARACIÓN --------------------
info "Actualizando lista de paquetes..."
apt update -qq

info "Reparando paquetes rotos si los hay..."
apt --fix-broken install -y

# -------------------- APACHE Y PUERTO --------------------
info "Configurando Apache..."
if command_exists apache2; then
    warn "Apache ya está instalado."
    # Detectar puerto activo
    if systemctl is-active --quiet apache2; then
        CURRENT_PORT=$(ss -tlnp 2>/dev/null | grep apache2 | grep -oP ':\K\d+' | head -1)
    fi
    [ -z "$CURRENT_PORT" ] && CURRENT_PORT=$(grep -i "^Listen" /etc/apache2/ports.conf 2>/dev/null | head -1 | awk '{print $2}')
    [ -z "$CURRENT_PORT" ] && CURRENT_PORT=80
    
    if [ "$CURRENT_PORT" != "$DEFAULT_PORT" ] && [ "$DEFAULT_PORT" != "80" ]; then
        warn "Apache ya escucha en el puerto $CURRENT_PORT. Se usará ese."
        PORT=$CURRENT_PORT
    else
        PORT=$DEFAULT_PORT
    fi
else
    info "Instalando Apache..."
    apt install -y apache2
    PORT=$DEFAULT_PORT
fi

# Configurar puerto si es necesario
if [ "$PORT" != "80" ]; then
    info "Ajustando Apache al puerto $PORT..."
    sed -i "s/^Listen 80/Listen $PORT/g" /etc/apache2/ports.conf
    grep -q "^Listen $PORT" /etc/apache2/ports.conf || echo "Listen $PORT" >> /etc/apache2/ports.conf
    systemctl restart apache2
fi

# -------------------- PHP Y EXTENSIONES (RESOLUCIÓN DEFINITIVA) --------------------
info "Verificando PHP..."

PHP_INSTALLED=false
USE_SURY=false

if command_exists php; then
    PHP_INSTALLED=true
    PHP_VER=$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;' 2>/dev/null)
    warn "PHP $PHP_VER detectado."
    
    # Determinar origen de PHP
    if apt-cache policy php | grep -q "sury.org"; then
        USE_SURY=true
        warn "PHP instalado desde el repositorio 'deb.sury.org'."
    fi
else
    info "PHP no está instalado. Se instalará desde los repositorios oficiales de Ubuntu."
fi

# Si PHP está presente pero no todas las extensiones, las instalamos según el origen
if [ "$PHP_INSTALLED" = true ]; then
    # Extensiones necesarias
    NEEDED_EXTS="bcmath gmp imagick mysql zip gd mbstring curl xml intl"
    MISSING_EXTS=""
    
    # Verificar qué extensiones faltan
    for ext in $NEEDED_EXTS; do
        case $ext in
            mysql)
                if ! php -m | grep -qiE "mysqli|pdo_mysql"; then
                    MISSING_EXTS="$MISSING_EXTS mysql"
                fi
                ;;
            *)
                if ! php -m | grep -qi "$ext"; then
                    MISSING_EXTS="$MISSING_EXTS $ext"
                fi
                ;;
        esac
    done
    
    if [ -n "$MISSING_EXTS" ]; then
        info "Faltan extensiones: $MISSING_EXTS"
        
        # Solución específica para el conflicto de php-imagick
        if echo "$MISSING_EXTS" | grep -q "imagick"; then
            warn "Se detectó conflicto con php-imagick. Eliminando versión incorrecta..."
            apt remove -y php-imagick 2>/dev/null || true
        fi
        
        # Instalar según el origen
        if [ "$USE_SURY" = true ]; then
            info "Instalando extensiones desde el PPA sury.org..."
            # Construir lista de paquetes específicos para sury
            SURY_PKGS=""
            for ext in $MISSING_EXTS; do
                case $ext in
                    mysql)    SURY_PKGS="$SURY_PKGS php${PHP_VER}-mysql" ;;
                    imagick)  SURY_PKGS="$SURY_PKGS php${PHP_VER}-imagick" ;;
                    bcmath)   SURY_PKGS="$SURY_PKGS php${PHP_VER}-bcmath" ;;
                    gmp)      SURY_PKGS="$SURY_PKGS php${PHP_VER}-gmp" ;;
                    zip)      SURY_PKGS="$SURY_PKGS php${PHP_VER}-zip" ;;
                    gd)       SURY_PKGS="$SURY_PKGS php${PHP_VER}-gd" ;;
                    mbstring) SURY_PKGS="$SURY_PKGS php${PHP_VER}-mbstring" ;;
                    curl)     SURY_PKGS="$SURY_PKGS php${PHP_VER}-curl" ;;
                    xml)      SURY_PKGS="$SURY_PKGS php${PHP_VER}-xml" ;;
                    intl)     SURY_PKGS="$SURY_PKGS php${PHP_VER}-intl" ;;
                esac
            done
            apt install -y --allow-downgrades $SURY_PKGS
        else
            # Repositorios oficiales de Ubuntu
            info "Instalando extensiones desde repositorios oficiales..."
            OFFICIAL_PKGS=""
            for ext in $MISSING_EXTS; do
                case $ext in
                    mysql)    OFFICIAL_PKGS="$OFFICIAL_PKGS php-mysql" ;;
                    imagick)  OFFICIAL_PKGS="$OFFICIAL_PKGS php-imagick" ;;
                    bcmath)   OFFICIAL_PKGS="$OFFICIAL_PKGS php-bcmath" ;;
                    gmp)      OFFICIAL_PKGS="$OFFICIAL_PKGS php-gmp" ;;
                    zip)      OFFICIAL_PKGS="$OFFICIAL_PKGS php-zip" ;;
                    gd)       OFFICIAL_PKGS="$OFFICIAL_PKGS php-gd" ;;
                    mbstring) OFFICIAL_PKGS="$OFFICIAL_PKGS php-mbstring" ;;
                    curl)     OFFICIAL_PKGS="$OFFICIAL_PKGS php-curl" ;;
                    xml)      OFFICIAL_PKGS="$OFFICIAL_PKGS php-xml" ;;
                    intl)     OFFICIAL_PKGS="$OFFICIAL_PKGS php-intl" ;;
                esac
            done
            apt install -y $OFFICIAL_PKGS
        fi
    else
        info "Todas las extensiones necesarias ya están presentes."
    fi
else
    # Instalación limpia de PHP (sin PPA previo)
    info "Instalando PHP 8.3 y extensiones desde repositorios oficiales..."
    apt install -y php8.3 php8.3-cli php8.3-common php8.3-mysql php8.3-zip \
        php8.3-gd php8.3-mbstring php8.3-curl php8.3-xml php8.3-bcmath \
        php8.3-intl php8.3-imagick php8.3-gmp libapache2-mod-php8.3
    PHP_VER="8.3"
fi

# Habilitar módulos de Apache
a2enmod rewrite headers env dir mime 2>/dev/null || true
systemctl restart apache2

# -------------------- BASE DE DATOS --------------------
info "Configurando base de datos..."
if command_exists mysql; then
    warn "MySQL/MariaDB ya está instalado."
    systemctl start mariadb 2>/dev/null || systemctl start mysql 2>/dev/null || true
else
    info "Instalando MariaDB..."
    apt install -y mariadb-server
    systemctl start mariadb
fi

# Crear base de datos y usuario (sin romper existentes)
create_database() {
    local db="$1" user="$2" pass="$3"
    
    if mysql -u root -e "exit" 2>/dev/null; then
        MYSQL_CMD="mysql -u root"
    else
        echo -ne "${YELLOW}Contraseña de root de MySQL:${NC} "
        read -s root_pass
        echo
        MYSQL_CMD="mysql -u root -p$root_pass"
    fi
    
    if $MYSQL_CMD -e "USE $db" 2>/dev/null; then
        warn "Base de datos '$db' ya existe. Se usará la existente."
    else
        info "Creando base de datos '$db'..."
        $MYSQL_CMD -e "CREATE DATABASE $db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
    fi
    
    if $MYSQL_CMD -e "SELECT 1 FROM mysql.user WHERE User='$user' AND Host='localhost'" | grep -q 1; then
        warn "Usuario '$user' ya existe. Actualizando contraseña..."
        $MYSQL_CMD -e "ALTER USER '$user'@'localhost' IDENTIFIED BY '$pass';"
    else
        info "Creando usuario '$user'..."
        $MYSQL_CMD -e "CREATE USER '$user'@'localhost' IDENTIFIED BY '$pass';"
    fi
    
    $MYSQL_CMD -e "GRANT ALL PRIVILEGES ON $db.* TO '$user'@'localhost';"
    $MYSQL_CMD -e "FLUSH PRIVILEGES;"
}

create_database "$DB_NAME" "$DB_USER" "$DB_PASS"

# -------------------- DESCARGA E INSTALACIÓN DE NEXTCLOUD --------------------
info "Preparando Nextcloud en $NC_DIR..."
mkdir -p "$(dirname "$NC_DIR")"
cd "$(dirname "$NC_DIR")"

if [ -d "$NC_DIR" ] && [ -f "$NC_DIR/config/config.php" ]; then
    warn "Parece que Nextcloud ya está instalado."
    if confirm "¿Deseas reinstalar (se perderán los datos)?"; then
        info "Eliminando instalación anterior..."
        rm -rf "$NC_DIR"
    else
        info "Manteniendo instalación existente. Saliendo."
        exit 0
    fi
fi

# Descargar Nextcloud
if [ ! -f latest.zip ]; then
    info "Descargando Nextcloud..."
    wget -q --show-progress https://download.nextcloud.com/server/releases/latest.zip
else
    warn "El archivo latest.zip ya existe. Usándolo."
fi

info "Instalando unzip..."
apt install -y unzip

info "Descomprimiendo..."
unzip -q -o latest.zip
rm -f latest.zip

# Si se movió el directorio de instalación
if [ "$NC_DIR" != "/var/www/nextcloud" ] && [ -d "/var/www/nextcloud" ]; then
    mv /var/www/nextcloud "$NC_DIR"
fi

# Configurar permisos
chown -R www-data:www-data "$NC_DIR"
chmod -R 755 "$NC_DIR"

# -------------------- CONFIGURACIÓN DE APACHE --------------------
info "Configurando VirtualHost de Apache..."
SERVER_IP=$(hostname -I | awk '{print $1}')

cat > /etc/apache2/sites-available/nextcloud.conf <<EOF
<VirtualHost *:$PORT>
    ServerName $SERVER_IP
    DocumentRoot $NC_DIR

    <Directory $NC_DIR>
        Require all granted
        AllowOverride All
        Options FollowSymLinks MultiViews
    </Directory>

    ErrorLog \${APACHE_LOG_DIR}/nextcloud_error.log
    CustomLog \${APACHE_LOG_DIR}/nextcloud_access.log combined
</VirtualHost>
EOF

# Deshabilitar sitio por defecto y habilitar Nextcloud
a2dissite 000-default.conf 2>/dev/null || true
a2ensite nextcloud.conf
systemctl reload apache2

# -------------------- RESUMEN FINAL --------------------
echo ""
echo "========================================="
echo -e "${GREEN}✅ INSTALACIÓN COMPLETA${NC}"
echo "========================================="
echo -e "${BLUE}🌐 Accede a Nextcloud en tu navegador:${NC}"
echo -e "   http://$SERVER_IP:$PORT"
echo ""
echo -e "${BLUE}📌 Datos de la base de datos:${NC}"
echo "   Nombre:     $DB_NAME"
echo "   Usuario:    $DB_USER"
echo "   Contraseña: $DB_PASS"
echo "   Host:       localhost"
echo ""
echo -e "${YELLOW}⚠️  IMPORTANTE: Completa la instalación en el navegador${NC}"
echo "   Selecciona 'MySQL/MariaDB' e introduce los datos de arriba."
echo "========================================="

# Guardar credenciales opcionalmente
if [ "$NONINTERACTIVE" != "true" ]; then
    if confirm "¿Guardar las credenciales en /root/nextcloud_credentials.txt?"; then
        cat > /root/nextcloud_credentials.txt <<EOF
# Credenciales de Nextcloud - $(date)
IP: $SERVER_IP
Puerto: $PORT
Base de datos: $DB_NAME
Usuario DB: $DB_USER
Contraseña DB: $DB_PASS
EOF
        chmod 600 /root/nextcloud_credentials.txt
        info "Credenciales guardadas en /root/nextcloud_credentials.txt"
    fi
fi

echo -e "${GREEN}¡Disfruta de Nextcloud! 🚀${NC}"

Cómo ejecutar el script

  1. Crear el archivo: nano install_nextcloud.sh
  2. Pegar el script y guardar
  3. Dar permisos: chmod +x install_nextcloud.sh
  4. Ejecutar: ./install_nextcloud.sh

Acceso a Nextcloud

Una vez terminado, abre tu navegador y entra a:

http://TU_IP

Desde ahí podrás crear el usuario administrador y conectar la base de datos.


Recomendaciones de seguridad

  • 🔐 Cambiar la contraseña de la base de datos
  • 🔒 Activar HTTPS con Let’s Encrypt
  • 🧱 Configurar firewall (UFW)
  • 💾 Hacer backups regularmente

Desinstalación

#!/bin/bash
# ==========================================
# Desinstalador automático de Nextcloud
# Ubuntu Server 24.04
# Revierte todos los cambios del instalador
# ==========================================

set -e

# -------------------- CONFIGURACIÓN --------------------
NC_DIR="${NEXTCLOUD_DIR:-/var/www/nextcloud}"
DB_NAME="${NEXTCLOUD_DB_NAME:-nextcloud}"
DB_USER="${NEXTCLOUD_DB_USER:-ncuser}"
APACHE_SITE="nextcloud.conf"

# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# -------------------- FUNCIONES --------------------
error_exit() {
    echo -e "${RED}ERROR: $1${NC}" >&2
    exit 1
}

info() {
    echo -e "${GREEN}➡️ $1${NC}"
}

warn() {
    echo -e "${YELLOW}⚠️ $1${NC}"
}

confirm() {
    read -p "$1 [s/N]: " -n 1 -r
    echo
    [[ $REPLY =~ ^[Ss]$ ]]
}

command_exists() {
    command -v "$1" >/dev/null 2>&1
}

# -------------------- VERIFICACIÓN DE EJECUCIÓN --------------------
if [ "$EUID" -ne 0 ]; then
    error_exit "Ejecuta con sudo o como root."
fi

echo ""
echo -e "${RED}=========================================${NC}"
echo -e "${RED}   DESINSTALADOR DE NEXTCLOUD${NC}"
echo -e "${RED}=========================================${NC}"
warn "Este script eliminará Nextcloud y sus configuraciones."
warn "Los datos de la base de datos y archivos se perderán permanentemente."
echo ""

if ! confirm "¿Estás seguro de que deseas continuar?"; then
    info "Desinstalación cancelada."
    exit 0
fi

# -------------------- 1. DETENER SERVICIOS --------------------
info "Deteniendo servicios relacionados..."
systemctl stop apache2 2>/dev/null || true
systemctl stop mariadb 2>/dev/null || true
systemctl stop mysql 2>/dev/null || true

# -------------------- 2. ELIMINAR ARCHIVOS DE NEXTCLOUD --------------------
if [ -d "$NC_DIR" ]; then
    info "Eliminando directorio de Nextcloud en $NC_DIR..."
    rm -rf "$NC_DIR"
else
    warn "El directorio $NC_DIR no existe."
fi

# Buscar y eliminar cualquier otro directorio de Nextcloud en /var/www
if [ -d "/var/www/nextcloud" ] && [ "/var/www/nextcloud" != "$NC_DIR" ]; then
    info "Eliminando /var/www/nextcloud..."
    rm -rf "/var/www/nextcloud"
fi

# -------------------- 3. ELIMINAR CONFIGURACIÓN DE APACHE --------------------
info "Eliminando configuración de Apache para Nextcloud..."
a2dissite "$APACHE_SITE" 2>/dev/null || true
rm -f "/etc/apache2/sites-available/$APACHE_SITE"
rm -f "/etc/apache2/sites-enabled/$APACHE_SITE"

# Opcionalmente restaurar el sitio por defecto si existe
if [ -f "/etc/apache2/sites-available/000-default.conf" ]; then
    a2ensite 000-default.conf 2>/dev/null || true
fi

# Revertir cambios en el puerto si solo se usó para Nextcloud (opcional avanzado)
# Nota: no revertimos automáticamente porque podría haber otros sitios usando ese puerto.

systemctl reload apache2 2>/dev/null || true

# -------------------- 4. ELIMINAR BASE DE DATOS Y USUARIO (OPCIONAL) --------------------
echo ""
if confirm "¿Eliminar la base de datos '$DB_NAME' y el usuario '$DB_USER'?"; then
    if command_exists mysql; then
        info "Eliminando base de datos y usuario..."
        # Determinar comando MySQL con o sin contraseña
        if mysql -u root -e "exit" 2>/dev/null; then
            MYSQL_CMD="mysql -u root"
        else
            echo -ne "${YELLOW}Ingresa la contraseña de root de MySQL:${NC} "
            read -s root_pass
            echo
            MYSQL_CMD="mysql -u root -p$root_pass"
        fi
        
        $MYSQL_CMD -e "DROP DATABASE IF EXISTS $DB_NAME;" 2>/dev/null && info "Base de datos $DB_NAME eliminada."
        $MYSQL_CMD -e "DROP USER IF EXISTS '$DB_USER'@'localhost';" 2>/dev/null && info "Usuario $DB_USER eliminado."
        $MYSQL_CMD -e "FLUSH PRIVILEGES;" 2>/dev/null
    else
        warn "MySQL/MariaDB no está instalado. No se pudo eliminar la base de datos."
    fi
else
    info "Base de datos conservada."
fi

# -------------------- 5. ELIMINAR PAQUETES (OPCIONAL) --------------------
echo ""
if confirm "¿Eliminar paquetes de Apache, PHP y MariaDB instalados por el script?"; then
    info "Eliminando paquetes..."
    
    # Lista de paquetes comunes del instalador
    PACKAGES_TO_REMOVE="apache2 mariadb-server mariadb-client"
    PHP_PACKAGES="php8.3 php8.3-cli php8.3-common php8.3-mysql php8.3-zip php8.3-gd php8.3-mbstring php8.3-curl php8.3-xml php8.3-bcmath php8.3-intl php8.3-imagick php8.3-gmp libapache2-mod-php8.3"
    
    # Añadir también los nombres genéricos por si acaso
    PHP_GENERIC="php php-cli php-common php-mysql php-zip php-gd php-mbstring php-curl php-xml php-bcmath php-intl php-imagick php-gmp libapache2-mod-php"
    
    # Preguntar si se quiere purgar (eliminar también configuraciones)
    PURGE=""
    if confirm "¿Eliminar también los archivos de configuración (purgar)?"; then
        PURGE="--purge"
    fi
    
    apt remove $PURGE -y $PACKAGES_TO_REMOVE $PHP_PACKAGES $PHP_GENERIC 2>/dev/null || true
    apt autoremove -y
    
    info "Paquetes eliminados."
else
    info "Paquetes conservados."
fi

# -------------------- 6. LIMPIEZA ADICIONAL --------------------
info "Limpiando archivos residuales..."
rm -f /var/www/latest.zip 2>/dev/null || true
rm -f /root/nextcloud_credentials.txt 2>/dev/null || true

# -------------------- 7. RESUMEN FINAL --------------------
echo ""
echo "========================================="
echo -e "${GREEN}✅ DESINSTALACIÓN COMPLETA${NC}"
echo "========================================="
echo -e "${YELLOW}Se han eliminado:${NC}"
echo "  - Nextcloud (directorio $NC_DIR)"
echo "  - Configuración de Apache (sitio nextcloud)"
if confirm "¿Eliminar base de datos?" 2>/dev/null; then
    echo "  - Base de datos $DB_NAME y usuario $DB_USER"
fi
if confirm "¿Eliminar paquetes?" 2>/dev/null; then
    echo "  - Paquetes de Apache, PHP y MariaDB (si se seleccionó)"
fi
echo ""
echo -e "${BLUE}Nota: Los datos personales de Nextcloud no son recuperables.${NC}"
echo "========================================="

Conclusión

Con este método puedes tener tu propia nube privada funcionando en pocos minutos. Es ideal para uso personal o incluso para pequeñas empresas que quieran tener control total sobre sus datos.


Apóyame

Si esta guía te fue útil y quieres apoyar más contenido como este, puedes hacerlo aquí:

👉 https://www.patreon.com/u74078772?

Página 1 de 11

Creado con WordPress & Tema de Anders Norén