Documentación para conectar la app Flutter de impresión con ComeYa
Flujo de impresión
Flujo de auth
https://comeya.base44.app/api/functions/printServerAuthAutenticar el servidor de impresión y obtener configuración inicial.
Request Body
{
"restaurant_id": "abc123",
"print_server_token": "pst_xxxxxxxxxxxx"
}Response 200
{
"success": true,
"restaurant": {
"id": "abc123",
"name": "Mi Restaurante"
},
"printers": [
{
"id": "p1",
"name": "Caja Principal",
"printer_name": "EPSON TM-T20",
"type": "ticket",
"paper_width": 80,
"auto_cut": true,
"copies": 1,
"trigger_on": ["order_received"]
}
]
}https://comeya.base44.app/api/functions/printServerConfigObtener configuración actualizada y enviar heartbeat. Llamar cada 5 minutos.
Request Body
{
"restaurant_id": "abc123",
"print_server_token": "pst_xxxx",
"server_port": 6789,
"version": "1.0.0"
}Response 200
{
"success": true,
"printers": [...],
"ticket_header": "Bienvenido!",
"ticket_footer": "Gracias por visitarnos"
}https://comeya.base44.app/api/functions/(local) localhost:6789/printEndpoint LOCAL del servidor Flutter. La app web llama esto directamente.
// Request desde comeya.app → localhost:6789/print
{
"type": "ticket", // "ticket" | "kitchen" | "bar"
"content": "<html>..ticket html..</html>",
"copies": 1
}
// Response del servidor Flutter
{ "success": true }El usuario ingresa su Restaurant ID y Token. Guárdalos con secure_storage.
// auth_service.dart
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class AuthService {
static const _storage = FlutterSecureStorage();
static const _baseUrl = 'https://comeya.base44.app/api/functions';
static Future<Map<String, dynamic>> login({
required String restaurantId,
required String printServerToken,
}) async {
final dio = Dio();
final response = await dio.post(
'$_baseUrl/printServerAuth',
data: {
'restaurant_id': restaurantId,
'print_server_token': printServerToken,
},
);
if (response.statusCode == 200) {
final data = response.data;
// Guardar credenciales localmente
await _storage.write(key: 'restaurant_id', value: restaurantId);
await _storage.write(key: 'print_server_token', value: printServerToken);
await _storage.write(key: 'restaurant_name', value: data['restaurant']['name']);
return data;
}
throw Exception(response.data['error'] ?? 'Error de autenticación');
}
static Future<bool> isLoggedIn() async {
final token = await _storage.read(key: 'print_server_token');
return token != null;
}
static Future<void> logout() async {
await _storage.deleteAll();
}
}Servidor HTTP en localhost:6789 que recibe trabajos de impresión desde el navegador.
// print_server.dart
import 'dart:convert';
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
class PrintServer {
static HttpServer? _server;
static int port = 6789;
static Future<void> start() async {
final handler = const Pipeline()
.addMiddleware(_corsMiddleware())
.addHandler(_router);
_server = await io.serve(handler, 'localhost', port);
print('Servidor de impresión en http://localhost:$port');
}
static Future<void> stop() async {
await _server?.close();
}
static Middleware _corsMiddleware() {
return (Handler handler) {
return (Request request) async {
if (request.method == 'OPTIONS') {
return Response.ok('', headers: _corsHeaders);
}
final response = await handler(request);
return response.change(headers: _corsHeaders);
};
};
}
static const _corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
static Future<Response> _router(Request request) async {
switch (request.url.path) {
case 'status':
return Response.ok(
jsonEncode({'status': 'active', 'port': port}),
headers: {'Content-Type': 'application/json'},
);
case 'print':
return _handlePrint(request);
default:
return Response.notFound('Ruta no encontrada');
}
}
static Future<Response> _handlePrint(Request request) async {
final body = await request.readAsString();
final data = jsonDecode(body) as Map<String, dynamic>;
final type = data['type'] as String; // ticket | kitchen | bar
final content = data['content'] as String; // HTML del ticket
final copies = (data['copies'] as num?)?.toInt() ?? 1;
// Buscar impresora configurada para este tipo
final printer = PrinterConfigCache.getByType(type);
if (printer == null) {
return Response(
404,
body: jsonEncode({'error': 'No hay impresora configurada para tipo: $type'}),
headers: {'Content-Type': 'application/json'},
);
}
// Imprimir
await PrintingService.printHtml(
printerName: printer['printer_name'],
htmlContent: content,
copies: copies,
paperWidth: printer['paper_width'] ?? 80,
);
return Response.ok(
jsonEncode({'success': true}),
headers: {'Content-Type': 'application/json'},
);
}
}Cada 5 minutos sincroniza la configuración de impresoras y envía el estado al servidor.
// heartbeat_service.dart
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class HeartbeatService {
static const _storage = FlutterSecureStorage();
static Timer? _timer;
static const _baseUrl = 'https://comeya.base44.app/api/functions';
static const version = '1.0.0';
static void start({ int port = 6789 }) {
_timer?.cancel();
// Enviar heartbeat cada 5 minutos
_timer = Timer.periodic(const Duration(minutes: 5), (_) => _send(port));
_send(port); // enviar inmediatamente
}
static void stop() => _timer?.cancel();
static Future<void> _send(int port) async {
final restaurantId = await _storage.read(key: 'restaurant_id');
final token = await _storage.read(key: 'print_server_token');
if (restaurantId == null || token == null) return;
try {
final dio = Dio();
final response = await dio.post(
'$_baseUrl/printServerConfig',
data: {
'restaurant_id': restaurantId,
'print_server_token': token,
'server_port': port,
'version': version,
},
);
// Actualizar config de impresoras si cambió
if (response.data['printers'] != null) {
PrinterConfigCache.update(response.data['printers']);
}
} catch (e) {
print('Heartbeat error: $e');
}
}
}Cómo detectar el servidor y enviar trabajos de impresión desde React.
// En tu app React (comeya.app)
// utils/printService.js
const PRINT_SERVER_URL = 'http://localhost:6789';
export const checkPrintServer = async () => {
try {
const r = await fetch(`${PRINT_SERVER_URL}/status`, {
signal: AbortSignal.timeout(1500)
});
return r.ok;
} catch {
return false;
}
};
export const printTicket = async (htmlContent, type = 'ticket', copies = 1) => {
const response = await fetch(`${PRINT_SERVER_URL}/print`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type, content: htmlContent, copies })
});
if (!response.ok) throw new Error('Error al imprimir');
return response.json();
};
// Uso en componente:
// const serverActive = await checkPrintServer();
// if (serverActive) await printTicket(ticketHTML, 'kitchen');