chekout update, mp study v1

This commit is contained in:
2026-03-07 03:07:45 -03:00
parent 423d563cc0
commit 6c5085093d
10 changed files with 453 additions and 2651 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@ pos_database.db
ScannerGO/ScannerGO-*
ScannerGO/config.json
DataToolsGO/imageTools-*
.env

View File

@@ -10,6 +10,7 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
COPY templates/ ./templates/
COPY static/ ./static/
COPY .env .
# Create the folder structure for the volume mounts
RUN mkdir -p /app/static/cache

File diff suppressed because it is too large Load Diff

View File

@@ -1,437 +0,0 @@
[
{
"PLU": "4924",
"Name": "Almonds",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4924-almonds_1630598936.jpg"
},
{
"PLU": "3525",
"Name": "Apples",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3486-cn121-apple-image_1627662287.JPG"
},
{
"PLU": "4218",
"Name": "Apricots",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3302-apricots-1-1614718940_1633443369.jpg"
},
{
"PLU": "4516",
"Name": "Artichokes",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4516-artichokes_1630598556.jpg"
},
{
"PLU": "4080",
"Name": "Asparagus",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4521-asparagus-01-1625671446_1632939137.jpg"
},
{
"PLU": "4046",
"Name": "Avocados",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4770-hass-avocado_1635960530.jpg"
},
{
"PLU": "3303",
"Name": "Babaco",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3303-babacoa_1614719281.JPG"
},
{
"PLU": "4186",
"Name": "Bananas",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4011-yellow-banana_1634142809.jpg"
},
{
"PLU": "4528",
"Name": "Beans",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4528-beans-fava-07_1625671831.jpg"
},
{
"PLU": "4537",
"Name": "Beets",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4537-baby-golden-beets_1635173500.jpg"
},
{
"PLU": "4247",
"Name": "Berries",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4028-berries-strawberries-06-1625013614_1633444773.jpg"
},
{
"PLU": "4926",
"Name": "Brazilnuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4926-brazil-nuts-02_1625756785.JPG"
},
{
"PLU": "4069",
"Name": "Cabbage",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4069-green-cabbage_1633958066.jpg"
},
{
"PLU": "4560",
"Name": "Carrots",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4560-carrots-baby_1625673556.jpg"
},
{
"PLU": "3105",
"Name": "Cashews",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3105-cashews-03_1614635878.JPG"
},
{
"PLU": "3321",
"Name": "Celery Root/Celeriac",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3321-celery-root-03_1614720213.jpg"
},
{
"PLU": "3357",
"Name": "Cherries",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4045-cherries-05-1625061853_1633445797.jpg"
},
{
"PLU": "4927",
"Name": "Chestnuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4927-chestnuts-italian-02_1625756943.jpg"
},
{
"PLU": "4261",
"Name": "Coconuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4261-coconuts-02_1625077777.jpg"
},
{
"PLU": "3087",
"Name": "Corn",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3087-corn-04_1614633780.jpg"
},
{
"PLU": "4862",
"Name": "Dates",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4862-dates_1630598790.jpg"
},
{
"PLU": "4892",
"Name": "Dill",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4892-dill-baby-02_1625755376.jpg"
},
{
"PLU": "4599",
"Name": "Eggplant (Aubergine)",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4599-baby-eggplant-aubergine_1633372314.jpg"
},
{
"PLU": "4606",
"Name": "Fiddlehead Ferns",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4606-fiddlehead-ferns-07_1625679559.jpg"
},
{
"PLU": "4266",
"Name": "Figs",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4266-figs-03_1625077969.jpg"
},
{
"PLU": "4608",
"Name": "Garlic",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4608-garlic-regular_1637184640.jpg"
},
{
"PLU": "4612",
"Name": "Ginger Root",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4612-ginger-root-06_1625679690.jpg"
},
{
"PLU": "3091",
"Name": "Gobo Root/Burdock",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3091gobo-root-02_1614634374.jpg"
},
{
"PLU": "4280",
"Name": "Grapefruit",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4492-grapefruit-ruby-red_1633960726.jpg"
},
{
"PLU": "3505",
"Name": "Grapes",
"URL": "https://server-ifps.accurateig.com/assets/commodities/arra-32-image-3_1629296403.PNG"
},
{
"PLU": "4614",
"Name": "Greens",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4614-collard-greens-trimmed_1625679839.JPG"
},
{
"PLU": "4625",
"Name": "Horseradish Root",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4625-horseradish-root-04_1625680026.jpg"
},
{
"PLU": "4626",
"Name": "Jicama/Yam Bean",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4626-jicama_1635179919.jpg"
},
{
"PLU": "3280",
"Name": "Kiwifruit",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4030-kiwi-fruit-04-1625013864_1633447661.jpg"
},
{
"PLU": "4303",
"Name": "Kumquat",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4303-kumquats-03_1625079229.jpg"
},
{
"PLU": "4629",
"Name": "Leeks",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4629-leeks-02_1625680225.jpg"
},
{
"PLU": "4033",
"Name": "Lemons",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4033-lemons_1630597866.jpg"
},
{
"PLU": "4328",
"Name": "Limequats",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4328-limequats-01_1625081231.jpg"
},
{
"PLU": "4048",
"Name": "Limes",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4048-limes-02_1625062144.jpg"
},
{
"PLU": "4308",
"Name": "Loquats",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4308-lychees-5_1625079574.jpg"
},
{
"PLU": "3099",
"Name": "Lotus Root",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3099-lotus-root-03_1614635240.jpg"
},
{
"PLU": "3106",
"Name": "Macadamia",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3106-macadamias_1614705972.JPG"
},
{
"PLU": "4644",
"Name": "Malanga",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4644-malanga_1633956612.jpg"
},
{
"PLU": "3475",
"Name": "Mint",
"URL": "https://server-ifps.accurateig.com/assets/commodities/peppermint-3475_1625790587.jpg"
},
{
"PLU": "4645",
"Name": "Mushrooms",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4645-mushrooms-white-button-02_1625681263.jpg"
},
{
"PLU": "3276",
"Name": "Name",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3276-name-white_1634146226.jpg"
},
{
"PLU": "4377",
"Name": "Nectarine",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4378-nectarines-1-1625081931_1633448513.jpg"
},
{
"PLU": "4665",
"Name": "Onions",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4093-onions-yellow-01-1625064621_1632941716.jpg"
},
{
"PLU": "4013",
"Name": "Oranges",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4012-navel-orange_1634141444.jpg"
},
{
"PLU": "4672",
"Name": "Parsnip",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4672-parsnip_1633958885.jpg"
},
{
"PLU": "3311",
"Name": "Passion Fruit",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3311-passion-fruit-02_1614719436.jpg"
},
{
"PLU": "4037",
"Name": "Peaches",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4403-peaches-1-1625664942_1633449692.jpg"
},
{
"PLU": "4931",
"Name": "Peanuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4931-peanuts_1625757234.JPG"
},
{
"PLU": "4025",
"Name": "Pears",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4025-anjou-pear_1629988897.PNG"
},
{
"PLU": "4673",
"Name": "Peas",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4673-peas-black-eyed-01_1625684339.jpg"
},
{
"PLU": "4936",
"Name": "Pecans",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4936-pecans-01_1625757366.JPG"
},
{
"PLU": "3459",
"Name": "Persimmon",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3459-shiny-red-persimmon_1486570958.JPG"
},
{
"PLU": "3039",
"Name": "Physalis/Cape Gooseberry/Ground Cherry",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3039-cape-gooseberry_1630596655.jpg"
},
{
"PLU": "4029",
"Name": "Pineapple",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4029-pineapple_1630597815.jpg"
},
{
"PLU": "4939",
"Name": "Pistachio",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4939-pistachios_1625757519.JPG"
},
{
"PLU": "3040",
"Name": "Pitahaya",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3040-pitaya-dragon-fruit-01_1614282658.jpg"
},
{
"PLU": "3611",
"Name": "Plumcot (Interspecific Plum)",
"URL": "https://server-ifps.accurateig.com/assets/commodities/interspecific-plum-black-picture_1629142350.JPG"
},
{
"PLU": "4434",
"Name": "Plums",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4435-plums-greengage-01-1625667846_1633453839.jpg"
},
{
"PLU": "4445",
"Name": "Pomegranate",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4445-pomegranates-07_1625668628.jpg"
},
{
"PLU": "3128",
"Name": "Potato",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3128-potatoes-purple-06_1614708225.jpg"
},
{
"PLU": "3631",
"Name": "Pumpkin",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3631-pink-pumpkin_1460472104.jpg"
},
{
"PLU": "3480",
"Name": "Pumpkin Vine",
"URL": "https://server-ifps.accurateig.com/assets/commodities/pumpkin-vine-3480_1625790222.jpg"
},
{
"PLU": "3478",
"Name": "Quelites",
"URL": "https://server-ifps.accurateig.com/assets/commodities/quelite-3478_1625790359.jpg"
},
{
"PLU": "4447",
"Name": "Quince",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4447-quince-1_1625668926.jpg"
},
{
"PLU": "3168",
"Name": "Radicchio",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3168-radicchio-castelfranco-02_1614718366.jpg"
},
{
"PLU": "4739",
"Name": "Radish",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4739-radish-black-03_1625751544.jpg"
},
{
"PLU": "4745",
"Name": "Rhubarb",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4745-rhubarb-01_1625751764.jpg"
},
{
"PLU": "4747",
"Name": "Rutabagas (Swede)",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4747-rutabaga_1635168852.jpg"
},
{
"PLU": "3137",
"Name": "Sapote",
"URL": "https://server-ifps.accurateig.com/assets/commodities/3137-sapote-white_1635264028.jpg"
},
{
"PLU": "4757",
"Name": "Squash",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4757-squash-banana_1625752222.JPG"
},
{
"PLU": "4790",
"Name": "Sugar Cane",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4790-sugar-cane_1630598686.jpg"
},
{
"PLU": "4791",
"Name": "Sunchokes (Jerusalem Artichokes)",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4791-sunchokes-03_1625753005.jpg"
},
{
"PLU": "4942",
"Name": "Sunflower Seeds",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4942-sunflower-seeds_1630599026.jpg"
},
{
"PLU": "4074",
"Name": "Sweet Potato/Yam/Kumara",
"URL": "https://server-ifps.accurateig.com/assets/commodities/sweet-potato-red-flesh-4074_1614281686.png"
},
{
"PLU": "4448",
"Name": "Tamarind",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4448-tamarindo-01_1625669100.jpg"
},
{
"PLU": "3428",
"Name": "Tangerines/Mandarins",
"URL": "https://server-ifps.accurateig.com/assets/commodities/murcott-mandarin-1629310772_1633456088.png"
},
{
"PLU": "4794",
"Name": "Taro Root (Dasheen)",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4795-taro-root-01-1625753179_1633374616.jpg"
},
{
"PLU": "4811",
"Name": "Turnip",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4811-turnip-purple-top_1635177577.jpg"
},
{
"PLU": "4943",
"Name": "Walnuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4943-walnuts_1625757652.JPG"
},
{
"PLU": "4814",
"Name": "Water Chestnuts",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4814-waterchestnuts-01_1625753939.jpg"
},
{
"PLU": "3481",
"Name": "Xpelon",
"URL": "https://server-ifps.accurateig.com/assets/commodities/expelon-3481_1625790150.jpg"
},
{
"PLU": "4819",
"Name": "Yuca Root/Cassava/Manioc",
"URL": "https://server-ifps.accurateig.com/assets/commodities/4819-yucca-root-03_1625754507.jpg"
}
]

