Fix: Solucionados problemas de permisos en Docker y agregada gestion de Webhook de Telegram
This commit is contained in:
45
.env.docker
Normal file
45
.env.docker
Normal file
@@ -0,0 +1,45 @@
|
||||
# Variables de entorno para Docker
|
||||
# Copiar a .env y completar los valores faltantes
|
||||
|
||||
APP_NAME="Nomina Ventas"
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_KEY=
|
||||
APP_URL=http://localhost:8004
|
||||
|
||||
APP_LOCALE=es
|
||||
APP_FALLBACK_LOCALE=es
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# Base de datos REMOTA
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=10.10.4.17
|
||||
DB_PORT=3391
|
||||
DB_DATABASE=nomina_pegaso
|
||||
DB_USERNAME=nickpons666
|
||||
DB_PASSWORD=
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=true
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
|
||||
# Telegram Bot (opcional)
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
TELEGRAM_WEBHOOK_URL=
|
||||
|
||||
# Docker Runtime
|
||||
DATA_DIR=/media/DATOS/AppData/nomina_pegaso
|
||||
APP_PORT=8004
|
||||
RUN_MIGRATIONS=false
|
||||
57
Dockerfile
57
Dockerfile
@@ -1,26 +1,15 @@
|
||||
# ============================================
|
||||
# Laravel PHP-FPM 8.3 - Production Optimized
|
||||
# Laravel - Single Image (Nginx + PHP-FPM)
|
||||
# ============================================
|
||||
|
||||
# Stage 1: Builder with Composer
|
||||
FROM composer:2 AS composer-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy composer files first for better layer caching
|
||||
COPY composer.json composer.lock* ./
|
||||
RUN composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist
|
||||
|
||||
# Stage 2: PHP-FPM Production Image
|
||||
FROM php:8.3-fpm-bookworm
|
||||
|
||||
# Build arguments for dynamic user UID/GID
|
||||
ARG PUID=1000
|
||||
ARG PGID=1000
|
||||
|
||||
# Labels
|
||||
LABEL maintainer="dev@local.dev" \
|
||||
description="Laravel PHP-FPM 8.3 production container"
|
||||
description="Laravel single container with Nginx"
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
@@ -37,6 +26,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libsqlite3-dev \
|
||||
unzip \
|
||||
git \
|
||||
nginx \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install PHP extensions
|
||||
@@ -59,48 +49,43 @@ RUN echo "opcache.enable=1" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.
|
||||
&& echo "opcache.revalidate_freq=2" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
|
||||
&& echo "opcache.fast_shutdown=1" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
|
||||
&& echo "upload_max_filesize=100M" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
|
||||
&& echo "post_max_size=100M" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
|
||||
&& echo "post_max_size=100M" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
|
||||
&& echo "listen = 0.0.0.0:9000" >> /usr/local/etc/php-fpm.d/www.conf \
|
||||
&& sed -i 's/user = www-data/user = laravel/g' /usr/local/etc/php-fpm.d/www.conf \
|
||||
&& sed -i 's/group = www-data/group = laravel/g' /usr/local/etc/php-fpm.d/www.conf \
|
||||
&& echo "sys_temp_dir = /tmp" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini \
|
||||
&& echo "upload_tmp_dir = /tmp" >> /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini
|
||||
|
||||
# Copy Composer from builder stage
|
||||
COPY --from=composer-builder /usr/bin/composer /usr/bin/composer
|
||||
COPY --from=composer-builder /root/.composer /root/.composer
|
||||
|
||||
# Create Laravel storage directories with proper permissions
|
||||
# Create Laravel storage directories
|
||||
RUN mkdir -p /var/www/html/storage/framework/{cache,sessions,views} \
|
||||
&& mkdir -p /var/www/html/storage/logs \
|
||||
&& mkdir -p /var/www/html/bootstrap/cache \
|
||||
&& chmod -R 775 /var/www/html/storage \
|
||||
&& chmod -R 775 /var/www/html/bootstrap/cache
|
||||
|
||||
# Create system user for Laravel (dynamic UID/GID)
|
||||
# Create system user for Laravel
|
||||
RUN groupadd -g ${PGID} laravel && \
|
||||
useradd -u ${PUID} -g laravel -m -s /bin/bash laravel
|
||||
|
||||
# Set working directory
|
||||
# Copy nginx config
|
||||
COPY docker/nginx/nginx.conf /etc/nginx/sites-available/default
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
# Copy application files
|
||||
COPY --chown=laravel:laravel . /var/www/html
|
||||
|
||||
# Fix storage permissions
|
||||
# Fix permissions
|
||||
RUN chown -R laravel:laravel /var/www/html/storage \
|
||||
&& chown -R laravel:laravel /var/www/html/bootstrap/cache \
|
||||
&& chmod -R 775 /var/www/html/storage \
|
||||
&& chmod -R 775 /var/www/html/bootstrap/cache
|
||||
|
||||
# Switch to non-root user
|
||||
USER laravel
|
||||
# Copy entrypoint
|
||||
COPY --chown=root:root docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# Expose port 9000 (PHP-FPM)
|
||||
EXPOSE 9000
|
||||
# Expose port 80 (interno)
|
||||
EXPOSE 80
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD php-fpm-healthcheck || exit 1
|
||||
|
||||
# Create healthcheck script
|
||||
RUN echo '#!/bin/sh' > /usr/local/bin/php-fpm-healthcheck \
|
||||
&& echo 'SCRIPT_NAME=/health.php SCRIPT_FILENAME=/health.php REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000 > /dev/null 2>&1' >> /usr/local/bin/php-fpm-healthcheck \
|
||||
&& chmod +x /usr/local/bin/php-fpm-healthcheck
|
||||
|
||||
CMD ["php-fpm"]
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -37,7 +37,10 @@ class TelegramController extends Controller
|
||||
$telegramAccount->update(['verification_code' => $verificationCode]);
|
||||
}
|
||||
|
||||
return view('telegram.verify', compact('telegramAccount'));
|
||||
return view('telegram.verify', [
|
||||
'telegramAccount' => $telegramAccount,
|
||||
'webhookInfo' => (new TelegramBotService())->getWebhookInfo()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,8 +101,7 @@ class TelegramController extends Controller
|
||||
Log::info('Telegram webhook received', $update);
|
||||
|
||||
if (empty($update)) {
|
||||
return response()->json(['ok' => true, 'message' => 'No update
|
||||
received']);
|
||||
return response()->json(['ok' => true, 'message' => 'No update received']);
|
||||
}
|
||||
|
||||
$botService = new TelegramBotService();
|
||||
@@ -117,24 +119,32 @@ received']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configurar webhook (ruta temporal para configurar desde el navegador)
|
||||
* Configurar webhook
|
||||
*/
|
||||
public function setupWebhook(Request $request)
|
||||
{
|
||||
$token = $request->get('token');
|
||||
|
||||
// Verificar token de seguridad simple
|
||||
if ($token !== config('app.key')) {
|
||||
abort(403, 'Token inválido');
|
||||
}
|
||||
|
||||
$botService = new TelegramBotService();
|
||||
$result = $botService->setWebhook();
|
||||
|
||||
if ($result) {
|
||||
return response()->json(['success' => true, 'message' => 'Webhook configurado correctamente']);
|
||||
return back()->with('success', 'Webhook configurado correctamente.');
|
||||
}
|
||||
|
||||
return response()->json(['success' => false, 'error' => 'Error al configurar webhook'], 500);
|
||||
return back()->with('error', 'Error al configurar webhook.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Borrar webhook
|
||||
*/
|
||||
public function deleteWebhook(Request $request)
|
||||
{
|
||||
$botService = new TelegramBotService();
|
||||
$result = $botService->deleteWebhook();
|
||||
|
||||
if ($result) {
|
||||
return back()->with('success', 'Webhook borrado correctamente.');
|
||||
}
|
||||
|
||||
return back()->with('error', 'Error al borrar el webhook.');
|
||||
}
|
||||
}
|
||||
@@ -297,4 +297,40 @@ class TelegramBotService
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener información del webhook
|
||||
*/
|
||||
public function getWebhookInfo(): array
|
||||
{
|
||||
if (!$this->botToken) {
|
||||
return ['ok' => false, 'error' => 'Bot token not configured'];
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::get("https://api.telegram.org/bot{$this->botToken}/getWebhookInfo");
|
||||
return $response->json();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Telegram get webhook info error', ['error' => $e->getMessage()]);
|
||||
return ['ok' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Borrar webhook
|
||||
*/
|
||||
public function deleteWebhook(): bool
|
||||
{
|
||||
if (!$this->botToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::post("https://api.telegram.org/bot{$this->botToken}/deleteWebhook");
|
||||
return $response->json('ok', false);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Telegram delete webhook error', ['error' => $e->getMessage()]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
build.sh
Executable file
93
build.sh
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
REGISTRY="registry-pons.duckdns.org"
|
||||
IMAGE_NAME="nomina-ventas"
|
||||
|
||||
echo "========================================"
|
||||
echo " Build Docker Image - Nomina Ventas"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
|
||||
# 1. Ask for tag
|
||||
read -p "Ingresa el tag de la imagen (ej: latest, v1.0.0): " TAG
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="latest"
|
||||
fi
|
||||
|
||||
FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}"
|
||||
echo "Imagen: $FULL_IMAGE"
|
||||
echo ""
|
||||
|
||||
# 2. Ask for cache
|
||||
echo "¿Quieres construir con cache (más rápido) o sin cache (más limpio)?"
|
||||
select CACHE_CHOICE in "Con cache" "Sin cache (--no-cache)"; do
|
||||
case $CACHE_CHOICE in
|
||||
"Con cache") BUILD_CACHE="" ;;
|
||||
"Sin cache (--no-cache)") BUILD_CACHE="--no-cache" ;;
|
||||
esac
|
||||
break
|
||||
done
|
||||
echo ""
|
||||
|
||||
# 3. Ask for registry push
|
||||
echo "¿Quieres subir la imagen al registry?"
|
||||
select PUSH_CHOICE in "Sí" "No (solo construir local)"; do
|
||||
case $PUSH_CHOICE in
|
||||
"Sí") DO_PUSH=true ;;
|
||||
"No (solo construir local)") DO_PUSH=false ;;
|
||||
esac
|
||||
break
|
||||
done
|
||||
echo ""
|
||||
|
||||
# 4. Build the image
|
||||
echo "========================================"
|
||||
echo " Construyendo imagen..."
|
||||
echo "========================================"
|
||||
docker build \
|
||||
--tag "${FULL_IMAGE}" \
|
||||
${BUILD_CACHE} \
|
||||
--build-arg PUID=$(id -u) \
|
||||
--build-arg PGID=$(id -g) \
|
||||
.
|
||||
|
||||
echo ""
|
||||
echo "✅ Imagen construida: ${FULL_IMAGE}"
|
||||
echo ""
|
||||
|
||||
# 5. Push if requested
|
||||
if [ "$DO_PUSH" = true ]; then
|
||||
echo "========================================"
|
||||
echo " Subiendo al registry..."
|
||||
echo "========================================"
|
||||
docker push "${FULL_IMAGE}"
|
||||
echo ""
|
||||
echo "✅ Imagen subida al registry"
|
||||
fi
|
||||
|
||||
# 6. Update docker-compose.prod.yml with the tag
|
||||
echo "========================================"
|
||||
echo " Actualizando docker-compose.prod.yml"
|
||||
echo "========================================"
|
||||
sed -i "s|image: \${REGISTRY:-registry-pons.duckdns.org}/\${IMAGE:-nomina-ventas}:\${TAG:-latest}|image: ${FULL_IMAGE}|g" docker-compose.prod.yml
|
||||
|
||||
echo "✅ docker-compose.prod.yml actualizado"
|
||||
echo ""
|
||||
|
||||
echo "========================================"
|
||||
echo " Resumen"
|
||||
echo "========================================"
|
||||
echo " Imagen local: ${FULL_IMAGE}"
|
||||
[ "$DO_PUSH" = true ] && echo " Registry: ${FULL_IMAGE}"
|
||||
echo ""
|
||||
echo " Para producción:"
|
||||
echo " 1. Edita .env.docker con tus valores"
|
||||
echo " 2. cp .env.docker .env"
|
||||
echo " 3. docker compose -f docker-compose.prod.yml up -d"
|
||||
echo ""
|
||||
echo " El puerto expuesto será: 8004"
|
||||
echo " Los datos se almacenarán en: /media/DATOS/AppData/nomina_pegaso"
|
||||
echo ""
|
||||
echo "✅ Completado!"
|
||||
44
docker-compose.prod.yml
Normal file
44
docker-compose.prod.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
services:
|
||||
app:
|
||||
image: registry-pons.duckdns.org/nomina-ventas:latest
|
||||
container_name: nomina_app
|
||||
ports:
|
||||
- "8004:80"
|
||||
volumes:
|
||||
- /media/DATOS/AppData/nomina_pegaso/storage:/var/www/html/storage
|
||||
- /media/DATOS/AppData/nomina_pegaso/bootstrap/cache:/var/www/html/bootstrap/cache
|
||||
environment:
|
||||
- APP_NAME=Nomina Ventas
|
||||
- APP_ENV=production
|
||||
- APP_DEBUG=true
|
||||
- APP_KEY=
|
||||
- APP_URL=https://nomina-pegaso.duckdns.org
|
||||
- APP_LOCALE=es
|
||||
- APP_FALLBACK_LOCALE=es
|
||||
- BCRYPT_ROUNDS=12
|
||||
- LOG_CHANNEL=stack
|
||||
- LOG_LEVEL=debug
|
||||
- DB_CONNECTION=mysql
|
||||
- DB_HOST=10.10.4.17
|
||||
- DB_PORT=3390
|
||||
- DB_DATABASE=nomina_pegaso
|
||||
- DB_USERNAME=nickpons666
|
||||
- DB_PASSWORD=MiPo6425@@
|
||||
- SESSION_DRIVER=database
|
||||
- SESSION_LIFETIME=120
|
||||
- SESSION_ENCRYPT=true
|
||||
- SESSION_PATH=/
|
||||
- SESSION_DOMAIN=
|
||||
- BROADCAST_CONNECTION=log
|
||||
- FILESYSTEM_DISK=local
|
||||
- QUEUE_CONNECTION=database
|
||||
- CACHE_STORE=database
|
||||
- TELEGRAM_BOT_TOKEN=8324407449:AAF2awMeZ9pgSIp0MvV1r5owu8lO7SEK70E
|
||||
- TELEGRAM_WEBHOOK_URL=
|
||||
- RUN_MIGRATIONS=false
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '1.0'
|
||||
memory: 512M
|
||||
@@ -1,28 +1,4 @@
|
||||
services:
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: nomina_nginx
|
||||
ports:
|
||||
- "${APP_PORT:-80}:80"
|
||||
volumes:
|
||||
- ./public:/var/www/html/public:ro
|
||||
- ./docker/nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- app
|
||||
networks:
|
||||
- app_network
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '0.5'
|
||||
memory: 256M
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
@@ -31,29 +7,26 @@ services:
|
||||
PUID: ${PUID:-1000}
|
||||
PGID: ${PGID:-1000}
|
||||
container_name: nomina_app
|
||||
ports:
|
||||
- "${APP_PORT:-8004}:80"
|
||||
volumes:
|
||||
- .:/var/www/html:delegated
|
||||
environment:
|
||||
- APP_ENV=${APP_ENV:-production}
|
||||
- APP_DEBUG=${APP_DEBUG:-false}
|
||||
- APP_ENV=${APP_ENV:-local}
|
||||
- APP_DEBUG=${APP_DEBUG:-true}
|
||||
- APP_KEY=${APP_KEY}
|
||||
- APP_URL=${APP_URL:-http://localhost}
|
||||
- APP_URL=${APP_URL:-http://localhost:8004}
|
||||
- DB_CONNECTION=${DB_CONNECTION:-mysql}
|
||||
- DB_HOST=${DB_HOST:-db}
|
||||
- DB_PORT=${DB_PORT:-3306}
|
||||
- DB_DATABASE=${DB_DATABASE:-nomina_ventas}
|
||||
- DB_USERNAME=${DB_USERNAME}
|
||||
- DB_HOST=${DB_HOST:-10.10.4.17}
|
||||
- DB_PORT=${DB_PORT:-3391}
|
||||
- DB_DATABASE=${DB_DATABASE:-nomina_pegaso}
|
||||
- DB_USERNAME=${DB_USERNAME:-nickpons666}
|
||||
- DB_PASSWORD=${DB_PASSWORD}
|
||||
- SESSION_DRIVER=${SESSION_DRIVER:-database}
|
||||
- CACHE_STORE=${CACHE_STORE:-database}
|
||||
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
|
||||
- TELEGRAM_WEBHOOK_URL=${TELEGRAM_WEBHOOK_URL}
|
||||
volumes:
|
||||
- ./:/var/www/html:delegated
|
||||
- storage_data:/var/www/html/storage
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- app_network
|
||||
- RUN_MIGRATIONS=${RUN_MIGRATIONS:-false}
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
@@ -66,13 +39,13 @@ services:
|
||||
container_name: nomina_mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
|
||||
- MYSQL_DATABASE=${DB_DATABASE:-nomina_ventas}
|
||||
- MYSQL_USER=${DB_USERNAME}
|
||||
- MYSQL_DATABASE=${DB_DATABASE:-nomina_pegaso}
|
||||
- MYSQL_USER=${DB_USERNAME:-nickpons666}
|
||||
- MYSQL_PASSWORD=${DB_PASSWORD}
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
networks:
|
||||
- app_network
|
||||
ports:
|
||||
- "${DB_PORT:-3391}:3306"
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
@@ -87,9 +60,8 @@ services:
|
||||
memory: 1G
|
||||
|
||||
networks:
|
||||
app_network:
|
||||
default:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
storage_data:
|
||||
mysql_data:
|
||||
100
docker/entrypoint.sh
Normal file
100
docker/entrypoint.sh
Normal file
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "========================================"
|
||||
echo " Nomina Ventas - Entrypoint"
|
||||
echo "========================================"
|
||||
|
||||
# Verificar que el código existe en /var/www/html
|
||||
if [ ! -f /var/www/html/artisan ]; then
|
||||
echo "ERROR: No se encontró el código en /var/www/html"
|
||||
echo "Montando volumen con el código..."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Asegurar que existan los directorios necesarios (importante para volúmenes montados)
|
||||
mkdir -p /var/www/html/storage/framework/{cache,sessions,views}
|
||||
mkdir -p /var/www/html/storage/logs
|
||||
mkdir -p /var/www/html/bootstrap/cache
|
||||
|
||||
# Asegurar permisos
|
||||
chown -R laravel:laravel /var/www/html/storage
|
||||
chown -R laravel:laravel /var/www/html/bootstrap/cache
|
||||
chmod -R 775 /var/www/html/storage
|
||||
chmod -R 775 /var/www/html/bootstrap/cache
|
||||
|
||||
# ===== GENERAR O ACTUALIZAR .env =====
|
||||
echo "Intentando sincronizar .env desde variables de Docker..."
|
||||
|
||||
# Generamos el contenido en un archivo temporal
|
||||
cat > /tmp/.env.tmp << EOF
|
||||
APP_NAME="${APP_NAME:-Laravel}"
|
||||
APP_ENV="${APP_ENV:-production}"
|
||||
APP_KEY="${APP_KEY}"
|
||||
APP_DEBUG=${APP_DEBUG:-false}
|
||||
APP_URL=${APP_URL:-http://localhost}
|
||||
APP_LOCALE=${APP_LOCALE:-es}
|
||||
APP_FALLBACK_LOCALE=${APP_FALLBACK_LOCALE:-es}
|
||||
LOG_CHANNEL=${LOG_CHANNEL:-stack}
|
||||
LOG_LEVEL=${LOG_LEVEL:-debug}
|
||||
DB_CONNECTION=${DB_CONNECTION:-mysql}
|
||||
DB_HOST=${DB_HOST:-127.0.0.1}
|
||||
DB_PORT=${DB_PORT:-3306}
|
||||
DB_DATABASE=${DB_DATABASE:-laravel}
|
||||
DB_USERNAME=${DB_USERNAME:-root}
|
||||
DB_PASSWORD='${DB_PASSWORD}'
|
||||
SESSION_DRIVER=${SESSION_DRIVER:-file}
|
||||
SESSION_LIFETIME=${SESSION_LIFETIME:-120}
|
||||
SESSION_ENCRYPT=${SESSION_ENCRYPT:-false}
|
||||
SESSION_PATH=${SESSION_PATH:-/}
|
||||
SESSION_DOMAIN=${SESSION_DOMAIN:-null}
|
||||
BROADCAST_CONNECTION=${BROADCAST_CONNECTION:-log}
|
||||
FILESYSTEM_DISK=${FILESYSTEM_DISK:-local}
|
||||
QUEUE_CONNECTION=${QUEUE_CONNECTION:-sync}
|
||||
CACHE_STORE=${CACHE_STORE:-database}
|
||||
TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
|
||||
TELEGRAM_WEBHOOK_URL=${TELEGRAM_WEBHOOK_URL}
|
||||
BCRYPT_ROUNDS=${BCRYPT_ROUNDS:-12}
|
||||
EOF
|
||||
|
||||
# Intentar mover el temporal al destino final
|
||||
if cp /tmp/.env.tmp /var/www/html/.env 2>/dev/null; then
|
||||
# Si pudimos copiarlo, aplicamos permisos
|
||||
chown laravel:laravel /var/www/html/.env
|
||||
chmod 640 /var/www/html/.env
|
||||
|
||||
# Generar APP_KEY si falta (solo si es escribible)
|
||||
if [ -z "$APP_KEY" ] || [ "$APP_KEY" == "" ]; then
|
||||
echo "APP_KEY no detectada, generando una nueva..."
|
||||
NEW_KEY=$(php /var/www/html/artisan key:generate --show --no-ansi)
|
||||
sed -i "s|APP_KEY=|APP_KEY=$NEW_KEY|g" /var/www/html/.env
|
||||
export APP_KEY=$NEW_KEY
|
||||
fi
|
||||
echo "✅ Archivo .env sincronizado correctamente."
|
||||
else
|
||||
echo "⚠️ ADVERTENCIA: No se pudo escribir en /var/www/html/.env"
|
||||
echo "ℹ️ Probablemente esté montado como volumen de solo lectura (:ro)."
|
||||
echo "ℹ️ Se usarán los valores del archivo montado externamente."
|
||||
fi
|
||||
rm -f /tmp/.env.tmp
|
||||
|
||||
# ===== LIMPIAR CACHE =====
|
||||
echo ">> Limpiando cache..."
|
||||
php /var/www/html/artisan view:clear 2>/dev/null || true
|
||||
php /var/www/html/artisan config:clear 2>/dev/null || true
|
||||
php /var/www/html/artisan cache:clear 2>/dev/null || true
|
||||
php /var/www/html/artisan route:clear 2>/dev/null || true
|
||||
|
||||
echo ">> Cacheando configuración..."
|
||||
php /var/www/html/artisan config:cache 2>&1 || true
|
||||
|
||||
# ===== VERIFICAR =====
|
||||
php /var/www/html/artisan about 2>&1 | head -10 || true
|
||||
|
||||
echo "========================================"
|
||||
echo " Iniciando servicios..."
|
||||
echo "========================================"
|
||||
|
||||
php-fpm &
|
||||
nginx -g "daemon off;"
|
||||
@@ -1,6 +1,6 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
server_name _;
|
||||
root /var/www/html/public;
|
||||
index index.php index.html;
|
||||
|
||||
@@ -24,7 +24,7 @@ server {
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
fastcgi_pass app:9000;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||
|
||||
159
prompt.txt
Normal file
159
prompt.txt
Normal file
@@ -0,0 +1,159 @@
|
||||
Crea una aplicación web para el control de ingresos por comisiones y gastos personales con soporte multiusuario y un bot de Telegram integrado.
|
||||
|
||||
## Descripción general
|
||||
|
||||
La aplicación debe permitir a múltiples usuarios registrar ventas diarias por mes mediante un calendario interactivo, calcular comisiones basadas en un porcentaje configurable, sumar un sueldo mensual fijo, registrar gastos y mostrar el dinero restante tanto mensual como por quincena.
|
||||
|
||||
Cada usuario debe tener acceso únicamente a su propia información.
|
||||
|
||||
## Funcionalidades principales
|
||||
|
||||
### 1. Sistema multiusuario
|
||||
|
||||
* Registro e inicio de sesión
|
||||
* Cada usuario solo puede ver y modificar sus propios datos
|
||||
* Relación de todos los datos con user_id
|
||||
|
||||
### 2. Gestión por mes
|
||||
|
||||
* Crear, ver y editar meses por usuario
|
||||
|
||||
### 3. Calendario de captura (IMPORTANTE)
|
||||
|
||||
* Cada mes debe mostrarse como un calendario visual
|
||||
* El usuario NO captura la fecha manualmente
|
||||
* El usuario selecciona un día haciendo clic en el calendario
|
||||
|
||||
Al seleccionar un día:
|
||||
|
||||
* Se abre un formulario/modal para capturar:
|
||||
|
||||
* Ventas registradas por el usuario
|
||||
* Ventas del sistema de la empresa
|
||||
|
||||
El sistema debe:
|
||||
|
||||
* Guardar automáticamente la fecha según el día seleccionado
|
||||
* Mostrar visualmente en el calendario:
|
||||
|
||||
* Días con información registrada
|
||||
* Días sin registrar
|
||||
* Indicadores rápidos (ej: colores o íconos)
|
||||
|
||||
### 4. Registro diario de ventas
|
||||
|
||||
Por cada día seleccionado:
|
||||
|
||||
* Ventas registradas por el usuario
|
||||
* Ventas del sistema de la empresa
|
||||
|
||||
Cálculos:
|
||||
|
||||
* Diferencia entre valores
|
||||
* Comisión diaria
|
||||
|
||||
### 5. Configuración de variables por usuario
|
||||
|
||||
Cada usuario puede definir:
|
||||
|
||||
* Porcentaje de comisión
|
||||
* Sueldo mensual
|
||||
|
||||
### 6. Cálculos automáticos
|
||||
|
||||
* Comisión diaria = ventas del sistema × porcentaje
|
||||
* Total mensual de comisiones
|
||||
* Total ingresos = sueldo + comisiones
|
||||
* Total gastos
|
||||
* Balance final
|
||||
|
||||
### 7. Gestión de gastos
|
||||
|
||||
* Agregar múltiples gastos:
|
||||
|
||||
* Descripción
|
||||
* Monto
|
||||
* Fecha (opcional o seleccionable desde calendario)
|
||||
|
||||
### 8. Cálculo por quincena
|
||||
|
||||
* Primera quincena (1–15)
|
||||
* Segunda quincena (16–fin)
|
||||
|
||||
Mostrar:
|
||||
|
||||
* Ingresos
|
||||
* Gastos
|
||||
* Balance
|
||||
|
||||
### 9. Dashboard
|
||||
|
||||
* Resumen mensual
|
||||
* Totales
|
||||
* Vista de calendario
|
||||
* Tabla opcional de ventas
|
||||
* Resumen por quincena
|
||||
|
||||
## Integración con Telegram
|
||||
|
||||
### Vinculación segura
|
||||
|
||||
* El usuario inicia sesión en la web
|
||||
* Genera un código único de vinculación
|
||||
* En Telegram, el usuario envía ese código al bot
|
||||
* El bot vincula el chat_id con el usuario
|
||||
|
||||
### Funcionalidades del bot
|
||||
|
||||
El usuario puede:
|
||||
|
||||
* Registrar ventas:
|
||||
comando: /venta 1500 1400
|
||||
(el sistema usa automáticamente la fecha actual)
|
||||
|
||||
* Registrar gastos:
|
||||
comando: /gasto comida 200
|
||||
|
||||
* Consultar resumen:
|
||||
comando: /resumen
|
||||
|
||||
* Consultar quincena:
|
||||
comando: /quincena
|
||||
|
||||
### Seguridad
|
||||
|
||||
* No pedir usuario/contraseña en Telegram
|
||||
* Validar chat_id vinculado
|
||||
* Solo permitir acceso a datos del usuario vinculado
|
||||
|
||||
## Base de datos (sugerida)
|
||||
|
||||
* users (id, name, email, password)
|
||||
* telegram_accounts (id, user_id, chat_id)
|
||||
* months (id, user_id, name, year)
|
||||
* daily_sales (id, user_id, month_id, date, user_sales, system_sales)
|
||||
* settings (id, user_id, commission_percentage, monthly_salary)
|
||||
* expenses (id, user_id, month_id, description, amount, date)
|
||||
|
||||
## Requisitos técnicos
|
||||
|
||||
* Backend: Laravel
|
||||
* Frontend: Blade o Vue
|
||||
* Base de datos: MySQL
|
||||
* Uso de librería de calendario (ej: FullCalendar)
|
||||
|
||||
## Extras opcionales
|
||||
|
||||
* Exportar a Excel/CSV
|
||||
* Gráficas
|
||||
* Notificaciones por Telegram
|
||||
|
||||
## UI/UX
|
||||
|
||||
* Interfaz tipo dashboard
|
||||
* Calendario como elemento principal
|
||||
* Colores para indicar estado de días
|
||||
* Captura rápida mediante modales
|
||||
|
||||
La aplicación debe ser rápida, intuitiva y enfocada en facilitar la captura diaria sin errores y todos los logs se deben de guardar en la carpeta logs de la raiz.
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Información</h5>
|
||||
</div>
|
||||
@@ -73,6 +73,64 @@
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-primary">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="mb-0"><i class="bi bi-robot"></i> Gestión Webhook</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if(isset($webhookInfo['ok']) && $webhookInfo['ok'])
|
||||
@php $info = $webhookInfo['result']; @endphp
|
||||
<div class="mb-3">
|
||||
<label class="small fw-bold text-muted d-block">Estado:</label>
|
||||
@if(!empty($info['url']))
|
||||
<span class="badge bg-success">Configurado</span>
|
||||
@else
|
||||
<span class="badge bg-warning text-dark">No configurado</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if(!empty($info['url']))
|
||||
<div class="mb-3">
|
||||
<label class="small fw-bold text-muted d-block">URL Webhook:</label>
|
||||
<code class="small break-all" style="word-break: break-all;">{{ $info['url'] }}</code>
|
||||
</div>
|
||||
<div class="mb-3 small">
|
||||
<label class="small fw-bold text-muted d-block">Pendientes:</label>
|
||||
{{ $info['pending_update_count'] ?? 0 }} mensajes
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<form action="{{ route('telegram.setup-webhook') }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-sm btn-outline-primary w-100">
|
||||
<i class="bi bi-gear-fill"></i> {{ empty($info['url']) ? 'Configurar Webhook' : 'Actualizar Webhook' }}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@if(!empty($info['url']))
|
||||
<form action="{{ route('telegram.delete-webhook') }}" method="POST">
|
||||
@csrf
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger w-100" onclick="return confirm('¿Seguro que deseas borrar el webhook?')">
|
||||
<i class="bi bi-trash-fill"></i> Borrar Webhook
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-danger small">
|
||||
<i class="bi bi-exclamation-triangle"></i> Error al conectar con Telegram API.
|
||||
@if(isset($webhookInfo['description']))
|
||||
<br><small>{{ $webhookInfo['description'] }}</small>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
<div class="card-footer bg-light">
|
||||
<small class="text-muted italic">Basado en APP_URL: {{ config('app.url') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -60,6 +60,8 @@ Route::middleware(['auth'])->group(function () {
|
||||
Route::get('/telegram/verify', [TelegramController::class, 'showVerifyPage'])->name('telegram.verify');
|
||||
Route::post('/telegram/regenerate', [TelegramController::class, 'regenerateCode'])->name('telegram.regenerate');
|
||||
Route::post('/telegram/unlink', [TelegramController::class, 'unlink'])->name('telegram.unlink');
|
||||
Route::post('/telegram/setup-webhook', [TelegramController::class, 'setupWebhook'])->name('telegram.setup-webhook');
|
||||
Route::post('/telegram/delete-webhook', [TelegramController::class, 'deleteWebhook'])->name('telegram.delete-webhook');
|
||||
|
||||
// Settings
|
||||
Route::get('/settings', [SettingsController::class, 'index'])->name('settings.index');
|
||||
|
||||
13
supervisor.conf
Normal file
13
supervisor.conf
Normal file
@@ -0,0 +1,13 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
logfile=/var/log/supervisor.log
|
||||
pidfile=/var/run/supervisord.pid
|
||||
|
||||
[program:php-fpm]
|
||||
command=/usr/local/sbin/php-fpm
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
autorestart=true
|
||||
Reference in New Issue
Block a user