Reorganiza repo como fuente de la verdad + propuesta v1.0 con emisión de facturas
- Estructura nueva: README maestro, bitacora/ (REGISTRO, PENDIENTES, plantillas), propuesta/, fuentes/; material superado a _archivado/ - Propuesta v1.0: MVP BIND-first con emisión asistida MXN/USD (dry-run + confirmación, timbra PAC de BIND), 112-136 h / $67,200-$81,600 + IVA, stack .NET 10 + EF Core + Angular 21 + PostgreSQL 17 sobre Azure - Bitácora: historial de correos + 2 llamadas (incl. revisión 4-jun) y pendientes - Prototipo y diagrama actualizados a v1.0; precios de Azure verificados - Archivo ajeno (proyecto EOS) retirado del repo Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Helpers para construir queries OData que entiende el API de BIND.
|
||||
*
|
||||
* Sintaxis observable en la doc oficial:
|
||||
* /api/Products?$filter=ID eq guid'bbe2cc0c-...'&$skip=0&$top=50&$orderby=Name asc
|
||||
*
|
||||
* Mantengo el builder muy chico — sólo lo que el MVP necesita.
|
||||
*/
|
||||
|
||||
export type ODataFilter = string;
|
||||
|
||||
export interface ODataQuery {
|
||||
filter?: ODataFilter;
|
||||
top?: number;
|
||||
skip?: number;
|
||||
orderby?: string;
|
||||
select?: string[];
|
||||
count?: boolean;
|
||||
}
|
||||
|
||||
export function buildQueryString(q: ODataQuery): string {
|
||||
const parts: string[] = [];
|
||||
if (q.filter) parts.push(`$filter=${encodeURIComponent(q.filter)}`);
|
||||
if (typeof q.top === "number") parts.push(`$top=${q.top}`);
|
||||
if (typeof q.skip === "number") parts.push(`$skip=${q.skip}`);
|
||||
if (q.orderby) parts.push(`$orderby=${encodeURIComponent(q.orderby)}`);
|
||||
if (q.select?.length) parts.push(`$select=${encodeURIComponent(q.select.join(","))}`);
|
||||
if (q.count) parts.push(`$count=true`);
|
||||
return parts.length ? `?${parts.join("&")}` : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Pequeño helper para escribir filtros legibles. No es un parser OData;
|
||||
* solo escapa comillas simples y envuelve guids.
|
||||
*
|
||||
* Ejemplos:
|
||||
* eq("ID", guid("bbe2...")) -> "ID eq guid'bbe2...'"
|
||||
* eq("Status", "issued") -> "Status eq 'issued'"
|
||||
* and(eq("Status","issued"), gt("Total", 1000))
|
||||
*/
|
||||
export const guid = (id: string): string => `guid'${id.replaceAll("'", "''")}'`;
|
||||
export const str = (s: string): string => `'${s.replaceAll("'", "''")}'`;
|
||||
export const eq = (field: string, value: string | number | boolean): string =>
|
||||
`${field} eq ${formatValue(value)}`;
|
||||
export const ne = (field: string, value: string | number | boolean): string =>
|
||||
`${field} ne ${formatValue(value)}`;
|
||||
export const gt = (field: string, value: string | number): string =>
|
||||
`${field} gt ${formatValue(value)}`;
|
||||
export const lt = (field: string, value: string | number): string =>
|
||||
`${field} lt ${formatValue(value)}`;
|
||||
export const ge = (field: string, value: string | number): string =>
|
||||
`${field} ge ${formatValue(value)}`;
|
||||
export const le = (field: string, value: string | number): string =>
|
||||
`${field} le ${formatValue(value)}`;
|
||||
export const and = (...parts: string[]): string => parts.join(" and ");
|
||||
export const or = (...parts: string[]): string => `(${parts.join(" or ")})`;
|
||||
|
||||
function formatValue(v: string | number | boolean): string {
|
||||
if (typeof v === "number" || typeof v === "boolean") return String(v);
|
||||
// Si ya viene formateado como guid'...' o '...' (string OData), respétalo.
|
||||
if (/^(guid'.*'|'.*')$/.test(v)) return v;
|
||||
return str(v);
|
||||
}
|
||||
Reference in New Issue
Block a user