From c4bd4b62e31865cd757a6eb6f81983cc922bdb73 Mon Sep 17 00:00:00 2001 From: nickpons666 Date: Sun, 19 Apr 2026 19:23:25 -0600 Subject: [PATCH] Fix: Solucionados problemas de permisos en Docker y agregada gestion de Webhook de Telegram --- .env.docker | 45 ++++++ Dockerfile | 57 +++---- app/Http/Controllers/TelegramController.php | 36 +++-- app/Services/TelegramBotService.php | 36 +++++ build.sh | 93 ++++++++++++ docker-compose.prod.yml | 44 ++++++ docker-compose.yml | 64 +++----- docker/entrypoint.sh | 100 ++++++++++++ docker/nginx/nginx.conf | 4 +- prompt.txt | 159 ++++++++++++++++++++ resources/views/telegram/verify.blade.php | 60 +++++++- routes/web.php | 2 + supervisor.conf | 13 ++ 13 files changed, 615 insertions(+), 98 deletions(-) create mode 100644 .env.docker create mode 100755 build.sh create mode 100644 docker-compose.prod.yml create mode 100644 docker/entrypoint.sh create mode 100644 prompt.txt create mode 100644 supervisor.conf diff --git a/.env.docker b/.env.docker new file mode 100644 index 0000000..6f22853 --- /dev/null +++ b/.env.docker @@ -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 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5c6164f..3f64624 100644 --- a/Dockerfile +++ b/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"] \ No newline at end of file diff --git a/app/Http/Controllers/TelegramController.php b/app/Http/Controllers/TelegramController.php index 7197d45..26783b0 100755 --- a/app/Http/Controllers/TelegramController.php +++ b/app/Http/Controllers/TelegramController.php @@ -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.'); } } \ No newline at end of file diff --git a/app/Services/TelegramBotService.php b/app/Services/TelegramBotService.php index 7144372..9fe743f 100755 --- a/app/Services/TelegramBotService.php +++ b/app/Services/TelegramBotService.php @@ -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; + } + } } \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..c96e045 --- /dev/null +++ b/build.sh @@ -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!" \ No newline at end of file diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..09fa2cb --- /dev/null +++ b/docker-compose.prod.yml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index 53e633e..dce2a79 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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: \ No newline at end of file + mysql_data: \ No newline at end of file diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..49d99a4 --- /dev/null +++ b/docker/entrypoint.sh @@ -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;" \ No newline at end of file diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf index 9004c96..57980af 100644 --- a/docker/nginx/nginx.conf +++ b/docker/nginx/nginx.conf @@ -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; diff --git a/prompt.txt b/prompt.txt new file mode 100644 index 0000000..4ad1803 --- /dev/null +++ b/prompt.txt @@ -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. + diff --git a/resources/views/telegram/verify.blade.php b/resources/views/telegram/verify.blade.php index ff97407..06edf89 100755 --- a/resources/views/telegram/verify.blade.php +++ b/resources/views/telegram/verify.blade.php @@ -60,7 +60,7 @@
-
+
Información
@@ -73,6 +73,64 @@
+ +
+
+
Gestión Webhook
+
+
+ @if(isset($webhookInfo['ok']) && $webhookInfo['ok']) + @php $info = $webhookInfo['result']; @endphp +
+ + @if(!empty($info['url'])) + Configurado + @else + No configurado + @endif +
+ + @if(!empty($info['url'])) +
+ + {{ $info['url'] }} +
+
+ + {{ $info['pending_update_count'] ?? 0 }} mensajes +
+ @endif + +
+
+ @csrf + +
+ + @if(!empty($info['url'])) +
+ @csrf + +
+ @endif +
+ @else +
+ Error al conectar con Telegram API. + @if(isset($webhookInfo['description'])) +
{{ $webhookInfo['description'] }} + @endif +
+ @endif +
+ +
@endsection \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index baeb126..2479d9d 100755 --- a/routes/web.php +++ b/routes/web.php @@ -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'); diff --git a/supervisor.conf b/supervisor.conf new file mode 100644 index 0000000..a1f06f7 --- /dev/null +++ b/supervisor.conf @@ -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 \ No newline at end of file