8
MPStudyGO/go.mod Normal file
View File

@@ -0,0 +1,8 @@
module MPPOS
go 1.25.7
require (
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
)

4
MPStudyGO/go.sum Normal file
View File

@@ -0,0 +1,4 @@
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=

143
MPStudyGO/main.go Normal file
View File

@@ -0,0 +1,143 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/google/uuid"
"github.com/joho/godotenv"
)
// Request structures based on MP documentation
type PrintRequest struct {
Type string `json:"type"`
ExternalReference string `json:"external_reference"`
Config PrintConfig `json:"config"`
Content string `json:"content"`
}
type PrintConfig struct {
Point PointSettings `json:"point"`
}
type PointSettings struct {
TerminalID string `json:"terminal_id"`
Subtype string `json:"subtype"`
}
func main() {
err := godotenv.Load("../.env")
if err != nil {
fmt.Println("Error loading .env file")
}
// Example receipt using supported tags: {b}, {center}, {br}, {s}
receiptContent := "{center}{b}SEKIPOS VENTA{/b}{br}" +
"--------------------------------{br}" +
"{left}Producto: Choripan Premium{br}" +
"{left}Total: $5.500{br}" +
"--------------------------------{br}" +
"{center}{s}Gracias por su compra{/s}{br}"
resp, err := SendPrintAction(receiptContent)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Response: %s\n", resp)
}
func SendPrintAction(content string) (string, error) {
apiURL := "https://api.mercadopago.com/terminals/v1/actions"
accessToken := os.Getenv("MP_ACCESS_TOKEN")
terminalID := os.Getenv("MP_TERMINAL_ID")
payload := PrintRequest{
Type: "print", // Required
ExternalReference: fmt.Sprintf("ref_%d", time.Now().Unix()),
Config: PrintConfig{
Point: PointSettings{
TerminalID: terminalID,
Subtype: "custom", // For text with tags
},
},
Content: content, // Must be 100-4096 chars
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+accessToken)
// Mandatory Unique UUID V4
req.Header.Set("X-Idempotency-Key", uuid.New().String())
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("MP Error %d: %s", resp.StatusCode, string(body))
}
return string(body), nil
}
func PartialRefund(orderID string, paymentID string, amount string) (string, error) {
// Endpoint para reembolsos según la referencia de API
apiURL := fmt.Sprintf("https://api.mercadopago.com/v1/orders/%s/refund", orderID)
token := os.Getenv("MP_ACCESS_TOKEN")
payload := map[string]interface{}{
"transactions": []map[string]string{
{
"id": paymentID,
"amount": amount, // Debe ser un string sin decimales
},
},
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Idempotency-Key", uuid.New().String())
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
resBody, _ := io.ReadAll(resp.Body)
return string(resBody), nil
}
func GetOrderStatus(orderID string) (string, error) {
apiURL := fmt.Sprintf("https://api.mercadopago.com/v1/orders/%s", orderID)
token := os.Getenv("MP_ACCESS_TOKEN")
req, _ := http.NewRequest("GET", apiURL, nil)
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
resBody, _ := io.ReadAll(resp.Body)
return string(resBody), nil
}

82
app.py
View File

@@ -7,6 +7,13 @@ from flask_login import LoginManager, UserMixin, login_user, login_required, log
from werkzeug.security import generate_password_hash, check_password_hash
import mimetypes
import time
import uuid
from dotenv import load_dotenv
load_dotenv()
MP_ACCESS_TOKEN = os.getenv('MP_ACCESS_TOKEN')
MP_TERMINAL_ID = os.getenv('MP_TERMINAL_ID')
app = Flask(__name__)
app.config['SECRET_KEY'] = 'seki_super_secret_key_99' # Change this if you have actual friends
@@ -281,6 +288,81 @@ def upload_image():
timestamp = int(time.time())
return jsonify({"status": "success", "image_url": f"/static/cache/{filename}?t={timestamp}"}), 200
# @app.route('/process_payment', methods=['POST'])
# @login_required
# def process_payment():
# data = request.get_json()
# total_amount = data.get('total')
# if not total_amount or total_amount <= 0:
# return jsonify({"error": "Invalid amount"}), 400
# url = "https://api.mercadopago.com/v1/orders"
# headers = {
# "Authorization": f"Bearer {MP_ACCESS_TOKEN}",
# "Content-Type": "application/json",
# "X-Idempotency-Key": str(uuid.uuid4())
# }
# # MP Point API often prefers integer strings for CLP or exact strings
# # We use int() here if you are dealing with CLP (no cents)
# formatted_amount = str(int(float(total_amount)))
# payload = {
# "type": "point",
# "external_reference": f"ref_{int(time.time())}",
# "description": "Venta SekiPOS",
# "expiration_time": "PT16M",
# "transactions": {
# "payments": [
# {
# "amount": formatted_amount
# }
# ]
# },
# "config": {
# "point": {
# "terminal_id": MP_TERMINAL_ID,
# "print_on_terminal": "no_ticket"
# },
# "payment_method": {
# "default_type": "credit_card"
# }
# },
# "integration_data": {
# "platform_id": "dev_1234567890",
# "integrator_id": "dev_1234567890"
# },
# "taxes": [
# {
# "payer_condition": "payment_taxable_iva"
# }
# ]
# }
# try:
# # Verify the payload in your terminal if it fails again
# response = requests.post(url, json=payload, headers=headers)
# if response.status_code != 201 and response.status_code != 200:
# print(f"DEBUG MP ERROR: {response.text}")
# return jsonify(response.json()), response.status_code
# except Exception as e:
# return jsonify({"error": str(e)}), 500
# @app.route('/api/mp-webhook', methods=['POST'])
# def webhook_notify():
# data = request.get_json()
# action = data.get('action', 'unknown')
# # Emitimos a todos los clientes conectados
# socketio.emit('payment_update', {
# "status": action,
# "id": data.get('data', {}).get('id')
# })
# return jsonify({"status": "ok"}), 200
if __name__ == '__main__':
init_db()
socketio.run(app, host='0.0.0.0', port=5000, debug=True)

View File

@@ -3,3 +3,4 @@ Flask-Login==0.6.3
Flask-SocketIO==5.6.1
requests==2.32.5
eventlet==0.36.1
python-dotenv==1.2.2

View File

@@ -173,10 +173,69 @@
[data-theme="dark"] .modal-body {
color: var(--text-main);
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] {
-moz-appearance: textfield;
}
</style>
</head>
<body>
<div class="modal fade" id="removeConfirmModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Quitar Producto</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
¿Estás seguro de que quieres quitar <strong id="removeItemName"></strong> del carrito?
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button class="btn btn-danger-discord" id="btn-confirm-remove">Quitar</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="clearCartModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Vaciar Carrito</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<i class="bi bi-cart-x text-danger" style="font-size: 3rem;"></i>
<p class="mt-3">¿Seguro que quieres eliminar todos los productos del carrito?</p>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal">No, volver</button>
<button class="btn btn-danger-discord" onclick="executeClearCart()">Sí, vaciar</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="successModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-success">
<div class="modal-body text-center py-4">
<i class="bi bi-check-circle-fill text-success" style="font-size: 4rem;"></i>
<h4 class="mt-3">¡Venta Exitosa!</h4>
<p class="text-muted">El carrito se ha procesado correctamente.</p>
<button class="btn btn-accent px-5" data-bs-dismiss="modal">Listo</button>
</div>
</div>
</div>
</div>
<nav class="navbar navbar-expand-md sticky-top px-3 mb-3">
<span class="navbar-brand">SekiPOS <small class="text-muted fw-normal"
style="font-size:0.65rem;">Caja</small></span>
@@ -222,6 +281,7 @@
<table class="table mt-3" id="cart-table">
<thead>
<tr>
<th>Código</th>
<th>Producto</th>
<th>Precio/U</th>
<th>Cant/Peso</th>
@@ -310,12 +370,6 @@
}
}
function addToCart(product, qty) {
const subtotal = product.price * qty;
cart.push({ ...product, qty, subtotal });
renderCart();
}
function renderCart() {
const tbody = document.getElementById('cart-items');
tbody.innerHTML = '';
@@ -340,16 +394,163 @@
document.getElementById('grand-total').innerText = clp.format(total);
}
// Updated removeItem with confirmation prompt
let itemIndexToRemove = null;
function removeItem(idx, name) {
if (confirm(`¿Quitar ${name} del carrito?`)) {
cart.splice(idx, 1);
itemIndexToRemove = idx;
document.getElementById('removeItemName').innerText = name;
const modal = new bootstrap.Modal(document.getElementById('removeConfirmModal'));
modal.show();
}
// Attach listener to the confirm button in the modal
document.getElementById('btn-confirm-remove').addEventListener('click', () => {
if (itemIndexToRemove !== null) {
cart.splice(itemIndexToRemove, 1);
renderCart();
bootstrap.Modal.getInstance(document.getElementById('removeConfirmModal')).hide();
itemIndexToRemove = null;
}
});
function clearCart() {
if (cart.length === 0) return;
const modal = new bootstrap.Modal(document.getElementById('clearCartModal'));
modal.show();
}
function executeClearCart() {
cart = [];
renderCart();
bootstrap.Modal.getInstance(document.getElementById('clearCartModal')).hide();
}
async function processSale() {
if (cart.length === 0) return;
const total = cart.reduce((sum, item) => sum + item.subtotal, 0);
// Disable button to prevent spam
const btn = document.querySelector('button[onclick="processSale()"]');
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Procesando...';
try {
alert("total: " total)
// const response = await fetch('/process_payment', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ total: total })
// });
// const result = await response.json();
// if (response.ok) {
// const modal = new bootstrap.Modal(document.getElementById('successModal'));
// modal.show();
// cart = [];
// renderCart();
// } else {
// alert("Error en el pago: " + (result.message || "Error desconocido"));
// }
} catch (err) {
alert("Error de conexión con el servidor.");
} finally {
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-cash-coin"></i> COBRAR';
}
}
function confirmWeight() {
const weightInput = document.getElementById('weight-input');
const weight = parseFloat(weightInput.value);
if (weight > 0) {
// For weighted items, we usually append new entries or you can sum them.
// Here we append to allow different weighings of the same product type.
addToCart(pendingProduct, weight);
bootstrap.Modal.getInstance('#weightModal').hide();
weightInput.value = '';
}
}
function addToCart(product, qty) {
// Check if product (unit-based only) already exists in cart
const existingIndex = cart.findIndex(item => item.barcode === product.barcode && item.unit !== 'kg');
if (existingIndex !== -1) {
cart[existingIndex].qty += qty;
cart[existingIndex].subtotal = cart[existingIndex].qty * cart[existingIndex].price;
} else {
const subtotal = product.price * qty;
cart.push({ ...product, qty, subtotal });
}
renderCart();
}
function updateQty(index, delta) {
if (cart[index].unit === 'kg') return; // Delta buttons disabled for weighted items
cart[index].qty += delta;
if (cart[index].qty <= 0) {
removeItem(index, cart[index].name);
} else {
cart[index].subtotal = cart[index].qty * cart[index].price;
renderCart();
}
}
function clearCart() { cart = []; renderCart(); }
function processSale() { alert("Venta procesada con éxito"); clearCart(); }
function manualQty(index, val) {
const newQty = parseFloat(val);
if (isNaN(newQty) || newQty <= 0) return;
cart[index].qty = newQty;
cart[index].subtotal = cart[index].qty * cart[index].price;
renderCart();
// Don't re-render immediately to avoid losing input focus while typing
}
function renderCart() {
const tbody = document.getElementById('cart-items');
tbody.innerHTML = '';
let total = 0;
cart.forEach((item, index) => {
total += item.subtotal;
const row = document.createElement('tr');
// Logic for quantity controls
let qtyControls;
if (item.unit === 'kg') {
qtyControls = `<span>${item.qty.toFixed(3)} kg</span>`;
} else {
qtyControls = `
<div class="d-flex align-items-center gap-1">
<button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="updateQty(${index}, -1)">-</button>
<input type="number" class="form-control form-control-sm text-center p-0"
style="width: 50px;" value="${item.qty}"
onchange="manualQty(${index}, this.value)"
onblur="renderCart()">
<button class="btn btn-sm btn-outline-secondary py-0 px-2" onclick="updateQty(${index}, 1)">+</button>
</div>
`;
}
row.innerHTML = `
<td class="font-monospace small text-muted">${item.barcode}</td>
<td>${item.name}</td>
<td>${clp.format(item.price)}</td>
<td>${qtyControls}</td>
<td>${clp.format(item.subtotal)}</td>
<td>
<button class="btn-danger-discord btn-sm" onclick="removeItem(${index}, '${item.name}')">
<i class="bi bi-trash"></i>
</button>
</td>
`;
tbody.appendChild(row);
});
document.getElementById('grand-total').innerText = clp.format(total);
}
function applyTheme(t) {
document.documentElement.setAttribute('data-theme', t);